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 |