X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;f=lib%2Fmcollective%2Fshell.rb;fp=lib%2Fmcollective%2Fshell.rb;h=257fc4b5a951f8c0a6fd7342292bc1cc21183ffc;hb=b87d2f4e68281062df1913440ca5753ae63314a9;hp=0000000000000000000000000000000000000000;hpb=ab0ea530b8ac956091f17b104ab2311336cfc250;p=packages%2Fprecise%2Fmcollective.git diff --git a/lib/mcollective/shell.rb b/lib/mcollective/shell.rb new file mode 100644 index 0000000..257fc4b --- /dev/null +++ b/lib/mcollective/shell.rb @@ -0,0 +1,87 @@ +module MCollective + # Wrapper around systemu that handles executing of system commands + # in a way that makes stdout, stderr and status available. Supports + # timeouts and sets a default sane environment. + # + # s = Shell.new("date", opts) + # s.runcommand + # puts s.stdout + # puts s.stderr + # puts s.status.exitstatus + # + # Options hash can have: + # + # cwd - the working directory the command will be run from + # stdin - a string that will be sent to stdin of the program + # stdout - a variable that will receive stdout, must support << + # stderr - a variable that will receive stdin, must support << + # environment - the shell environment, defaults to include LC_ALL=C + # set to nil to clear the environment even of LC_ALL + # + class Shell + attr_reader :environment, :command, :status, :stdout, :stderr, :stdin, :cwd + + def initialize(command, options={}) + @environment = {"LC_ALL" => "C"} + @command = command + @status = nil + @stdout = "" + @stderr = "" + @stdin = nil + @cwd = Dir.tmpdir + + options.each do |opt, val| + case opt.to_s + when "stdout" + raise "stdout should support <<" unless val.respond_to?("<<") + @stdout = val + + when "stderr" + raise "stderr should support <<" unless val.respond_to?("<<") + @stderr = val + + when "stdin" + raise "stdin should be a String" unless val.is_a?(String) + @stdin = val + + when "cwd" + raise "Directory #{val} does not exist" unless File.directory?(val) + @cwd = val + + when "environment" + if val.nil? + @environment = {} + else + @environment.merge!(val.dup) + end + end + end + end + + # Actually does the systemu call passing in the correct environment, stdout and stderr + def runcommand + opts = {"env" => @environment, + "stdout" => @stdout, + "stderr" => @stderr, + "cwd" => @cwd} + + opts["stdin"] = @stdin if @stdin + + # Running waitpid on the cid here will start a thread + # with the waitpid in it, this way even if the thread + # that started this process gets killed due to agent + # timeout or such there will still be a waitpid waiting + # for the child to exit and not leave zombies. + @status = systemu(@command, opts) do |cid| + begin + sleep 1 + Process::waitpid(cid) + rescue SystemExit + rescue Errno::ECHILD + rescue Exception => e + Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}") + end + end + end + end +end