Commit | Line | Data |
---|---|---|
7f254ad8 AE |
1 | <?php |
2 | // {{{ license | |
3 | ||
4 | // +----------------------------------------------------------------------+ | |
5 | // | PHP Version 4.0 | | |
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 | // +----------------------------------------------------------------------+ | |
20 | ||
21 | // $Id: Command.php,v 1.9 2007/04/20 21:08:48 cconstantine Exp $ | |
22 | ||
23 | // }}} | |
24 | // {{{ includes | |
25 | ||
26 | require_once 'PEAR.php'; | |
27 | require_once 'System.php'; | |
28 | ||
29 | // }}} | |
30 | // {{{ constants | |
31 | ||
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); | |
45 | ||
46 | // }}} | |
47 | ||
48 | // {{{ class System_Command | |
49 | ||
50 | /** | |
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). | |
55 | * | |
56 | * @author Anders Johannsen <anders@johannsen.com> | |
57 | * @author Dan Allen <dan@mojavelinux.com> | |
58 | * @version $Revision: 1.9 $ | |
59 | */ | |
60 | ||
61 | // }}} | |
62 | class System_Command { | |
63 | // {{{ properties | |
64 | ||
65 | /** | |
66 | * Array of settings used when creating the shell command | |
67 | * | |
68 | * @var array | |
69 | * @access private | |
70 | */ | |
71 | var $options = array(); | |
72 | ||
73 | /** | |
74 | * Array of available shells to use to execute the command | |
75 | * | |
76 | * @var array | |
77 | * @access private | |
78 | */ | |
79 | var $shells = array(); | |
80 | ||
81 | /** | |
82 | * Array of available control operators used between commands | |
83 | * | |
84 | * @var array | |
85 | * @access private | |
86 | */ | |
87 | var $controlOperators = array(); | |
88 | ||
89 | /** | |
90 | * The system command to be executed | |
91 | * | |
92 | * @var string | |
93 | * @access private | |
94 | */ | |
95 | var $systemCommand = null; | |
96 | ||
97 | /** | |
98 | * Previously added part to the command string | |
99 | * | |
100 | * @var string | |
101 | * @access private | |
102 | */ | |
103 | var $previousElement = null; | |
104 | ||
105 | /** | |
106 | * Directory for writing stderr output | |
107 | * | |
108 | * @var string | |
109 | * @access private | |
110 | */ | |
111 | var $tmpDir = null; | |
112 | ||
113 | /** | |
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 | |
117 | * | |
118 | * @var int | |
119 | * @access private | |
120 | */ | |
121 | var $commandStatus = 0; | |
122 | ||
123 | /** | |
124 | * Hold initialization PEAR_Error | |
125 | * | |
126 | * @var object | |
127 | * @access private | |
128 | **/ | |
129 | var $_initError = null; | |
130 | ||
131 | // }}} | |
132 | // {{{ constructor | |
133 | ||
134 | /** | |
135 | * Class constructor | |
136 | * | |
137 | * Defines all necessary constants and sets defaults | |
138 | * | |
139 | * @access public | |
140 | */ | |
141 | function System_Command($in_shell = null) | |
142 | { | |
143 | // Defining constants | |
144 | $this->options = array( | |
145 | 'SEQUENCE' => true, | |
146 | 'SHUTDOWN' => false, | |
147 | 'SHELL' => $this->which($in_shell), | |
148 | 'OUTPUT' => true, | |
149 | 'NOHUP' => false, | |
150 | 'BACKGROUND' => false, | |
151 | 'STDERR' => false | |
152 | ); | |
153 | ||
154 | // prepare the available control operators | |
155 | $this->controlOperators = array( | |
156 | 'PIPE' => '|', | |
157 | 'AND' => '&&', | |
158 | 'OR' => '||', | |
159 | 'GROUP' => ';', | |
160 | 'LFIFO' => '<', | |
161 | 'RFIFO' => '>', | |
162 | ); | |
163 | ||
164 | // List of allowed/available shells | |
165 | $this->shells = array( | |
166 | 'sh', | |
167 | 'bash', | |
168 | 'zsh', | |
169 | 'tcsh', | |
170 | 'csh', | |
171 | 'ash', | |
172 | 'sash', | |
173 | 'esh', | |
174 | 'ksh' | |
175 | ); | |
176 | ||
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)) { | |
181 | break; | |
182 | } | |
183 | } | |
184 | ||
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); | |
188 | return; | |
189 | } | |
190 | } | |
191 | ||
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); | |
196 | return; | |
197 | } | |
198 | } | |
199 | ||
200 | // }}} | |
201 | // {{{ setOption() | |
202 | ||
203 | /** | |
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: | |
207 | * | |
208 | * 'SEQUENCE' Allow a sequence command or not (right now this is always on); | |
209 | * | |
210 | * 'SHUTDOWN' Execute commands via a shutdown function; | |
211 | * | |
212 | * 'SHELL' Path to shell; | |
213 | * | |
214 | * 'OUTPUT' Output stdout from process; | |
215 | * | |
216 | * 'NOHUP' Use nohup to detach process; | |
217 | * | |
218 | * 'BACKGROUND' Run as a background process with &; | |
219 | * | |
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 | |
224 | * execute().; | |
225 | * | |
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 | |
230 | * @access public | |
231 | * @return bool true if succes, else false | |
232 | */ | |
233 | function setOption($in_option, $in_setting) | |
234 | { | |
235 | if ($this->_initError) { | |
236 | return $this->_initError; | |
237 | } | |
238 | ||
239 | $option = strtoupper($in_option); | |
240 | ||
241 | if (!isset($this->options[$option])) { | |
242 | PEAR::raiseError(null, SYSTEM_COMMAND_ERROR, null, E_USER_NOTICE, null, 'System_Command_Error', true); | |
243 | return false; | |
244 | } | |
245 | ||
246 | switch ($option) { | |
247 | case 'OUTPUT': | |
248 | case 'SHUTDOWN': | |
249 | case 'SEQUENCE': | |
250 | case 'BACKGROUND': | |
251 | case 'STDERR': | |
252 | $this->options[$option] = !empty($in_setting); | |
253 | return true; | |
254 | break; | |
255 | ||
256 | case 'SHELL': | |
257 | if (($shell = $this->which($in_setting)) !== false) { | |
258 | $this->options[$option] = $shell; | |
259 | return true; | |
260 | } | |
261 | else { | |
262 | PEAR::raiseError(null, SYSTEM_COMMAND_NO_SHELL, null, E_USER_NOTICE, $in_setting, 'System_Command_Error', true); | |
263 | return false; | |
264 | } | |
265 | break; | |
266 | ||
267 | case 'NOHUP': | |
268 | if (empty($in_setting)) { | |
269 | $this->options[$option] = false; | |
270 | } | |
271 | else if ($location = $this->which('nohup')) { | |
272 | $this->options[$option] = $location; | |
273 | } | |
274 | else { | |
275 | PEAR::raiseError(null, SYSTEM_COMMAND_NOHUP_MISSING, null, E_USER_NOTICE, null, 'System_Command_Error', true); | |
276 | return false; | |
277 | } | |
278 | break; | |
279 | } | |
280 | } | |
281 | ||
282 | // }}} | |
283 | // {{{ pushCommand() | |
284 | ||
285 | /** | |
286 | * Used to push a command onto the running command to be executed | |
287 | * | |
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 | |
291 | * @param ... | |
292 | * | |
293 | * @access public | |
294 | * @return boolean true on success {or System_Command_Error Exception} | |
295 | */ | |
296 | function pushCommand($in_command) | |
297 | { | |
298 | if ($this->_initError) { | |
299 | return $this->_initError; | |
300 | } | |
301 | ||
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); | |
305 | } | |
306 | ||
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); | |
311 | } | |
312 | ||
313 | $argv = func_get_args(); | |
314 | array_shift($argv); | |
315 | foreach($argv as $arg) { | |
316 | if (strpos($arg, '-') === 0) { | |
317 | $command .= ' ' . $arg; | |
318 | } | |
319 | elseif ($arg != '') { | |
320 | $command .= ' ' . escapeshellarg($arg); | |
321 | } | |
322 | } | |
323 | ||
324 | $this->previousElement = $command; | |
325 | $this->systemCommand .= $command; | |
326 | ||
327 | return isset($error) ? $error : true; | |
328 | } | |
329 | ||
330 | // }}} | |
331 | // {{{ pushOperator() | |
332 | ||
333 | /** | |
334 | * Used to push an operator onto the running command to be executed | |
335 | * | |
336 | * @param string $in_operator Either string reprentation of operator or system character | |
337 | * | |
338 | * @access public | |
339 | * @return boolean true on success {or System_Command_Error Exception} | |
340 | */ | |
341 | function pushOperator($in_operator) | |
342 | { | |
343 | if ($this->_initError) { | |
344 | return $this->_initError; | |
345 | } | |
346 | ||
347 | $operator = isset($this->controlOperators[$in_operator]) ? $this->controlOperators[$in_operator] : $in_operator; | |
348 | ||
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); | |
352 | } | |
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); | |
356 | } | |
357 | ||
358 | $this->previousElement = $operator; | |
359 | $this->systemCommand .= ' ' . $operator . ' '; | |
360 | return isset($error) ? $error : true; | |
361 | } | |
362 | ||
363 | // }}} | |
364 | // {{{ execute() | |
365 | ||
366 | /** | |
367 | * Executes the code according to given options | |
368 | * | |
369 | * @return bool true if success {or System_Command_Exception} | |
370 | * | |
371 | * @access public | |
372 | */ | |
373 | function execute() | |
374 | { | |
375 | if ($this->_initError) { | |
376 | return $this->_initError; | |
377 | } | |
378 | ||
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); | |
382 | } | |
383 | ||
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); | |
388 | } | |
389 | } | |
390 | ||
391 | // if this is not going to stdout, then redirect to /dev/null | |
392 | if (empty($this->options['OUTPUT'])) { | |
393 | $this->systemCommand .= ' >/dev/null'; | |
394 | } | |
395 | ||
396 | $suffix = ''; | |
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; | |
400 | } | |
401 | // run a background process (only if not nohup) | |
402 | elseif (!empty($this->options['BACKGROUND'])) { | |
403 | $suffix = ' &'; | |
404 | } | |
405 | ||
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); | |
411 | return true; | |
412 | } | |
413 | else { | |
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); | |
419 | ||
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); | |
423 | } | |
424 | else if (!$this->options['STDERR']) { | |
425 | // caller does not care about stderr; return success | |
426 | $return = implode("\n", $result); | |
427 | } | |
428 | else { | |
429 | // our caller cares about stderr; check stderr output | |
430 | clearstatcache(); | |
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); | |
435 | } else { | |
436 | // total success; return stdout gathered by exec() | |
437 | $return = implode("\n", $result); | |
438 | } | |
439 | } | |
440 | ||
441 | unlink($tmpFile); | |
442 | return $return; | |
443 | } | |
444 | } | |
445 | ||
446 | // }}} | |
447 | // {{{ which() | |
448 | ||
449 | /** | |
450 | * Functionality similiar to unix 'which'. Searches the path | |
451 | * for the specified program. | |
452 | * | |
453 | * @param $cmd name of the executable to search for | |
454 | * | |
455 | * @access private | |
456 | * @return string returns the full path if found, false if not | |
457 | */ | |
458 | function which($in_cmd) | |
459 | { | |
460 | // only pass non-empty strings to System::which() | |
461 | if (!is_string($in_cmd) || '' === $in_cmd) { | |
462 | return(false); | |
463 | } | |
464 | ||
465 | // explicitly pass false as fallback value | |
466 | return System::which($in_cmd, false); | |
467 | } | |
468 | ||
469 | // }}} | |
470 | // {{{ reset() | |
471 | ||
472 | /** | |
473 | * Prepare for a new command to be built | |
474 | * | |
475 | * @access public | |
476 | * @return void | |
477 | */ | |
478 | function reset() | |
479 | { | |
480 | $this->previousElement = null; | |
481 | $this->systemCommand = null; | |
482 | $this->commandStatus = 0; | |
483 | } | |
484 | ||
485 | // }}} | |
486 | // {{{ errorMessage() | |
487 | ||
488 | /** | |
489 | * Return a textual error message for a System_Command error code | |
490 | * | |
491 | * @param integer error code | |
492 | * | |
493 | * @return string error message, or false if the error code was | |
494 | * not recognized | |
495 | */ | |
496 | function errorMessage($in_value) | |
497 | { | |
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', | |
514 | ); | |
515 | } | |
516 | ||
517 | if (System_Command::isError($in_value)) { | |
518 | $in_value = $in_value->getCode(); | |
519 | } | |
520 | ||
521 | return isset($errorMessages[$in_value]) ? $errorMessages[$in_value] : $errorMessages[SYSTEM_COMMAND_ERROR]; | |
522 | } | |
523 | ||
524 | // }}} | |
525 | // {{{ isError() | |
526 | ||
527 | /** | |
528 | * Tell whether a result code from a System_Command method is an error | |
529 | * | |
530 | * @param int result code | |
531 | * | |
532 | * @return bool whether $in_value is an error | |
533 | * | |
534 | * @access public | |
535 | */ | |
536 | function isError($in_value) | |
537 | { | |
538 | return (is_object($in_value) && | |
539 | (strtolower(get_class($in_value)) == 'system_command_error' || | |
540 | is_subclass_of($in_value, 'system_command_error'))); | |
541 | } | |
542 | ||
543 | // }}} | |
544 | } | |
545 | ||
546 | // {{{ class System_Command_Error | |
547 | ||
548 | /** | |
549 | * System_Command_Error constructor. | |
550 | * | |
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 | |
555 | * | |
556 | * @access public | |
557 | * | |
558 | * @see PEAR_Error | |
559 | */ | |
560 | ||
561 | // }}} | |
562 | class System_Command_Error extends PEAR_Error | |
563 | { | |
564 | // {{{ properties | |
565 | ||
566 | /** | |
567 | * Message in front of the error message | |
568 | * @var string $error_message_prefix | |
569 | */ | |
570 | var $error_message_prefix = 'System_Command Error: '; | |
571 | ||
572 | // }}} | |
573 | // {{{ constructor | |
574 | ||
575 | function System_Command_Error($code = SYSTEM_COMMAND_ERROR, $mode = PEAR_ERROR_RETURN, | |
576 | $level = E_USER_NOTICE, $debuginfo = null) | |
577 | { | |
578 | if (is_int($code)) { | |
579 | $this->PEAR_Error(System_Command::errorMessage($code), $code, $mode, $level, $debuginfo); | |
580 | } else { | |
581 | $this->PEAR_Error("Invalid error code: $code", SYSTEM_COMMAND_ERROR, $mode, $level, $debuginfo); | |
582 | } | |
583 | } | |
584 | ||
585 | // }}} | |
586 | } | |
587 | ?> |