Attach to existing pseudoterminal
This commit is contained in:
		| @@ -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" | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user