Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/* vim: set noai expandtab tabstop=4 softtabstop=4 shiftwidth=4: */
3
 
4
/**
5
 * System_Daemon turns PHP-CLI scripts into daemons.
6
 *
7
 * PHP version 5
8
 *
9
 * @category  System
10
 * @package   System_Daemon
11
 * @author    Kevin van Zonneveld <kevin@vanzonneveld.net>
12
 * @copyright 2008 Kevin van Zonneveld (http://kevin.vanzonneveld.net)
13
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD Licence
14
 * @version   SVN: Release: $Id: Daemon.php 554 2011-07-16 23:18:29Z tiefland $
15
 * @link      http://trac.plutonia.nl/projects/system_daemon
16
 */
17
 
18
// Autoloader borrowed from PHP_CodeSniffer, see function for credits
19
spl_autoload_register(array('System_Daemon', 'autoload'));
20
 
21
/**
22
 * System_Daemon. Create daemons with practicle functions
23
 * like System_Daemon::start()
24
 *
25
 * Requires PHP build with --enable-cli --with-pcntl.
26
 * Only runs on *NIX systems, because Windows lacks of the pcntl ext.
27
 *
28
 * PHP version 5
29
 *
30
 * @category  System
31
 * @package   System_Daemon
32
 * @author    Kevin van Zonneveld <kevin@vanzonneveld.net>
33
 * @copyright 2008 Kevin van Zonneveld (http://kevin.vanzonneveld.net)
34
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD Licence
35
 * @version   SVN: Release: $Id: Daemon.php 554 2011-07-16 23:18:29Z tiefland $
36
 * @link      http://trac.plutonia.nl/projects/system_daemon
37
 *
38
 */
39
class System_Daemon
40
{
41
    // Make these corresponding with PEAR
42
    // Ensures compatibility while maintaining independency
43
 
44
    /**
45
     * System is unusable (will throw a System_Daemon_Exception as well)
46
     */
47
    const LOG_EMERG = 0;
48
 
49
    /**
50
     * Immediate action required (will throw a System_Daemon_Exception as well)
51
     */
52
    const LOG_ALERT = 1;
53
 
54
    /**
55
     * Critical conditions (will throw a System_Daemon_Exception as well)
56
     */
57
    const LOG_CRIT = 2;
58
 
59
    /**
60
     * Error conditions
61
     */
62
    const LOG_ERR = 3;
63
 
64
    /**
65
     * Warning conditions
66
     */
67
    const LOG_WARNING = 4;
68
 
69
    /**
70
     * Normal but significant
71
     */
72
    const LOG_NOTICE = 5;
73
 
74
    /**
75
     * Informational
76
     */
77
    const LOG_INFO = 6;
78
 
79
    /**
80
     * Debug-level messages
81
     */
82
    const LOG_DEBUG = 7;
83
 
84
 
85
 
86
    /**
87
     * The current process identifier
88
     *
89
     * @var integer
90
     */
91
    static protected $_processId = 0;
92
 
93
    /**
94
     * Whether the our daemon is being killed
95
     *
96
     * @var boolean
97
     */
98
    static protected $_isDying = false;
99
 
100
    /**
101
     * Whether the current process is a forked child
102
     *
103
     * @var boolean
104
     */
105
    static protected $_processIsChild = false;
106
 
107
    /**
108
     * Whether SAFE_MODE is on or off. This is important for ini_set
109
     * behavior
110
     *
111
     * @var boolean
112
     */
113
    static protected $_safeMode = false;
114
 
115
    /**
116
     * Available log levels
117
     *
118
     * @var array
119
     */
120
    static protected $_logLevels = array(
121
        self::LOG_EMERG => 'emerg',
122
        self::LOG_ALERT => 'alert',
123
        self::LOG_CRIT => 'crit',
124
        self::LOG_ERR => 'err',
125
        self::LOG_WARNING => 'warning',
126
        self::LOG_NOTICE => 'notice',
127
        self::LOG_INFO => 'info',
128
        self::LOG_DEBUG => 'debug',
129
    );
130
 
131
    /**
132
     * Available PHP error levels and their meaning in POSIX loglevel terms
133
     * Some ERROR constants are not supported in all PHP versions
134
     * and will conditionally be translated from strings to constants,
135
     * or else: removed from this mapping at start().
136
     *
137
     * @var array
138
     */
139
    static protected $_logPhpMapping = array(
140
        E_ERROR => array(self::LOG_ERR, 'Error'),
141
        E_WARNING => array(self::LOG_WARNING, 'Warning'),
142
        E_PARSE => array(self::LOG_EMERG, 'Parse'),
143
        E_NOTICE => array(self::LOG_DEBUG, 'Notice'),
144
        E_CORE_ERROR => array(self::LOG_EMERG, 'Core Error'),
145
        E_CORE_WARNING => array(self::LOG_WARNING, 'Core Warning'),
146
        E_COMPILE_ERROR => array(self::LOG_EMERG, 'Compile Error'),
147
        E_COMPILE_WARNING => array(self::LOG_WARNING, 'Compile Warning'),
148
        E_USER_ERROR => array(self::LOG_ERR, 'User Error'),
149
        E_USER_WARNING => array(self::LOG_WARNING, 'User Warning'),
150
        E_USER_NOTICE => array(self::LOG_DEBUG, 'User Notice'),
151
        'E_RECOVERABLE_ERROR' => array(self::LOG_WARNING, 'Recoverable Error'),
152
        'E_DEPRECATED' => array(self::LOG_NOTICE, 'Deprecated'),
153
        'E_USER_DEPRECATED' => array(self::LOG_NOTICE, 'User Deprecated'),
154
    );
155
 
156
    /**
157
     * Holds Option Object
158
     *
159
     * @var mixed object or boolean
160
     */
161
    static protected $_optObj = false;
162
 
163
    /**
164
     * Holds OS Object
165
     *
166
     * @var mixed object or boolean
167
     */
168
    static protected $_osObj = false;
169
 
170
    /**
171
     * Definitions for all Options
172
     *
173
     * @var array
174
     * @see setOption()
175
     * @see getOption()
176
     */
177
    static protected $_optionDefinitions = array(
178
        'usePEAR' => array(
179
            'type' => 'boolean',
180
            'default' => true,
181
            'punch' => 'Whether to run this class using PEAR',
182
            'detail' => 'Will run standalone when false',
183
            'required' => true,
184
        ),
185
        'usePEARLogInstance' => array(
186
            'type' => 'boolean|object',
187
            'default' => false,
188
            'punch' => 'Accepts a PEAR_Log instance to handle all logging',
189
            'detail' => 'This will replace System_Daemon\'s own logging facility',
190
            'required' => true,
191
        ),
192
 
193
        'authorName' => array(
194
            'type' => 'string/0-50',
195
            'punch' => 'Author name',
196
            'example' => 'Kevin van zonneveld',
197
            'detail' => 'Required for forging init.d script',
198
        ),
199
        'authorEmail' => array(
200
            'type' => 'string/email',
201
            'punch' => 'Author e-mail',
202
            'example' => 'kevin@vanzonneveld.net',
203
            'detail' => 'Required for forging init.d script',
204
        ),
205
        'appName' => array(
206
            'type' => 'string/unix',
207
            'punch' => 'The application name',
208
            'example' => 'logparser',
209
            'detail' => 'Must be UNIX-proof; Required for running daemon',
210
            'required' => true,
211
        ),
212
        'appDescription' => array(
213
            'type' => 'string',
214
            'punch' => 'Daemon description',
215
            'example' => 'Parses logfiles of vsftpd and stores them in MySQL',
216
            'detail' => 'Required for forging init.d script',
217
        ),
218
        'appDir' => array(
219
            'type' => 'string/existing_dirpath',
220
            'default' => '@dirname({SERVER.SCRIPT_NAME})',
221
            'punch' => 'The home directory of the daemon',
222
            'example' => '/usr/local/logparser',
223
            'detail' => 'Highly recommended to set this yourself',
224
            'required' => true,
225
        ),
226
        'appExecutable' => array(
227
            'type' => 'string/existing_filepath',
228
            'default' => '@basename({SERVER.SCRIPT_NAME})',
229
            'punch' => 'The executable daemon file',
230
            'example' => 'logparser.php',
231
            'detail' => 'Recommended to set this yourself; Required for init.d',
232
            'required' => true
233
        ),
234
 
235
        'logVerbosity' => array(
236
            'type' => 'number/0-7',
237
            'default' => self::LOG_INFO,
238
            'punch' => 'Messages below this log level are ignored',
239
            'example' => '',
240
            'detail' => 'Not written to logfile; not displayed on screen',
241
            'required' => true,
242
        ),
243
        'logLocation' => array(
244
            'type' => 'string/creatable_filepath',
245
            'default' => '/var/log/{OPTIONS.appName}.log',
246
            'punch' => 'The log filepath',
247
            'example' => '/var/log/logparser_daemon.log',
248
            'detail' => 'Not applicable if you use PEAR Log',
249
            'required' => false,
250
        ),
251
        'logPhpErrors' => array(
252
            'type' => 'boolean',
253
            'default' => true,
254
            'punch' => 'Reroute PHP errors to log function',
255
            'detail' => '',
256
            'required' => true,
257
        ),
258
        'logFilePosition' => array(
259
            'type' => 'boolean',
260
            'default' => false,
261
            'punch' => 'Show file in which the log message was generated',
262
            'detail' => '',
263
            'required' => true,
264
        ),
265
        'logTrimAppDir' => array(
266
            'type' => 'boolean',
267
            'default' => true,
268
            'punch' => 'Strip the application dir from file positions in log msgs',
269
            'detail' => '',
270
            'required' => true,
271
        ),
272
        'logLinePosition' => array(
273
            'type' => 'boolean',
274
            'default' => true,
275
            'punch' => 'Show the line number in which the log message was generated',
276
            'detail' => '',
277
            'required' => true,
278
        ),
279
        'appRunAsUID' => array(
280
            'type' => 'number/0-65000',
281
            'default' => 0,
282
            'punch' => 'The user id under which to run the process',
283
            'example' => '1000',
284
            'detail' => 'Defaults to root which is insecure!',
285
            'required' => true,
286
        ),
287
        'appRunAsGID' => array(
288
            'type' => 'number/0-65000',
289
            'default' => 0,
290
            'punch' => 'The group id under which to run the process',
291
            'example' => '1000',
292
            'detail' => 'Defaults to root which is insecure!',
293
            'required' => true,
294
        ),
295
        'appPidLocation' => array(
296
            'type' => 'string/unix_filepath',
297
            'default' => '/var/run/{OPTIONS.appName}/{OPTIONS.appName}.pid',
298
            'punch' => 'The pid filepath',
299
            'example' => '/var/run/logparser/logparser.pid',
300
            'detail' => '',
301
            'required' => true,
302
        ),
303
        'appChkConfig' => array(
304
             'type' => 'string',
305
             'default' => '- 99 0',
306
             'punch' => 'chkconfig parameters for init.d',
307
             'detail' => 'runlevel startpriority stoppriority',
308
         ),
309
        'appDieOnIdentityCrisis' => array(
310
            'type' => 'boolean',
311
            'default' => true,
312
            'punch' => 'Kill daemon if it cannot assume the identity',
313
            'detail' => '',
314
            'required' => true,
315
        ),
316
 
317
        'sysMaxExecutionTime' => array(
318
            'type' => 'number',
319
            'default' => 0,
320
            'punch' => 'Maximum execution time of each script in seconds',
321
            'detail' => '0 is infinite',
322
        ),
323
        'sysMaxInputTime' => array(
324
            'type' => 'number',
325
            'default' => 0,
326
            'punch' => 'Maximum time to spend parsing request data',
327
            'detail' => '0 is infinite',
328
        ),
329
        'sysMemoryLimit' => array(
330
            'type' => 'string',
331
            'default' => '128M',
332
            'punch' => 'Maximum amount of memory a script may consume',
333
            'detail' => '0 is infinite',
334
        ),
335
 
336
        'runTemplateLocation' => array(
337
            'type' => 'string/existing_filepath',
338
            'default' => false,
339
            'punch' => 'The filepath to a custom autorun Template',
340
            'example' => '/etc/init.d/skeleton',
341
            'detail' => 'Sometimes it\'s better to stick with the OS default,
342
                and use something like /etc/default/<name> for customization',
343
        ),
344
    );
345
 
346
 
347
    /**
348
     * Available signal handlers
349
     * setSigHandler can overwrite these values individually.
350
     *
351
     * Available POSIX SIGNALS and their PHP handler functions.
352
     * Some SIGNALS constants are not supported in all PHP versions
353
     * and will conditionally be translated from strings to constants,
354
     * or else: removed from this mapping at start().
355
     *
356
     * 'kill -l' gives you a list of signals available on your UNIX.
357
     * Eg. Ubuntu:
358
     *
359
     *  1) SIGHUP      2) SIGINT      3) SIGQUIT      4) SIGILL
360
     *  5) SIGTRAP      6) SIGABRT      7) SIGBUS      8) SIGFPE
361
     *  9) SIGKILL    10) SIGUSR1    11) SIGSEGV    12) SIGUSR2
362
     * 13) SIGPIPE    14) SIGALRM    15) SIGTERM    17) SIGCHLD
363
     * 18) SIGCONT    19) SIGSTOP    20) SIGTSTP    21) SIGTTIN
364
     * 22) SIGTTOU    23) SIGURG      24) SIGXCPU    25) SIGXFSZ
365
     * 26) SIGVTALRM  27) SIGPROF    28) SIGWINCH    29) SIGIO
366
     * 30) SIGPWR      31) SIGSYS      33) SIGRTMIN    34) SIGRTMIN+1
367
     * 35) SIGRTMIN+2  36) SIGRTMIN+3  37) SIGRTMIN+4  38) SIGRTMIN+5
368
     * 39) SIGRTMIN+6  40) SIGRTMIN+7  41) SIGRTMIN+8  42) SIGRTMIN+9
369
     * 43) SIGRTMIN+10 44) SIGRTMIN+11 45) SIGRTMIN+12 46) SIGRTMIN+13
370
     * 47) SIGRTMIN+14 48) SIGRTMIN+15 49) SIGRTMAX-15 50) SIGRTMAX-14
371
     * 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10
372
     * 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6
373
     * 59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
374
     * 63) SIGRTMAX-1  64) SIGRTMAX
375
     *
376
     * SIG_IGN, SIG_DFL, SIG_ERR are no real signals
377
     *
378
     * @var array
379
     * @see setSigHandler()
380
     */
381
    static protected $_sigHandlers = array(
382
        SIGHUP => array('System_Daemon', 'defaultSigHandler'),
383
        SIGINT => array('System_Daemon', 'defaultSigHandler'),
384
        SIGQUIT => array('System_Daemon', 'defaultSigHandler'),
385
        SIGILL => array('System_Daemon', 'defaultSigHandler'),
386
        SIGTRAP => array('System_Daemon', 'defaultSigHandler'),
387
        SIGABRT => array('System_Daemon', 'defaultSigHandler'),
388
        'SIGIOT' => array('System_Daemon', 'defaultSigHandler'),
389
        SIGBUS => array('System_Daemon', 'defaultSigHandler'),
390
        SIGFPE => array('System_Daemon', 'defaultSigHandler'),
391
        SIGUSR1 => array('System_Daemon', 'defaultSigHandler'),
392
        SIGSEGV => array('System_Daemon', 'defaultSigHandler'),
393
        SIGUSR2 => array('System_Daemon', 'defaultSigHandler'),
394
        SIGPIPE => SIG_IGN,
395
        SIGALRM => array('System_Daemon', 'defaultSigHandler'),
396
        SIGTERM => array('System_Daemon', 'defaultSigHandler'),
397
        'SIGSTKFLT' => array('System_Daemon', 'defaultSigHandler'),
398
        'SIGCLD' => array('System_Daemon', 'defaultSigHandler'),
399
        'SIGCHLD' => array('System_Daemon', 'defaultSigHandler'),
400
        SIGCONT => array('System_Daemon', 'defaultSigHandler'),
401
        SIGTSTP => array('System_Daemon', 'defaultSigHandler'),
402
        SIGTTIN => array('System_Daemon', 'defaultSigHandler'),
403
        SIGTTOU => array('System_Daemon', 'defaultSigHandler'),
404
        SIGURG => array('System_Daemon', 'defaultSigHandler'),
405
        SIGXCPU => array('System_Daemon', 'defaultSigHandler'),
406
        SIGXFSZ => array('System_Daemon', 'defaultSigHandler'),
407
        SIGVTALRM => array('System_Daemon', 'defaultSigHandler'),
408
        SIGPROF => array('System_Daemon', 'defaultSigHandler'),
409
        SIGWINCH => array('System_Daemon', 'defaultSigHandler'),
410
        'SIGPOLL' => array('System_Daemon', 'defaultSigHandler'),
411
        SIGIO => array('System_Daemon', 'defaultSigHandler'),
412
        'SIGPWR' => array('System_Daemon', 'defaultSigHandler'),
413
        'SIGSYS' => array('System_Daemon', 'defaultSigHandler'),
414
        SIGBABY => array('System_Daemon', 'defaultSigHandler'),
415
        'SIG_BLOCK' => array('System_Daemon', 'defaultSigHandler'),
416
        'SIG_UNBLOCK' => array('System_Daemon', 'defaultSigHandler'),
417
        'SIG_SETMASK' => array('System_Daemon', 'defaultSigHandler'),
418
    );
419
 
420
 
421
    /**
422
     * Making the class non-abstract with a protected constructor does a better
423
     * job of preventing instantiation than just marking the class as abstract.
424
     *
425
     * @see start()
426
     */
427
    protected function __construct()
428
    {
429
 
430
    }
431
 
432
 
433
 
434
    /**
435
     * Autoload static method for loading classes and interfaces.
436
     * Code from the PHP_CodeSniffer package by Greg Sherwood and
437
     * Marc McIntyre
438
     *
439
     * @param string $className The name of the class or interface.
440
     *
441
     * @return void
442
     */
443
    static public function autoload($className)
444
    {
445
        $parent     = 'System_';
446
        $parent_len = strlen($parent);
447
        if (substr($className, 0, $parent_len) == $parent) {
448
            $newClassName = substr($className, $parent_len);
449
        } else {
450
            $newClassName = $className;
451
        }
452
 
453
        $path = str_replace('_', '/', $newClassName).'.php';
454
 
455
        if (is_file(dirname(__FILE__).'/'.$path) === true) {
456
            // Check standard file locations based on class name.
457
            include(dirname(__FILE__).'/'.$path);
458
        } else {
459
            // Everything else.
460
            @include($path);
461
        }
462
    }
463
 
464
 
465
    /**
466
     * Spawn daemon process.
467
     *
468
     * @return boolean
469
     * @see iterate()
470
     * @see stop()
471
     * @see autoload()
472
     * @see _optionsInit()
473
     * @see _summon()
474
     */
475
    static public function start()
476
    {
477
        // Conditionally add loglevel mappings that are not supported in
478
        // all PHP versions.
479
        // They will be in string representation and have to be
480
        // converted & unset
481
        foreach (self::$_logPhpMapping as $phpConstant => $props) {
482
            if (!is_numeric($phpConstant)) {
483
                if (defined($phpConstant)) {
484
                    self::$_logPhpMapping[constant($phpConstant)] = $props;
485
                }
486
                unset(self::$_logPhpMapping[$phpConstant]);
487
            }
488
        }
489
        // Same goes for POSIX signals. Not all Constants are available on
490
        // all platforms.
491
        foreach (self::$_sigHandlers as $signal => $handler) {
492
            if (is_string($signal) || !$signal) {
493
                if (defined($signal) && ($const = constant($signal))) {
494
                    self::$_sigHandlers[$const] = $handler;
495
                }
496
                unset(self::$_sigHandlers[$signal]);
497
            }
498
        }
499
 
500
        // Quickly initialize some defaults like usePEAR
501
        // by adding the $premature flag
502
        self::_optionsInit(true);
503
 
504
        if (self::opt('logPhpErrors')) {
505
            set_error_handler(array('System_Daemon', 'phpErrors'), E_ALL);
506
        }
507
 
508
        // To run as a part of PEAR
509
        if (self::opt('usePEAR')) {
510
            // SPL's autoload will make sure classes are automatically loaded
511
            if (false === class_exists('PEAR', true)) {
512
                $msg = 'PEAR not found. Install PEAR or run with option: '.
513
                    'usePEAR = false';
514
                trigger_error($msg, E_USER_ERROR);
515
            }
516
 
517
            if (false === class_exists('PEAR_Exception', true)) {
518
                $msg = 'PEAR_Exception not found?!';
519
                trigger_error($msg, E_USER_ERROR);
520
            }
521
 
522
            if (false === class_exists('System_Daemon_Exception', true)) {
523
                // PEAR_Exception is OK. PEAR was found already.
524
                throw new PEAR_Exception('Class System_Daemon_Exception not found');
525
            }
526
        }
527
 
528
        // Check the PHP configuration
529
        if (!defined('SIGHUP')) {
530
            $msg = 'PHP is compiled without --enable-pcntl directive';
531
            if (self::opt('usePEAR')) {
532
                throw new System_Daemon_Exception($msg);
533
            } else {
534
                trigger_error($msg, E_USER_ERROR);
535
            }
536
        }
537
 
538
        // Check for CLI
539
        if ((php_sapi_name() !== 'cli')) {
540
            $msg = 'You can only create daemon from the command line (CLI-mode)';
541
            if (self::opt('usePEAR')) {
542
                throw new System_Daemon_Exception($msg);
543
            } else {
544
                trigger_error($msg, E_USER_ERROR);
545
            }
546
        }
547
 
548
        // Check for POSIX
549
        if (!function_exists('posix_getpid')) {
550
            $msg = 'PHP is compiled without --enable-posix directive';
551
            if (self::opt('usePEAR')) {
552
                throw new System_Daemon_Exception($msg);
553
            } else {
554
                trigger_error($msg, E_USER_ERROR);
555
            }
556
        }
557
 
558
        // Enable Garbage Collector (PHP >= 5.3)
559
        if (function_exists('gc_enable')) {
560
            gc_enable();
561
        }
562
 
563
        // Initialize & check variables
564
        if (false === self::_optionsInit(false)) {
565
            if (is_object(self::$_optObj) && is_array(self::$_optObj->errors)) {
566
                foreach (self::$_optObj->errors as $error) {
567
                    self::notice($error);
568
                }
569
            }
570
 
571
            $msg = 'Crucial options are not set. Review log:';
572
            if (self::opt('usePEAR')) {
573
                throw new System_Daemon_Exception($msg);
574
            } else {
575
                trigger_error($msg, E_USER_ERROR);
576
            }
577
        }
578
        // Become daemon
579
        self::_summon();
580
 
581
        return true;
582
 
583
    }
584
 
585
    /**
586
     * Protects your daemon by e.g. clearing statcache. Can optionally
587
     * be used as a replacement for sleep as well.
588
     *
589
     * @param integer $sleepSeconds Optionally put your daemon to rest for X s.
590
     *
591
     * @return void
592
     * @see start()
593
     * @see stop()
594
     */
595
    static public function iterate($sleepSeconds = 0)
596
    {
597
        self::_optionObjSetup();
598
        if ($sleepSeconds !== 0) {
599
            usleep($sleepSeconds*1000000);
600
        }
601
 
602
        clearstatcache();
603
 
604
        // Garbage Collection (PHP >= 5.3)
605
        if (function_exists('gc_collect_cycles')) {
606
            gc_collect_cycles();
607
        }
608
 
609
        return true;
610
    }
611
 
612
    /**
613
     * Stop daemon process.
614
     *
615
     * @return void
616
     * @see start()
617
     */
618
    static public function stop()
619
    {
620
        self::info('Stopping {appName}');
621
        self::_die(false);
622
    }
623
 
624
    /**
625
     * Restart daemon process.
626
     *
627
     * @return void
628
     * @see _die()
629
     */
630
    static public function restart()
631
    {
632
        self::info('Restarting {appName}');
633
        self::_die(true);
634
    }
635
 
636
    /**
637
     * Overrule or add signal handlers.
638
     *
639
     * @param string $signal  Signal constant (e.g. SIGHUP)
640
     * @param mixed  $handler Which handler to call on signal
641
     *
642
     * @return boolean
643
     * @see $_sigHandlers
644
     */
645
    static public function setSigHandler($signal, $handler)
646
    {
647
        if (!isset(self::$_sigHandlers[$signal])) {
648
            // The signal should be defined already
649
            self::notice(
650
                'Can only overrule on of these signal handlers: %s',
651
                join(', ', array_keys(self::$_sigHandlers))
652
            );
653
            return false;
654
        }
655
 
656
        // Overwrite on existance
657
        self::$_sigHandlers[$signal] = $handler;
658
        return true;
659
    }
660
 
661
    /**
662
     * Sets any option found in $_optionDefinitions
663
     * Public interface to talk with with protected option methods
664
     *
665
     * @param string $name  Name of the Option
666
     * @param mixed  $value Value of the Option
667
     *
668
     * @return boolean
669
     */
670
    static public function setOption($name, $value)
671
    {
672
        if (!self::_optionObjSetup()) {
673
            return false;
674
        }
675
 
676
        return self::$_optObj->setOption($name, $value);
677
    }
678
 
679
    /**
680
     * Sets an array of options found in $_optionDefinitions
681
     * Public interface to talk with with protected option methods
682
     *
683
     * @param array $use_options Array with Options
684
     *
685
     * @return boolean
686
     */
687
    static public function setOptions($use_options)
688
    {
689
        if (!self::_optionObjSetup()) {
690
            return false;
691
        }
692
 
693
        return self::$_optObj->setOptions($use_options);
694
    }
695
 
696
    /**
697
     * Shortcut for getOption & setOption
698
     *
699
     * @param string $name Option to set or get
700
     *
701
     * @return mixed
702
     */
703
    static public function opt($name)
704
    {
705
        $args = func_get_args();
706
        if (count($args) > 1) {
707
            return self::setOption($name, $args[1]);
708
        } else {
709
            return self::getOption($name);
710
        }
711
    }
712
 
713
 
714
    /**
715
     * Gets any option found in $_optionDefinitions
716
     * Public interface to talk with with protected option methods
717
     *
718
     * @param string $name Name of the Option
719
     *
720
     * @return mixed
721
     */
722
    static public function getOption($name)
723
    {
724
        if (!self::_optionObjSetup()) {
725
            return false;
726
        }
727
 
728
        return self::$_optObj->getOption($name);
729
    }
730
 
731
    /**
732
     * Gets an array of options found
733
     *
734
     * @return array
735
     */
736
    static public function getOptions()
737
    {
738
        if (!self::_optionObjSetup()) {
739
            return false;
740
        }
741
 
742
        return self::$_optObj->getOptions();
743
    }
744
 
745
    /**
746
     * Catches PHP Errors and forwards them to log function
747
     *
748
     * @param integer $errno   Level
749
     * @param string  $errstr  Error
750
     * @param string  $errfile File
751
     * @param integer $errline Line
752
     *
753
     * @return boolean
754
     */
755
    static public function phpErrors ($errno, $errstr, $errfile, $errline)
756
    {
757
        // Ignore suppressed errors (prefixed by '@')
758
        if (error_reporting() == 0) {
759
            return;
760
        }
761
 
762
        // Map PHP error level to System_Daemon log level
763
        if (!isset(self::$_logPhpMapping[$errno][0])) {
764
            self::warning('Unknown PHP errorno: %s', $errno);
765
            $phpLvl = self::LOG_ERR;
766
        } else {
767
            list($logLvl, $phpLvl) = self::$_logPhpMapping[$errno];
768
        }
769
 
770
        // Log it
771
        // No shortcuts this time!
772
        self::log(
773
            $logLvl, '[PHP ' . $phpLvl . '] '.$errstr, $errfile, __CLASS__,
774
            __FUNCTION__, $errline
775
        );
776
 
777
        return true;
778
    }
779
 
780
    /**
781
     * Abbreviate a string. e.g: Kevin van zonneveld -> Kevin van Z...
782
     *
783
     * @param string  $str    Data
784
     * @param integer $cutAt  Where to cut
785
     * @param string  $suffix Suffix with something?
786
     *
787
     * @return string
788
     */
789
    static public function abbr($str, $cutAt = 30, $suffix = '...')
790
    {
791
        if (strlen($str) <= 30) {
792
            return $str;
793
        }
794
 
795
        $canBe = $cutAt - strlen($suffix);
796
 
797
        return substr($str, 0, $canBe). $suffix;
798
    }
799
 
800
    /**
801
     * Tries to return the most significant information as a string
802
     * based on any given argument.
803
     *
804
     * @param mixed $arguments Any type of variable
805
     *
806
     * @return string
807
     */
808
    static public function semantify($arguments)
809
    {
810
        if (is_object($arguments)) {
811
            return get_class($arguments);
812
        }
813
        if (!is_array($arguments)) {
814
            if (!is_numeric($arguments) && !is_bool($arguments)) {
815
                $arguments = '\''.$arguments.'\'';
816
            }
817
            return $arguments;
818
        }
819
        $arr = array();
820
        foreach ($arguments as $key=>$val) {
821
            if (is_array($val)) {
822
                $val = json_encode($val);
823
            } elseif (!is_numeric($val) && !is_bool($val)) {
824
                $val = '\''.$val.'\'';
825
            }
826
 
827
            $val = self::abbr($val);
828
 
829
            $arr[] = $key.': '.$val;
830
        }
831
        return join(', ', $arr);
832
    }
833
 
834
    /**
835
     * Logging shortcut
836
     *
837
     * @return boolean
838
     */
839
    public static function emerg()
840
    {
841
        $arguments = func_get_args(); array_unshift($arguments, __FUNCTION__);
842
        call_user_func_array(array('System_Daemon', '_ilog'), $arguments);
843
        return false;
844
    }
845
 
846
    /**
847
     * Logging shortcut
848
     *
849
     * @return boolean
850
     */
851
    public static function crit()
852
    {
853
        $arguments = func_get_args(); array_unshift($arguments, __FUNCTION__);
854
        call_user_func_array(array('System_Daemon', '_ilog'), $arguments);
855
        return false;
856
    }
857
 
858
    /**
859
     * Logging shortcut
860
     *
861
     * @return boolean
862
     */
863
    public static function err()
864
    {
865
        $arguments = func_get_args(); array_unshift($arguments, __FUNCTION__);
866
        call_user_func_array(array('System_Daemon', '_ilog'), $arguments);
867
        return false;
868
    }
869
 
870
    /**
871
     * Logging shortcut
872
     *
873
     * @return boolean
874
     */
875
    public static function warning()
876
    {
877
        $arguments = func_get_args(); array_unshift($arguments, __FUNCTION__);
878
        call_user_func_array(array('System_Daemon', '_ilog'), $arguments);
879
        return false;
880
    }
881
 
882
    /**
883
     * Logging shortcut
884
     *
885
     * @return boolean
886
     */
887
    public static function notice()
888
    {
889
        $arguments = func_get_args(); array_unshift($arguments, __FUNCTION__);
890
        call_user_func_array(array('System_Daemon', '_ilog'), $arguments);
891
        return true;
892
    }
893
 
894
    /**
895
     * Logging shortcut
896
     *
897
     * @return boolean
898
     */
899
    public static function info()
900
    {
901
        $arguments = func_get_args(); array_unshift($arguments, __FUNCTION__);
902
        call_user_func_array(array('System_Daemon', '_ilog'), $arguments);
903
        return true;
904
    }
905
 
906
    /**
907
     * Logging shortcut
908
     *
909
     * @return boolean
910
     */
911
    public static function debug()
912
    {
913
        $arguments = func_get_args(); array_unshift($arguments, __FUNCTION__);
914
        call_user_func_array(array('System_Daemon', '_ilog'), $arguments);
915
        return true;
916
    }
917
 
918
    /**
919
     * Internal logging function. Bridge between shortcuts like:
920
     * err(), warning(), info() and the actual log() function
921
     *
922
     * @param mixed $level As string or constant
923
     * @param mixed $str   Message
924
     *
925
     * @return boolean
926
     */
927
    protected static function _ilog($level, $str)
928
    {
929
        $arguments = func_get_args();
930
        $level     = $arguments[0];
931
        $format    = $arguments[1];
932
 
933
        if (is_string($level)) {
934
            if (false === ($l = array_search($level, self::$_logLevels))) {
935
                self::log(LOG_EMERG, 'No such loglevel: '. $level);
936
            } else {
937
                $level = $l;
938
            }
939
        }
940
 
941
        unset($arguments[0]);
942
        unset($arguments[1]);
943
 
944
        $str = $format;
945
        if (count($arguments)) {
946
            foreach ($arguments as $k => $v) {
947
                $arguments[$k] = self::semantify($v);
948
            }
949
            $str = vsprintf($str, $arguments);
950
        }
951
 
952
        self::_optionObjSetup();
953
        $str = preg_replace_callback(
954
            '/\{([^\{\}]+)\}/is',
955
            array(self::$_optObj, 'replaceVars'),
956
            $str
957
        );
958
 
959
 
960
        $history  = 2;
961
        $dbg_bt   = @debug_backtrace();
962
        $class    = (string)@$dbg_bt[($history-1)]['class'];
963
        $function = (string)@$dbg_bt[($history-1)]['function'];
964
        $file     = (string)@$dbg_bt[$history]['file'];
965
        $line     = (string)@$dbg_bt[$history]['line'];
966
        return self::log($level, $str, $file, $class, $function, $line);
967
    }
968
 
969
    /**
970
     * Almost every deamon requires a log file, this function can
971
     * facilitate that. Also handles class-generated errors, chooses
972
     * either PEAR handling or PEAR-independant handling, depending on:
973
     * self::opt('usePEAR').
974
     * Also supports PEAR_Log if you referenc to a valid instance of it
975
     * in self::opt('usePEARLogInstance').
976
     *
977
     * It logs a string according to error levels specified in array:
978
     * self::$_logLevels (0 is fatal and handles daemon's death)
979
     *
980
     * @param integer $level    What function the log record is from
981
     * @param string  $str      The log record
982
     * @param string  $file     What code file the log record is from
983
     * @param string  $class    What class the log record is from
984
     * @param string  $function What function the log record is from
985
     * @param integer $line     What code line the log record is from
986
     *
987
     * @throws System_Daemon_Exception
988
     * @return boolean
989
     * @see _logLevels
990
     * @see logLocation
991
     */
992
    static public function log ($level, $str, $file = false, $class = false,
993
    $function = false, $line = false) {
994
        // If verbosity level is not matched, don't do anything
995
        if (null === self::opt('logVerbosity')
996
            || false === self::opt('logVerbosity')
997
        ) {
998
            // Somebody is calling log before launching daemon..
999
            // fair enough, but we have to init some log options
1000
            self::_optionsInit(true);
1001
        }
1002
        if (!self::opt('appName')) {
1003
            // Not logging for anything without a name
1004
            return false;
1005
        }
1006
 
1007
        if ($level > self::opt('logVerbosity')) {
1008
            return true;
1009
        }
1010
 
1011
        // Make the tail of log massage.
1012
        $log_tail = '';
1013
        if ($level < self::LOG_NOTICE) {
1014
            if (self::opt('logFilePosition')) {
1015
                if (self::opt('logTrimAppDir')) {
1016
                    $file = substr($file, strlen(self::opt('appDir')));
1017
                }
1018
 
1019
                $log_tail .= ' [f:'.$file.']';
1020
            }
1021
            if (self::opt('logLinePosition')) {
1022
                $log_tail .= ' [l:'.$line.']';
1023
            }
1024
        }
1025
 
1026
        // Make use of a PEAR_Log() instance
1027
        if (self::opt('usePEARLogInstance') !== false) {
1028
            self::opt('usePEARLogInstance')->log($str . $log_tail, $level);
1029
            return true;
1030
        }
1031
 
1032
        // Save resources if arguments are passed.
1033
        // But by falling back to debug_backtrace() it still works
1034
        // if someone forgets to pass them.
1035
        if (function_exists('debug_backtrace') && (!$file || !$line)) {
1036
            $dbg_bt   = @debug_backtrace();
1037
            $class    = (isset($dbg_bt[1]['class'])?$dbg_bt[1]['class']:'');
1038
            $function = (isset($dbg_bt[1]['function'])?$dbg_bt[1]['function']:'');
1039
            $file     = $dbg_bt[0]['file'];
1040
            $line     = $dbg_bt[0]['line'];
1041
        }
1042
 
1043
        // Determine what process the log is originating from and forge a logline
1044
        //$str_ident = '@'.substr(self::_whatIAm(), 0, 1).'-'.posix_getpid();
1045
        $str_date  = '[' . date('M d H:i:s') . ']';
1046
        $str_level = str_pad(self::$_logLevels[$level] . '', 8, ' ', STR_PAD_LEFT);
1047
        $log_line  = $str_date . ' ' . $str_level . ': ' . $str . $log_tail; // $str_ident
1048
 
1049
        $non_debug      = ($level < self::LOG_DEBUG);
1050
        $log_succeeded = true;
1051
        $log_echoed     = false;
1052
 
1053
        if (!self::isInBackground() && $non_debug && !$log_echoed) {
1054
            // It's okay to echo if you're running as a foreground process.
1055
            // Maybe the command to write an init.d file was issued.
1056
            // In such a case it's important to echo failures to the
1057
            // STDOUT
1058
            echo $log_line . "\n";
1059
            $log_echoed = true;
1060
            // but still try to also log to file for future reference
1061
        }
1062
 
1063
        if (!self::opt('logLocation')) {
1064
            throw new System_Daemon_Exception('Either use PEAR Log or specify '.
1065
                'a logLocation');
1066
        }
1067
 
1068
        // 'Touch' logfile
1069
        if (!file_exists(self::opt('logLocation'))) {
1070
            file_put_contents(self::opt('logLocation'), '');
1071
        }
1072
 
1073
        // Not writable even after touch? Allowed to echo again!!
1074
        if (!is_writable(self::opt('logLocation'))
1075
            && $non_debug && !$log_echoed
1076
        ) {
1077
            echo $log_line . "\n";
1078
            $log_echoed    = true;
1079
            $log_succeeded = false;
1080
        }
1081
 
1082
        // Append to logfile
1083
        $f = file_put_contents(
1084
            self::opt('logLocation'),
1085
            $log_line . "\n",
1086
            FILE_APPEND
1087
        );
1088
        if (!$f) {
1089
            $log_succeeded = false;
1090
        }
1091
 
1092
        // These are pretty serious errors
1093
        if ($level < self::LOG_ERR) {
1094
            // An emergency logentry is reason for the deamon to
1095
            // die immediately
1096
            if ($level === self::LOG_EMERG) {
1097
                self::_die();
1098
            }
1099
        }
1100
 
1101
        return $log_succeeded;
1102
    }
1103
 
1104
    /**
1105
     * Uses OS class to write an: 'init.d' script on the filesystem
1106
     *
1107
     * @param boolean $overwrite May the existing init.d file be overwritten?
1108
     *
1109
     * @return boolean
1110
     */
1111
    static public function writeAutoRun($overwrite=false)
1112
    {
1113
        // Init Options (needed for properties of init.d script)
1114
        if (false === self::_optionsInit(false)) {
1115
            return false;
1116
        }
1117
 
1118
        // Init OS Object
1119
        if (!self::_osObjSetup()) {
1120
            return false;
1121
        }
1122
 
1123
        // Get daemon properties
1124
        $options = self::getOptions();
1125
 
1126
        // Try to write init.d
1127
        $res = self::$_osObj->writeAutoRun($options, $overwrite);
1128
        if (false === $res) {
1129
            if (is_array(self::$_osObj->errors)) {
1130
                foreach (self::$_osObj->errors as $error) {
1131
                    self::notice($error);
1132
                }
1133
            }
1134
            return self::warning('Unable to create startup file');
1135
        }
1136
 
1137
        if ($res === true) {
1138
            self::notice('Startup was already written');
1139
            return true;
1140
        } else {
1141
            self::notice('Startup written to %s', $res);
1142
        }
1143
 
1144
        return $res;
1145
    }
1146
 
1147
    /**
1148
     * Default signal handler.
1149
     * You can overrule various signals with the
1150
     * setSigHandler() method
1151
     *
1152
     * @param integer $signo The posix signal received.
1153
     *
1154
     * @return void
1155
     * @see setSigHandler()
1156
     * @see $_sigHandlers
1157
     */
1158
    static public function defaultSigHandler($signo)
1159
    {
1160
        // Must be public or else will throw a
1161
        // fatal error: Call to protected method
1162
        self::debug('Received signal: %s', $signo);
1163
 
1164
        switch ($signo) {
1165
        case SIGTERM:
1166
            // Handle shutdown tasks
1167
            if (self::isInBackground()) {
1168
                self::_die();
1169
            } else {
1170
                exit;
1171
            }
1172
            break;
1173
        case SIGHUP:
1174
            // Handle restart tasks
1175
            self::debug('Received signal: restart');
1176
            break;
1177
        case SIGCHLD:
1178
            // A child process has died
1179
            self::debug('Received signal: child');
1180
            while (pcntl_wait($status, WNOHANG OR WUNTRACED) > 0) {
1181
                usleep(1000);
1182
            }
1183
            break;
1184
        default:
1185
            // Handle all other signals
1186
            break;
1187
        }
1188
    }
1189
 
1190
    /**
1191
     * Whether the class is already running in the background
1192
     *
1193
     * @return boolean
1194
     */
1195
    static public function isInBackground()
1196
    {
1197
        return self::$_processIsChild && self::isRunning();
1198
    }
1199
 
1200
    /**
1201
     * Whether the our daemon is being killed, you might
1202
     * want to include this in your loop
1203
     *
1204
     * @return boolean
1205
     */
1206
    static public function isDying()
1207
    {
1208
        return self::$_isDying;
1209
    }
1210
 
1211
    /**
1212
     * Check if a previous process with same pidfile was already running
1213
     *
1214
     * @return boolean
1215
     */
1216
    static public function isRunning()
1217
    {
1218
        $appPidLocation = self::opt('appPidLocation');
1219
 
1220
        if (!file_exists($appPidLocation)) {
1221
            unset($appPidLocation);
1222
            return false;
1223
        }
1224
 
1225
        $pid = self::fileread($appPidLocation);
1226
        if (!$pid) {
1227
            return false;
1228
        }
1229
 
1230
        // Ping app
1231
        if (!posix_kill(intval($pid), 0)) {
1232
            // Not responding so unlink pidfile
1233
            @unlink($appPidLocation);
1234
            return self::warning(
1235
                'Orphaned pidfile found and removed: ' .
1236
                '{appPidLocation}. Previous process crashed?'
1237
            );
1238
        }
1239
 
1240
        return true;
1241
    }
1242
 
1243
 
1244
 
1245
    /**
1246
     * Put the running script in background
1247
     *
1248
     * @return void
1249
     */
1250
    static protected function _summon()
1251
    {
1252
        if (self::opt('usePEARLogInstance')) {
1253
            $logLoc = '(PEAR Log)';
1254
        } else {
1255
            $logLoc = self::opt('logLocation');
1256
        }
1257
 
1258
        self::notice('Starting {appName} daemon, output in: %s', $logLoc);
1259
 
1260
        // Allowed?
1261
        if (self::isRunning()) {
1262
            return self::emerg('{appName} daemon is still running. Exiting');
1263
        }
1264
 
1265
        // Reset Process Information
1266
        self::$_safeMode       = !!@ini_get('safe_mode');
1267
        self::$_processId      = 0;
1268
        self::$_processIsChild = false;
1269
 
1270
        // Fork process!
1271
        if (!self::_fork()) {
1272
            return self::emerg('Unable to fork');
1273
        }
1274
 
1275
        // Additional PID succeeded check
1276
        if (!is_numeric(self::$_processId) || self::$_processId < 1) {
1277
            return self::emerg('No valid pid: %s', self::$_processId);
1278
        }
1279
 
1280
        // Change umask
1281
        @umask(0);
1282
 
1283
        // Write pidfile
1284
        $p = self::_writePid(self::opt('appPidLocation'), self::$_processId);
1285
        if (false === $p) {
1286
            return self::emerg('Unable to write pid file {appPidLocation}');
1287
        }
1288
 
1289
        // Change identity. maybe
1290
        $c = self::_changeIdentity(
1291
            self::opt('appRunAsGID'),
1292
            self::opt('appRunAsUID')
1293
        );
1294
        if (false === $c) {
1295
            self::crit('Unable to change identity');
1296
            if (self::opt('appDieOnIdentityCrisis')) {
1297
                self::emerg('Cannot continue after this');
1298
            }
1299
        }
1300
 
1301
        // Important for daemons
1302
        // See http://www.php.net/manual/en/function.pcntl-signal.php
1303
        declare(ticks = 1);
1304
 
1305
        // Setup signal handlers
1306
        // Handlers for individual signals can be overrulled with
1307
        // setSigHandler()
1308
        foreach (self::$_sigHandlers as $signal => $handler) {
1309
            if (!is_callable($handler) && $handler != SIG_IGN && $handler != SIG_DFL) {
1310
                return self::emerg(
1311
                    'You want to assign signal %s to handler %s but ' .
1312
                    'it\'s not callable',
1313
                    $signal,
1314
                    $handler
1315
                );
1316
            } else if (!pcntl_signal($signal, $handler)) {
1317
                return self::emerg(
1318
                    'Unable to reroute signal handler: %s',
1319
                    $signal
1320
                );
1321
            }
1322
        }
1323
 
1324
        // Change dir
1325
        @chdir(self::opt('appDir'));
1326
 
1327
        return true;
1328
    }
1329
 
1330
    /**
1331
     * Determine whether pidfilelocation is valid
1332
     *
1333
     * @param string  $pidFilePath Pid location
1334
     * @param boolean $log         Allow this function to log directly on error
1335
     *
1336
     * @return boolean
1337
     */
1338
    static protected function _isValidPidLocation($pidFilePath, $log = true)
1339
    {
1340
        if (empty($pidFilePath)) {
1341
            return self::err(
1342
                '{appName} daemon encountered an empty appPidLocation'
1343
            );
1344
        }
1345
 
1346
        $pidDirPath = dirname($pidFilePath);
1347
        $parts      = explode('/', $pidDirPath);
1348
        if (count($parts) <= 3 || end($parts) != self::opt('appName')) {
1349
            // like: /var/run/x.pid
1350
            return self::err(
1351
                'Since version 0.6.3, the pidfile needs to be ' .
1352
                'in it\'s own subdirectory like: %s/{appName}/{appName}.pid'
1353
            );
1354
        }
1355
 
1356
        return true;
1357
    }
1358
 
1359
    /**
1360
     * Creates pid dir and writes process id to pid file
1361
     *
1362
     * @param string  $pidFilePath PID File path
1363
     * @param integer $pid         PID
1364
     *
1365
     * @return boolean
1366
     */
1367
    static protected function _writePid($pidFilePath = null, $pid = null)
1368
    {
1369
        if (empty($pid)) {
1370
            return self::err('{appName} daemon encountered an empty PID');
1371
        }
1372
 
1373
        if (!self::_isValidPidLocation($pidFilePath, true)) {
1374
            return false;
1375
        }
1376
 
1377
        $pidDirPath = dirname($pidFilePath);
1378
 
1379
        if (!self::_mkdirr($pidDirPath, 0755)) {
1380
            return self::err('Unable to create directory: %s', $pidDirPath);
1381
        }
1382
 
1383
        if (!file_put_contents($pidFilePath, $pid)) {
1384
            return self::err('Unable to write pidfile: %s', $pidFilePath);
1385
        }
1386
 
1387
        if (!chmod($pidFilePath, 0644)) {
1388
            return self::err('Unable to chmod pidfile: %s', $pidFilePath);;
1389
        }
1390
 
1391
        return true;
1392
    }
1393
 
1394
    /**
1395
     * Read a file. file_get_contents() leaks memory! (#18031 for more info)
1396
     *
1397
     * @param string $filepath
1398
     *
1399
     * @return string
1400
     */
1401
    static public function fileread ($filepath) {
1402
        $f = fopen($filepath, 'r');
1403
        if (!$f) {
1404
            return false;
1405
        }
1406
        $data = fread($f, filesize($filepath));
1407
        fclose($f);
1408
        return $data;
1409
    }
1410
 
1411
    /**
1412
     * Recursive alternative to mkdir
1413
     *
1414
     * @param string  $dirPath Directory to create
1415
     * @param integer $mode    Umask
1416
     *
1417
     * @return boolean
1418
     */
1419
    static protected function _mkdirr($dirPath, $mode)
1420
    {
1421
        is_dir(dirname($dirPath)) || self::_mkdirr(dirname($dirPath), $mode);
1422
        return is_dir($dirPath) || @mkdir($dirPath, $mode);
1423
    }
1424
 
1425
    /**
1426
     * Change identity of process & resources if needed.
1427
     *
1428
     * @param integer $gid Group identifier (number)
1429
     * @param integer $uid User identifier (number)
1430
     *
1431
     * @return boolean
1432
     */
1433
    static protected function _changeIdentity($gid = 0, $uid = 0)
1434
    {
1435
        // What files need to be chowned?
1436
        $chownFiles = array();
1437
        if (self::_isValidPidLocation(self::opt('appPidLocation'), true)) {
1438
            $chownFiles[] = dirname(self::opt('appPidLocation'));
1439
        }
1440
        $chownFiles[] = self::opt('appPidLocation');
1441
        if (!is_object(self::opt('usePEARLogInstance'))) {
1442
            $chownFiles[] = self::opt('logLocation');
1443
        }
1444
 
1445
        // Chown pid- & log file
1446
        // We have to change owner in case of identity change.
1447
        // This way we can modify the files even after we're not root anymore
1448
        foreach ($chownFiles as $filePath) {
1449
            // Change File GID
1450
            $doGid = (filegroup($filePath) != $gid ? $gid : false);
1451
            if (false !== $doGid && !@chgrp($filePath, intval($gid))) {
1452
                return self::err(
1453
                    'Unable to change group of file %s to %s',
1454
                    $filePath,
1455
                    $gid
1456
                );
1457
            }
1458
 
1459
            // Change File UID
1460
            $doUid = (fileowner($filePath) != $uid ? $uid : false);
1461
            if (false !== $doUid && !@chown($filePath, intval($uid))) {
1462
                return self::err(
1463
                    'Unable to change user of file %s to %s',
1464
                    $filePath,
1465
                    $uid
1466
                );
1467
            }
1468
 
1469
			// Export correct homedir
1470
			if (($info = posix_getpwuid($uid)) && is_dir($info['dir'])) {
1471
				system('export HOME="' . $info['dir'] . '"');
1472
			}
1473
        }
1474
 
1475
        // Change Process GID
1476
        $doGid = (posix_getgid() !== $gid ? $gid : false);
1477
        if (false !== $doGid && !@posix_setgid($gid)) {
1478
            return self::err('Unable to change group of process to %s', $gid);
1479
        }
1480
 
1481
        // Change Process UID
1482
        $doUid = (posix_getuid() !== $uid ? $uid : false);
1483
        if (false !== $doUid && !@posix_setuid($uid)) {
1484
            return self::err('Unable to change user of process to %s', $uid);
1485
        }
1486
 
1487
        $group = posix_getgrgid($gid);
1488
        $user  = posix_getpwuid($uid);
1489
 
1490
        return self::info(
1491
            'Changed identify to %s:%s',
1492
            $group['name'],
1493
            $user['name']
1494
        );
1495
    }
1496
 
1497
    /**
1498
     * Fork process and kill parent process, the heart of the 'daemonization'
1499
     *
1500
     * @return boolean
1501
     */
1502
    static protected function _fork()
1503
    {
1504
        self::debug('forking {appName} daemon');
1505
        $pid = pcntl_fork();
1506
        if ($pid === -1) {
1507
            // Error
1508
            return self::warning('Process could not be forked');
1509
        } else if ($pid) {
1510
            // Parent
1511
            self::debug('Ending {appName} parent process');
1512
            // Die without attracting attention
1513
            exit();
1514
        } else {
1515
            // Child
1516
            self::$_processIsChild = true;
1517
            self::$_isDying        = false;
1518
            self::$_processId      = posix_getpid();
1519
            return true;
1520
        }
1521
    }
1522
 
1523
    /**
1524
     * Return what the current process is: child or parent
1525
     *
1526
     * @return string
1527
     */
1528
    static protected function _whatIAm()
1529
    {
1530
        return (self::isInBackground() ? 'child' : 'parent');
1531
    }
1532
 
1533
    /**
1534
     * Sytem_Daemon::_die()
1535
     * Kill the daemon
1536
     * Keep this function as independent from complex logic as possible
1537
     *
1538
     * @param boolean $restart Whether to restart after die
1539
     *
1540
     * @return void
1541
     */
1542
    static protected function _die($restart = false)
1543
    {
1544
        if (self::isDying()) {
1545
            return null;
1546
        }
1547
 
1548
        self::$_isDying = true;
1549
        // Following caused a bug if pid couldn't be written because of
1550
        // privileges
1551
        // || !file_exists(self::opt('appPidLocation'))
1552
        if (!self::isInBackground()) {
1553
            self::info(
1554
                'Process was not daemonized yet, ' .
1555
                'just halting current process'
1556
            );
1557
            die();
1558
        }
1559
 
1560
        $pid = file_get_contents(
1561
            System_Daemon::getOption('appPidLocation')
1562
        );
1563
        @unlink(self::opt('appPidLocation'));
1564
 
1565
        if ($restart) {
1566
            // So instead we should:
1567
            die(exec(join(' ', $GLOBALS['argv']) . ' > /dev/null &'));
1568
        } else {
1569
            passthru('kill -9 ' . $pid);
1570
            die();
1571
        }
1572
    }
1573
 
1574
 
1575
    /**
1576
     * Sets up OS instance
1577
     *
1578
     * @return boolean
1579
     */
1580
    static protected function _osObjSetup()
1581
    {
1582
        // Create Option Object if nescessary
1583
        if (!self::$_osObj) {
1584
            self::$_osObj = System_Daemon_OS::factory();
1585
        }
1586
 
1587
        // Still false? This was an error!
1588
        if (!self::$_osObj) {
1589
            return self::emerg('Unable to setup OS object');
1590
        }
1591
 
1592
        return true;
1593
    }
1594
 
1595
    /**
1596
     * Sets up Option Object instance
1597
     *
1598
     * @return boolean
1599
     */
1600
    static protected function _optionObjSetup()
1601
    {
1602
        // Create Option Object if nescessary
1603
        if (!self::$_optObj) {
1604
            self::$_optObj = new System_Daemon_Options(self::$_optionDefinitions);
1605
        }
1606
 
1607
        // Still false? This was an error!
1608
        if (!self::$_optObj) {
1609
            return self::emerg('Unable to setup Options object. ');
1610
        }
1611
 
1612
        return true;
1613
    }
1614
 
1615
    /**
1616
     * Checks if all the required options are set.
1617
     * Initializes, sanitizes & defaults unset variables
1618
     *
1619
     * @param boolean $premature Whether to do a premature option init
1620
     *
1621
     * @return mixed integer or boolean
1622
     */
1623
    static protected function _optionsInit($premature=false)
1624
    {
1625
        if (!self::_optionObjSetup()) {
1626
            return false;
1627
        }
1628
 
1629
        return self::$_optObj->init($premature);
1630
    }
1631
}