Changeset View
Changeset View
Standalone View
Standalone View
src/future/exec/ExecFuture.php
| Show All 26 Lines | final class ExecFuture extends Future { | ||||
| private $timeout = null; | private $timeout = null; | ||||
| private $procStatus = null; | private $procStatus = null; | ||||
| private $stdout = null; | private $stdout = null; | ||||
| private $stderr = null; | private $stderr = null; | ||||
| private $stdin = null; | private $stdin = null; | ||||
| private $closePipe = true; | private $closePipe = true; | ||||
| private $powershellXml = false; | |||||
| private $powershellStderr = null; | |||||
| private $stdoutPos = 0; | private $stdoutPos = 0; | ||||
| private $stderrPos = 0; | private $stderrPos = 0; | ||||
| private $command = null; | private $command = null; | ||||
| private $env = null; | private $env = null; | ||||
| private $cwd; | private $cwd; | ||||
| private $readBufferSize; | private $readBufferSize; | ||||
| private $stdoutSizeLimit = PHP_INT_MAX; | private $stdoutSizeLimit = PHP_INT_MAX; | ||||
| ▲ Show 20 Lines • Show All 159 Lines • ▼ Show 20 Lines | public function setEnv($env, $wipe_process_env = false) { | ||||
| } else { | } else { | ||||
| $this->env = $env + $_ENV; | $this->env = $env + $_ENV; | ||||
| } | } | ||||
| return $this; | return $this; | ||||
| } | } | ||||
| /** | /** | ||||
| * Enable parsing the nonsense XML crap that Powershell outputs to stderr, | |||||
| * which wraps all standard error output as CLIXML. | |||||
| * | |||||
| * @param bool Whether this future should parse standard error as CLIXML. | |||||
| * @return this | |||||
| */ | |||||
| public function setPowershellXML($powershell_xml) { | |||||
| $this->powershellXml = $powershell_xml; | |||||
| return $this; | |||||
| } | |||||
| /** | |||||
| * Set the value of a specific environmental variable for this command. | * Set the value of a specific environmental variable for this command. | ||||
| * | * | ||||
| * @param string Environmental variable name. | * @param string Environmental variable name. | ||||
| * @param string|null New value, or null to remove this variable. | * @param string|null New value, or null to remove this variable. | ||||
| * @return this | * @return this | ||||
| * @task config | * @task config | ||||
| */ | */ | ||||
| public function updateEnv($key, $value) { | public function updateEnv($key, $value) { | ||||
| ▲ Show 20 Lines • Show All 505 Lines • ▼ Show 20 Lines | if ($max_stdout_read_bytes > 0) { | ||||
| $this->stdout .= $this->readAndDiscard( | $this->stdout .= $this->readAndDiscard( | ||||
| $stdout, | $stdout, | ||||
| $this->getStdoutSizeLimit() - strlen($this->stdout), | $this->getStdoutSizeLimit() - strlen($this->stdout), | ||||
| 'stdout', | 'stdout', | ||||
| $max_stdout_read_bytes); | $max_stdout_read_bytes); | ||||
| } | } | ||||
| if ($max_stderr_read_bytes > 0) { | if ($max_stderr_read_bytes > 0) { | ||||
| if ($this->powershellXml) { | |||||
| $this->powershellStderr .= $this->readAndDiscard( | |||||
| $stderr, | |||||
| $this->getStderrSizeLimit() - strlen($this->powershellStderr), | |||||
| 'stderr', | |||||
| $max_stderr_read_bytes); | |||||
| list($parsed_stderr, $stderr_taken) = $this->parsePowershellXML( | |||||
| $this->powershellStderr); | |||||
| $this->stderr .= $parsed_stderr; | |||||
| $this->powershellStderr = substr( | |||||
| $this->powershellStderr, | |||||
| $stderr_taken); | |||||
| } else { | |||||
| $this->stderr .= $this->readAndDiscard( | $this->stderr .= $this->readAndDiscard( | ||||
| $stderr, | $stderr, | ||||
| $this->getStderrSizeLimit() - strlen($this->stderr), | $this->getStderrSizeLimit() - strlen($this->stderr), | ||||
| 'stderr', | 'stderr', | ||||
| $max_stderr_read_bytes); | $max_stderr_read_bytes); | ||||
| } | } | ||||
| } | |||||
| if (!$status['running']) { | if (!$status['running']) { | ||||
| $this->result = array( | $this->result = array( | ||||
| $status['exitcode'], | $status['exitcode'], | ||||
| $this->stdout, | $this->stdout, | ||||
| $this->stderr, | $this->stderr, | ||||
| ); | ); | ||||
| $this->closeProcess(); | $this->closeProcess(); | ||||
| return true; | return true; | ||||
| } | } | ||||
| $elapsed = (microtime(true) - $this->start); | $elapsed = (microtime(true) - $this->start); | ||||
| if ($this->timeout && ($elapsed >= $this->timeout)) { | if ($this->timeout && ($elapsed >= $this->timeout)) { | ||||
| $this->killedByTimeout = true; | $this->killedByTimeout = true; | ||||
| $this->resolveKill(); | $this->resolveKill(); | ||||
| return true; | return true; | ||||
| } | } | ||||
| } | } | ||||
| /** | /** | ||||
| * Parse the powershell XML and convert it into output for CLIXML. | |||||
| */ | |||||
| public function parsePowershellXML($powershell_xml) { | |||||
| $lines = phutil_split_lines($powershell_xml); | |||||
| $result = ''; | |||||
| $consumed_characters = 0; | |||||
| $previous_characters = 0; | |||||
| // With CLIXML, each line contains XML, or the CLIXML start line. | |||||
| for ($i = 0; $i < count($lines); $i++) { | |||||
| $line = $lines[$i]; | |||||
| // Calculate how many characters we're consuming. | |||||
| $previous_characters = $consumed_characters; | |||||
| $consumed_characters += strlen($line); | |||||
| while ( | |||||
| $consumed_characters < strlen($powershell_xml) && ( | |||||
| $powershell_xml[$consumed_characters] === "\r" || | |||||
| $powershell_xml[$consumed_characters] === "\n")) { | |||||
| $consumed_characters += 1; | |||||
| } | |||||
| // CLIXML outputs "#< CLIXML" on the first line, so we discard it. | |||||
| if (trim($line) === '#< CLIXML') { | |||||
| continue; | |||||
| } | |||||
| // Try and load the line as XML. Because other processes can write to | |||||
| // standard error, it can be random output from other processes (yay!) | |||||
| $xml = @simplexml_load_string($line); | |||||
| if ($xml === false) { | |||||
| // Check to see if this is the last line; if it is, we might not be | |||||
| // able to parse it because it's not fully read yet. | |||||
| if ($i === count($lines) - 1) { | |||||
| return array($result, $previous_characters); | |||||
| } | |||||
| // If we've fully read this line, and we still can't read it, then it | |||||
| // might be output from another program, so just return it as part | |||||
| // of the results. | |||||
| $result .= $line; | |||||
| continue; | |||||
| } | |||||
| $xml->registerXPathNamespace( | |||||
| 'ns', | |||||
| 'http://schemas.microsoft.com/powershell/2004/04'); | |||||
| $error_lines = $xml->xpath('//ns:S[@S=\'Error\']'); | |||||
| foreach ($error_lines as $error) { | |||||
| // Microsoft; land of the completely made-up standards. | |||||
| $error = str_replace('_x000D_', "\r", $error); | |||||
| $error = str_replace('_x000A_', "\n", $error); | |||||
| $result .= $error; | |||||
| } | |||||
| } | |||||
| return array($result, $consumed_characters); | |||||
| } | |||||
| /** | |||||
| * @return void | * @return void | ||||
| * @task internal | * @task internal | ||||
| */ | */ | ||||
| public function __destruct() { | public function __destruct() { | ||||
| if (!$this->proc) { | if (!$this->proc) { | ||||
| return; | return; | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 107 Lines • Show Last 20 Lines | |||||