From 2741f970507e6171567a4b6cb9739d41fa7dd881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrick=20L=C3=BChne?= Date: Tue, 26 May 2020 00:15:22 +0200 Subject: [PATCH] Attach to existing pseudoterminal --- github-fast-env.rb | 41 ++++++++-------------- github-fast-envd.rb | 84 +++++++++++++++++++++------------------------ 2 files changed, 54 insertions(+), 71 deletions(-) diff --git a/github-fast-env.rb b/github-fast-env.rb index d287834..0b0b46e 100755 --- a/github-fast-env.rb +++ b/github-fast-env.rb @@ -48,15 +48,15 @@ def log(level, message) end end -def read_command(control_socket) - ready_ios = IO.select([control_socket], [], [], 10) +def read_command + ready_ios = IO.select([$control_socket], [], [], 10) if not ready_ios log "error", "timeout while communicating with github-fast-envd" exit 1 end - response = control_socket.readline.strip.split(" ", 2) + response = $control_socket.readline.strip.split(" ", 2) if response.empty? log "error", "malformed response from github-fast-envd" @@ -98,8 +98,13 @@ log "info", "connected to control socket" encoded_script_path = Base64.encode64(script_path).delete("\n") +read_ios = [$control_socket] + if $options[:interactive] - $control_socket.puts "new v1 pseudoterminal #{encoded_script_path}" + pseudoterminal_path = File.readlink("/proc/self/fd/0") + encoded_pseudoterminal_path = Base64.encode64(pseudoterminal_path).delete("\n") + + $control_socket.puts "new v1 pseudoterminal #{encoded_pseudoterminal_path} #{encoded_script_path}" else $control_socket.puts "new v1 named-pipes #{encoded_script_path}" end @@ -107,7 +112,7 @@ end pipes = {"stdin" => nil, "stdout" => nil, "stderr" => nil} while true - command, arguments = read_command($control_socket) + command, arguments = read_command if command == "ready" break @@ -128,36 +133,18 @@ while true pipes["stderr"] = File::open("#{pipe_base_path}.stderr", "r") pipes["stderr"].sync = true - read_ios = [$control_socket, $stdin, pipes["stdout"], pipes["stderr"]] + read_ios += [$stdin, pipes["stdout"], pipes["stderr"]] log "info", "connected via named pipes" - elsif command == "pseudoterminal" - if arguments.empty? - log "error", "malformed response" - exit 1 - end - pseudoterminal_path = Base64.decode64(arguments[0]) - - log "info", "connecting to pseudoterminal at #{pseudoterminal_path}" - - pseudoterminal_io = File::open(pseudoterminal_path, File::RDWR | File::NOCTTY) - - pipes["stdin"] = pseudoterminal_io - pipes["stdout"] = pseudoterminal_io - - read_ios = [$control_socket, $stdin, pipes["stdout"]] - - log "info", "connected via pseudoterminal" + log "info", "ready" + $control_socket.puts "ready" else log "error", "malformed response" exit 1 end end -log "info", "ready" -$control_socket.puts "ready" - exit_code = "unknown" while read_ios.include?($control_socket) or read_ios.include?(pipes["stdout"]) or read_ios.include?(pipes["stderr"]) @@ -180,7 +167,7 @@ while read_ios.include?($control_socket) or read_ios.include?(pipes["stdout"]) o $stderr.write ready_read_io.readpartial(4096) elsif ready_read_io.equal? $control_socket log "trace", "reading from control socket" - command, arguments = read_command($control_socket) + command, arguments = read_command if command != "done" log "error", "malformed response from github-fast-envd" diff --git a/github-fast-envd.rb b/github-fast-envd.rb index d177ffc..aad0286 100755 --- a/github-fast-envd.rb +++ b/github-fast-envd.rb @@ -23,6 +23,10 @@ class ClientScriptError < StandardError end end +$original_stdin = $stdin.dup +$original_stdout = $stdout.dup +$original_stderr = $stderr.dup + control_socket_path = "/tmp/github-fast-envd.sock" if File.exist?(control_socket_path) and File.socket?(control_socket_path) @@ -36,7 +40,7 @@ File.chmod 0700, control_socket_path # control_server.close #end -$stderr.puts "serving control socket" +$original_stderr.puts "serving control socket" connection_id = 0 @@ -101,7 +105,7 @@ def set_up_named_pipes(control_socket, connection_id) stderr = File::open(stderr, "w") stderr.sync = true - $stderr.puts " set up named pipes" + $original_stderr.puts " set up named pipes" control_socket.puts "ready" @@ -117,38 +121,23 @@ def set_up_named_pipes(control_socket, connection_id) $stderr.reopen(stderr) end -def set_up_pseudoterminal(control_socket) - require "pty" +def set_up_pseudoterminal(control_socket, pseudoterminal_path) + pseudoterminal_io = File::open(pseudoterminal_path, File::RDWR | File::NOCTTY) - master, client = PTY.open - master.raw! + $original_stderr.puts " connecting to pseudoterminal #{pseudoterminal_path}" - File.chmod 0600, client.path + $stdin.reopen(pseudoterminal_io) + $stdout.reopen(pseudoterminal_io) + $stderr.reopen(pseudoterminal_io) - client_path = Base64.encode64(client.path).delete("\n") - client.close - - control_socket.puts "pseudoterminal #{client_path}" - - $stderr.puts " set up pseudoterminal" + $original_stderr.puts " connected to pseudoterminal #{pseudoterminal_path}" control_socket.puts "ready" - - response = control_socket.readline.strip - - if response != "ready" - raise ClientError.new "invalid command" - Kernel.exit! - end - - $stdin.reopen(master) - $stdout.reopen(master) - $stderr.reopen(master) end while true control_socket = control_server.accept - $stderr.puts "- new connection" + $original_stderr.puts "- new connection" begin command, arguments = read_command(control_socket) @@ -157,7 +146,7 @@ while true raise ClientError.new "unexpected command" end - if arguments.length != 3 + if arguments.empty? raise ClientError.new "malformed command" end @@ -169,59 +158,65 @@ while true mode = arguments[1] - if not ["named-pipes", "pseudoterminal"].include?(mode) + if mode == "named-pipes" + if arguments.length < 3 + raise ClientError.new "malformed command" + end + elsif mode == "pseudoterminal" + if arguments.length < 4 + raise ClientError.new "malformed command" + end + + pseudoterminal_path = Base64.decode64(arguments[2]) + else raise ClientError.new "unknown mode (#{mode})" end - script_path = Base64.decode64(arguments[2]) + script_path = Base64.decode64(arguments.last) connection_id += 1 child_process = fork { - original_stdin = $stdin.dup - original_stdout = $stdout.dup - original_stderr = $stderr.dup - exit_code = "unknown" if mode == "named-pipes" set_up_named_pipes(control_socket, connection_id) else - set_up_pseudoterminal(control_socket) + set_up_pseudoterminal(control_socket, pseudoterminal_path) end - original_stderr.puts " executing script " + script_path + $original_stderr.puts " executing script " + script_path begin begin load script_path, true rescue SystemExit => error - original_stderr.puts " exit code: #{error.status}" + $original_stderr.puts " exit code: #{error.status}" exit_code = error.status rescue StandardError => error - $stdin = original_stdin - $stdout = original_stdout - $stderr = original_stderr + $stdin = $original_stdin + $stdout = $original_stdout + $stderr = $original_stderr raise ClientScriptError.new error end rescue ClientScriptError => error # TODO: Restore pipes to make sure that syntax errors are caught encoded_error_output = Base64.encode64(error.source.full_message).delete("\n") - original_stderr.puts " error executing script, ignoring request" + $original_stderr.puts " error executing script, ignoring request" # TODO: if the begin/rescue blog has syntax errors, these go unnoticed begin control_socket.puts "script_error #{encoded_error_output}" rescue end rescue ClientError => error - original_stderr.puts " error communicating with client, ignoring request (#{error})" + $original_stderr.puts " error communicating with client, ignoring request (#{error})" begin control_socket.puts "error #{error}" rescue end rescue StandardError => error - original_stderr.puts " error, ignoring request (#{error})" + $original_stderr.puts " error, ignoring request (#{error})" begin control_socket.puts "error internal server error" rescue @@ -229,25 +224,26 @@ while true end begin + # TODO: which exit code to return when interrupted? control_socket.puts "done #{exit_code}" rescue end control_socket.close - original_stderr.puts " finished handling request" + $original_stderr.puts " finished handling request" Kernel.exit! } Process.detach(child_process) rescue ClientError => error - $stderr.puts " error communicating with client, ignoring request (#{error})" + $original_stderr.puts " error communicating with client, ignoring request (#{error})" begin control_socket.puts "error #{error}" rescue end rescue StandardError => error - $stderr.puts " error, ignoring request (#{error})" + $original_stderr.puts " error, ignoring request (#{error})" begin control_socket.puts "error internal server error" rescue