posix-spawn-0.3.13/0000755000175000017500000000000013041401323013711 5ustar uwabamiuwabamiposix-spawn-0.3.13/Rakefile0000644000175000017500000000212313041401323015354 0ustar uwabamiuwabamitask :default => :test # ========================================================== # Packaging # ========================================================== GEMSPEC = eval(File.read('posix-spawn.gemspec')) require 'rubygems/package_task' Gem::PackageTask.new(GEMSPEC) do |pkg| end # ========================================================== # Ruby Extension # ========================================================== begin require 'rake/extensiontask' rescue LoadError => boom warn "ERROR: The rake-compiler gem dependency is missing." warn "Please run `bundle install' and try again." raise end Rake::ExtensionTask.new('posix_spawn_ext', GEMSPEC) do |ext| ext.ext_dir = 'ext' end task :build => :compile # ========================================================== # Testing # ========================================================== require 'rake/testtask' Rake::TestTask.new 'test' do |t| t.libs << "test" t.test_files = FileList['test/test_*.rb'] end task :test => :build desc 'Run some benchmarks' task :benchmark => :build do ruby '-Ilib', 'bin/posix-spawn-benchmark' end posix-spawn-0.3.13/bin/0000755000175000017500000000000013041401323014461 5ustar uwabamiuwabamiposix-spawn-0.3.13/bin/posix-spawn-benchmark0000755000175000017500000000066313041401323020634 0ustar uwabamiuwabami#!/usr/bin/env ruby # frozen_string_literal: true # # This file was generated by Bundler. # # The application 'posix-spawn-benchmark' is installed as part of a gem, and # this file is here to facilitate running it. # require "pathname" ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", Pathname.new(__FILE__).realpath) require "rubygems" require "bundler/setup" load Gem.bin_path("posix-spawn", "posix-spawn-benchmark") posix-spawn-0.3.13/Gemfile0000644000175000017500000000004613041401323015204 0ustar uwabamiuwabamisource 'https://rubygems.org' gemspec posix-spawn-0.3.13/posix-spawn.gemspec0000644000175000017500000000136613041401323017554 0ustar uwabamiuwabamirequire File.expand_path('../lib/posix/spawn/version', __FILE__) Gem::Specification.new do |s| s.name = 'posix-spawn' s.version = POSIX::Spawn::VERSION s.summary = 'posix_spawnp(2) for ruby' s.description = 'posix-spawn uses posix_spawnp(2) for faster process spawning' s.homepage = 'https://github.com/rtomayko/posix-spawn' s.authors = ['Ryan Tomayko', 'Aman Gupta'] s.email = ['r@tomayko.com', 'aman@tmm1.net'] s.licenses = ['MIT'] s.add_development_dependency 'rake-compiler', '0.7.6' s.add_development_dependency 'minitest', '>= 4' s.extensions = ['ext/extconf.rb'] s.executables << 'posix-spawn-benchmark' s.require_paths = ['lib'] s.files = `git ls-files`.split("\n") s.extra_rdoc_files = %w[ COPYING HACKING ] end posix-spawn-0.3.13/lib/0000755000175000017500000000000013041401323014457 5ustar uwabamiuwabamiposix-spawn-0.3.13/lib/posix/0000755000175000017500000000000013041401323015621 5ustar uwabamiuwabamiposix-spawn-0.3.13/lib/posix/spawn.rb0000644000175000017500000004560513041401323017310 0ustar uwabamiuwabamiunless RUBY_PLATFORM =~ /(mswin|mingw|cygwin|bccwin)/ require 'posix_spawn_ext' end require 'posix/spawn/version' require 'posix/spawn/child' class IO if defined? JRUBY_VERSION require 'jruby' def posix_fileno case self when STDIN, $stdin 0 when STDOUT, $stdout 1 when STDERR, $stderr 2 else JRuby.reference(self).getOpenFile.getMainStream.getDescriptor.getChannel.getFDVal end end else alias :posix_fileno :fileno end end module POSIX # The POSIX::Spawn module implements a compatible subset of Ruby 1.9's # Process::spawn and related methods using the IEEE Std 1003.1 posix_spawn(2) # system interfaces where available, or a pure Ruby fork/exec based # implementation when not. # # In Ruby 1.9, a versatile new process spawning interface was added # (Process::spawn) as the foundation for enhanced versions of existing # process-related methods like Kernel#system, Kernel#`, and IO#popen. These # methods are backward compatible with their Ruby 1.8 counterparts but # support a large number of new options. The POSIX::Spawn module implements # many of these methods with support for most of Ruby 1.9's features. # # The argument signatures for all of these methods follow a new convention, # making it possible to take advantage of Process::spawn features: # # spawn([env], command, [argv1, ...], [options]) # system([env], command, [argv1, ...], [options]) # popen([[env], command, [argv1, ...]], mode="r", [options]) # # The env, command, and options arguments are described below. # # == Environment # # If a hash is given in the first argument (env), the child process's # environment becomes a merge of the parent's and any modifications # specified in the hash. When a value in env is nil, the variable is # unset in the child: # # # set FOO as BAR and unset BAZ. # spawn({"FOO" => "BAR", "BAZ" => nil}, 'echo', 'hello world') # # == Command # # The command and optional argvN string arguments specify the command to # execute and any program arguments. When only command is given and # includes a space character, the command text is executed by the system # shell interpreter, as if by: # # /bin/sh -c 'command' # # When command does not include a space character, or one or more argvN # arguments are given, the command is executed as if by execve(2) with # each argument forming the new program's argv. # # NOTE: Use of the shell variation is generally discouraged unless you # indeed want to execute a shell program. Specifying an explicitly argv is # typically more secure and less error prone in most cases. # # == Options # # When a hash is given in the last argument (options), it specifies a # current directory and zero or more fd redirects for the child process. # # The :chdir option specifies the current directory. Note that :chdir is not # thread-safe on systems that provide posix_spawn(2), because it forces a # temporary change of the working directory of the calling process. # # spawn(command, :chdir => "/var/tmp") # # The :in, :out, :err, an Integer, an IO object or an Array option specify # fd redirection. For example, stderr can be merged into stdout as follows: # # spawn(command, :err => :out) # spawn(command, 2 => 1) # spawn(command, STDERR => :out) # spawn(command, STDERR => STDOUT) # # The key is a fd in the newly spawned child process (stderr in this case). # The value is a fd in the parent process (stdout in this case). # # You can also specify a filename for redirection instead of an fd: # # spawn(command, :in => "/dev/null") # read mode # spawn(command, :out => "/dev/null") # write mode # spawn(command, :err => "log") # write mode # spawn(command, 3 => "/dev/null") # read mode # # When redirecting to stdout or stderr, the files are opened in write mode; # otherwise, read mode is used. # # It's also possible to control the open flags and file permissions # directly by passing an array value: # # spawn(command, :in=>["file"]) # read mode assumed # spawn(command, :in=>["file", "r"]) # explicit read mode # spawn(command, :out=>["log", "w"]) # explicit write mode, 0644 assumed # spawn(command, :out=>["log", "w", 0600]) # spawn(command, :out=>["log", File::APPEND | File::CREAT, 0600]) # # The array is a [filename, open_mode, perms] tuple. open_mode can be a # string or an integer. When open_mode is omitted or nil, File::RDONLY is # assumed. The perms element should be an integer. When perms is omitted or # nil, 0644 is assumed. # # The :close It's possible to direct an fd be closed in the child process. This is # important for implementing `popen`-style logic and other forms of IPC between # processes using `IO.pipe`: # # rd, wr = IO.pipe # pid = spawn('echo', 'hello world', rd => :close, :stdout => wr) # wr.close # output = rd.read # Process.wait(pid) # # == Spawn Implementation # # The POSIX::Spawn#spawn method uses the best available implementation given # the current platform and Ruby version. In order of preference, they are: # # 1. The posix_spawn based C extension method (pspawn). # 2. Process::spawn when available (Ruby 1.9 only). # 3. A simple pure-Ruby fork/exec based spawn implementation compatible # with Ruby >= 1.8.7. # module Spawn extend self # Spawn a child process with a variety of options using the best # available implementation for the current platform and Ruby version. # # spawn([env], command, [argv1, ...], [options]) # # env - Optional hash specifying the new process's environment. # command - A string command name, or shell program, used to determine the # program to execute. # argvN - Zero or more string program arguments (argv). # options - Optional hash of operations to perform before executing the # new child process. # # Returns the integer pid of the newly spawned process. # Raises any number of Errno:: exceptions on failure. def spawn(*args) if respond_to?(:_pspawn) pspawn(*args) elsif ::Process.respond_to?(:spawn) ::Process::spawn(*args) else fspawn(*args) end end # Spawn a child process with a variety of options using the posix_spawn(2) # systems interfaces. Supports the standard spawn interface as described in # the POSIX::Spawn module documentation. # # Raises NotImplementedError when the posix_spawn_ext module could not be # loaded due to lack of platform support. 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 # Spawn a child process with a variety of options using a pure # Ruby fork + exec. Supports the standard spawn interface as described in # the POSIX::Spawn module documentation. 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 # Executes a command and waits for it to complete. The command's exit # status is available as $?. Supports the standard spawn interface as # described in the POSIX::Spawn module documentation. # # This method is compatible with Kernel#system. # # Returns true if the command returns a zero exit status, or false for # non-zero exit. def system(*args) pid = spawn(*args) return false if pid <= 0 ::Process.waitpid(pid) $?.exitstatus == 0 rescue Errno::ENOENT false end # Executes a command in a subshell using the system's shell interpreter # and returns anything written to the new process's stdout. This method # is compatible with Kernel#`. # # Returns the String output of the command. 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 # Spawn a child process with all standard IO streams piped in and out of # the spawning process. Supports the standard spawn interface as described # in the POSIX::Spawn module documentation. # # Returns a [pid, stdin, stdout, stderr] tuple, where pid is the new # process's pid, stdin is a writeable IO object, and stdout / stderr are # readable IO objects. The caller should take care to close all IO objects # when finished and the child process's status must be collected by a call # to Process::waitpid or equivalent. 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 ## # Process::Spawn::Child Exceptions # Exception raised when the total number of bytes output on the command's # stderr and stdout streams exceeds the maximum output size (:max option). # Currently class MaximumOutputExceeded < StandardError end # Exception raised when timeout is exceeded. class TimeoutExceeded < StandardError end private # Turns the various varargs incantations supported by Process::spawn into a # simple [env, argv, options] tuple. This just makes life easier for the # extension functions. # # The following method signature is supported: # Process::spawn([env], command, ..., [options]) # # The env and options hashes are optional. The command may be a variable # number of strings or an Array full of strings that make up the new process's # argv. # # Returns an [env, argv, options] tuple. All elements are guaranteed to be # non-nil. When no env or options are given, empty hashes are returned. 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 # Convert { [fd1, fd2, ...] => (:close|fd) } options to individual keys, # like: { fd1 => :close, fd2 => :close }. This just makes life easier for the # spawn implementations. # # options - The options hash. This is modified in place. # # Returns the modified options hash. 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 # Mapping of string open modes to integer oflag versions. OFLAGS = { "r" => File::RDONLY, "r+" => File::RDWR | File::CREAT, "w" => File::WRONLY | File::CREAT | File::TRUNC, "w+" => File::RDWR | File::CREAT | File::TRUNC, "a" => File::WRONLY | File::APPEND | File::CREAT, "a+" => File::RDWR | File::APPEND | File::CREAT } # Convert variations of redirecting to a file to a standard tuple. # # :in => '/some/file' => ['/some/file', 'r', 0644] # :out => '/some/file' => ['/some/file', 'w', 0644] # :err => '/some/file' => ['/some/file', 'w', 0644] # STDIN => '/some/file' => ['/some/file', 'r', 0644] # # Returns the modified options hash. 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 # The default [file, flags, mode] tuple for a given fd and filename. The # default flags vary based on the what fd is being redirected. stdout and # stderr default to write, while stdin and all other fds default to read. # # fd - The file descriptor that is being redirected. This may be an IO # object, integer fd number, or :in, :out, :err for one of the standard # streams. # file - The string path to the file that fd should be redirected to. # # Returns a [file, flags, mode] tuple. 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 # Determine whether object is fd-like. # # Returns true if object is an instance of IO, Integer >= 0, or one of the # the symbolic names :in, :out, or :err. 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 # Convert a fd identifier to an IO object. # # Returns nil or an instance of IO. 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 # Derives the shell command to use when running the spawn. # # On a Windows machine, this will yield: # [['cmd.exe', 'cmd.exe'], '/c'] # Note: 'cmd.exe' is used if the COMSPEC environment variable # is not specified. If you would like to use something other # than 'cmd.exe', specify its path in ENV['COMSPEC'] # # On all other systems, this will yield: # [['/bin/sh', '/bin/sh'], '-c'] # # Returns a platform-specific [[, ], ] array. 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 # Converts the various supported command argument variations into a # standard argv suitable for use with exec. This includes detecting commands # to be run through the shell (single argument strings with spaces). # # The args array may follow any of these variations: # # 'true' => [['true', 'true']] # 'echo', 'hello', 'world' => [['echo', 'echo'], 'hello', 'world'] # 'echo hello world' => [['/bin/sh', '/bin/sh'], '-c', 'echo hello world'] # ['echo', 'fuuu'], 'hello' => [['echo', 'fuuu'], 'hello'] # # Returns a [[cmdname, argv0], argv1, ...] array. def adjust_process_spawn_argv(args) if args.size == 1 && 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 end end posix-spawn-0.3.13/lib/posix/spawn/0000755000175000017500000000000013041401323016751 5ustar uwabamiuwabamiposix-spawn-0.3.13/lib/posix/spawn/child.rb0000644000175000017500000002436513041401323020373 0ustar uwabamiuwabamirequire 'posix/spawn' module POSIX module Spawn # POSIX::Spawn::Child includes logic for executing child processes and # reading/writing from their standard input, output, and error streams. It's # designed to take all input in a single string and provides all output # (stderr and stdout) as single strings and is therefore not well-suited # to streaming large quantities of data in and out of commands. # # Create and run a process to completion: # # >> child = POSIX::Spawn::Child.new('git', '--help') # # Retrieve stdout or stderr output: # # >> child.out # => "usage: git [--version] [--exec-path[=GIT_EXEC_PATH]]\n ..." # >> child.err # => "" # # Check process exit status information: # # >> child.status # => # # # To write data on the new process's stdin immediately after spawning: # # >> child = POSIX::Spawn::Child.new('bc', :input => '40 + 2') # >> child.out # "42\n" # # To access output from the process even if an exception was raised: # # >> child = POSIX::Spawn::Child.build('git', 'log', :max => 1000) # >> begin # ?> child.exec! # ?> rescue POSIX::Spawn::MaximumOutputExceeded # ?> # just so you know # ?> end # >> child.out # "... first 1000 characters of log output ..." # # Q: Why use POSIX::Spawn::Child instead of popen3, hand rolled fork/exec # code, or Process::spawn? # # - It's more efficient than popen3 and provides meaningful process # hierarchies because it performs a single fork/exec. (popen3 double forks # to avoid needing to collect the exit status and also calls # Process::detach which creates a Ruby Thread!!!!). # # - It handles all max pipe buffer (PIPE_BUF) hang cases when reading and # writing semi-large amounts of data. This is non-trivial to implement # correctly and must be accounted for with popen3, spawn, or hand rolled # fork/exec code. # # - It's more portable than hand rolled pipe, fork, exec code because # fork(2) and exec aren't available on all platforms. In those cases, # POSIX::Spawn::Child falls back to using whatever janky substitutes # the platform provides. class Child include POSIX::Spawn # Spawn a new process, write all input and read all output, and wait for # the program to exit. Supports the standard spawn interface as described # in the POSIX::Spawn module documentation: # # new([env], command, [argv1, ...], [options]) # # The following options are supported in addition to the standard # POSIX::Spawn options: # # :input => str Write str to the new process's standard input. # :timeout => int Maximum number of seconds to allow the process # to execute before aborting with a TimeoutExceeded # exception. # :max => total Maximum number of bytes of output to allow the # process to generate before aborting with a # MaximumOutputExceeded exception. # :pgroup_kill => bool Boolean specifying whether to kill the process # group (true) or individual process (false, default). # Setting this option true implies :pgroup => true. # # Returns a new Child instance whose underlying process has already # executed to completion. The out, err, and status attributes are # immediately available. def initialize(*args) @env, @argv, options = extract_process_spawn_arguments(*args) @options = options.dup @input = @options.delete(:input) @timeout = @options.delete(:timeout) @max = @options.delete(:max) if @options.delete(:pgroup_kill) @pgroup_kill = true @options[:pgroup] = true end @options.delete(:chdir) if @options[:chdir].nil? exec! if !@options.delete(:noexec) end # Set up a new process to spawn, but do not actually spawn it. # # Invoke this just like the normal constructor to set up a process # to be run. Call `exec!` to actually run the child process, send # the input, read the output, and wait for completion. Use this # alternative way of constructing a POSIX::Spawn::Child if you want # to read any partial output from the child process even after an # exception. # # child = POSIX::Spawn::Child.build(... arguments ...) # child.exec! # # The arguments are the same as the regular constructor. # # Returns a new Child instance but does not run the underlying process. def self.build(*args) options = if args[-1].respond_to?(:to_hash) args.pop.to_hash else {} end new(*(args + [{ :noexec => true }.merge(options)])) end # All data written to the child process's stdout stream as a String. attr_reader :out # All data written to the child process's stderr stream as a String. attr_reader :err # A Process::Status object with information on how the child exited. attr_reader :status # Total command execution time (wall-clock time) attr_reader :runtime # The pid of the spawned child process. This is unlikely to be a valid # current pid since Child#exec! doesn't return until the process finishes # and is reaped. attr_reader :pid # Determine if the process did exit with a zero exit status. def success? @status && @status.success? end # Execute command, write input, and read output. This is called # immediately when a new instance of this object is created, or # can be called explicitly when creating the Child via `build`. def exec! # spawn the process and hook up the pipes pid, stdin, stdout, stderr = popen4(@env, *(@argv + [@options])) @pid = pid # async read from all streams into buffers read_and_write(@input, stdin, stdout, stderr, @timeout, @max) # grab exit status @status = waitpid(pid) rescue Object [stdin, stdout, stderr].each { |fd| fd.close rescue nil } if @status.nil? if !@pgroup_kill ::Process.kill('TERM', pid) rescue nil else ::Process.kill('-TERM', pid) rescue nil end @status = waitpid(pid) rescue nil end raise ensure # let's be absolutely certain these are closed [stdin, stdout, stderr].each { |fd| fd.close rescue nil } end private # Maximum buffer size for reading BUFSIZE = (32 * 1024) # Start a select loop writing any input on the child's stdin and reading # any output from the child's stdout or stderr. # # input - String input to write on stdin. May be nil. # stdin - The write side IO object for the child's stdin stream. # stdout - The read side IO object for the child's stdout stream. # stderr - The read side IO object for the child's stderr stream. # timeout - An optional Numeric specifying the total number of seconds # the read/write operations should occur for. # # Returns an [out, err] tuple where both elements are strings with all # data written to the stdout and stderr streams, respectively. # Raises TimeoutExceeded when all data has not been read / written within # the duration specified in the timeout argument. # Raises MaximumOutputExceeded when the total number of bytes output # exceeds the amount specified by the max argument. def read_and_write(input, stdin, stdout, stderr, timeout=nil, max=nil) max = nil if max && max <= 0 @out, @err = '', '' # force all string and IO encodings to BINARY under 1.9 for now if @out.respond_to?(:force_encoding) and stdin.respond_to?(:set_encoding) [stdin, stdout, stderr].each do |fd| fd.set_encoding('BINARY', 'BINARY') end @out.force_encoding('BINARY') @err.force_encoding('BINARY') input = input.dup.force_encoding('BINARY') if input end timeout = nil if timeout && timeout <= 0.0 @runtime = 0.0 start = Time.now readers = [stdout, stderr] writers = if input [stdin] else stdin.close [] end slice_method = input.respond_to?(:byteslice) ? :byteslice : :slice t = timeout while readers.any? || writers.any? ready = IO.select(readers, writers, readers + writers, t) raise TimeoutExceeded if ready.nil? # write to stdin stream ready[1].each do |fd| begin boom = nil size = fd.write_nonblock(input) input = input.send(slice_method, size..-1) rescue Errno::EPIPE => boom rescue Errno::EAGAIN, Errno::EINTR end if boom || input.bytesize == 0 stdin.close writers.delete(stdin) end end # read from stdout and stderr streams ready[0].each do |fd| buf = (fd == stdout) ? @out : @err begin buf << fd.readpartial(BUFSIZE) rescue Errno::EAGAIN, Errno::EINTR rescue EOFError readers.delete(fd) fd.close end end # keep tabs on the total amount of time we've spent here @runtime = Time.now - start if timeout t = timeout - @runtime raise TimeoutExceeded if t < 0.0 end # maybe we've hit our max output if max && ready[0].any? && (@out.size + @err.size) > max raise MaximumOutputExceeded end end [@out, @err] end # Wait for the child process to exit # # Returns the Process::Status object obtained by reaping the process. def waitpid(pid) ::Process::waitpid(pid) $? end end end end posix-spawn-0.3.13/lib/posix/spawn/version.rb0000644000175000017500000000007513041401323020765 0ustar uwabamiuwabamimodule POSIX module Spawn VERSION = '0.3.13' end end posix-spawn-0.3.13/lib/posix-spawn.rb0000644000175000017500000000002613041401323017272 0ustar uwabamiuwabamirequire "posix/spawn" posix-spawn-0.3.13/test/0000755000175000017500000000000013041401323014670 5ustar uwabamiuwabamiposix-spawn-0.3.13/test/test_backtick.rb0000644000175000017500000000151013041401323020024 0ustar uwabamiuwabamirequire 'test_helper' class BacktickTest < Minitest::Test include POSIX::Spawn def test_backtick_simple out = `exit` assert_equal '', out assert_equal 0, $?.exitstatus end def test_backtick_output out = `echo 123` assert_equal "123\n", out assert_equal 0, $?.exitstatus, 0 end def test_backtick_failure out = `nosuchcmd 2> /dev/null` assert_equal '', out assert_equal 127, $?.exitstatus end def test_backtick_redirect out = `nosuchcmd 2>&1` regex = %r{/bin/sh: (1: )?nosuchcmd: (command )?not found} assert regex.match(out), "Got #{out.inspect}, expected match of pattern #{regex.inspect}" assert_equal 127, $?.exitstatus, 127 end def test_backtick_huge out = `yes | head -50000` assert_equal 100000, out.size assert_equal 0, $?.exitstatus end end posix-spawn-0.3.13/test/test_spawn.rb0000644000175000017500000002602413041401323017410 0ustar uwabamiuwabamirequire 'test_helper' module SpawnImplementationTests def test_spawn_simple pid = _spawn('true') assert_process_exit_ok pid end def test_spawn_with_args pid = _spawn('true', 'with', 'some stuff') assert_process_exit_ok pid end def test_spawn_with_shell pid = _spawn('true && exit 13') assert_process_exit_status pid, 13 end def test_spawn_with_cmdname_and_argv0_tuple pid = _spawn(['true', 'not-true'], 'some', 'args', 'toooo') assert_process_exit_ok pid end def test_spawn_with_invalid_argv assert_raises ArgumentError do _spawn(['echo','b','c','d']) end end ## # Environ def test_spawn_inherit_env ENV['PSPAWN'] = 'parent' pid = _spawn('test "$PSPAWN" = "parent"') assert_process_exit_ok pid ensure ENV.delete('PSPAWN') end def test_spawn_clean_env ENV['PSPAWN'] = 'parent' pid = _spawn({'TEMP'=>'child'}, 'test -z "$PSPAWN" && test "$TEMP" = "child"', :unsetenv_others => true) assert_process_exit_ok pid ensure ENV.delete('PSPAWN') end def test_spawn_set_env ENV['PSPAWN'] = 'parent' pid = _spawn({'PSPAWN'=>'child'}, 'test "$PSPAWN" = "child"') assert_process_exit_ok pid ensure ENV.delete('PSPAWN') end def test_spawn_unset_env ENV['PSPAWN'] = 'parent' pid = _spawn({'PSPAWN'=>nil}, 'test -z "$PSPAWN"') assert_process_exit_ok pid ensure ENV.delete('PSPAWN') end ## # FD => :close options def test_sanity_of_checking_clone_with_sh rd, wr = IO.pipe pid = _spawn("exec 2>/dev/null 9<&#{rd.posix_fileno} || exit 1", rd => rd) assert_process_exit_status pid, 0 ensure [rd, wr].each { |fd| fd.close rescue nil } end def test_spawn_close_option_with_symbolic_standard_stream_names pid = _spawn('true 2>/dev/null 9<&0 || exit 1', :in => :close) assert_process_exit_status pid, 1 pid = _spawn('true 2>/dev/null 9>&1 8>&2 || exit 1', :out => :close, :err => :close) assert_process_exit_status pid, 1 end def test_spawn_close_on_standard_stream_io_object pid = _spawn('true 2>/dev/null 9<&0 || exit 1', STDIN => :close) assert_process_exit_status pid, 1 pid = _spawn('true 2>/dev/null 9>&1 8>&2 || exit 1', STDOUT => :close, STDOUT => :close) assert_process_exit_status pid, 1 end def test_spawn_close_option_with_fd_number rd, wr = IO.pipe pid = _spawn("true 2>/dev/null 9<&#{rd.posix_fileno} || exit 1", rd.posix_fileno => :close) assert_process_exit_status pid, 1 assert !rd.closed? assert !wr.closed? ensure [rd, wr].each { |fd| fd.close rescue nil } end def test_spawn_close_option_with_io_object rd, wr = IO.pipe pid = _spawn("true 2>/dev/null 9<&#{rd.posix_fileno} || exit 1", rd => :close) assert_process_exit_status pid, 1 assert !rd.closed? assert !wr.closed? ensure [rd, wr].each { |fd| fd.close rescue nil } end def test_spawn_close_invalid_fd_raises_exception pid = _spawn("echo", "hiya", 250 => :close) assert_process_exit_status pid, 127 rescue Errno::EBADF # this happens on darwin only. GNU does spawn and exits 127. end def test_spawn_invalid_chdir_raises_exception pid = _spawn("echo", "hiya", :chdir => "/this/does/not/exist") # fspawn does chdir in child, so it exits with 127 assert_process_exit_status pid, 127 rescue Errno::ENOENT # pspawn and native spawn do chdir in parent, so they throw an exception end def test_spawn_closing_multiple_fds_with_array_keys rd, wr = IO.pipe pid = _spawn("true 2>/dev/null 9>&#{wr.posix_fileno} || exit 1", [rd, wr, :out] => :close) assert_process_exit_status pid, 1 ensure [rd, wr].each { |fd| fd.close rescue nil } end ## # FD => FD options def test_spawn_redirect_fds_with_symbolic_names_and_io_objects rd, wr = IO.pipe pid = _spawn("echo", "hello world", :out => wr, rd => :close) wr.close output = rd.read assert_process_exit_ok pid assert_equal "hello world\n", output ensure [rd, wr].each { |fd| fd.close rescue nil } end def test_spawn_redirect_fds_with_fd_numbers rd, wr = IO.pipe pid = _spawn("echo", "hello world", 1 => wr.posix_fileno, rd.posix_fileno => :close) wr.close output = rd.read assert_process_exit_ok pid assert_equal "hello world\n", output ensure [rd, wr].each { |fd| fd.close rescue nil } end def test_spawn_redirect_invalid_fds_raises_exception pid = _spawn("echo", "hiya", 1 => 250) assert_process_exit_status pid, 127 rescue Errno::EBADF # this happens on darwin only. GNU does spawn and exits 127. end def test_spawn_redirect_stderr_and_stdout_to_same_fd rd, wr = IO.pipe pid = _spawn("echo hello world 1>&2", :err => wr, :out => wr, rd => :close) wr.close output = rd.read assert_process_exit_ok pid assert_equal "hello world\n", output ensure [rd, wr].each { |fd| fd.close rescue nil } end def test_spawn_does_not_close_fd_when_redirecting pid = _spawn("exec 2>&1", :err => :out) assert_process_exit_ok pid end # Ruby 1.9 Process::spawn closes all fds by default. To keep an fd open, you # have to pass it explicitly as fd => fd. def test_explicitly_passing_an_fd_as_open rd, wr = IO.pipe pid = _spawn("exec 9>&#{wr.posix_fileno} || exit 1", wr => wr) assert_process_exit_ok pid ensure [rd, wr].each { |fd| fd.close rescue nil } end ## # FD => file options def test_spawn_redirect_fd_to_file_with_symbolic_name file = File.expand_path('../test-output', __FILE__) text = 'redirect_fd_to_file_with_symbolic_name' pid = _spawn('echo', text, :out => file) assert_process_exit_ok pid assert File.exist?(file) assert_equal "#{text}\n", File.read(file) ensure File.unlink(file) rescue nil end def test_spawn_redirect_fd_to_file_with_fd_number file = File.expand_path('../test-output', __FILE__) text = 'redirect_fd_to_file_with_fd_number' pid = _spawn('echo', text, 1 => file) assert_process_exit_ok pid assert File.exist?(file) assert_equal "#{text}\n", File.read(file) ensure File.unlink(file) rescue nil end def test_spawn_redirect_fd_to_file_with_io_object file = File.expand_path('../test-output', __FILE__) text = 'redirect_fd_to_file_with_io_object' pid = _spawn('echo', text, STDOUT => file) assert_process_exit_ok pid assert File.exist?(file) assert_equal "#{text}\n", File.read(file) ensure File.unlink(file) rescue nil end def test_spawn_redirect_fd_from_file_with_symbolic_name file = File.expand_path('../test-input', __FILE__) text = 'redirect_fd_from_file_with_symbolic_name' File.open(file, 'w') { |fd| fd.write(text) } pid = _spawn(%Q{test "$(cat)" = "#{text}"}, :in => file) assert_process_exit_ok pid ensure File.unlink(file) rescue nil end def test_spawn_redirect_fd_from_file_with_fd_number file = File.expand_path('../test-input', __FILE__) text = 'redirect_fd_from_file_with_fd_number' File.open(file, 'w') { |fd| fd.write(text) } pid = _spawn(%Q{test "$(cat)" = "#{text}"}, 0 => file) assert_process_exit_ok pid ensure File.unlink(file) rescue nil end def test_spawn_redirect_fd_from_file_with_io_object file = File.expand_path('../test-input', __FILE__) text = 'redirect_fd_from_file_with_io_object' File.open(file, 'w') { |fd| fd.write(text) } pid = _spawn(%Q{test "$(cat)" = "#{text}"}, STDIN => file) assert_process_exit_ok pid ensure File.unlink(file) rescue nil end def test_spawn_redirect_fd_to_file_with_symbolic_name_and_flags file = File.expand_path('../test-output', __FILE__) text = 'redirect_fd_to_file_with_symbolic_name' 5.times do pid = _spawn('echo', text, :out => [file, 'a']) assert_process_exit_ok pid end assert File.exist?(file) assert_equal "#{text}\n" * 5, File.read(file) ensure File.unlink(file) rescue nil end ## # :pgroup => def test_spawn_inherit_pgroup_from_parent_by_default pgrp = Process.getpgrp pid = _spawn("ruby", "-e", "exit(Process.getpgrp == #{pgrp} ? 0 : 1)") assert_process_exit_ok pid end def test_spawn_inherit_pgroup_from_parent_when_nil pgrp = Process.getpgrp pid = _spawn("ruby", "-e", "exit(Process.getpgrp == #{pgrp} ? 0 : 1)", :pgroup => nil) assert_process_exit_ok pid end def test_spawn_new_pgroup_with_true pid = _spawn("ruby", "-e", "exit(Process.getpgrp == $$ ? 0 : 1)", :pgroup => true) assert_process_exit_ok pid end def test_spawn_new_pgroup_with_zero pid = _spawn("ruby", "-e", "exit(Process.getpgrp == $$ ? 0 : 1)", :pgroup => 0) assert_process_exit_ok pid end def test_spawn_explicit_pgroup pgrp = Process.getpgrp pid = _spawn("ruby", "-e", "exit(Process.getpgrp == #{pgrp} ? 0 : 1)", :pgroup => pgrp) assert_process_exit_ok pid end ## # Exceptions def test_spawn_raises_exception_on_unsupported_options exception = nil assert_raises ArgumentError do begin _spawn('echo howdy', :out => '/dev/null', :oops => 'blaahh') rescue Exception => e exception = e raise e end end assert_match /oops/, exception.message end ## # Assertion Helpers def assert_process_exit_ok(pid) assert_process_exit_status pid, 0 end def assert_process_exit_status(pid, status) assert pid.to_i > 0, "pid [#{pid}] should be > 0" chpid = ::Process.wait(pid) assert_equal chpid, pid assert_equal status, $?.exitstatus end end class SpawnTest < Minitest::Test include POSIX::Spawn def test_spawn_methods_exposed_at_module_level assert POSIX::Spawn.respond_to?(:pspawn) assert POSIX::Spawn.respond_to?(:_pspawn) end ## # Options Preprocessing def test_extract_process_spawn_arguments_with_options assert_equal [{}, [['echo', 'echo'], 'hello', 'world'], {:err => :close}], extract_process_spawn_arguments('echo', 'hello', 'world', :err => :close) end def test_extract_process_spawn_arguments_with_options_and_env options = {:err => :close} env = {'X' => 'Y'} assert_equal [env, [['echo', 'echo'], 'hello world'], options], extract_process_spawn_arguments(env, 'echo', 'hello world', options) end def test_extract_process_spawn_arguments_with_shell_command assert_equal [{}, [['/bin/sh', '/bin/sh'], '-c', 'echo hello world'], {}], extract_process_spawn_arguments('echo hello world') end def test_extract_process_spawn_arguments_with_special_cmdname_argv_tuple assert_equal [{}, [['echo', 'fuuu'], 'hello world'], {}], extract_process_spawn_arguments(['echo', 'fuuu'], 'hello world') end end class PosixSpawnTest < Minitest::Test include SpawnImplementationTests def _spawn(*argv) POSIX::Spawn.pspawn(*argv) end end class ForkSpawnTest < Minitest::Test include SpawnImplementationTests def _spawn(*argv) POSIX::Spawn.fspawn(*argv) end end if ::Process::respond_to?(:spawn) class NativeSpawnTest < Minitest::Test include SpawnImplementationTests def _spawn(*argv) ::Process.spawn(*argv) end end end posix-spawn-0.3.13/test/test_child.rb0000644000175000017500000001542313041401323017344 0ustar uwabamiuwabami# coding: UTF-8 require 'test_helper' class ChildTest < Minitest::Test include POSIX::Spawn # Become a new process group. def setup Process.setpgrp end # Kill any orphaned processes in our process group before continuing but # ignore the TERM signal we receive. def teardown trap("TERM") { trap("TERM", "DEFAULT") } begin Process.kill("-TERM", Process.pid) Process.wait rescue Errno::ECHILD end end # verify the process is no longer running and has been reaped. def assert_process_reaped(pid) Process.kill(0, pid) assert false, "Process #{pid} still running" rescue Errno::ESRCH end # verifies that all processes in the given process group are no longer running # and have been reaped. The current ruby test process is excluded. # XXX It's weird to use the SUT here but the :pgroup option is useful. Could # be a IO.popen under Ruby >= 1.9 since it also supports :pgroup. def assert_process_group_reaped(pgid) command = "ps axo pgid,pid,args | grep '^#{pgid} ' | grep -v '^#{pgid} #$$'" procs = POSIX::Spawn::Child.new(command, :pgroup => true).out assert procs.empty?, "Processes in group #{pgid} still running:\n#{procs}" end def test_sanity assert_same POSIX::Spawn::Child, Child end def test_argv_array_execs p = Child.new('printf', '%s %s %s', '1', '2', '3 4') assert p.success? assert_equal "1 2 3 4", p.out end def test_argv_string_uses_sh p = Child.new("echo via /bin/sh") assert p.success? assert_equal "via /bin/sh\n", p.out end def test_stdout p = Child.new('echo', 'boom') assert_equal "boom\n", p.out assert_equal "", p.err end def test_stderr p = Child.new('echo boom 1>&2') assert_equal "", p.out assert_equal "boom\n", p.err end def test_status p = Child.new('exit 3') assert !p.status.success? assert_equal 3, p.status.exitstatus end def test_env p = Child.new({ 'FOO' => 'BOOYAH' }, 'echo $FOO') assert_equal "BOOYAH\n", p.out end def test_chdir p = Child.new("pwd", :chdir => File.dirname(Dir.pwd)) assert_equal File.dirname(Dir.pwd) + "\n", p.out end def test_input input = "HEY NOW\n" * 100_000 # 800K p = Child.new('wc', '-l', :input => input) assert_equal 100_000, p.out.strip.to_i end def test_max child = Child.build('yes', :max => 100_000) assert_raises(MaximumOutputExceeded) { child.exec! } assert_process_reaped child.pid assert_process_group_reaped Process.pid end def test_max_pgroup_kill child = Child.build('yes', :max => 100_000, :pgroup_kill => true) assert_raises(MaximumOutputExceeded) { child.exec! } assert_process_reaped child.pid assert_process_group_reaped child.pid end def test_max_with_child_hierarchy child = Child.build('/bin/sh', '-c', 'true && yes', :max => 100_000) assert_raises(MaximumOutputExceeded) { child.exec! } assert_process_reaped child.pid assert_process_group_reaped Process.pid end def test_max_with_child_hierarchy_pgroup_kill child = Child.build('/bin/sh', '-c', 'true && yes', :max => 100_000, :pgroup_kill => true) assert_raises(MaximumOutputExceeded) { child.exec! } assert_process_reaped child.pid assert_process_group_reaped child.pid end def test_max_with_stubborn_child child = Child.build("trap '' TERM; yes", :max => 100_000) assert_raises(MaximumOutputExceeded) { child.exec! } assert_process_reaped child.pid assert_process_group_reaped Process.pid end def test_max_with_stubborn_child_pgroup_kill child = Child.build("trap '' TERM; yes", :max => 100_000, :pgroup_kill => true) assert_raises(MaximumOutputExceeded) { child.exec! } assert_process_reaped child.pid assert_process_group_reaped child.pid end def test_max_with_partial_output p = Child.build('yes', :max => 100_000) assert_nil p.out assert_raises MaximumOutputExceeded do p.exec! end assert_output_exceeds_repeated_string("y\n", 100_000, p.out) assert_process_reaped p.pid assert_process_group_reaped Process.pid end def test_max_with_partial_output_long_lines p = Child.build('yes', "nice to meet you", :max => 10_000) assert_raises MaximumOutputExceeded do p.exec! end assert_output_exceeds_repeated_string("nice to meet you\n", 10_000, p.out) assert_process_reaped p.pid assert_process_group_reaped Process.pid end def test_timeout start = Time.now child = Child.build('sleep', '1', :timeout => 0.05) assert_raises(TimeoutExceeded) { child.exec! } assert_process_reaped child.pid assert_process_group_reaped Process.pid assert (Time.now-start) <= 0.2 end def test_timeout_pgroup_kill start = Time.now child = Child.build('sleep', '1', :timeout => 0.05, :pgroup_kill => true) assert_raises(TimeoutExceeded) { child.exec! } assert_process_reaped child.pid assert_process_group_reaped child.pid assert (Time.now-start) <= 0.2 end def test_timeout_with_child_hierarchy child = Child.build('/bin/sh', '-c', 'true && sleep 1', :timeout => 0.05) assert_raises(TimeoutExceeded) { child.exec! } assert_process_reaped child.pid end def test_timeout_with_child_hierarchy_pgroup_kill child = Child.build('/bin/sh', '-c', 'true && sleep 1', :timeout => 0.05, :pgroup_kill => true) assert_raises(TimeoutExceeded) { child.exec! } assert_process_reaped child.pid assert_process_group_reaped child.pid end def test_timeout_with_partial_output start = Time.now p = Child.build('echo Hello; sleep 1', :timeout => 0.05, :pgroup_kill => true) assert_raises(TimeoutExceeded) { p.exec! } assert_process_reaped p.pid assert_process_group_reaped Process.pid assert (Time.now-start) <= 0.2 assert_equal "Hello\n", p.out end def test_lots_of_input_and_lots_of_output_at_the_same_time input = "stuff on stdin \n" * 1_000 command = " while read line do echo stuff on stdout; echo stuff on stderr 1>&2; done " p = Child.new(command, :input => input) assert_equal input.size, p.out.size assert_equal input.size, p.err.size assert p.success? end def test_input_cannot_be_written_due_to_broken_pipe input = "1" * 100_000 p = Child.new('false', :input => input) assert !p.success? end def test_utf8_input input = "hålø" p = Child.new('cat', :input => input) assert p.success? end def test_utf8_input_long input = "hålø" * 10_000 p = Child.new('cat', :input => input) assert p.success? end ## # Assertion Helpers def assert_output_exceeds_repeated_string(str, len, actual) assert_operator actual.length, :>=, len expected = (str * (len / str.length + 1)).slice(0, len) assert_equal expected, actual.slice(0, len) end end posix-spawn-0.3.13/test/test_helper.rb0000644000175000017500000000035013041401323017531 0ustar uwabamiuwabamirequire 'minitest/autorun' require 'posix-spawn' if Minitest.const_defined?('Test') # We're on Minitest 5+. Nothing to do here. else # Minitest 4 doesn't have Minitest::Test yet. Minitest::Test = MiniTest::Unit::TestCase end posix-spawn-0.3.13/test/test_system.rb0000644000175000017500000000102513041401323017576 0ustar uwabamiuwabamirequire 'test_helper' class SystemTest < Minitest::Test include POSIX::Spawn def test_system ret = system("true") assert_equal true, ret assert_equal 0, $?.exitstatus end def test_system_nonzero ret = system("false") assert_equal false, ret assert_equal 1, $?.exitstatus end def test_system_nonzero_via_sh ret = system("exit 1") assert_equal false, ret assert_equal 1, $?.exitstatus end def test_system_failure ret = system("nosuch") assert_equal false, ret end end posix-spawn-0.3.13/test/test_popen.rb0000644000175000017500000000051513041401323017376 0ustar uwabamiuwabamirequire 'test_helper' class PopenTest < Minitest::Test include POSIX::Spawn def test_popen4 pid, i, o, e = popen4("cat") i.write "hello world" i.close ::Process.wait(pid) assert_equal "hello world", o.read assert_equal 0, $?.exitstatus ensure [i, o, e].each{ |io| io.close rescue nil } end end posix-spawn-0.3.13/HACKING0000644000175000017500000000127713041401323014707 0ustar uwabamiuwabamiClone the project: git clone http://github.com/rtomayko/posix-spawn.git cd posix-spawn bundle install Rake tasks can be run without further setup: rake build rake test rake benchmark Just `rake' builds the extension and runs the tests. If you want to run the benchmark scripts or tests directly out of a working copy, first setup your PATH and RUBYLIB environment: PATH="$(pwd)/bin:$PATH" RUBYLIB="$(pwd)/lib:$(pwd)/ext:$RUBYLIB" export RUBYLIB Or, use the following rbdev script to quickly setup your PATH and RUBYLIB environment for this and other projects adhering to the Ruby Packaging Standard: https://github.com/rtomayko/dotfiles/blob/rtomayko/bin/rbdev posix-spawn-0.3.13/TODO0000644000175000017500000000163613041401323014407 0ustar uwabamiuwabami[x] license (LGPL) [x] fucking name this thing [x] fastspawn-bm should take iterations and memsize arguments [x] high level Grit::Process like class (string based) (tmm1) [x] add FD => '/path/to/file' (all variations) (rtomayko) [x] raise exception on unhandled options [x] benchmarks in README (tmm1) [x] POSIX::Spawn::spawn usage examples in README [x] POSIX::Spawn#pspawn should be just #spawn [x] :err => :out case -- currently closing out after dup2'ing [x] POSIX::Spawn::Process.new should have same method signature as Process::spawn [x] POSIX::Spawn::Process renamed to POSIX::Spawn::Child [x] Better POSIX::Spawn#spawn comment docs [x] POSIX::Spawn::Child usage examples in README [ ] popen* interfaces [x] system interface [x] ` interface [ ] jruby Grit::Process stuff [ ] make :vfork an option to Spawn#spawn [ ] check all posix_spawn_* function call return values [ ] POSIX::Spawn as ::Spawn? (maybe, we'll see) posix-spawn-0.3.13/ext/0000755000175000017500000000000013041401323014511 5ustar uwabamiuwabamiposix-spawn-0.3.13/ext/posix-spawn.c0000644000175000017500000003035513041401323017153 0ustar uwabamiuwabami/* we want GNU extensions like POSIX_SPAWN_USEVFORK */ #ifndef _GNU_SOURCE #define _GNU_SOURCE 1 #endif #include #include #include #include #include #include #include #include #include #ifdef RUBY_VM #include #else #include #endif #ifndef RARRAY_LEN #define RARRAY_LEN(ary) RARRAY(ary)->len #endif #ifndef RARRAY_PTR #define RARRAY_PTR(ary) RARRAY(ary)->ptr #endif #ifndef RHASH_SIZE #define RHASH_SIZE(hash) RHASH(hash)->tbl->num_entries #endif #ifdef __APPLE__ #include #define environ (*_NSGetEnviron()) #else extern char **environ; #endif static VALUE rb_mPOSIX; static VALUE rb_mPOSIXSpawn; /* Determine the fd number for a Ruby object VALUE. * * obj - This can be any valid Ruby object, but only the following return * an actual fd number: * - The symbols :in, :out, or :err for fds 0, 1, or 2. * - An IO object. (IO#fileno is returned) * - An Integer. * * Returns the fd number >= 0 if one could be established, or -1 if the object * does not map to an fd. */ static int posixspawn_obj_to_fd(VALUE obj) { int fd = -1; switch (TYPE(obj)) { case T_FIXNUM: case T_BIGNUM: /* Integer fd number * rb_fix2int takes care of raising if the provided object is a * Bignum and is out of range of an int */ fd = (int)rb_fix2int(obj); break; case T_SYMBOL: /* (:in|:out|:err) */ if (SYM2ID(obj) == rb_intern("in")) fd = 0; else if (SYM2ID(obj) == rb_intern("out")) fd = 1; else if (SYM2ID(obj) == rb_intern("err")) fd = 2; break; case T_FILE: /* IO object */ if (rb_respond_to(obj, rb_intern("posix_fileno"))) { fd = FIX2INT(rb_funcall(obj, rb_intern("posix_fileno"), 0)); } else { fd = FIX2INT(rb_funcall(obj, rb_intern("fileno"), 0)); } break; case T_OBJECT: /* some other object */ if (rb_respond_to(obj, rb_intern("to_io"))) { obj = rb_funcall(obj, rb_intern("to_io"), 0); if (rb_respond_to(obj, rb_intern("posix_fileno"))) { fd = FIX2INT(rb_funcall(obj, rb_intern("posix_fileno"), 0)); } else { fd = FIX2INT(rb_funcall(obj, rb_intern("fileno"), 0)); } } break; } return fd; } /* * Hash iterator that sets up the posix_spawn_file_actions_t with addclose * operations. Only hash pairs whose value is :close are processed. Keys may * be the :in, :out, :err, an IO object, or an Integer fd number. * * Returns ST_DELETE when an addclose operation was added; ST_CONTINUE when * no operation was performed. */ static int posixspawn_file_actions_addclose(VALUE key, VALUE val, posix_spawn_file_actions_t *fops) { int fd; /* we only care about { (IO|FD|:in|:out|:err) => :close } */ if (TYPE(val) != T_SYMBOL || SYM2ID(val) != rb_intern("close")) return ST_CONTINUE; fd = posixspawn_obj_to_fd(key); if (fd >= 0) { /* raise an exception if 'fd' is invalid */ if (fcntl(fd, F_GETFD) == -1) { char error_context[32]; snprintf(error_context, sizeof(error_context), "when closing fd %d", fd); rb_sys_fail(error_context); return ST_DELETE; } posix_spawn_file_actions_addclose(fops, fd); return ST_DELETE; } else { return ST_CONTINUE; } } /* * Hash iterator that sets up the posix_spawn_file_actions_t with adddup2 + * close operations for all redirects. Only hash pairs whose key and value * represent fd numbers are processed. * * Returns ST_DELETE when an adddup2 operation was added; ST_CONTINUE when * no operation was performed. */ static int posixspawn_file_actions_adddup2(VALUE key, VALUE val, posix_spawn_file_actions_t *fops) { int fd, newfd; newfd = posixspawn_obj_to_fd(key); if (newfd < 0) return ST_CONTINUE; fd = posixspawn_obj_to_fd(val); if (fd < 0) return ST_CONTINUE; fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) & ~FD_CLOEXEC); fcntl(newfd, F_SETFD, fcntl(newfd, F_GETFD) & ~FD_CLOEXEC); posix_spawn_file_actions_adddup2(fops, fd, newfd); return ST_DELETE; } /* * Hash iterator that sets up the posix_spawn_file_actions_t with adddup2 + * clone operations for all file redirects. Only hash pairs whose key is an * fd number and value is a valid three-tuple [file, flags, mode] are * processed. * * Returns ST_DELETE when an adddup2 operation was added; ST_CONTINUE when * no operation was performed. */ static int posixspawn_file_actions_addopen(VALUE key, VALUE val, posix_spawn_file_actions_t *fops) { int fd; char *path; int oflag; mode_t mode; fd = posixspawn_obj_to_fd(key); if (fd < 0) return ST_CONTINUE; if (TYPE(val) != T_ARRAY || RARRAY_LEN(val) != 3) return ST_CONTINUE; path = StringValuePtr(RARRAY_PTR(val)[0]); oflag = FIX2INT(RARRAY_PTR(val)[1]); mode = FIX2INT(RARRAY_PTR(val)[2]); posix_spawn_file_actions_addopen(fops, fd, path, oflag, mode); return ST_DELETE; } /* * Main entry point for iterating over the options hash to perform file actions. * This function dispatches to the addclose and adddup2 functions, stopping once * an operation was added. * * Returns ST_DELETE if one of the handlers performed an operation; ST_CONTINUE * if not. */ static int posixspawn_file_actions_operations_iter(VALUE key, VALUE val, posix_spawn_file_actions_t *fops) { int act; act = posixspawn_file_actions_addclose(key, val, fops); if (act != ST_CONTINUE) return act; act = posixspawn_file_actions_adddup2(key, val, fops); if (act != ST_CONTINUE) return act; act = posixspawn_file_actions_addopen(key, val, fops); if (act != ST_CONTINUE) return act; return ST_CONTINUE; } /* * Initialize the posix_spawn_file_actions_t structure and add operations from * the options hash. Keys in the options Hash that are processed by handlers are * removed. * * Returns nothing. */ static void posixspawn_file_actions_init(posix_spawn_file_actions_t *fops, VALUE options) { posix_spawn_file_actions_init(fops); rb_hash_foreach(options, posixspawn_file_actions_operations_iter, (VALUE)fops); } /* * Initialize pgroup related flags in the posix_spawnattr struct based on the * options Hash. * * :pgroup => 0 | true - spawned process is in a new process group with the * same id as the new process's pid. * :pgroup => pgid - spawned process is in a new process group with id * pgid. * :pgroup => nil - spawned process has the same pgid as the parent * process (this is the default). * * The options Hash is modified in place with the :pgroup key being removed. */ static void posixspawn_set_pgroup(VALUE options, posix_spawnattr_t *pattr, short *pflags) { VALUE pgroup_val; pgroup_val = rb_hash_delete(options, ID2SYM(rb_intern("pgroup"))); switch (TYPE(pgroup_val)) { case T_TRUE: (*pflags) |= POSIX_SPAWN_SETPGROUP; posix_spawnattr_setpgroup(pattr, 0); break; case T_FIXNUM: (*pflags) |= POSIX_SPAWN_SETPGROUP; posix_spawnattr_setpgroup(pattr, FIX2INT(pgroup_val)); break; case T_NIL: break; default: rb_raise(rb_eTypeError, ":pgroup option is invalid"); break; } } static int each_env_check_i(VALUE key, VALUE val, VALUE arg) { StringValuePtr(key); if (!NIL_P(val)) StringValuePtr(val); return ST_CONTINUE; } static int each_env_i(VALUE key, VALUE val, VALUE arg) { const char *name = StringValuePtr(key); const size_t name_len = strlen(name); char **envp = (char **)arg; size_t i, j; for (i = 0; envp[i];) { const char *ev = envp[i]; if (strlen(ev) > name_len && !memcmp(ev, name, name_len) && ev[name_len] == '=') { for (j = i; envp[j]; ++j) envp[j] = envp[j + 1]; continue; } i++; } /* * Insert the new value if we have one. We can assume there is space * at the end of the list, since ep was preallocated to be big enough * for the new entries. */ if (RTEST(val)) { char **ep = (char **)arg; char *cval = StringValuePtr(val); size_t cval_len = strlen(cval); size_t ep_len = name_len + 1 + cval_len + 1; /* +2 for null terminator and '=' separator */ /* find the last entry */ while (*ep != NULL) ++ep; *ep = malloc(ep_len); strncpy(*ep, name, name_len); (*ep)[name_len] = '='; strncpy(*ep + name_len + 1, cval, cval_len); (*ep)[ep_len-1] = 0; } return ST_CONTINUE; } /* * POSIX::Spawn#_pspawn(env, argv, options) * * env - Hash of the new environment. * argv - The [[cmdname, argv0], argv1, ...] exec array. * options - The options hash with fd redirect and close operations. * * Returns the pid of the newly spawned process. */ static VALUE rb_posixspawn_pspawn(VALUE self, VALUE env, VALUE argv, VALUE options) { int i, ret = 0; char **envp = NULL; VALUE dirname; VALUE cmdname; VALUE unsetenv_others_p = Qfalse; char *file; char *cwd = NULL; pid_t pid; posix_spawn_file_actions_t fops; posix_spawnattr_t attr; sigset_t mask; short flags = 0; /* argv is a [[cmdname, argv0], argv1, argvN, ...] array. */ if (TYPE(argv) != T_ARRAY || TYPE(RARRAY_PTR(argv)[0]) != T_ARRAY || RARRAY_LEN(RARRAY_PTR(argv)[0]) != 2) rb_raise(rb_eArgError, "Invalid command name"); long argc = RARRAY_LEN(argv); char *cargv[argc + 1]; cmdname = RARRAY_PTR(argv)[0]; file = StringValuePtr(RARRAY_PTR(cmdname)[0]); cargv[0] = StringValuePtr(RARRAY_PTR(cmdname)[1]); for (i = 1; i < argc; i++) cargv[i] = StringValuePtr(RARRAY_PTR(argv)[i]); cargv[argc] = NULL; if (TYPE(options) == T_HASH) { unsetenv_others_p = rb_hash_delete(options, ID2SYM(rb_intern("unsetenv_others"))); } if (RTEST(env)) { /* * Make sure env is a hash, and all keys and values are strings. * We do this before allocating space for the new environment to * prevent a leak when raising an exception after the calloc() below. */ Check_Type(env, T_HASH); rb_hash_foreach(env, each_env_check_i, 0); if (RHASH_SIZE(env) > 0) { int size = 0; char **new_env; char **curr = environ; if (curr) { while (*curr != NULL) ++curr, ++size; } if (unsetenv_others_p == Qtrue) { /* * ignore the parent's environment by pretending it had * no entries. the loop below will do nothing. */ size = 0; } new_env = calloc(size+RHASH_SIZE(env)+1, sizeof(char*)); for (i = 0; i < size; i++) { new_env[i] = strdup(environ[i]); } envp = new_env; rb_hash_foreach(env, each_env_i, (VALUE)envp); } } posixspawn_file_actions_init(&fops, options); posix_spawnattr_init(&attr); /* child does not block any signals */ flags |= POSIX_SPAWN_SETSIGMASK; sigemptyset(&mask); posix_spawnattr_setsigmask(&attr, &mask); /* Child reverts SIGPIPE handler to the default. */ flags |= POSIX_SPAWN_SETSIGDEF; sigaddset(&mask, SIGPIPE); posix_spawnattr_setsigdefault(&attr, &mask); #if defined(POSIX_SPAWN_USEVFORK) || defined(__GLIBC__) /* Force USEVFORK on GNU libc. If this is undefined, it's probably * because you forgot to define _GNU_SOURCE at the top of this file. */ flags |= POSIX_SPAWN_USEVFORK; #endif /* setup pgroup options */ posixspawn_set_pgroup(options, &attr, &flags); posix_spawnattr_setflags(&attr, flags); if (RTEST(dirname = rb_hash_delete(options, ID2SYM(rb_intern("chdir"))))) { char *new_cwd = StringValuePtr(dirname); cwd = getcwd(NULL, 0); if (chdir(new_cwd) == -1) { free(cwd); cwd = NULL; ret = errno; } } if (ret == 0) { if (RHASH_SIZE(options) == 0) { ret = posix_spawnp(&pid, file, &fops, &attr, cargv, envp ? envp : environ); if (cwd) { /* Ignore chdir failures here. There's already a child running, so * raising an exception here would do more harm than good. */ if (chdir(cwd) == -1) {} } } else { ret = -1; } } if (cwd) free(cwd); posix_spawn_file_actions_destroy(&fops); posix_spawnattr_destroy(&attr); if (envp) { char **ep = envp; while (*ep != NULL) free(*ep), ++ep; free(envp); } if (RHASH_SIZE(options) > 0) { rb_raise(rb_eArgError, "Invalid option: %s", RSTRING_PTR(rb_inspect(rb_funcall(options, rb_intern("first"), 0)))); return -1; } if (ret != 0) { char error_context[PATH_MAX+32]; snprintf(error_context, sizeof(error_context), "when spawning '%s'", file); errno = ret; rb_sys_fail(error_context); } return INT2FIX(pid); } void Init_posix_spawn_ext() { rb_mPOSIX = rb_define_module("POSIX"); rb_mPOSIXSpawn = rb_define_module_under(rb_mPOSIX, "Spawn"); rb_define_method(rb_mPOSIXSpawn, "_pspawn", rb_posixspawn_pspawn, 3); } /* vim: set noexpandtab sts=0 ts=4 sw=4: */ posix-spawn-0.3.13/ext/extconf.rb0000644000175000017500000000037613041401323016512 0ustar uwabamiuwabamirequire 'mkmf' # warnings save lives $CFLAGS << " -Wall " if RbConfig::CONFIG['GCC'] != "" if RUBY_PLATFORM =~ /(mswin|mingw|cygwin|bccwin)/ File.open('Makefile','w'){|f| f.puts "default: \ninstall: " } else create_makefile('posix_spawn_ext') end posix-spawn-0.3.13/README.md0000644000175000017500000002720213041401323015173 0ustar uwabamiuwabami# posix-spawn `fork(2)` calls slow down as the parent process uses more memory due to the need to copy page tables. In many common uses of fork(), where it is followed by one of the exec family of functions to spawn child processes (`Kernel#system`, `IO::popen`, `Process::spawn`, etc.), it's possible to remove this overhead by using special process spawning interfaces (`posix_spawn()`, `vfork()`, etc.) The posix-spawn library aims to implement a subset of the Ruby 1.9 `Process::spawn` interface in a way that takes advantage of fast process spawning interfaces when available and provides sane fallbacks on systems that do not. ### FEATURES - Fast, constant-time spawn times across a variety of platforms. - A largish compatible subset of Ruby 1.9's `Process::spawn` interface and enhanced versions of `Kernel#system`, Kernel#`, etc. under Ruby >= 1.8.7 (currently MRI only). - High level `POSIX::Spawn::Child` class for quick (but correct!) non-streaming IPC scenarios. ## BENCHMARKS The following benchmarks illustrate time needed to fork/exec a child process at increasing resident memory sizes on Linux 2.6 and MacOS X. Tests were run using the [`posix-spawn-benchmark`][pb] program included with the package. [pb]: https://github.com/rtomayko/posix-spawn/tree/master/bin ### Linux ![](https://chart.googleapis.com/chart?chbh=a,5,25&chxr=1,0,36,7&chd=t:5.77,10.37,15.72,18.31,19.73,25.13,26.70,29.31,31.44,35.49|0.86,0.82,1.06,0.99,0.79,1.06,0.84,0.79,0.93,0.94&chxs=1N**%20secs&chs=900x200&chds=0,36&chxl=0:|50%20MB|100%20MB|150%20MB|200%20MB|250%20MB|300%20MB|350%20MB|400%20MB|450%20MB|500%20MB&cht=bvg&chdl=fspawn%20%28fork%2Bexec%29|pspawn%20%28posix_spawn%29&chtt=posix-spawn-benchmark%20--graph%20--count%20500%20--mem-size%20500%20%28x86_64-linux%29&chco=1f77b4,ff7f0e&chf=bg,s,f8f8f8&chxt=x,y#.png) `posix_spawn` is faster than `fork+exec`, and executes in constant time when used with `POSIX_SPAWN_USEVFORK`. `fork+exec` is extremely slow for large parent processes. ### OSX ![](https://chart.googleapis.com/chart?chxl=0:|50%20MB|100%20MB|150%20MB|200%20MB|250%20MB|300%20MB|350%20MB|400%20MB|450%20MB|500%20MB&cht=bvg&chdl=fspawn%20%28fork%2Bexec%29|pspawn%20%28posix_spawn%29&chtt=posix-spawn-benchmark%20--graph%20--count%20500%20--mem-size%20500%20%28i686-darwin10.5.0%29&chco=1f77b4,ff7f0e&chf=bg,s,f8f8f8&chxt=x,y&chbh=a,5,25&chxr=1,0,3,0&chd=t:1.95,2.07,2.56,2.29,2.21,2.32,2.15,2.25,1.96,2.02|0.84,0.97,0.89,0.82,1.13,0.89,0.93,0.81,0.83,0.81&chxs=1N**%20secs&chs=900x200&chds=0,3#.png) `posix_spawn` is faster than `fork+exec`, but neither is affected by the size of the parent process. ## USAGE This library includes two distinct interfaces: `POSIX::Spawn::spawn`, a lower level process spawning interface based on the new Ruby 1.9 `Process::spawn` method, and `POSIX::Spawn::Child`, a higher level class geared toward easy spawning of processes with simple string based standard input/output/error stream handling. The former is much more versatile, the latter requires much less code for certain common scenarios. ### POSIX::Spawn::spawn The `POSIX::Spawn` module (with help from the accompanying C extension) implements a subset of the [Ruby 1.9 Process::spawn][ps] interface, largely through the use of the [IEEE Std 1003.1 `posix_spawn(2)` systems interfaces][po]. These are widely supported by various UNIX operating systems. [ps]: http://www.ruby-doc.org/core-1.9/classes/Process.html#M002230 [po]: http://pubs.opengroup.org/onlinepubs/009695399/functions/posix_spawn.html In its simplest form, the `POSIX::Spawn::spawn` method can be used to execute a child process similar to `Kernel#system`: require 'posix/spawn' pid = POSIX::Spawn::spawn('echo', 'hello world') stat = Process::waitpid(pid) The first line executes `echo` with a single argument and immediately returns the new process's `pid`. The second line waits for the process to complete and returns a `Process::Status` object. Note that `spawn` *does not* wait for the process to finish execution like `system` and does not reap the child's exit status -- you must call `Process::waitpid` (or equivalent) or the process will become a zombie. The `spawn` method is capable of performing a large number of additional operations, from setting up the new process's environment, to changing the child's working directory, to redirecting arbitrary file descriptors. See the Ruby 1.9 [`Process::spawn` documentation][ps] for details and the `STATUS` section below for a full account of the various `Process::spawn` features supported by `POSIX::Spawn::spawn`. ### `system`, `popen4`, and ` In addition to the `spawn` method, Ruby 1.9 compatible implementations of `Kernel#system` and Kernel#\` are provided in the `POSIX::Spawn` module. The `popen4` method can be used to spawn a process with redirected stdin, stdout, and stderr objects. ### POSIX::Spawn as a Mixin The `POSIX::Spawn` module can also be mixed in to classes and modules to include `spawn` and all utility methods in that namespace: require 'posix/spawn' class YourGreatClass include POSIX::Spawn def speak(message) pid = spawn('echo', message) Process::waitpid(pid) end def calculate(expression) pid, in, out, err = popen4('bc') in.write(expression) in.close out.read ensure [in, out, err].each { |io| io.close if !io.closed? } Process::waitpid(pid) end end ### POSIX::Spawn::Child The `POSIX::Spawn::Child` class includes logic for executing child processes and reading/writing from their standard input, output, and error streams. It's designed to take all input in a single string and provides all output as single strings and is therefore not well-suited to streaming large quantities of data in and out of commands. That said, it has some benefits: - **Simple** - requires little code for simple stream input and capture. - **Internally non-blocking** (using `select(2)`) - handles all pipe hang cases due to exceeding `PIPE_BUF` limits on one or more streams. - **Potentially portable** - abstracts lower-level process and stream management APIs so the class can be made to work on platforms like Java and Windows where UNIX process spawning and stream APIs are not supported. `POSIX::Spawn::Child` takes the standard `spawn` arguments when instantiated, and runs the process to completion after writing all input and reading all output: >> require 'posix/spawn' >> child = POSIX::Spawn::Child.new('git', '--help') Retrieve process output written to stdout / stderr, or inspect the process's exit status: >> child.out => "usage: git [--version] [--exec-path[=GIT_EXEC_PATH]]\n ..." >> child.err => "" >> child.status => # Use the `:input` option to write data on the new process's stdin immediately after spawning: >> child = POSIX::Spawn::Child.new('bc', :input => '40 + 2') >> child.out "42\n" Additional options can be used to specify the maximum output size (`:max`) and time of execution (`:timeout`) before the child process is aborted. See the `POSIX::Spawn::Child` docs for more info. #### Reading Partial Results `POSIX::Spawn::Child.new` spawns the process immediately when instantiated. As a result, if it is interrupted by an exception (either from reaching the maximum output size, the time limit, or another factor), it is not possible to access the `out` or `err` results because the constructor did not complete. If you want to get the `out` and `err` data was available when the process was interrupted, use the `POSIX::Spawn::Child.build` alternate form to create the child without immediately spawning the process. Call `exec!` to run the command at a place where you can catch any exceptions: >> child = POSIX::Spawn::Child.build('git', 'log', :max => 100) >> begin ?> child.exec! ?> rescue POSIX::Spawn::MaximumOutputExceeded ?> # limit was reached ?> end >> child.out "commit fa54abe139fd045bf6dc1cc259c0f4c06a9285bb\n..." Please note that when the `MaximumOutputExceeded` exception is raised, the actual combined `out` and `err` data may be a bit longer than the `:max` value due to internal buffering. ## STATUS The `POSIX::Spawn::spawn` method is designed to be as compatible with Ruby 1.9's `Process::spawn` as possible. Right now, it is a compatible subset. These `Process::spawn` arguments are currently supported to any of `Spawn::spawn`, `Spawn::system`, `Spawn::popen4`, and `Spawn::Child.new`: env: hash name => val : set the environment variable name => nil : unset the environment variable command...: commandline : command line string which is passed to a shell cmdname, arg1, ... : command name and one or more arguments (no shell) [cmdname, argv0], arg1, ... : command name, argv[0] and zero or more arguments (no shell) options: hash clearing environment variables: :unsetenv_others => true : clear environment variables except specified by env :unsetenv_others => false : don't clear (default) current directory: :chdir => str : Not thread-safe when using posix_spawn (see below) process group: :pgroup => true or 0 : make a new process group :pgroup => pgid : join to specified process group :pgroup => nil : don't change the process group (default) redirection: key: FD : single file descriptor in child process [FD, FD, ...] : multiple file descriptor in child process value: FD : redirect to the file descriptor in parent process :close : close the file descriptor in child process string : redirect to file with open(string, "r" or "w") [string] : redirect to file with open(string, File::RDONLY) [string, open_mode] : redirect to file with open(string, open_mode, 0644) [string, open_mode, perm] : redirect to file with open(string, open_mode, perm) FD is one of follows :in : the file descriptor 0 which is the standard input :out : the file descriptor 1 which is the standard output :err : the file descriptor 2 which is the standard error integer : the file descriptor of specified the integer io : the file descriptor specified as io.fileno These options are currently NOT supported: options: hash resource limit: resourcename is core, cpu, data, etc. See Process.setrlimit. :rlimit_resourcename => limit :rlimit_resourcename => [cur_limit, max_limit] umask: :umask => int redirection: value: [:child, FD] : redirect to the redirected file descriptor file descriptor inheritance: close non-redirected non-standard fds (3, 4, 5, ...) or not :close_others => false : inherit fds (default for system and exec) :close_others => true : don't inherit (default for spawn and IO.popen) The `:chdir` option provided by Posix::Spawn::Child, Posix::Spawn#spawn, Posix::Spawn#system and Posix::Spawn#popen4 is not thread-safe because processes spawned with the posix_spawn(2) system call inherit the working directory of the calling process. The posix-spawn gem works around this limitation in the system call by changing the working directory of the calling process immediately before and after spawning the child process. ## ACKNOWLEDGEMENTS Copyright (c) by [Ryan Tomayko](http://tomayko.com/about) and [Aman Gupta](https://github.com/tmm1). See the `COPYING` file for more information on license and redistribution. posix-spawn-0.3.13/COPYING0000644000175000017500000000220313041401323014741 0ustar uwabamiuwabamiCopyright (c) 2011 by Ryan Tomayko and Aman Gupta Permission is hereby granted, free of charge, to any person ob- taining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restric- tion, including without limitation the rights to use, copy, modi- fy, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is fur- nished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONIN- FRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.