4 // +----------------------------------------------------------------------+
6 // +----------------------------------------------------------------------+
7 // | Copyright (c) 1997-2003 The PHP Group |
8 // +----------------------------------------------------------------------+
9 // | This source file is subject to version 2.02 of the PHP license, |
10 // | that is bundled with this package in the file LICENSE, and is |
11 // | available at through the world-wide-web at |
12 // | http://www.php.net/license/2_02.txt. |
13 // | If you did not receive a copy of the PHP license and are unable to |
14 // | obtain it through the world-wide-web, please send a note to |
15 // | license@php.net so we can mail you a copy immediately. |
16 // +----------------------------------------------------------------------+
17 // | Author: Anders Johannsen <anders@johannsen.com> |
18 // | Author: Dan Allen <dan@mojavelinux.com>
19 // +----------------------------------------------------------------------+
21 // $Id: Command.php,v 1.9 2007/04/20 21:08:48 cconstantine Exp $
26 require_once 'PEAR.php';
27 require_once 'System.php';
32 define('SYSTEM_COMMAND_OK', 1);
33 define('SYSTEM_COMMAND_ERROR', -1);
34 define('SYSTEM_COMMAND_NO_SHELL', -2);
35 define('SYSTEM_COMMAND_INVALID_SHELL', -3);
36 define('SYSTEM_COMMAND_TMPDIR_ERROR', -4);
37 define('SYSTEM_COMMAND_INVALID_OPERATOR', -5);
38 define('SYSTEM_COMMAND_INVALID_COMMAND', -6);
39 define('SYSTEM_COMMAND_OPERATOR_PLACEMENT',-7);
40 define('SYSTEM_COMMAND_COMMAND_PLACEMENT', -8);
41 define('SYSTEM_COMMAND_NOHUP_MISSING', -9);
42 define('SYSTEM_COMMAND_NO_OUTPUT', -10);
43 define('SYSTEM_COMMAND_STDERR', -11);
44 define('SYSTEM_COMMAND_NONZERO_EXIT', -12);
48 // {{{ class System_Command
51 * The System_Command:: class implements an abstraction for various ways
52 * of executing commands (directly using the backtick operator,
53 * as a background task after the script has terminated using
54 * register_shutdown_function() or as a detached process using nohup).
56 * @author Anders Johannsen <anders@johannsen.com>
57 * @author Dan Allen <dan@mojavelinux.com>
58 * @version $Revision: 1.9 $
62 class System_Command {
66 * Array of settings used when creating the shell command
71 var $options = array();
74 * Array of available shells to use to execute the command
79 var $shells = array();
82 * Array of available control operators used between commands
87 var $controlOperators = array();
90 * The system command to be executed
95 var $systemCommand = null;
98 * Previously added part to the command string
103 var $previousElement = null;
106 * Directory for writing stderr output
114 * To allow the pear error object to accumulate when building
115 * the command, we use the command status to keep track when
116 * a pear error is raised
121 var $commandStatus = 0;
124 * Hold initialization PEAR_Error
129 var $_initError = null;
137 * Defines all necessary constants and sets defaults
141 function System_Command($in_shell = null)
143 // Defining constants
144 $this->options = array(
147 'SHELL' => $this->which($in_shell),
150 'BACKGROUND' => false,
154 // prepare the available control operators
155 $this->controlOperators = array(
164 // List of allowed/available shells
165 $this->shells = array(
177 // Find the first available shell
178 if (empty($this->options['SHELL'])) {
179 foreach ($this->shells as $shell) {
180 if ($this->options['SHELL'] = $this->which($shell)) {
185 // see if we still have no shell
186 if (empty($this->options['SHELL'])) {
187 $this->_initError =& PEAR::raiseError(null, SYSTEM_COMMAND_NO_SHELL, null, E_USER_WARNING, null, 'System_Command_Error', true);
192 // Caputre a temporary directory for capturing stderr from commands
193 $this->tmpDir = System::tmpdir();
194 if (!System::mkDir("-p {$this->tmpDir}")) {
195 $this->_initError =& PEAR::raiseError(null, SYSTEM_COMMAND_TMPDIR_ERROR, null, E_USER_WARNING, null, 'System_Command_Error', true);
204 * Sets the value for an option. Each option should be set to true
205 * or false; except the 'SHELL' option which should be a string
206 * naming a shell. The options are:
208 * 'SEQUENCE' Allow a sequence command or not (right now this is always on);
210 * 'SHUTDOWN' Execute commands via a shutdown function;
212 * 'SHELL' Path to shell;
214 * 'OUTPUT' Output stdout from process;
216 * 'NOHUP' Use nohup to detach process;
218 * 'BACKGROUND' Run as a background process with &;
220 * 'STDERR' Output on stderr will raise an error, even if
221 * the command's exit value is zero. The output from
222 * stderr can be retrieved using the getDebugInfo()
223 * method of the Pear_ERROR object returned by
226 * @param string $in_option is a case-sensitive string,
227 * corresponding to the option
228 * that should be changed
229 * @param mixed $in_setting is the new value for the option
231 * @return bool true if succes, else false
233 function setOption($in_option, $in_setting)
235 if ($this->_initError) {
236 return $this->_initError;
239 $option = strtoupper($in_option);
241 if (!isset($this->options[$option])) {
242 PEAR::raiseError(null, SYSTEM_COMMAND_ERROR, null, E_USER_NOTICE, null, 'System_Command_Error', true);
252 $this->options[$option] = !empty($in_setting);
257 if (($shell = $this->which($in_setting)) !== false) {
258 $this->options[$option] = $shell;
262 PEAR::raiseError(null, SYSTEM_COMMAND_NO_SHELL, null, E_USER_NOTICE, $in_setting, 'System_Command_Error', true);
268 if (empty($in_setting)) {
269 $this->options[$option] = false;
271 else if ($location = $this->which('nohup')) {
272 $this->options[$option] = $location;
275 PEAR::raiseError(null, SYSTEM_COMMAND_NOHUP_MISSING, null, E_USER_NOTICE, null, 'System_Command_Error', true);
286 * Used to push a command onto the running command to be executed
288 * @param string $in_command binary to be run
289 * @param string $in_argument either an option or argument value, to be handled appropriately
290 * @param string $in_argument
294 * @return boolean true on success {or System_Command_Error Exception}
296 function pushCommand($in_command)
298 if ($this->_initError) {
299 return $this->_initError;
302 if (!is_null($this->previousElement) && !in_array($this->previousElement, $this->controlOperators)) {
303 $this->commandStatus = -1;
304 $error = PEAR::raiseError(null, SYSTEM_COMMAND_COMMAND_PLACEMENT, null, E_USER_WARNING, null, 'System_Command_Error', true);
307 // check for error here
308 $command = escapeshellcmd($this->which($in_command));
309 if ($command === false) {
310 $error = PEAR::raiseError(null, SYSTEM_COMMAND_INVALID_COMMAND, null, E_USER_WARNING, null, 'System_Command_Error', true);
313 $argv = func_get_args();
315 foreach($argv as $arg) {
316 if (strpos($arg, '-') === 0) {
317 $command .= ' ' . $arg;
319 elseif ($arg != '') {
320 $command .= ' ' . escapeshellarg($arg);
324 $this->previousElement = $command;
325 $this->systemCommand .= $command;
327 return isset($error) ? $error : true;
331 // {{{ pushOperator()
334 * Used to push an operator onto the running command to be executed
336 * @param string $in_operator Either string reprentation of operator or system character
339 * @return boolean true on success {or System_Command_Error Exception}
341 function pushOperator($in_operator)
343 if ($this->_initError) {
344 return $this->_initError;
347 $operator = isset($this->controlOperators[$in_operator]) ? $this->controlOperators[$in_operator] : $in_operator;
349 if (is_null($this->previousElement) || in_array($this->previousElement, $this->controlOperators)) {
350 $this->commandStatus = -1;
351 $error = PEAR::raiseError(null, SYSTEM_COMMAND_OPERATOR_PLACEMENT, null, E_USER_WARNING, null, 'System_Command_Error', true);
353 elseif (!in_array($operator, $this->controlOperators)) {
354 $this->commandStatus = -1;
355 $error = PEAR::raiseError(null, SYSTEM_COMMAND_INVALID_OPERATOR, null, E_USER_WARNING, $operator, 'System_Command_Error', true);
358 $this->previousElement = $operator;
359 $this->systemCommand .= ' ' . $operator . ' ';
360 return isset($error) ? $error : true;
367 * Executes the code according to given options
369 * @return bool true if success {or System_Command_Exception}
375 if ($this->_initError) {
376 return $this->_initError;
379 // if the command is empty or if the last element was a control operator, we can't continue
380 if (is_null($this->previousElement) || $this->commandStatus == -1 || in_array($this->previousElement, $this->controlOperators)) {
381 return PEAR::raiseError(null, SYSTEM_COMMAND_INVALID_COMMAND, null, E_USER_WARNING, $this->systemCommand, 'System_Command_Error', true);
384 // Warning about impossible mix of options
385 if (!empty($this->options['OUTPUT'])) {
386 if (!empty($this->options['SHUTDOWN']) || !empty($this->options['NOHUP'])) {
387 return PEAR::raiseError(null, SYSTEM_COMMAND_NO_OUTPUT, null, E_USER_WARNING, null, 'System_Command_Error', true);
391 // if this is not going to stdout, then redirect to /dev/null
392 if (empty($this->options['OUTPUT'])) {
393 $this->systemCommand .= ' >/dev/null';
397 // run a command immune to hangups, with output to a non-tty
398 if (!empty($this->options['NOHUP'])) {
399 $this->systemCommand = $this->options['NOHUP'] . $this->systemCommand;
401 // run a background process (only if not nohup)
402 elseif (!empty($this->options['BACKGROUND'])) {
406 // Register to be run on shutdown
407 if (!empty($this->options['SHUTDOWN'])) {
408 $line = "system(\"{$this->systemCommand}$suffix\");";
409 $function = create_function('', $line);
410 register_shutdown_function($function);
414 // send stderr to a file so that we can reap the error message
415 $tmpFile = tempnam($this->tmpDir, 'System_Command-');
416 $this->systemCommand .= ' 2>' . $tmpFile . $suffix;
417 $shellPipe = $this->which('echo') . ' ' . escapeshellarg($this->systemCommand) . ' | ' . $this->options['SHELL'];
418 exec($shellPipe, $result, $returnVal);
420 if ($returnVal !== 0) {
421 // command returned nonzero; that's always an error
422 $return = PEAR::raiseError(null, SYSTEM_COMMAND_NONZERO_EXIT, null, E_USER_WARNING, null, 'System_Command_Error', true);
424 else if (!$this->options['STDERR']) {
425 // caller does not care about stderr; return success
426 $return = implode("\n", $result);
429 // our caller cares about stderr; check stderr output
431 if (filesize($tmpFile) > 0) {
432 // the command actually wrote to stderr
433 $stderr_output = file_get_contents($tmpFile);
434 $return = PEAR::raiseError(null, SYSTEM_COMMAND_STDERR, null, E_USER_WARNING, $stderr_output, 'System_Command_Error', true);
436 // total success; return stdout gathered by exec()
437 $return = implode("\n", $result);
450 * Functionality similiar to unix 'which'. Searches the path
451 * for the specified program.
453 * @param $cmd name of the executable to search for
456 * @return string returns the full path if found, false if not
458 function which($in_cmd)
460 // only pass non-empty strings to System::which()
461 if (!is_string($in_cmd) || '' === $in_cmd) {
465 // explicitly pass false as fallback value
466 return System::which($in_cmd, false);
473 * Prepare for a new command to be built
480 $this->previousElement = null;
481 $this->systemCommand = null;
482 $this->commandStatus = 0;
486 // {{{ errorMessage()
489 * Return a textual error message for a System_Command error code
491 * @param integer error code
493 * @return string error message, or false if the error code was
496 function errorMessage($in_value)
498 static $errorMessages;
499 if (!isset($errorMessages)) {
500 $errorMessages = array(
501 SYSTEM_COMMAND_OK => 'no error',
502 SYSTEM_COMMAND_ERROR => 'unknown error',
503 SYSTEM_COMMAND_NO_SHELL => 'no shell found',
504 SYSTEM_COMMAND_INVALID_SHELL => 'invalid shell',
505 SYSTEM_COMMAND_TMPDIR_ERROR => 'could not create temporary directory',
506 SYSTEM_COMMAND_INVALID_OPERATOR => 'control operator invalid',
507 SYSTEM_COMMAND_INVALID_COMMAND => 'invalid system command',
508 SYSTEM_COMMAND_OPERATOR_PLACEMENT => 'invalid placement of control operator',
509 SYSTEM_COMMAND_COMMAND_PLACEMENT => 'invalid placement of command',
510 SYSTEM_COMMAND_NOHUP_MISSING => 'nohup not found on system',
511 SYSTEM_COMMAND_NO_OUTPUT => 'output not allowed',
512 SYSTEM_COMMAND_STDERR => 'command wrote to stderr',
513 SYSTEM_COMMAND_NONZERO_EXIT => 'non-zero exit value from command',
517 if (System_Command::isError($in_value)) {
518 $in_value = $in_value->getCode();
521 return isset($errorMessages[$in_value]) ? $errorMessages[$in_value] : $errorMessages[SYSTEM_COMMAND_ERROR];
528 * Tell whether a result code from a System_Command method is an error
530 * @param int result code
532 * @return bool whether $in_value is an error
536 function isError($in_value)
538 return (is_object($in_value) &&
539 (strtolower(get_class($in_value)) == 'system_command_error' ||
540 is_subclass_of($in_value, 'system_command_error')));
546 // {{{ class System_Command_Error
549 * System_Command_Error constructor.
551 * @param mixed System_Command error code, or string with error message.
552 * @param integer what "error mode" to operate in
553 * @param integer what error level to use for $mode & PEAR_ERROR_TRIGGER
554 * @param mixed additional debug info, such as the last query
562 class System_Command_Error extends PEAR_Error
567 * Message in front of the error message
568 * @var string $error_message_prefix
570 var $error_message_prefix = 'System_Command Error: ';
575 function System_Command_Error($code = SYSTEM_COMMAND_ERROR, $mode = PEAR_ERROR_RETURN,
576 $level = E_USER_NOTICE, $debuginfo = null)
579 $this->PEAR_Error(System_Command::errorMessage($code), $code, $mode, $level, $debuginfo);
581 $this->PEAR_Error("Invalid error code: $code", SYSTEM_COMMAND_ERROR, $mode, $level, $debuginfo);