Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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 234202 2007-04-20 21:08:48Z cconstantine $
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: 234202 $
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
?>