Attach to existing pseudoterminal

This commit is contained in:
Patrick Lühne 2020-05-26 00:15:22 +02:00
parent bbdda36e4c
commit 2741f97050
Signed by: patrick
GPG Key ID: 05F3611E97A70ABF
2 changed files with 54 additions and 71 deletions

View File

@ -48,15 +48,15 @@ def log(level, message)
end end
end end
def read_command(control_socket) def read_command
ready_ios = IO.select([control_socket], [], [], 10) ready_ios = IO.select([$control_socket], [], [], 10)
if not ready_ios if not ready_ios
log "error", "timeout while communicating with github-fast-envd" log "error", "timeout while communicating with github-fast-envd"
exit 1 exit 1
end end
response = control_socket.readline.strip.split(" ", 2) response = $control_socket.readline.strip.split(" ", 2)
if response.empty? if response.empty?
log "error", "malformed response from github-fast-envd" 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") encoded_script_path = Base64.encode64(script_path).delete("\n")
read_ios = [$control_socket]
if $options[:interactive] 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 else
$control_socket.puts "new v1 named-pipes #{encoded_script_path}" $control_socket.puts "new v1 named-pipes #{encoded_script_path}"
end end
@ -107,7 +112,7 @@ end
pipes = {"stdin" => nil, "stdout" => nil, "stderr" => nil} pipes = {"stdin" => nil, "stdout" => nil, "stderr" => nil}
while true while true
command, arguments = read_command($control_socket) command, arguments = read_command
if command == "ready" if command == "ready"
break break
@ -128,36 +133,18 @@ while true
pipes["stderr"] = File::open("#{pipe_base_path}.stderr", "r") pipes["stderr"] = File::open("#{pipe_base_path}.stderr", "r")
pipes["stderr"].sync = true 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" 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", "ready"
$control_socket.puts "ready"
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"
else else
log "error", "malformed response" log "error", "malformed response"
exit 1 exit 1
end end
end end
log "info", "ready"
$control_socket.puts "ready"
exit_code = "unknown" exit_code = "unknown"
while read_ios.include?($control_socket) or read_ios.include?(pipes["stdout"]) or read_ios.include?(pipes["stderr"]) 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) $stderr.write ready_read_io.readpartial(4096)
elsif ready_read_io.equal? $control_socket elsif ready_read_io.equal? $control_socket
log "trace", "reading from control socket" log "trace", "reading from control socket"
command, arguments = read_command($control_socket) command, arguments = read_command
if command != "done" if command != "done"
log "error", "malformed response from github-fast-envd" log "error", "malformed response from github-fast-envd"

View File

@ -23,6 +23,10 @@ class ClientScriptError < StandardError
end end
end end
$original_stdin = $stdin.dup
$original_stdout = $stdout.dup
$original_stderr = $stderr.dup
control_socket_path = "/tmp/github-fast-envd.sock" control_socket_path = "/tmp/github-fast-envd.sock"
if File.exist?(control_socket_path) and File.socket?(control_socket_path) 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 # control_server.close
#end #end
$stderr.puts "serving control socket" $original_stderr.puts "serving control socket"
connection_id = 0 connection_id = 0
@ -101,7 +105,7 @@ def set_up_named_pipes(control_socket, connection_id)
stderr = File::open(stderr, "w") stderr = File::open(stderr, "w")
stderr.sync = true stderr.sync = true
$stderr.puts " set up named pipes" $original_stderr.puts " set up named pipes"
control_socket.puts "ready" control_socket.puts "ready"
@ -117,38 +121,23 @@ def set_up_named_pipes(control_socket, connection_id)
$stderr.reopen(stderr) $stderr.reopen(stderr)
end end
def set_up_pseudoterminal(control_socket) def set_up_pseudoterminal(control_socket, pseudoterminal_path)
require "pty" pseudoterminal_io = File::open(pseudoterminal_path, File::RDWR | File::NOCTTY)
master, client = PTY.open $original_stderr.puts " connecting to pseudoterminal #{pseudoterminal_path}"
master.raw!
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") $original_stderr.puts " connected to pseudoterminal #{pseudoterminal_path}"
client.close
control_socket.puts "pseudoterminal #{client_path}"
$stderr.puts " set up pseudoterminal"
control_socket.puts "ready" 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 end
while true while true
control_socket = control_server.accept control_socket = control_server.accept
$stderr.puts "- new connection" $original_stderr.puts "- new connection"
begin begin
command, arguments = read_command(control_socket) command, arguments = read_command(control_socket)
@ -157,7 +146,7 @@ while true
raise ClientError.new "unexpected command" raise ClientError.new "unexpected command"
end end
if arguments.length != 3 if arguments.empty?
raise ClientError.new "malformed command" raise ClientError.new "malformed command"
end end
@ -169,59 +158,65 @@ while true
mode = arguments[1] 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})" raise ClientError.new "unknown mode (#{mode})"
end end
script_path = Base64.decode64(arguments[2]) script_path = Base64.decode64(arguments.last)
connection_id += 1 connection_id += 1
child_process = fork { child_process = fork {
original_stdin = $stdin.dup
original_stdout = $stdout.dup
original_stderr = $stderr.dup
exit_code = "unknown" exit_code = "unknown"
if mode == "named-pipes" if mode == "named-pipes"
set_up_named_pipes(control_socket, connection_id) set_up_named_pipes(control_socket, connection_id)
else else
set_up_pseudoterminal(control_socket) set_up_pseudoterminal(control_socket, pseudoterminal_path)
end end
original_stderr.puts " executing script " + script_path $original_stderr.puts " executing script " + script_path
begin begin
begin begin
load script_path, true load script_path, true
rescue SystemExit => error rescue SystemExit => error
original_stderr.puts " exit code: #{error.status}" $original_stderr.puts " exit code: #{error.status}"
exit_code = error.status exit_code = error.status
rescue StandardError => error rescue StandardError => error
$stdin = original_stdin $stdin = $original_stdin
$stdout = original_stdout $stdout = $original_stdout
$stderr = original_stderr $stderr = $original_stderr
raise ClientScriptError.new error raise ClientScriptError.new error
end end
rescue ClientScriptError => error rescue ClientScriptError => error
# TODO: Restore pipes to make sure that syntax errors are caught # TODO: Restore pipes to make sure that syntax errors are caught
encoded_error_output = Base64.encode64(error.source.full_message).delete("\n") 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 # TODO: if the begin/rescue blog has syntax errors, these go unnoticed
begin begin
control_socket.puts "script_error #{encoded_error_output}" control_socket.puts "script_error #{encoded_error_output}"
rescue rescue
end end
rescue ClientError => error rescue ClientError => error
original_stderr.puts " error communicating with client, ignoring request (#{error})" $original_stderr.puts " error communicating with client, ignoring request (#{error})"
begin begin
control_socket.puts "error #{error}" control_socket.puts "error #{error}"
rescue rescue
end end
rescue StandardError => error rescue StandardError => error
original_stderr.puts " error, ignoring request (#{error})" $original_stderr.puts " error, ignoring request (#{error})"
begin begin
control_socket.puts "error internal server error" control_socket.puts "error internal server error"
rescue rescue
@ -229,25 +224,26 @@ while true
end end
begin begin
# TODO: which exit code to return when interrupted?
control_socket.puts "done #{exit_code}" control_socket.puts "done #{exit_code}"
rescue rescue
end end
control_socket.close control_socket.close
original_stderr.puts " finished handling request" $original_stderr.puts " finished handling request"
Kernel.exit! Kernel.exit!
} }
Process.detach(child_process) Process.detach(child_process)
rescue ClientError => error rescue ClientError => error
$stderr.puts " error communicating with client, ignoring request (#{error})" $original_stderr.puts " error communicating with client, ignoring request (#{error})"
begin begin
control_socket.puts "error #{error}" control_socket.puts "error #{error}"
rescue rescue
end end
rescue StandardError => error rescue StandardError => error
$stderr.puts " error, ignoring request (#{error})" $original_stderr.puts " error, ignoring request (#{error})"
begin begin
control_socket.puts "error internal server error" control_socket.puts "error internal server error"
rescue rescue