logger = new \PrefixedLogger($logger, "Console Reader Daemon"); $this->prepareSubprocess(); } private function prepareSubprocess() : void{ $server = stream_socket_server("tcp://127.0.0.1:0"); if($server === false){ throw new \RuntimeException("Failed to open console reader socket server"); } $address = Utils::assumeNotFalse(stream_socket_get_name($server, false), "stream_socket_get_name() shouldn't return false here"); //Windows sucks, and likes to corrupt UTF-8 file paths when they travel to the subprocess, so we base64 encode //the path to avoid the problem. This is an abysmally shitty hack, but here we are :( $sub = Utils::assumeNotFalse(proc_open( [PHP_BINARY, '-dopcache.enable_cli=0', '-r', sprintf('require base64_decode("%s", true);', base64_encode(Path::join(__DIR__, 'ConsoleReaderChildProcess.php'))), $address], [ 2 => fopen("php://stderr", "w"), ], $pipes ), "Something has gone horribly wrong"); $client = stream_socket_accept($server, 15); if($client === false){ throw new AssumptionFailedError("stream_socket_accept() returned false"); } stream_socket_shutdown($server, STREAM_SHUT_RDWR); $this->subprocess = $sub; $this->socket = $client; } private function shutdownSubprocess() : void{ //we have no way to signal to the subprocess to shut down gracefully; besides, Windows sucks, and the subprocess //gets stuck in a blocking fgets() read because stream_select() is a hunk of junk (hence the separate process in //the first place). proc_terminate($this->subprocess); proc_close($this->subprocess); stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR); } public function readLine() : ?string{ $r = [$this->socket]; $w = null; $e = null; if(stream_select($r, $w, $e, 0, 0) === 1){ $command = fgets($this->socket); if($command === false){ $this->logger->debug("Lost connection to subprocess, restarting (maybe the child process was killed from outside?)"); $this->shutdownSubprocess(); $this->prepareSubprocess(); return null; } $command = preg_replace("#\\x1b\\x5b([^\\x1b]*\\x7e|[\\x40-\\x50])#", "", trim($command)) ?? throw new AssumptionFailedError("This regex is assumed to be valid"); $command = preg_replace('/[[:cntrl:]]/', '', $command) ?? throw new AssumptionFailedError("This regex is assumed to be valid"); return $command !== "" ? $command : null; } return null; } public function quit() : void{ $this->shutdownSubprocess(); } }