commiting uncommited changes on live site
[weblabels.fsf.org.git] / crm.fsf.org / 20131203 / files / sites / all / modules-old / civicrm / packages / System / Command.php
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 ?>