Module Ohai::Mixin::Command
In: lib/ohai/mixin/command.rb
Mixlib::CLI Application System RuntimeError Exec Config Log lib/ohai/config.rb lib/ohai/log.rb lib/ohai/system.rb lib/ohai/application.rb Command Ec2Metadata FromFile Mixin lib/ohai/exception.rb Exceptions Ohai dot/m_140_0.png

Methods

Public Instance methods

This is taken directly from Ara T Howard‘s Open4 library, and then modified to suit the needs of Ohai. Any bugs here are most likely my own, and not Ara‘s.

The original appears in external/open4.rb in its unmodified form.

Thanks Ara!

[Source]

     # File lib/ohai/mixin/command.rb, line 130
130:       def popen4(cmd, args={}, &b)
131:         
132:         ## Disable garbage collection to work around possible bug in MRI
133:   # Ruby 1.8 suffers from intermittent segfaults believed to be due to GC while IO.select
134:   # See OHAI-330 / CHEF-2916 / CHEF-1305
135:         GC.disable
136:         
137:         # Waitlast - this is magic.
138:         #
139:         # Do we wait for the child process to die before we yield
140:         # to the block, or after?  That is the magic of waitlast.
141:         #
142:         # By default, we are waiting before we yield the block.
143:         args[:waitlast] ||= false
144: 
145:         args[:user] ||= nil
146:         unless args[:user].kind_of?(Integer)
147:           args[:user] = Etc.getpwnam(args[:user]).uid if args[:user]
148:         end
149:         args[:group] ||= nil
150:         unless args[:group].kind_of?(Integer)
151:           args[:group] = Etc.getgrnam(args[:group]).gid if args[:group]
152:         end
153:         args[:environment] ||= {}
154: 
155:         # Default on C locale so parsing commands output can be done
156:         # independently of the node's default locale.
157:         # "LC_ALL" could be set to nil, in which case we also must ignore it.
158:         unless args[:environment].has_key?("LC_ALL")
159:           args[:environment]["LC_ALL"] = "C"
160:         end
161: 
162:         pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
163: 
164:         verbose = $VERBOSE
165:         begin
166:           $VERBOSE = nil
167:           ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
168: 
169:           cid = fork {
170:             Process.setsid
171: 
172:             pw.last.close
173:             STDIN.reopen pw.first
174:             pw.first.close
175: 
176:             pr.first.close
177:             STDOUT.reopen pr.last
178:             pr.last.close
179: 
180:             pe.first.close
181:             STDERR.reopen pe.last
182:             pe.last.close
183: 
184:             STDOUT.sync = STDERR.sync = true
185: 
186:             if args[:group]
187:               Process.egid = args[:group]
188:               Process.gid = args[:group]
189:             end
190: 
191:             if args[:user]
192:               Process.euid = args[:user]
193:               Process.uid = args[:user]
194:             end
195: 
196:             args[:environment].each do |key,value|
197:               ENV[key] = value
198:             end
199: 
200:             if args[:umask]
201:               umask = ((args[:umask].respond_to?(:oct) ? args[:umask].oct : args[:umask].to_i) & 007777)
202:               File.umask(umask)
203:             end
204: 
205:             begin
206:               if cmd.kind_of?(Array)
207:                 exec(*cmd)
208:               else
209:                 exec(cmd)
210:               end
211:               raise 'forty-two'
212:             rescue Exception => e
213:               Marshal.dump(e, ps.last)
214:               ps.last.flush
215:             end
216:             ps.last.close unless (ps.last.closed?)
217:             exit!
218:           }
219:         ensure
220:           $VERBOSE = verbose
221:         end
222: 
223:         [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
224: 
225:         begin
226:           e = Marshal.load ps.first
227:           raise(Exception === e ? e : "unknown failure!")
228:         rescue EOFError # If we get an EOF error, then the exec was successful
229:           42
230:         ensure
231:           ps.first.close
232:         end
233: 
234:         pw.last.sync = true
235: 
236:         pi = [pw.last, pr.first, pe.first]
237: 
238:         if b
239:           begin
240:             if args[:waitlast]
241:               b[cid, *pi]
242:               # send EOF so that if the child process is reading from STDIN
243:               # it will actually finish up and exit
244:               pi[0].close_write
245:               Process.waitpid2(cid).last
246:             else
247:               # This took some doing.
248:               # The trick here is to close STDIN
249:               # Then set our end of the childs pipes to be O_NONBLOCK
250:               # Then wait for the child to die, which means any IO it
251:               # wants to do must be done - it's dead.  If it isn't,
252:               # it's because something totally skanky is happening,
253:               # and we don't care.
254:               o = StringIO.new
255:               e = StringIO.new
256: 
257:               #pi[0].close
258: 
259:               stdout = pi[1]
260:               stderr = pi[2]
261: 
262:               stdout.sync = true
263:               stderr.sync = true
264: 
265:               stdout.fcntl(Fcntl::F_SETFL, pi[1].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
266:               stderr.fcntl(Fcntl::F_SETFL, pi[2].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
267: 
268:               stdout_finished = false
269:               stderr_finished = false
270: 
271:               results = nil
272: 
273:               while !stdout_finished || !stderr_finished
274:                 begin
275:                   channels_to_watch = []
276:                   channels_to_watch << stdout if !stdout_finished
277:                   channels_to_watch << stderr if !stderr_finished
278:                   ready = IO.select(channels_to_watch, nil, nil, 1.0)
279:                 rescue Errno::EAGAIN
280:                 ensure
281:                   results = Process.waitpid2(cid, Process::WNOHANG)
282:                   if results
283:                     stdout_finished = true
284:                     stderr_finished = true
285:                   end
286:                 end
287: 
288:                 if ready && ready.first.include?(stdout)
289:                   line = results ? stdout.gets(nil) : stdout.gets
290:                   if line
291:                     o.write(line)
292:                   else
293:                     stdout_finished = true
294:                   end
295:                 end
296:                 if ready && ready.first.include?(stderr)
297:                   line = results ? stderr.gets(nil) : stderr.gets
298:                   if line
299:                     e.write(line)
300:                   else
301:                     stderr_finished = true
302:                   end
303:                 end
304:               end
305:               results = Process.waitpid2(cid) unless results
306:               o.rewind
307:               e.rewind
308: 
309:               # **OHAI-275**
310:               # The way we read from the pipes causes ruby to mark the strings
311:               # as ASCII-8BIT (i.e., binary), but the content should be encoded
312:               # as the default external encoding. For example, a command may
313:               # return data encoded as UTF-8, but the strings will be marked as
314:               # ASCII-8BIT. Later, when you attempt to print the values as
315:               # UTF-8, Ruby will try to convert them and fail, raising an
316:               # error.
317:               #
318:               # Ruby always marks strings as binary when read from IO in
319:               # incomplete chunks, since you may have split the data within a
320:               # multibyte char. In our case, we concat the chunks back
321:               # together, so any multibyte chars will be reassembled.
322:               #
323:               # Note that all of this applies only to Ruby 1.9, which we check
324:               # for by making sure that the Encoding class exists and strings
325:               # have encoding methods.
326:               if "".respond_to?(:force_encoding) && defined?(Encoding)
327:                 o.string.force_encoding(Encoding.default_external)
328:                 e.string.force_encoding(Encoding.default_external)
329:               end
330: 
331:               b[cid, pi[0], o, e]
332:               results.last
333:             end
334:           ensure
335:             pi.each{|fd| fd.close unless fd.closed?}
336:           end
337:         else
338:           [cid, pw.last, pr.first, pe.first]
339:         end
340:       rescue Errno::ENOENT
341:         raise Ohai::Exceptions::Exec, "command #{cmd} doesn't exist or is not in the PATH"
342:       ensure
343:         # we disabled GC entering
344:         GC.enable
345:       end

[Source]

    # File lib/ohai/mixin/command.rb, line 32
32:       def run_command(args={})
33:         if args.has_key?(:creates)
34:           if File.exists?(args[:creates])
35:             Ohai::Log.debug("Skipping #{args[:command]} - creates #{args[:creates]} exists.")
36:             return false
37:           end
38:         end
39: 
40:         stdout_string = nil
41:         stderr_string = nil
42: 
43:         args[:cwd] ||= Dir.tmpdir
44:         unless File.directory?(args[:cwd])
45:           raise Ohai::Exceptions::Exec, "#{args[:cwd]} does not exist or is not a directory"
46:         end
47: 
48:         status = nil
49:         Dir.chdir(args[:cwd]) do
50:           status, stdout_string, stderr_string = run_command_backend(args[:command], args[:timeout])
51:           # systemu returns 42 when it hits unexpected errors
52:           if status.exitstatus == 42 and stderr_string == ""
53:             stderr_string = "Failed to run: #{args[:command]}, assuming command not found"
54:             Ohai::Log.debug(stderr_string)
55:           end
56: 
57:           if stdout_string
58:             Ohai::Log.debug("---- Begin #{args[:command]} STDOUT ----")
59:             Ohai::Log.debug(stdout_string.strip)
60:             Ohai::Log.debug("---- End #{args[:command]} STDOUT ----")
61:           end
62:           if stderr_string
63:             Ohai::Log.debug("---- Begin #{args[:command]} STDERR ----")
64:             Ohai::Log.debug(stderr_string.strip)
65:             Ohai::Log.debug("---- End #{args[:command]} STDERR ----")
66:           end
67: 
68:           args[:returns] ||= 0
69:           args[:no_status_check] ||= false
70:           if status.exitstatus != args[:returns] and not args[:no_status_check]
71:             raise Ohai::Exceptions::Exec, "#{args[:command_string]} returned #{status.exitstatus}, expected #{args[:returns]}"
72:           else
73:             Ohai::Log.debug("Ran #{args[:command_string]} (#{args[:command]}) returned #{status.exitstatus}")
74:           end
75:         end
76:         return status, stdout_string, stderr_string
77:       end
run_command_backend(command, timeout)
run_command_backend(command, timeout)

Alias for run_command_unix

[Source]

     # File lib/ohai/mixin/command.rb, line 81
 81:       def run_command_unix(command, timeout)
 82:         stderr_string, stdout_string, status = "", "", nil
 83: 
 84:         exec_processing_block = lambda do |pid, stdin, stdout, stderr|
 85:           stdout_string, stderr_string = stdout.string.chomp, stderr.string.chomp
 86:         end
 87: 
 88:         if timeout
 89:           begin
 90:             Timeout.timeout(timeout) do
 91:               status = popen4(command, {}, &exec_processing_block)
 92:             end
 93:           rescue Timeout::Error => e
 94:             Chef::Log.error("#{command} exceeded timeout #{timeout}")
 95:             raise(e)
 96:           end
 97:         else
 98:           status = popen4(command, {}, &exec_processing_block)
 99:         end
100:         return status, stdout_string, stderr_string
101:       end

[Source]

     # File lib/ohai/mixin/command.rb, line 103
103:       def run_command_windows(command, timeout)
104:         if timeout
105:           begin
106:             systemu(command)
107:           rescue SystemExit => e
108:             raise
109:           rescue Timeout::Error => e
110:             Ohai::Log.error("#{command} exceeded timeout #{timeout}")
111:             raise(e)
112:           end
113:         else
114:           systemu(command)
115:         end
116:       end

[Validate]