require 'voltos/logger' require 'pty' require 'open3' require 'readline' module Voltos class Process def initialize(input = STDIN, output = STDOUT, err = STDOUT) Readline.input = input @in = input @out = output @err = err end def tty? STDIN.tty? end def pid=(pid) logger.debug "Storing spawned process pid: #{pid}" @pid = pid end def pid @pid end def run(command, *args) logger.debug "Running: #{command} #{args.join(' ')}" Signal.trap('INT') do ctrl_c! end Signal.trap('TERM') do ctrl_c! end if tty? run_as_tty(command, *args) else run_as_daemon(command, *args) end end def ctrl_c! pid_to_kill = pid || ::Process.pid logger.debug "Sending ^C to: #{pid_to_kill}" ::Process.kill('TERM', pid_to_kill) ::Process.waitall end def ctrl_d! logger.debug "Sending ^D" @process_stdin.close if @process_stdin @process_stdout.close if @process_stdout logger.debug "Sent." end private def logger return @logger if @logger @logger = Voltos::Logger end def run_as_tty(bin, *args) @out.sync = true status = PTY.spawn(bin, *args) do |stdout, stdin, pid| self.pid = pid @process_stdin = stdin @process_stdout = stdout stdout.sync = true Thread.new do loop do @out.print stdout.getc end end Thread.new do loop do input = Readline.readline('', true) if input.nil? logger.debug "Cleaning up STDIN & STDOUT..." stdout.flush stdout.close stdin.close end stdin.puts input.strip end end begin logger.debug "Waiting for process to finish: #{pid}" ::Process::waitpid(pid) rescue nil ::Process.waitall logger.debug "Process finished." while out = stdout.getc do @out.print out end rescue SystemExit, Interrupt logger.debug "Rescued interrupt." ctrl_c! retry rescue EOFError logger.debug "Rescued EOF." rescue IOError, Errno::EIO logger.debug "Rescued IOError." end end status end def run_as_daemon(bin, *args) process = nil Open3.popen3(bin, *args) do |stdin, stdout, stderr, thread| self.pid = thread.pid Thread.new do while !stdin.closed? do input = Readline.readline('', true).strip stdin.puts input end end errThread = Thread.new do while !stderr.eof? do @err.putc stderr.readchar end end outThread = Thread.new do while !stdout.eof? do putc stdout.readchar end end begin logger.debug "Waiting for process to finish #{thread.pid}." Process::waitpid(thread.pid) rescue nil errThread.join outThread.join logger.debug "Process finished." process = thread.value rescue SystemExit, Interrupt logger.debug "Rescued interrupt." retry end end return process.exitstatus if process end end end