module POSIX::Spawn

def `(cmd)

Returns the String output of the command.

is compatible with Kernel#`.
and returns anything written to the new process's stdout. This method
Executes a command in a subshell using the system's shell interpreter
def `(cmd)
  r, w = IO.pipe
  command_and_args = system_command_prefixes + [cmd, {:out => w, r => :close}]
  pid = spawn(*command_and_args)
  if pid > 0
    w.close
    out = r.read
    ::Process.waitpid(pid)
    out
  else
    ''
  end
ensure
  [r, w].each{ |io| io.close rescue nil }
end

def adjust_process_spawn_argv(args)

Returns a [[cmdname, argv0], argv1, ...] array.

['echo', 'fuuu'], 'hello' => [['echo', 'fuuu'], 'hello']
'echo hello world' => [['/bin/sh', '/bin/sh'], '-c', 'echo hello world']
'echo', 'hello', 'world' => [['echo', 'echo'], 'hello', 'world']
'true' => [['true', 'true']]

The args array may follow any of these variations:

to be run through the shell (single argument strings with spaces).
standard argv suitable for use with exec. This includes detecting commands
Converts the various supported command argument variations into a
def adjust_process_spawn_argv(args)
  if args.size == 1 && args[0].is_a?(String) && args[0] =~ /[ |>]/
    # single string with these characters means run it through the shell
    command_and_args = system_command_prefixes + [args[0]]
    [*command_and_args]
  elsif !args[0].respond_to?(:to_ary)
    # [argv0, argv1, ...]
    [[args[0], args[0]], *args[1..-1]]
  else
    # [[cmdname, argv0], argv1, ...]
    args
  end
end

def default_file_reopen_info(fd, file)

Returns a [file, flags, mode] tuple.

file - The string path to the file that fd should be redirected to.
streams.
object, integer fd number, or :in, :out, :err for one of the standard
fd - The file descriptor that is being redirected. This may be an IO

stderr default to write, while stdin and all other fds default to read.
default flags vary based on the what fd is being redirected. stdout and
The default [file, flags, mode] tuple for a given fd and filename. The
def default_file_reopen_info(fd, file)
  case fd
  when :in, STDIN, $stdin, 0
    [file, "r", 0644]
  when :out, STDOUT, $stdout, 1
    [file, "w", 0644]
  when :err, STDERR, $stderr, 2
    [file, "w", 0644]
  else
    [file, "r", 0644]
  end
end

def extract_process_spawn_arguments(*args)

non-nil. When no env or options are given, empty hashes are returned.
Returns an [env, argv, options] tuple. All elements are guaranteed to be

argv.
number of strings or an Array full of strings that make up the new process's
The env and options hashes are optional. The command may be a variable

Process::spawn([env], command, ..., [options])
The following method signature is supported:

extension functions.
simple [env, argv, options] tuple. This just makes life easier for the
Turns the various varargs incantations supported by Process::spawn into a
def extract_process_spawn_arguments(*args)
  # pop the options hash off the end if it's there
  options =
    if args[-1].respond_to?(:to_hash)
      args.pop.to_hash
    else
      {}
    end
  flatten_process_spawn_options!(options)
  normalize_process_spawn_redirect_file_options!(options)
  # shift the environ hash off the front if it's there and account for
  # possible :env key in options hash.
  env =
    if args[0].respond_to?(:to_hash)
      args.shift.to_hash
    else
      {}
    end
  env.merge!(options.delete(:env)) if options.key?(:env)
  # remaining arguments are the argv supporting a number of variations.
  argv = adjust_process_spawn_argv(args)
  [env, argv, options]
end

def fd?(object)

the symbolic names :in, :out, or :err.
Returns true if object is an instance of IO, Integer >= 0, or one of the

Determine whether object is fd-like.
def fd?(object)
  case object
  when Integer
    object >= 0
  when :in, :out, :err, STDIN, STDOUT, STDERR, $stdin, $stdout, $stderr, IO
    true
  else
    object.respond_to?(:to_io) && !object.to_io.nil?
  end
end

def fd_to_io(object)

Returns nil or an instance of IO.

Convert a fd identifier to an IO object.
def fd_to_io(object)
  case object
  when STDIN, STDOUT, STDERR, $stdin, $stdout, $stderr
    object
  when :in, 0
    STDIN
  when :out, 1
    STDOUT
  when :err, 2
    STDERR
  when Integer
    object >= 0 ? IO.for_fd(object) : nil
  when IO
    object
  else
    object.respond_to?(:to_io) ? object.to_io : nil
  end
end

def flatten_process_spawn_options!(options)

Returns the modified options hash.

options - The options hash. This is modified in place.

spawn implementations.
like: { fd1 => :close, fd2 => :close }. This just makes life easier for the
Convert { [fd1, fd2, ...] => (:close|fd) } options to individual keys,
def flatten_process_spawn_options!(options)
  options.to_a.each do |key, value|
    if key.respond_to?(:to_ary)
      key.to_ary.each { |fd| options[fd] = value }
      options.delete(key)
    end
  end
end

def fspawn(*args)

the POSIX::Spawn module documentation.
Ruby fork + exec. Supports the standard spawn interface as described in
Spawn a child process with a variety of options using a pure
def fspawn(*args)
  env, argv, options = extract_process_spawn_arguments(*args)
  valid_options = [:chdir, :unsetenv_others, :pgroup]
  if badopt = options.find{ |key,val| !fd?(key) && !valid_options.include?(key) }
    raise ArgumentError, "Invalid option: #{badopt[0].inspect}"
  elsif !argv.is_a?(Array) || !argv[0].is_a?(Array) || argv[0].size != 2
    raise ArgumentError, "Invalid command name"
  end
  fork do
    begin
      # handle FD => {FD, :close, [file,mode,perms]} options
      options.each do |key, val|
        if fd?(key)
          key = fd_to_io(key)
          if fd?(val)
            val = fd_to_io(val)
            key.reopen(val)
            if key.respond_to?(:close_on_exec=)
              key.close_on_exec = false
              val.close_on_exec = false
            end
          elsif val == :close
            if key.respond_to?(:close_on_exec=)
              key.close_on_exec = true
            else
              key.close
            end
          elsif val.is_a?(Array)
            file, mode_string, perms = *val
            key.reopen(File.open(file, mode_string, perms))
          end
        end
      end
      # setup child environment
      ENV.replace({}) if options[:unsetenv_others] == true
      env.each { |k, v| ENV[k] = v }
      # { :chdir => '/' } in options means change into that dir
      ::Dir.chdir(options[:chdir]) if options[:chdir]
      # { :pgroup => pgid } options
      pgroup = options[:pgroup]
      pgroup = 0 if pgroup == true
      Process::setpgid(0, pgroup) if pgroup
      # do the deed
      if RUBY_VERSION =~ /\A1\.8/
        ::Kernel::exec(*argv)
      else
        argv_and_options = argv + [{:close_others=>false}]
        ::Kernel::exec(*argv_and_options)
      end
    ensure
      exit!(127)
    end
  end
end

def normalize_process_spawn_redirect_file_options!(options)

Returns the modified options hash.

STDIN => '/some/file' => ['/some/file', 'r', 0644]
:err => '/some/file' => ['/some/file', 'w', 0644]
:out => '/some/file' => ['/some/file', 'w', 0644]
:in => '/some/file' => ['/some/file', 'r', 0644]

Convert variations of redirecting to a file to a standard tuple.
def normalize_process_spawn_redirect_file_options!(options)
  options.to_a.each do |key, value|
    next if !fd?(key)
    # convert string and short array values to
    if value.respond_to?(:to_str)
      value = default_file_reopen_info(key, value)
    elsif value.respond_to?(:to_ary) && value.size < 3
      defaults = default_file_reopen_info(key, value[0])
      value += defaults[value.size..-1]
    else
      value = nil
    end
    # replace string open mode flag maybe and replace original value
    if value
      value[1] = OFLAGS[value[1]] if value[1].respond_to?(:to_str)
      options[key] = value
    end
  end
end

def popen4(*argv)

to Process::waitpid or equivalent.
when finished and the child process's status must be collected by a call
readable IO objects. The caller should take care to close all IO objects
process's pid, stdin is a writeable IO object, and stdout / stderr are
Returns a [pid, stdin, stdout, stderr] tuple, where pid is the new

in the POSIX::Spawn module documentation.
the spawning process. Supports the standard spawn interface as described
Spawn a child process with all standard IO streams piped in and out of
def popen4(*argv)
  # create some pipes (see pipe(2) manual -- the ruby docs suck)
  ird, iwr = IO.pipe
  ord, owr = IO.pipe
  erd, ewr = IO.pipe
  # spawn the child process with either end of pipes hooked together
  opts =
    ((argv.pop if argv[-1].is_a?(Hash)) || {}).merge(
      # redirect fds        # close other sides
      :in  => ird,          iwr  => :close,
      :out => owr,          ord  => :close,
      :err => ewr,          erd  => :close
    )
  pid = spawn(*(argv + [opts]))
  [pid, iwr, ord, erd]
ensure
  # we're in the parent, close child-side fds
  [ird, owr, ewr].each { |fd| fd.close if fd }
end

def pspawn(*args)

loaded due to lack of platform support.
Raises NotImplementedError when the posix_spawn_ext module could not be

the POSIX::Spawn module documentation.
systems interfaces. Supports the standard spawn interface as described in
Spawn a child process with a variety of options using the posix_spawn(2)
def pspawn(*args)
  env, argv, options = extract_process_spawn_arguments(*args)
  raise NotImplementedError unless respond_to?(:_pspawn)
  if defined? JRUBY_VERSION
    # On the JVM, changes made to the environment are not propagated down
    # to C via get/setenv, so we have to fake it here.
    unless options[:unsetenv_others] == true
      env = ENV.merge(env)
      options[:unsetenv_others] = true
    end
  end
  _pspawn(env, argv, options)
end

def spawn(*args)

Raises any number of Errno:: exceptions on failure.
Returns the integer pid of the newly spawned process.

new child process.
options - Optional hash of operations to perform before executing the
argvN - Zero or more string program arguments (argv).
program to execute.
command - A string command name, or shell program, used to determine the
env - Optional hash specifying the new process's environment.

spawn([env], command, [argv1, ...], [options])

available implementation for the current platform and Ruby version.
Spawn a child process with a variety of options using the best
def spawn(*args)
  if respond_to?(:_pspawn)
    pspawn(*args)
  elsif ::Process.respond_to?(:spawn)
    ::Process::spawn(*args)
  else
    fspawn(*args)
  end
end

def system(*args)

non-zero exit.
Returns true if the command returns a zero exit status, or false for

This method is compatible with Kernel#system.

described in the POSIX::Spawn module documentation.
status is available as $?. Supports the standard spawn interface as
Executes a command and waits for it to complete. The command's exit
def system(*args)
  pid = spawn(*args)
  return false if pid <= 0
  ::Process.waitpid(pid)
  $?.exitstatus == 0
rescue Errno::ENOENT
  false
end

def system_command_prefixes

Returns a platform-specific [[, ], ] array.

[['/bin/sh', '/bin/sh'], '-c']
On all other systems, this will yield:

than 'cmd.exe', specify its path in ENV['COMSPEC']
is not specified. If you would like to use something other
Note: 'cmd.exe' is used if the COMSPEC environment variable
[['cmd.exe', 'cmd.exe'], '/c']
On a Windows machine, this will yield:

Derives the shell command to use when running the spawn.
def system_command_prefixes
  if RUBY_PLATFORM =~ /(mswin|mingw|cygwin|bccwin)/
    sh = ENV['COMSPEC'] || 'cmd.exe'
    [[sh, sh], '/c']
  else
    [['/bin/sh', '/bin/sh'], '-c']
  end
end