Page MenuHomec4science

PhutilExecPassthru.php
No OneTemporary

File Metadata

Created
Fri, Nov 22, 13:37

PhutilExecPassthru.php

<?php
/**
* Execute a command which takes over stdin, stdout and stderr, similar to
* `passthru()`, but which preserves TTY semantics, escapes arguments, and is
* traceable.
*
* Passthru commands use the `STDIN`, `STDOUT` and `STDERR` of the parent
* process, so input can be read from the console and output is printed to it.
* This is primarily useful for executing things like `$EDITOR` from command
* line scripts.
*
* $exec = new PhutilExecPassthru('ls %s', $dir);
* $err = $exec->execute();
*
* You can set the current working directory for the command with
* @{method:setCWD}, and set the environment with @{method:setEnv}.
*
* @task command Executing Passthru Commands
* @task config Configuring Passthru Commands
*/
final class PhutilExecPassthru extends Phobject {
private $command;
private $env;
private $cwd;
/* -( Executing Passthru Commands )---------------------------------------- */
/**
* Build a new passthru command.
*
* $exec = new PhutilExecPassthru('ls %s', $dir);
*
* @param string Command pattern. See @{function:csprintf}.
* @param ... Pattern arguments.
*
* @task command
*/
public function __construct($pattern /* , ... */) {
$args = func_get_args();
$this->command = call_user_func_array('csprintf', $args);
}
/**
* Execute this command.
*
* @return int Error code returned by the subprocess.
*
* @task command
*/
public function execute() {
$command = $this->command;
$profiler = PhutilServiceProfiler::getInstance();
$call_id = $profiler->beginServiceCall(
array(
'type' => 'exec',
'subtype' => 'passthru',
'command' => $command,
));
$spec = array(STDIN, STDOUT, STDERR);
$pipes = array();
if ($command instanceof PhutilCommandString) {
$unmasked_command = $command->getUnmaskedString();
} else {
$unmasked_command = $command;
}
$env = $this->env;
$cwd = $this->cwd;
$options = array();
if (phutil_is_windows()) {
// Without 'bypass_shell', things like launching vim don't work properly,
// and we can't execute commands with spaces in them, and all commands
// invoked from git bash fail horridly, and everything is a mess in
// general.
$options['bypass_shell'] = true;
}
$trap = new PhutilErrorTrap();
$proc = @proc_open(
$unmasked_command,
$spec,
$pipes,
$cwd,
$env,
$options);
$errors = $trap->getErrorsAsString();
$trap->destroy();
if (!is_resource($proc)) {
throw new Exception(
pht(
'Failed to passthru %s: %s',
'proc_open()',
$errors));
}
$err = proc_close($proc);
$profiler->endServiceCall(
$call_id,
array(
'err' => $err,
));
return $err;
}
/* -( Configuring Passthru Commands )-------------------------------------- */
/**
* Set environmental variables for the subprocess.
*
* By default, variables are added to the environment of this process. You
* can optionally wipe the environment and pass only the specified values.
*
* // Env will have "X" and current env ("PATH", etc.)
* $exec->setEnv(array('X' => 'y'));
*
* // Env will have ONLY "X".
* $exec->setEnv(array('X' => 'y'), $wipe_process_env = true);
*
* @param dict Dictionary of environmental variables.
* @param bool Optionally, pass true to wipe the existing environment clean.
* @return this
*
* @task config
*/
public function setEnv(array $env, $wipe_process_env = false) {
if ($wipe_process_env) {
$this->env = $env;
} else {
$this->env = $env + $_ENV;
}
return $this->env;
}
/**
* Set the current working directory for the subprocess (that is, set where
* the subprocess will execute). If not set, the default value is the parent's
* current working directory.
*
* @param string Directory to execute the subprocess in.
* @return this
*
* @task config
*/
public function setCWD($cwd) {
$this->cwd = $cwd;
return $this;
}
}

Event Timeline