def popen4(cmd, args={}, &b)
args[:waitlast] ||= false
args[:user] ||= nil
unless args[:user].kind_of?(Integer)
args[:user] = Etc.getpwnam(args[:user]).uid if args[:user]
end
args[:group] ||= nil
unless args[:group].kind_of?(Integer)
args[:group] = Etc.getgrnam(args[:group]).gid if args[:group]
end
args[:environment] ||= {}
unless args[:environment].has_key?("LC_ALL")
args[:environment]["LC_ALL"] = "C"
end
pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
verbose = $VERBOSE
begin
$VERBOSE = nil
ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
cid = fork {
Process.setsid
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
if args[:group]
Process.egid = args[:group]
Process.gid = args[:group]
end
if args[:user]
Process.euid = args[:user]
Process.uid = args[:user]
end
args[:environment].each do |key,value|
ENV[key] = value
end
if args[:umask]
umask = ((args[:umask].respond_to?(:oct) ? args[:umask].oct : args[:umask].to_i) & 007777)
File.umask(umask)
end
begin
if cmd.kind_of?(Array)
exec(*cmd)
else
exec(cmd)
end
raise 'forty-two'
rescue Exception => e
Marshal.dump(e, ps.last)
ps.last.flush
end
ps.last.close unless (ps.last.closed?)
exit!
}
ensure
$VERBOSE = verbose
end
[pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
begin
e = Marshal.load ps.first
raise(Exception === e ? e : "unknown failure!")
rescue EOFError
42
ensure
ps.first.close
end
pw.last.sync = true
pi = [pw.last, pr.first, pe.first]
if b
begin
if args[:waitlast]
b[cid, *pi]
pi[0].close_write
Process.waitpid2(cid).last
else
o = StringIO.new
e = StringIO.new
stdout = pi[1]
stderr = pi[2]
stdout.sync = true
stderr.sync = true
stdout.fcntl(Fcntl::F_SETFL, pi[1].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
stderr.fcntl(Fcntl::F_SETFL, pi[2].fcntl(Fcntl::F_GETFL) | Fcntl::O_NONBLOCK)
stdout_finished = false
stderr_finished = false
results = nil
while !stdout_finished || !stderr_finished
begin
channels_to_watch = []
channels_to_watch << stdout if !stdout_finished
channels_to_watch << stderr if !stderr_finished
ready = IO.select(channels_to_watch, nil, nil, 1.0)
rescue Errno::EAGAIN
ensure
results = Process.waitpid2(cid, Process::WNOHANG)
if results
stdout_finished = true
stderr_finished = true
end
end
if ready && ready.first.include?(stdout)
line = results ? stdout.gets(nil) : stdout.gets
if line
o.write(line)
else
stdout_finished = true
end
end
if ready && ready.first.include?(stderr)
line = results ? stderr.gets(nil) : stderr.gets
if line
e.write(line)
else
stderr_finished = true
end
end
end
results = Process.waitpid2(cid) unless results
o.rewind
e.rewind
b[cid, pi[0], o, e]
results.last
end
ensure
pi.each{|fd| fd.close unless fd.closed?}
end
else
[cid, pw.last, pr.first, pe.first]
end
rescue Errno::ENOENT
raise Ohai::Exceptions::Exec, "command #{cmd} doesn't exist or is not in the PATH"
end