class MCollective::Shell - mcollective version 2.12.5

# class MCollective::Shell

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
    timeout     - a timeout in seconds after which the subprocess is killed,
                  the special value :on_thread_exit kills the subprocess
                  when the invoking thread (typically the agent) has ended

### Attributes

command[R]
cwd[R]
environment[R]
status[R]
stderr[R]
stdin[R]
stdout[R]
timeout[R]

### Public Class Methods

new(command, options={}) click to toggle source

       # File lib/mcollective/shell.rb
    27 def initialize(command, options={})
    28   @environment = {"LC_ALL" => "C"}
    29   @command = command
    30   @status = nil
    31   @stdout = ""
    32   @stderr = ""
    33   @stdin = nil
    34   @cwd = Dir.tmpdir
    35   @timeout = nil
    36
    37   options.each do |opt, val|
    38     case opt.to_s
    39       when "stdout"
    40         raise "stdout should support <<" unless val.respond_to?("<<")
    41         @stdout = val
    42
    43       when "stderr"
    44         raise "stderr should support <<" unless val.respond_to?("<<")
    45         @stderr = val
    46
    47       when "stdin"
    48         raise "stdin should be a String" unless val.is_a?(String)
    49         @stdin = val
    50
    51       when "cwd"
    52         raise "Directory #{val} does not exist" unless File.directory?(val)
    53         @cwd = val
    54
    55       when "environment"
    56         if val.nil?
    57           @environment = {}
    58         else
    59           @environment.merge!(val.dup)
    60           @environment = @environment.delete_if { |k,v| v.nil? }
    61         end
    62
    63       when "timeout"
    64         raise "timeout should be a positive integer or the symbol :on_thread_exit symbol" unless val.eql?(:on_thread_exit) || ( val.is_a?(Integer) && val>0 )
    65         @timeout = val
    66     end
    67   end
    68 end

### Public Instance Methods

runcommand() click to toggle source

Actually does the systemu call passing in the correct environment, stdout and stderr

        # File lib/mcollective/shell.rb
     71 def runcommand
     72   opts = {"env"    => @environment,
     73           "stdout" => @stdout,
     74           "stderr" => @stderr,
     75           "cwd"    => @cwd}
     76
     77   opts["stdin"] = @stdin if @stdin
     78
     79
     80   thread = Thread.current
     81   # Start a double fork and exec with systemu which implies a guard thread.
     82   # If a valid timeout is configured the guard thread will terminate the
     83   # executing process and reap the pid.
     84   # If no timeout is specified the process will run to completion with the
     85   # guard thread reaping the pid on completion.
     86   @status = systemu(@command, opts) do |cid|
     87     begin
     88       if timeout.is_a?(Integer)
     89         # wait for the specified timeout
     90         sleep timeout
     91       else
     92         # sleep while the agent thread is still alive
     93         while(thread.alive?)
     94           sleep 0.1
     95         end
     96       end
     97
     98       # if the process is still running
     99       if (Process.kill(0, cid))
    100         # and a timeout was specified
    101         if timeout
    102           if Util.windows?
    103             Process.kill('KILL', cid)
    104           else
    105             # Kill the process
    106             Process.kill('TERM', cid)
    107             sleep 2
    108             Process.kill('KILL', cid) if (Process.kill(0, cid))
    109           end
    110         end
    111         # only wait if the parent thread is dead
    112         Process.waitpid(cid) unless thread.alive?
    113       end
    114     rescue SystemExit
    115     rescue Errno::ESRCH
    116     rescue Errno::ECHILD
    117       Log.warn("Could not reap process '#{cid}'.")
    118     rescue Exception => e
    119       Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}")
    120     end
    121   end
    122   @status.thread.kill
    123   @status
    124 end