#!/usr/bin/env ruby require "base64" require "socket" require "/data/github/current/config/environment" class ClientError < StandardError def initialize(message) super(message) end end class ClientScriptError < StandardError def initialize(source) super(source.message) @source = source end def source @source end end control_socket_path = "/tmp/github-fast-envd.sock" if File.exist?(control_socket_path) and File.socket?(control_socket_path) File.unlink(control_socket_path) end control_server = UNIXServer.new(control_socket_path) File.chmod 0700, control_socket_path #at_exit do # control_server.close #end $stderr.puts "serving control socket" connection_id = 0 def open_pipe(connection_id, name) pipe_path = "/tmp/github-fast-envd.#{connection_id}.#{name}" if File.exist?(pipe_path) and File.pipe?(pipe_path) File.unlink(pipe_path) end File.mkfifo(pipe_path, mode = 0600) pipe_path end def read_command(control_socket) 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) if response.empty? log "error", "malformed response from github-fast-envd" exit 1 end command = response[0] if command == "error" if response.length < 2 log "error", "malformed error response from github-fast-envd" exit 1 end error_message = response[1] log "error", "#{error_message}" exit 1 end arguments = response[1].nil? ? [] : response[1].split(" ") return command, arguments end while true control_socket = control_server.accept $stderr.puts "- new connection" begin command, arguments = read_command(control_socket) if command != "new" raise ClientError.new "unexpected command" end if arguments.length != 2 raise ClientError.new "malformed command" end if arguments[0] != "v1" raise ClientError.new "unsupported protocol version" end connection_id += 1 child_process = fork { original_stdin = $stdin.dup original_stdout = $stdout.dup original_stderr = $stderr.dup exit_code = "unknown" begin stdin = open_pipe(connection_id, "stdin") control_socket.puts "stdin #{stdin}" stdin = File::open(stdin, "r") # TODO: reconsider stdin.sync = true stdout = open_pipe(connection_id, "stdout") control_socket.puts "stdout #{stdout}" stdout = File::open(stdout, "w") stdout.sync = true stderr = open_pipe(connection_id, "stderr") control_socket.puts "stderr #{stderr}" stderr = File::open(stderr, "w") stderr.sync = true $stderr.puts " set up pipes" control_socket.puts "ready" response = control_socket.readline.strip if response != "ready" raise ClientError.new "invalid command" Kernel.exit! end script_path = Base64.decode64(arguments[1]) $stderr.puts " executing script " + script_path $stdin.reopen(stdin) $stdout.reopen(stdout) $stderr.reopen(stderr) begin load script_path, true rescue SystemExit => error original_stderr.puts " exit code: #{error.status}" exit_code = error.status rescue => error $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" # 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})" begin control_socket.puts "error #{error}" rescue end rescue StandardError => error original_stderr.puts " error, ignoring request (#{error})" begin control_socket.puts "error internal server error" rescue end end begin control_socket.puts "done #{exit_code}" rescue end control_socket.close original_stderr.puts " finished handling request" Kernel.exit! } Process.detach(child_process) rescue ClientError => error $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})" begin control_socket.puts "error internal server error" rescue end end control_socket.close end