Method: Subprocess::Process#initialize
- Defined in:
- lib/subprocess.rb
#initialize(cmd, opts = {}) {|process| ... } ⇒ Process
Create a new process.
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 |
# File 'lib/subprocess.rb', line 226 def initialize(cmd, opts={}, &blk) raise ArgumentError, "cmd must be an Array" unless Array === cmd raise ArgumentError, "cmd cannot be empty" if cmd.empty? @command = cmd # Figure out what file descriptors we should pass on to the child (and # make externally visible ourselves) @child_stdin, @stdin = parse_fd(opts[:stdin], 'r') @child_stdout, @stdout = parse_fd(opts[:stdout], 'w') unless opts[:stderr] == STDOUT @child_stderr, @stderr = parse_fd(opts[:stderr], 'w') end retained_fds = Set.new(opts[:retain_fds] || []) # A control pipe for ferrying errors back from the child control_r, control_w = IO.pipe @pid = fork do begin FileUtils.cd(opts[:cwd]) if opts[:cwd] ::STDIN.reopen(@child_stdin) if @child_stdin ::STDOUT.reopen(@child_stdout) if @child_stdout if opts[:stderr] == STDOUT ::STDERR.reopen(::STDOUT) else ::STDERR.reopen(@child_stderr) if @child_stderr end # Set up a new environment if we're requested to do so. if opts[:env] ENV.clear ENV.update(opts[:env]) end # Call the user back, maybe? opts[:preexec_fn].call if opts[:preexec_fn] = {close_others: true}.merge(opts.fetch(:exec_opts, {})) if opts[:retain_fds] retained_fds.each { |fd| [fd] = fd } end # Ruby's Kernel#exec will call an exec(3) variant if called with two # or more arguments, but when called with just a single argument will # spawn a subshell with that argument as the command. Since we always # want to call exec(3), we use the third exec form, which passes a # [cmdname, argv0] array as its first argument and never invokes a # subshell. exec([cmd[0], cmd[0]], *cmd[1..-1], ) rescue Exception => e # Dump all errors up to the parent through the control socket Marshal.dump(e, control_w) control_w.flush end # Something has gone terribly, terribly wrong if we're hitting this :( exit!(1) end # Meanwhile, in the parent process... # First, let's close some things we shouldn't have access to @child_stdin.close if our_fd?(opts[:stdin]) @child_stdout.close if our_fd?(opts[:stdout]) @child_stderr.close if our_fd?(opts[:stderr]) control_w.close # Any errors during the spawn process? We'll get past this point when the # child execs and the OS closes control_w begin e = Marshal.load(control_r) e = "Unknown Failure" unless e.is_a?(Exception) || e.is_a?(String) # Because we're throwing an exception and not returning a # Process, we need to make sure the child gets reaped wait raise e rescue EOFError # Nothing to read? Great! ensure control_r.close end # Everything is okay. Good job, team! blk.call(self) if blk end |