263 lines
5.6 KiB
Ruby
263 lines
5.6 KiB
Ruby
require 'forwardable'
|
|
require 'shellwords'
|
|
require 'socket'
|
|
require 'json'
|
|
|
|
class Sh
|
|
class ForkError < RuntimeError
|
|
end
|
|
class ForkFailed < ForkError
|
|
end
|
|
class ProcessError < ForkError
|
|
end
|
|
|
|
class Fork
|
|
extend Forwardable
|
|
def_delegators :@status, :exitstatus
|
|
attr_reader :pid, :status
|
|
|
|
def initialize &exe
|
|
#@io, s = UNIXSocket.pair
|
|
@pid =
|
|
Process.fork do
|
|
Thread.list.each do |th|
|
|
th.kill unless Thread.current == th
|
|
end
|
|
#STDOUT.reopen s.dup
|
|
#STDIN.reopen s
|
|
#STDOUT.close_read
|
|
#STDIN.close_write
|
|
yield
|
|
end
|
|
raise ForkFailed unless @pid
|
|
#s.close
|
|
end
|
|
|
|
def wait
|
|
_, @status = Process.waitpid2 @pid
|
|
@status
|
|
end
|
|
|
|
def kill signal = nil
|
|
Process.kill signal||9, @pid
|
|
end
|
|
|
|
def ok?() 0 == @status.exitstatus end
|
|
def failed?() 0 != @status.exitstatus end
|
|
end
|
|
|
|
class IOFork < Fork
|
|
extend Forwardable
|
|
def_delegators :@io, :close, :closed?, :close_read, :closed_read?, :close_write, :closed_write?
|
|
def_delegators :@io, :getbyte, :getc, :getch, :getpass, :gets
|
|
def_delegators :@io, :read, :read_nonblock, :readbyte, :readchar, :readline, :readlines, :readpartial, :sysread
|
|
def_delegators :@io, :syswrite, :write, :write_nonblock
|
|
def_delegators :@io, :putc, :puts, :print, :printf
|
|
def_delegators :@io, :each_line
|
|
attr_reader :io
|
|
|
|
def initialize &exe
|
|
@io, s = UNIXSocket.pair
|
|
r = nil
|
|
super do
|
|
@io.close
|
|
STDOUT.reopen s
|
|
STDIN.reopen s
|
|
#STDOUT.close_read
|
|
#STDIN.close_write
|
|
r = yield
|
|
end
|
|
s.close
|
|
r
|
|
end
|
|
end
|
|
|
|
class Command
|
|
attr_reader :shell, :command, :args, :opts
|
|
|
|
def initialize shell, command, *args, **opts
|
|
@shell, @command, @args, @opts = shell, command, args, opts
|
|
end
|
|
|
|
def fork **opts
|
|
opts = @opts.merge opts
|
|
cl =
|
|
case opts[:mode]
|
|
when :io then IOFork
|
|
when Class then opts[:mode]
|
|
else Fork
|
|
end
|
|
cl.new do
|
|
cmd = @command.to_s
|
|
$0 = cmd
|
|
#STDERR.printf "\n|\n| %p\n|\n", cmd: cmd, args: @args
|
|
if opts.has_key? :stdio
|
|
stdin, stdout, stderr = *opts[:stdio]
|
|
stdin ? STDIN.reopen( stdin) : STDIN.close
|
|
STDOUT.reopen stdout
|
|
STDERR.reopen stderr if stderr
|
|
end
|
|
if opts[:chroot]
|
|
STDERR.printf "\e[1;34mchroot(%s)\e[0m ", opts[:chroot].to_s
|
|
Dir.chroot opts[:chroot].to_s
|
|
Dir.chdir '/'
|
|
end
|
|
if @shell.opts[:dir]
|
|
STDERR.printf "%s ", @shell.opts[:dir]
|
|
Dir.chdir @shell.opts[:dir]
|
|
end
|
|
#STDERR.puts "\e[0m#{opts[:return] ? '<=' : '#'} \e[33m#{cmd.shellescape} \e[35m#{usable_args.shelljoin}\e[0m"
|
|
STDERR.printf "\e[30;1m%s \e[0;33m%s \e[1;35m%s\e[0m\n",
|
|
opts[:return] ? '<=' : '#',
|
|
cmd.shellescape,
|
|
usable_args.shelljoin
|
|
exec cmd, *usable_args
|
|
raise ForkFailed, "Cannot execute"
|
|
end
|
|
rescue ForkFailed => e
|
|
raise ForkFailed, "<<#{self}>> cannot be forked"
|
|
end
|
|
|
|
def usable_args
|
|
@uargs ||= @args.flatten.map &:to_s
|
|
end
|
|
|
|
def to_s
|
|
"#{@command.to_s.shellescape}" + (usable_args.empty? ? '' : " #{usable_args.shelljoin}")
|
|
end
|
|
|
|
def run **opts, &exe
|
|
opts = @opts.merge opts
|
|
case opts[:return]
|
|
when :lines
|
|
opts[:mode], exe = :io, lambda( &:readlines)
|
|
when :string
|
|
opts[:mode], exe = :io, lambda( &:read)
|
|
when :line
|
|
opts[:mode], exe = :io, lambda {|f| f.read.chomp }
|
|
when :json
|
|
opts[:mode] = :io
|
|
if opts[:could_be_empty]
|
|
exe = lambda do |f|
|
|
r = f.read
|
|
r.empty? ? [] : JSON.parse( r)
|
|
end
|
|
else
|
|
exe = lambda {|f| JSON.parse f.read }
|
|
end
|
|
when :jsonl
|
|
opts[:mode], exe = :io, lambda {|f| f.each_line.map {|l| JSON.parse l } }
|
|
end
|
|
r = f = fork **opts
|
|
begin
|
|
r = exe.call f if exe
|
|
rescue Object
|
|
f.kill
|
|
raise $!
|
|
ensure
|
|
f.close if f.respond_to? :close
|
|
f.wait
|
|
end
|
|
unless opts[:expect_status].nil? or opts[:expect_status] == f.exitstatus
|
|
raise ProcessError, "«#{self}» exited: #{f.status}"
|
|
end
|
|
r
|
|
end
|
|
|
|
def run! **opts, &exe
|
|
run expect_status: 0, **opts, &exe
|
|
end
|
|
alias call run!
|
|
alias !@ run!
|
|
end
|
|
|
|
attr_reader :opts
|
|
|
|
def initialize dir = nil, **opts
|
|
opts = opts.merge dir: dir if dir
|
|
@opts = opts.dup
|
|
(@opts[:commands]||={}).each do |name, exe|
|
|
define_singleton_method name, &exe
|
|
end
|
|
@opts.freeze
|
|
end
|
|
|
|
def chdir dir
|
|
Sh.new dir, **opts
|
|
end
|
|
alias cd chdir
|
|
def newopts **opts
|
|
Sh.new **@opts.merge( opts)
|
|
end
|
|
|
|
def chroot dir
|
|
newopts chroot: dir
|
|
end
|
|
|
|
def return_string
|
|
newopts mode: nil, return: :string
|
|
end
|
|
|
|
alias _ return_string
|
|
def return_lines
|
|
newopts mode: nil, return: :lines
|
|
end
|
|
alias [] return_lines
|
|
|
|
def with_io
|
|
newopts mode: :io
|
|
end
|
|
alias io with_io
|
|
|
|
def system command, *args, immediately: nil, **opts, &exe
|
|
cmd = Command.new self, command, *args, **@opts.merge( opts)
|
|
if immediately or @opts[:immediately] or block_given?
|
|
cmd.run &exe
|
|
else
|
|
cmd
|
|
end
|
|
end
|
|
|
|
def define_command name, &exe
|
|
@opts[:commands][name] = exe
|
|
define_singleton_method name, &exe
|
|
end
|
|
|
|
def def_system_command name, cmd = nil
|
|
cmd ||= name
|
|
define_command name do |*args, **opts, &exe|
|
|
system cmd, *args, **opts, &exe
|
|
end
|
|
#e = <<-EOF
|
|
# def #{name}(*args, **opts, &exe)
|
|
# Command.new self, #{cmd.to_s.inspect}, *args, **opts
|
|
# end
|
|
#EOF
|
|
#eval e, nil, __FILE__, __LINE__ - 4
|
|
end
|
|
|
|
def def_system_commands *names
|
|
names.each {|cmd| def_system_command cmd }
|
|
end
|
|
|
|
def alias_command name, cmd, *args, **opts, &exe
|
|
opts = @opts.merge opts
|
|
define_command name do |*as, **os, &e|
|
|
*as = exe.call( *as) if exe
|
|
system cmd, *args, *as, **opts, **os
|
|
end
|
|
end
|
|
end
|
|
|
|
#f = Sh::IOFork.new do
|
|
# STDERR.puts f.inspect
|
|
# STDOUT.puts "hallo welt"
|
|
# sleep 5
|
|
#end
|
|
#
|
|
#STDERR.puts "> #{f.inspect}"
|
|
#f.each_line do |l|
|
|
# STDERR.puts "> #{l}"
|
|
#end
|
|
#f.wait
|
|
#exit 1
|