tolvmxfs/lib/to_lvm_xfs/sh.rb

247 lines
5.2 KiB
Ruby

require 'forwardable'
require 'shellwords'
require 'socket'
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
Dir.chdir @shell.opts[:pwd] if @shell.opts[:pwd]
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
end
#STDERR.puts "\e[0m#{opts[:return] ? '<=' : '#'} \e[33m#{cmd.shellescape} \e[35m#{usable_args.shelljoin}\e[0m"
STDERR.printf "\e[0m%s \e[33m%s \e[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}
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