Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

[OHAI-136] import popen4 from chef into ohai

  • Loading branch information...
commit 8cc64bd23fb248b6498e53ed4199c88324021b2c 1 parent 1c0505c
@danielsdeleo danielsdeleo authored
Showing with 161 additions and 52 deletions.
  1. +1 −1  bin/ohai
  2. +157 −48 lib/ohai/mixin/command.rb
  3. +3 −3 lib/ohai/system.rb
View
2  bin/ohai
@@ -29,7 +29,7 @@ end
begin
# if we're in a source code checkout, we want to run the code from that.
# have to do this *after* rubygems is loaded.
- $:.unshift(File.expand_path(File.dirname(__FILE__) + '/../lib'))
+ $:.unshift File.expand_path('../../lib', __FILE__)
require 'ohai/application'
rescue LoadError
if missing_rubygems
View
205 lib/ohai/mixin/command.rb
@@ -6,9 +6,9 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
-#
+#
# http://www.apache.org/licenses/LICENSE-2.0
-#
+#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -27,44 +27,30 @@
module Ohai
module Mixin
module Command
-
- def run_command(args={})
+
+ def run_command(args={})
if args.has_key?(:creates)
if File.exists?(args[:creates])
Ohai::Log.debug("Skipping #{args[:command]} - creates #{args[:creates]} exists.")
return false
end
end
-
+
stdout_string = nil
stderr_string = nil
-
- args[:cwd] ||= Dir.tmpdir
+
+ args[:cwd] ||= Dir.tmpdir
unless File.directory?(args[:cwd])
raise Ohai::Exceptions::Exec, "#{args[:cwd]} does not exist or is not a directory"
end
-
+
status = nil
Dir.chdir(args[:cwd]) do
- if args[:timeout]
- begin
- Timeout.timeout(args[:timeout]) do
- status, stdout_string, stderr_string = systemu(args[:command])
- end
- rescue SystemExit => e
- raise
- rescue Exception => e
- Ohai::Log.error("#{args[:command_string]} exceeded timeout #{args[:timeout]}")
- raise(e)
- end
- else
- status, stdout_string, stderr_string = systemu(args[:command])
- end
-
+ status, stdout_string, stderr_string = run_command_backend(args[:command], args[:timeout])
# systemu returns 42 when it hits unexpected errors
if status.exitstatus == 42 and stderr_string == ""
stderr_string = "Failed to run: #{args[:command]}, assuming command not found"
- Ohai::Log.debug(stderr_string)
+ Ohai::Log.debug(stderr_string)
end
if stdout_string
@@ -77,7 +63,7 @@ def run_command(args={})
Ohai::Log.debug(stderr_string.strip)
Ohai::Log.debug("---- End #{args[:command]} STDERR ----")
end
-
+
args[:returns] ||= 0
args[:no_status_check] ||= false
if status.exitstatus != args[:returns] and not args[:no_status_check]
@@ -90,16 +76,66 @@ def run_command(args={})
end
module_function :run_command
-
- # This is taken directly from Ara T Howard's Open4 library, and then
+
+ def run_command_unix(command, timeout)
+ stderr_string, stdout_string, status = "", "", nil
+
+ exec_processing_block = lambda do |pid, stdin, stdout, stderr|
+ stdout_string, stderr_string = stdout.string.chomp, stderr.string.chomp
+ end
+
+ if timeout
+ begin
+ Timeout.timeout(timeout) do
+ status = popen4(command, {}, &exec_processing_block)
+ end
+ rescue Timeout::Error => e
+ Chef::Log.error("#{command} exceeded timeout #{timeout}")
+ raise(e)
+ end
+ else
+ status = popen4(command, {}, &exec_processing_block)
+ end
+ return status, stdout_string, stderr_string
+ end
+
+ def run_comand_windows(command, timeout)
+ if timeout
+ begin
+ systemu(command)
+ rescue SystemExit => e
+ raise
+ rescue Timeout::Error => e
+ Ohai::Log.error("#{command} exceeded timeout #{timeout}")
+ raise(e)
+ end
+ else
+ systemu(command)
+ end
+ end
+
+ if RUBY_PLATFORM =~ /mswin|mingw32|windows/
+ alias :run_command_backend :run_command_windows
+ else
+ alias :run_command_backend :run_command_unix
+ end
+ # 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.
+ # The original appears in external/open4.rb in its unmodified form.
#
# Thanks Ara!
def popen4(cmd, args={}, &b)
-
+
+ # Waitlast - this is magic.
+ #
+ # Do we wait for the child process to die before we yield
+ # to the block, or after? That is the magic of waitlast.
+ #
+ # By default, we are waiting before we yield the block.
+ args[:waitlast] ||= false
+
args[:user] ||= nil
unless args[:user].kind_of?(Integer)
args[:user] = Etc.getpwnam(args[:user]).uid if args[:user]
@@ -116,7 +152,7 @@ def popen4(cmd, args={}, &b)
unless args[:environment].has_key?("LC_ALL")
args[:environment]["LC_ALL"] = "C"
end
-
+
pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
verbose = $VERBOSE
@@ -125,6 +161,8 @@ def popen4(cmd, args={}, &b)
ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
cid = fork {
+ Process.setsid
+
pw.last.close
STDIN.reopen pw.first
pw.first.close
@@ -139,29 +177,32 @@ def popen4(cmd, args={}, &b)
STDOUT.sync = STDERR.sync = true
- if args[:user]
- Process.euid = args[:user]
- Process.uid = args[:user]
- end
-
if args[:group]
Process.egid = args[:group]
Process.gid = args[:group]
end
-
+
+ if args[:user]
+ Process.euid = args[:user]
+ Process.uid = args[:user]
+ end
+
args[:environment].each do |key,value|
ENV[key] = value
end
-
+
+ if args[:umask]
+ umask = ((args[:umask].respond_to?(:oct) ? args[:umask].oct : args[:umask].to_i) & 007777)
+ File.umask(umask)
+ end
+
begin
if cmd.kind_of?(Array)
exec(*cmd)
else
exec(cmd)
end
- raise 'forty-two'
- rescue SystemExit => e
- raise
+ raise 'forty-two'
rescue Exception => e
Marshal.dump(e, ps.last)
ps.last.flush
@@ -177,9 +218,6 @@ def popen4(cmd, args={}, &b)
begin
e = Marshal.load ps.first
- # If we get here, exec failed. Collect status of child to prevent
- # zombies.
- Process.waitpid(cid)
raise(Exception === e ? e : "unknown failure!")
rescue EOFError # If we get an EOF error, then the exec was successful
42
@@ -191,18 +229,89 @@ def popen4(cmd, args={}, &b)
pi = [pw.last, pr.first, pe.first]
- if b
+ if b
begin
- b[cid, *pi]
- Process.waitpid2(cid).last
+ if args[:waitlast]
+ b[cid, *pi]
+ # send EOF so that if the child process is reading from STDIN
+ # it will actually finish up and exit
+ pi[0].close_write
+ Process.waitpid2(cid).last
+ else
+ # This took some doing.
+ # The trick here is to close STDIN
+ # Then set our end of the childs pipes to be O_NONBLOCK
+ # Then wait for the child to die, which means any IO it
+ # wants to do must be done - it's dead. If it isn't,
+ # it's because something totally skanky is happening,
+ # and we don't care.
+ o = StringIO.new
+ e = StringIO.new
+
+ #pi[0].close
+
+ stdout = pi[1]
+ stderr = pi[2]
+
+ stdout.sync = true
+ stderr.sync = true
+
+ stdout.fcntl(Fcntl::F_SETFL, pi[1].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
+ stderr.fcntl(Fcntl::F_SETFL, pi[2].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
+
+ stdout_finished = false
+ stderr_finished = false
+
+ results = nil
+
+ while !stdout_finished || !stderr_finished
+ begin
+ channels_to_watch = []
+ channels_to_watch << stdout if !stdout_finished
+ channels_to_watch << stderr if !stderr_finished
+ ready = IO.select(channels_to_watch, nil, nil, 1.0)
+ rescue Errno::EAGAIN
+ ensure
+ results = Process.waitpid2(cid, Process::WNOHANG)
+ if results
+ stdout_finished = true
+ stderr_finished = true
+ end
+ end
+
+ if ready && ready.first.include?(stdout)
+ line = results ? stdout.gets(nil) : stdout.gets
+ if line
+ o.write(line)
+ else
+ stdout_finished = true
+ end
+ end
+ if ready && ready.first.include?(stderr)
+ line = results ? stderr.gets(nil) : stderr.gets
+ if line
+ e.write(line)
+ else
+ stderr_finished = true
+ end
+ end
+ end
+ results = Process.waitpid2(cid) unless results
+ o.rewind
+ e.rewind
+ b[cid, pi[0], o, e]
+ results.last
+ end
ensure
pi.each{|fd| fd.close unless fd.closed?}
end
else
[cid, pw.last, pr.first, pe.first]
end
- end
-
+ rescue Errno::ENOENT
+ raise Ohai::Exceptions::Exec, "command #{cmd} doesn't exist or is not in the PATH"
+ end
+
module_function :popen4
end
end
View
6 lib/ohai/system.rb
@@ -63,7 +63,7 @@ def set(name, *value)
def from(cmd)
status, stdout, stderr = run_command(:command => cmd)
return "" if stdout.nil? || stdout.empty?
- stdout.chomp!.strip
+ stdout.strip
end
def provides(*paths)
@@ -189,9 +189,9 @@ def require_plugin(plugin_name, force=false)
Ohai::Log.debug("Loading plugin #{plugin_name}")
from_file(check_path)
return true
- rescue IOError => e
+ rescue Errno::ENOENT => e
Ohai::Log.debug("No #{plugin_name} at #{check_path}")
- rescue SystemExit => e
+ rescue SystemExit, Interrupt
raise
rescue Exception,Errno::ENOENT => e
Ohai::Log.debug("Plugin #{plugin_name} threw exception #{e.inspect} #{e.backtrace.join("\n")}")
Please sign in to comment.
Something went wrong with that request. Please try again.