module Open4

def self.do_popen(b = nil, exception_propagation_at = nil, closefds=false, &cmd)

def self.do_popen(b = nil, exception_propagation_at = nil, closefds=false, &cmd)
  pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
  verbose = $VERBOSE
  begin
    $VERBOSE = nil
    cid = fork {
      if closefds
        exlist = [0, 1, 2] | [pw,pr,pe,ps].map{|p| [p.first.fileno, p.last.fileno] }.flatten
        ObjectSpace.each_object(IO){|io|
          io.close if (not io.closed?) and (not exlist.include? io.fileno) rescue nil
        }
      end
      pw.last.close
      STDIN.reopen pw.first
      pw.first.close
      pr.first.close
      STDOUT.reopen pr.last
      pr.last.close
      pe.first.close
      STDERR.reopen pe.last
      pe.last.close
      STDOUT.sync = STDERR.sync = true
      begin
        cmd.call(ps)
      rescue Exception => e
        Marshal.dump(e, ps.last)
        ps.last.flush
      ensure
        ps.last.close unless ps.last.closed?
      end
      exit!
    }
  ensure
    $VERBOSE = verbose
  end
  [ pw.first, pr.last, pe.last, ps.last ].each { |fd| fd.close }
  Open4.propagate_exception cid, ps.first if exception_propagation_at == :init
  pw.last.sync = true
  pi = [ pw.last, pr.first, pe.first ]
  begin
    return [cid, *pi] unless b
    begin
      b.call(cid, *pi)
    ensure
      pi.each { |fd| fd.close unless fd.closed? }
    end
    Open4.propagate_exception cid, ps.first if exception_propagation_at == :block
    Process.waitpid2(cid).last
  ensure
    ps.first.close unless ps.first.closed?
  end
end