Skip to content

Instantly share code, notes, and snippets.

@andrewmackrodt
Created February 20, 2015 13:20
Show Gist options
  • Select an option

  • Save andrewmackrodt/568c8bd9e5a27a739256 to your computer and use it in GitHub Desktop.

Select an option

Save andrewmackrodt/568c8bd9e5a27a739256 to your computer and use it in GitHub Desktop.
class Command {
private static $descriptorSpec = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'w'));
private static $shellCache = array();
private $command;
private $shell;
public function __construct($command, $shell = '') {
$this->command = $command;
$this->shell = $shell ?: self::getUserDefaultShell();
}
public function run() {
// keep the ide happy
$proc = null;
$pipes = null;
try {
$this->runInternal($proc, $pipes);
$this->finish($proc, $pipes);
} catch (Exception $e) {
$this->finish($proc, $pipes);
throw $e;
}
}
private function runInternal(&$proc, &$pipes) {
$proc = proc_open(sprintf(
"script -efq -c %s /dev/null",
escapeshellarg(sprintf("$this->shell -lc %s", escapeshellarg($this->command)))
), self::$descriptorSpec, $pipes);
if (count($pipes) !== 3) {
throw new Exception();
}
foreach ($pipes as $i => $pipe) {
if (!stream_set_blocking($pipe, 0)) {
throw new Exception();
}
}
$stdin = '';
$stdout = '';
$stderr = '';
$pipeIds = array(
(int)STDIN => &$stdin,
(int)$pipes[1] => &$stdout,
(int)$pipes[2] => &$stderr
);
while ($pipes) {
foreach ($pipes as $i => $pipe) {
if (!is_resource($pipe) || feof($pipe)) {
unset($pipes[$i]);
}
}
$write = $stdin && isset($pipes[0]) ? array($pipes[0]) : array();
$read = array();
if (isset($pipes[1])) $read[] = $pipes[1];
if (isset($pipes[2])) $read[] = $pipes[2];
if (!$read) {
foreach ($write as $pipe) {
fclose($pipe);
}
break;
}
$read[] = STDIN;
$except = array_merge($read, $write);
if ($count = stream_select($read, $write, $except, 2)) {
foreach ($except as $k => $pipe) {
throw new Exception();
}
foreach ($read as $k => $pipe) {
$pipeId = (int)$pipe;
$str = fgets($pipe);
$pipeIds[$pipeId] .= $str;
if ($pipe !== STDIN) {
echo $str;
}
}
if ($stdin) {
foreach ($write as $pipe) {
$written = fputs($pipe, $stdin);
$stdin = substr($stdin, $written) ?: '';
}
}
} else if ($count === false) {
throw new Exception();
}
}
}
private function finish($proc, $pipes) {
if (is_array($pipes)) {
foreach ($pipes as $pipe) {
if (is_resource($pipe)) {
fclose($pipe);
}
}
}
if (is_resource($proc)) {
proc_close($proc);
}
}
private static function getUserDefaultShell($uid = '') {
self::initShellCache();
$uid = is_numeric($uid) ? $uid : getmyuid();
if (isset(self::$shellCache[$uid])) {
$shell = self::$shellCache[$uid];
} else {
$shell = '/bin/sh';
trigger_error("Could not determine the default shell for user $uid", E_USER_WARNING);
}
return $shell;
}
private static function initShellCache() {
if (!self::$shellCache) {
preg_match_all(
'/^[^:]+:x:([0-9]+):.+:([^:\n]+)$/m',
file_get_contents('/etc/passwd'),
$matches,
PREG_SET_ORDER);
foreach ($matches as $match) {
$uid = $match[1];
$shell = $match[2];
self::$shellCache[$uid] = $shell;
}
}
}
}
$command = new Command('mysql --help');
$command->run();
exit;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment