Current File : //opt/RZphp80/includes/System/Daemon.php
<?php
/* vim: set noai expandtab tabstop=4 softtabstop=4 shiftwidth=4: */

/**
 * System_Daemon turns PHP-CLI scripts into daemons.
 *
 * PHP version 5
 *
 * @category  System
 * @package   System_Daemon
 * @author    Kevin van Zonneveld <kevin@vanzonneveld.net>
 * @copyright 2008 Kevin van Zonneveld (http://kevin.vanzonneveld.net)
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD Licence
 * @version   SVN: Release: $Id$
 * @link      http://trac.plutonia.nl/projects/system_daemon
 */

// Autoloader borrowed from PHP_CodeSniffer, see function for credits
spl_autoload_register(array('System_Daemon', 'autoload'));

/**
 * System_Daemon. Create daemons with practicle functions
 * like System_Daemon::start()
 *
 * Requires PHP build with --enable-cli --with-pcntl.
 * Only runs on *NIX systems, because Windows lacks of the pcntl ext.
 *
 * PHP version 5
 *
 * @category  System
 * @package   System_Daemon
 * @author    Kevin van Zonneveld <kevin@vanzonneveld.net>
 * @copyright 2008 Kevin van Zonneveld (http://kevin.vanzonneveld.net)
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD Licence
 * @version   SVN: Release: $Id$
 * @link      http://trac.plutonia.nl/projects/system_daemon
 *
 */
class System_Daemon
{
    // Make these corresponding with PEAR
    // Ensures compatibility while maintaining independency

    /**
     * System is unusable (will throw a System_Daemon_Exception as well)
     */
    const LOG_EMERG = 0;

    /**
     * Immediate action required (will throw a System_Daemon_Exception as well)
     */
    const LOG_ALERT = 1;

    /**
     * Critical conditions (will throw a System_Daemon_Exception as well)
     */
    const LOG_CRIT = 2;

    /**
     * Error conditions
     */
    const LOG_ERR = 3;

    /**
     * Warning conditions
     */
    const LOG_WARNING = 4;

    /**
     * Normal but significant
     */
    const LOG_NOTICE = 5;

    /**
     * Informational
     */
    const LOG_INFO = 6;

    /**
     * Debug-level messages
     */
    const LOG_DEBUG = 7;



    /**
     * The current process identifier
     *
     * @var integer
     */
    static protected $_processId = 0;

    /**
     * Whether the our daemon is being killed
     *
     * @var boolean
     */
    static protected $_isDying = false;

    /**
     * Whether the current process is a forked child
     *
     * @var boolean
     */
    static protected $_processIsChild = false;

    /**
     * Whether SAFE_MODE is on or off. This is important for ini_set
     * behavior
     *
     * @var boolean
     */
    static protected $_safeMode = false;

    /**
     * Available log levels
     *
     * @var array
     */
    static protected $_logLevels = array(
        self::LOG_EMERG => 'emerg',
        self::LOG_ALERT => 'alert',
        self::LOG_CRIT => 'crit',
        self::LOG_ERR => 'err',
        self::LOG_WARNING => 'warning',
        self::LOG_NOTICE => 'notice',
        self::LOG_INFO => 'info',
        self::LOG_DEBUG => 'debug',
    );

    /**
     * Available PHP error levels and their meaning in POSIX loglevel terms
     * Some ERROR constants are not supported in all PHP versions
     * and will conditionally be translated from strings to constants,
     * or else: removed from this mapping at start().
     *
     * @var array
     */
    static protected $_logPhpMapping = array(
        E_ERROR => array(self::LOG_ERR, 'Error'),
        E_WARNING => array(self::LOG_WARNING, 'Warning'),
        E_PARSE => array(self::LOG_EMERG, 'Parse'),
        E_NOTICE => array(self::LOG_DEBUG, 'Notice'),
        E_CORE_ERROR => array(self::LOG_EMERG, 'Core Error'),
        E_CORE_WARNING => array(self::LOG_WARNING, 'Core Warning'),
        E_COMPILE_ERROR => array(self::LOG_EMERG, 'Compile Error'),
        E_COMPILE_WARNING => array(self::LOG_WARNING, 'Compile Warning'),
        E_USER_ERROR => array(self::LOG_ERR, 'User Error'),
        E_USER_WARNING => array(self::LOG_WARNING, 'User Warning'),
        E_USER_NOTICE => array(self::LOG_DEBUG, 'User Notice'),
        'E_RECOVERABLE_ERROR' => array(self::LOG_WARNING, 'Recoverable Error'),
        'E_DEPRECATED' => array(self::LOG_NOTICE, 'Deprecated'),
        'E_USER_DEPRECATED' => array(self::LOG_NOTICE, 'User Deprecated'),
    );

    /**
     * Holds Option Object
     *
     * @var mixed object or boolean
     */
    static protected $_optObj = false;

    /**
     * Holds OS Object
     *
     * @var mixed object or boolean
     */
    static protected $_osObj = false;

    /**
     * Definitions for all Options
     *
     * @var array
     * @see setOption()
     * @see getOption()
     */
    static protected $_optionDefinitions = array(
        'usePEAR' => array(
            'type' => 'boolean',
            'default' => true,
            'punch' => 'Whether to run this class using PEAR',
            'detail' => 'Will run standalone when false',
            'required' => true,
        ),
        'usePEARLogInstance' => array(
            'type' => 'boolean|object',
            'default' => false,
            'punch' => 'Accepts a PEAR_Log instance to handle all logging',
            'detail' => 'This will replace System_Daemon\'s own logging facility',
            'required' => true,
        ),
        'useCustomLogHandler' => array(
            'type' => 'boolean|object',
            'default' => false,
            'punch' => 'Accepts any callable method to handle all logging',
            'detail' => 'This will replace System_Daemon\'s own logging facility',
            'required' => true,
        ),

        'authorName' => array(
            'type' => 'string/0-50',
            'punch' => 'Author name',
            'example' => 'Kevin van zonneveld',
            'detail' => 'Required for forging init.d script',
        ),
        'authorEmail' => array(
            'type' => 'string/email',
            'punch' => 'Author e-mail',
            'example' => 'kevin@vanzonneveld.net',
            'detail' => 'Required for forging init.d script',
        ),
        'appName' => array(
            'type' => 'string/unix',
            'punch' => 'The application name',
            'example' => 'logparser',
            'detail' => 'Must be UNIX-proof; Required for running daemon',
            'required' => true,
        ),
        'appDescription' => array(
            'type' => 'string',
            'punch' => 'Daemon description',
            'example' => 'Parses logfiles of vsftpd and stores them in MySQL',
            'detail' => 'Required for forging init.d script',
        ),
        'appDir' => array(
            'type' => 'string/existing_dirpath',
            'default' => '@dirname({SERVER.SCRIPT_NAME})',
            'punch' => 'The home directory of the daemon',
            'example' => '/usr/local/logparser',
            'detail' => 'Highly recommended to set this yourself',
            'required' => true,
        ),
        'appExecutable' => array(
            'type' => 'string/existing_filepath',
            'default' => '@basename({SERVER.SCRIPT_NAME})',
            'punch' => 'The executable daemon file',
            'example' => 'logparser.php',
            'detail' => 'Recommended to set this yourself; Required for init.d',
            'required' => true
        ),

        'logVerbosity' => array(
            'type' => 'number/0-7',
            'default' => self::LOG_INFO,
            'punch' => 'Messages below this log level are ignored',
            'example' => '',
            'detail' => 'Not written to logfile; not displayed on screen',
            'required' => true,
        ),
        'logLocation' => array(
            'type' => 'string/creatable_filepath',
            'default' => '/var/log/{OPTIONS.appName}.log',
            'punch' => 'The log filepath',
            'example' => '/var/log/logparser_daemon.log',
            'detail' => 'Not applicable if you use PEAR Log',
            'required' => false,
        ),
        'logPhpErrors' => array(
            'type' => 'boolean',
            'default' => true,
            'punch' => 'Reroute PHP errors to log function',
            'detail' => '',
            'required' => true,
        ),
        'logFilePosition' => array(
            'type' => 'boolean',
            'default' => false,
            'punch' => 'Show file in which the log message was generated',
            'detail' => '',
            'required' => true,
        ),
        'logTrimAppDir' => array(
            'type' => 'boolean',
            'default' => true,
            'punch' => 'Strip the application dir from file positions in log msgs',
            'detail' => '',
            'required' => true,
        ),
        'logLinePosition' => array(
            'type' => 'boolean',
            'default' => true,
            'punch' => 'Show the line number in which the log message was generated',
            'detail' => '',
            'required' => true,
        ),
        'appRunAsUID' => array(
            'type' => 'number/0-65000',
            'default' => 0,
            'punch' => 'The user id under which to run the process',
            'example' => '1000',
            'detail' => 'Defaults to root which is insecure!',
            'required' => true,
        ),
        'appRunAsGID' => array(
            'type' => 'number/0-65000',
            'default' => 0,
            'punch' => 'The group id under which to run the process',
            'example' => '1000',
            'detail' => 'Defaults to root which is insecure!',
            'required' => true,
        ),
        'appPidLocation' => array(
            'type' => 'string/unix_filepath',
            'default' => '/var/run/{OPTIONS.appName}/{OPTIONS.appName}.pid',
            'punch' => 'The pid filepath',
            'example' => '/var/run/logparser/logparser.pid',
            'detail' => '',
            'required' => true,
        ),
        'appChkConfig' => array(
             'type' => 'string',
             'default' => '- 99 0',
             'punch' => 'chkconfig parameters for init.d',
             'detail' => 'runlevel startpriority stoppriority',
         ),
        'appDieOnIdentityCrisis' => array(
            'type' => 'boolean',
            'default' => true,
            'punch' => 'Kill daemon if it cannot assume the identity',
            'detail' => '',
            'required' => true,
        ),

        'sysMaxExecutionTime' => array(
            'type' => 'number',
            'default' => 0,
            'punch' => 'Maximum execution time of each script in seconds',
            'detail' => '0 is infinite',
        ),
        'sysMaxInputTime' => array(
            'type' => 'number',
            'default' => 0,
            'punch' => 'Maximum time to spend parsing request data',
            'detail' => '0 is infinite',
        ),
        'sysMemoryLimit' => array(
            'type' => 'string',
            'default' => '128M',
            'punch' => 'Maximum amount of memory a script may consume',
            'detail' => '0 is infinite',
        ),

        'runTemplateLocation' => array(
            'type' => 'string/existing_filepath',
            'default' => false,
            'punch' => 'The filepath to a custom autorun Template',
            'example' => '/etc/init.d/skeleton',
            'detail' => 'Sometimes it\'s better to stick with the OS default,
                and use something like /etc/default/<name> for customization',
        ),
    );


    /**
     * Available signal handlers
     * setSigHandler can overwrite these values individually.
     *
     * Available POSIX SIGNALS and their PHP handler functions.
     * Some SIGNALS constants are not supported in all PHP versions
     * and will conditionally be translated from strings to constants,
     * or else: removed from this mapping at start().
     *
     * 'kill -l' gives you a list of signals available on your UNIX.
     * Eg. Ubuntu:
     *
     *  1) SIGHUP      2) SIGINT      3) SIGQUIT      4) SIGILL
     *  5) SIGTRAP      6) SIGABRT      7) SIGBUS      8) SIGFPE
     *  9) SIGKILL    10) SIGUSR1    11) SIGSEGV    12) SIGUSR2
     * 13) SIGPIPE    14) SIGALRM    15) SIGTERM    17) SIGCHLD
     * 18) SIGCONT    19) SIGSTOP    20) SIGTSTP    21) SIGTTIN
     * 22) SIGTTOU    23) SIGURG      24) SIGXCPU    25) SIGXFSZ
     * 26) SIGVTALRM  27) SIGPROF    28) SIGWINCH    29) SIGIO
     * 30) SIGPWR      31) SIGSYS      33) SIGRTMIN    34) SIGRTMIN+1
     * 35) SIGRTMIN+2  36) SIGRTMIN+3  37) SIGRTMIN+4  38) SIGRTMIN+5
     * 39) SIGRTMIN+6  40) SIGRTMIN+7  41) SIGRTMIN+8  42) SIGRTMIN+9
     * 43) SIGRTMIN+10 44) SIGRTMIN+11 45) SIGRTMIN+12 46) SIGRTMIN+13
     * 47) SIGRTMIN+14 48) SIGRTMIN+15 49) SIGRTMAX-15 50) SIGRTMAX-14
     * 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10
     * 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7  58) SIGRTMAX-6
     * 59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
     * 63) SIGRTMAX-1  64) SIGRTMAX
     *
     * SIG_IGN, SIG_DFL, SIG_ERR are no real signals
     *
     * @var array
     * @see setSigHandler()
     */
    static protected $_sigHandlers = array(
        SIGHUP => array('System_Daemon', 'defaultSigHandler'),
        SIGINT => array('System_Daemon', 'defaultSigHandler'),
        SIGQUIT => array('System_Daemon', 'defaultSigHandler'),
        SIGILL => array('System_Daemon', 'defaultSigHandler'),
        SIGTRAP => array('System_Daemon', 'defaultSigHandler'),
        SIGABRT => array('System_Daemon', 'defaultSigHandler'),
        'SIGIOT' => array('System_Daemon', 'defaultSigHandler'),
        SIGBUS => array('System_Daemon', 'defaultSigHandler'),
        SIGFPE => array('System_Daemon', 'defaultSigHandler'),
        SIGUSR1 => array('System_Daemon', 'defaultSigHandler'),
        SIGSEGV => array('System_Daemon', 'defaultSigHandler'),
        SIGUSR2 => array('System_Daemon', 'defaultSigHandler'),
        SIGPIPE => SIG_IGN,
        SIGALRM => array('System_Daemon', 'defaultSigHandler'),
        SIGTERM => array('System_Daemon', 'defaultSigHandler'),
        'SIGSTKFLT' => array('System_Daemon', 'defaultSigHandler'),
        'SIGCLD' => array('System_Daemon', 'defaultSigHandler'),
        'SIGCHLD' => array('System_Daemon', 'defaultSigHandler'),
        SIGCONT => array('System_Daemon', 'defaultSigHandler'),
        SIGTSTP => array('System_Daemon', 'defaultSigHandler'),
        SIGTTIN => array('System_Daemon', 'defaultSigHandler'),
        SIGTTOU => array('System_Daemon', 'defaultSigHandler'),
        SIGURG => array('System_Daemon', 'defaultSigHandler'),
        SIGXCPU => array('System_Daemon', 'defaultSigHandler'),
        SIGXFSZ => array('System_Daemon', 'defaultSigHandler'),
        SIGVTALRM => array('System_Daemon', 'defaultSigHandler'),
        SIGPROF => array('System_Daemon', 'defaultSigHandler'),
        SIGWINCH => array('System_Daemon', 'defaultSigHandler'),
        'SIGPOLL' => array('System_Daemon', 'defaultSigHandler'),
        SIGIO => array('System_Daemon', 'defaultSigHandler'),
        'SIGPWR' => array('System_Daemon', 'defaultSigHandler'),
        'SIGSYS' => array('System_Daemon', 'defaultSigHandler'),
        SIGBABY => array('System_Daemon', 'defaultSigHandler'),
        'SIG_BLOCK' => array('System_Daemon', 'defaultSigHandler'),
        'SIG_UNBLOCK' => array('System_Daemon', 'defaultSigHandler'),
        'SIG_SETMASK' => array('System_Daemon', 'defaultSigHandler'),
    );


    /**
     * Making the class non-abstract with a protected constructor does a better
     * job of preventing instantiation than just marking the class as abstract.
     *
     * @see start()
     */
    protected function __construct()
    {

    }



    /**
     * Autoload static method for loading classes and interfaces.
     * Code from the PHP_CodeSniffer package by Greg Sherwood and
     * Marc McIntyre
     *
     * @param string $className The name of the class or interface.
     *
     * @return void
     */
    static public function autoload($className)
    {
        $parent     = 'System_';
        $parent_len = strlen($parent);
        if (substr($className, 0, $parent_len) == $parent) {
            $newClassName = substr($className, $parent_len);
        } else {
            $newClassName = $className;
        }

        $path = str_replace('_', '/', $newClassName).'.php';

        if (is_file(dirname(__FILE__).'/'.$path) === true) {
            // Check standard file locations based on class name.
            include(dirname(__FILE__).'/'.$path);
        } else {
            // Everything else.
            // Check for $path within each include_path
            foreach (explode(PATH_SEPARATOR,ini_get('include_path')) as $prefix) {
                $prefixed_path = $prefix.'/'.$path;
                if (file_exists($prefixed_path)) {
                    include $prefixed_path;
                    return true;
                }
            }

            // Since include_path might not contain current
            // directory, we'll check that ourselves.
            if (file_exists($path)) {
                include $path;
            }
        }
    }

    /**
     * Spawn daemon process.
     *
     * @return boolean
     * @see iterate()
     * @see stop()
     * @see autoload()
     * @see _optionsInit()
     * @see _summon()
     */
    static public function start()
    {
        // Conditionally add loglevel mappings that are not supported in
        // all PHP versions.
        // They will be in string representation and have to be
        // converted & unset
        foreach (self::$_logPhpMapping as $phpConstant => $props) {
            if (!is_numeric($phpConstant)) {
                if (defined($phpConstant)) {
                    self::$_logPhpMapping[constant($phpConstant)] = $props;
                }
                unset(self::$_logPhpMapping[$phpConstant]);
            }
        }
        // Same goes for POSIX signals. Not all Constants are available on
        // all platforms.
        foreach (self::$_sigHandlers as $signal => $handler) {
            if (is_string($signal) || !$signal) {
                if (defined($signal) && ($const = constant($signal))) {
                    self::$_sigHandlers[$const] = $handler;
                }
                unset(self::$_sigHandlers[$signal]);
            }
        }

        // Quickly initialize some defaults like usePEAR
        // by adding the $premature flag
        self::_optionsInit(true);

        if (self::opt('logPhpErrors')) {
            set_error_handler(array('System_Daemon', 'phpErrors'), E_ALL);
        }

        // To run as a part of PEAR
        if (self::opt('usePEAR')) {
            // SPL's autoload will make sure classes are automatically loaded
            if (false === class_exists('PEAR', true)) {
                $msg = 'PEAR not found. Install PEAR or run with option: '.
                    'usePEAR = false';
                trigger_error($msg, E_USER_ERROR);
            }

            if (false === class_exists('PEAR_Exception', true)) {
                $msg = 'PEAR_Exception not found?!';
                trigger_error($msg, E_USER_ERROR);
            }

            if (false === class_exists('System_Daemon_Exception', true)) {
                // PEAR_Exception is OK. PEAR was found already.
                throw new PEAR_Exception('Class System_Daemon_Exception not found');
            }
        }

        // Check the PHP configuration
        if (!defined('SIGHUP')) {
            $msg = 'PHP is compiled without --enable-pcntl directive';
            if (self::opt('usePEAR')) {
                throw new System_Daemon_Exception($msg);
            } else {
                trigger_error($msg, E_USER_ERROR);
            }
        }

        // Check for CLI
        if ((php_sapi_name() !== 'cli')) {
            $msg = 'You can only create daemon from the command line (CLI-mode)';
            if (self::opt('usePEAR')) {
                throw new System_Daemon_Exception($msg);
            } else {
                trigger_error($msg, E_USER_ERROR);
            }
        }

        // Check for POSIX
        if (!function_exists('posix_getpid')) {
            $msg = 'PHP is compiled without --enable-posix directive';
            if (self::opt('usePEAR')) {
                throw new System_Daemon_Exception($msg);
            } else {
                trigger_error($msg, E_USER_ERROR);
            }
        }

        // Enable Garbage Collector (PHP >= 5.3)
        if (function_exists('gc_enable')) {
            gc_enable();
        }

        // Initialize & check variables
        if (false === self::_optionsInit(false)) {
            if (is_object(self::$_optObj) && is_array(self::$_optObj->errors)) {
                foreach (self::$_optObj->errors as $error) {
                    self::notice($error);
                }
            }

            $msg = 'Crucial options are not set. Review log:';
            if (self::opt('usePEAR')) {
                throw new System_Daemon_Exception($msg);
            } else {
                trigger_error($msg, E_USER_ERROR);
            }
        }

        // Option validation, more advanced than can be handled by
        // declaration processing
        if (self::opt('appName') !== strtolower(self::opt('appName'))) {
            return self::crit('Option: appName should be lowercase');
        }
        if (strlen(self::opt('appName')) > 16) {
            return self::crit('Option: appName should be no longer than 16 characters');
        }

        // Become daemon
        self::_summon();

        return true;

    }

    /**
     * Protects your daemon by e.g. clearing statcache. Can optionally
     * be used as a replacement for sleep as well.
     *
     * @param integer $sleepSeconds Optionally put your daemon to rest for X s.
     *
     * @return void
     * @see start()
     * @see stop()
     */
    static public function iterate($sleepSeconds = 0)
    {
        self::_optionObjSetup();
        if ($sleepSeconds >= 1) {
            sleep($sleepSeconds);
        } else if (is_numeric($sleepSeconds)) {
            usleep($sleepSeconds * 1000000);
        }

        clearstatcache();

        // Garbage Collection (PHP >= 5.3)
        if (function_exists('gc_collect_cycles')) {
            gc_collect_cycles();
        }

        return true;
    }

    /**
     * Stop daemon process.
     *
     * @return void
     * @see start()
     */
    static public function stop()
    {
        self::info('Stopping {appName}');
        self::_die(false);
    }

    /**
     * Restart daemon process.
     *
     * @return void
     * @see _die()
     */
    static public function restart()
    {
        self::info('Restarting {appName}');
        self::_die(true);
    }

    /**
     * Overrule or add signal handlers.
     *
     * @param string $signal  Signal constant (e.g. SIGHUP)
     * @param mixed  $handler Which handler to call on signal
     *
     * @return boolean
     * @see $_sigHandlers
     */
    static public function setSigHandler($signal, $handler)
    {
        if (!isset(self::$_sigHandlers[$signal])) {
            // The signal should be defined already
            self::notice(
                'Can only overrule on of these signal handlers: %s',
                join(', ', array_keys(self::$_sigHandlers))
            );
            return false;
        }

        // Overwrite on existance
        self::$_sigHandlers[$signal] = $handler;
        return true;
    }

    /**
     * Sets any option found in $_optionDefinitions
     * Public interface to talk with with protected option methods
     *
     * @param string $name  Name of the Option
     * @param mixed  $value Value of the Option
     *
     * @return boolean
     */
    static public function setOption($name, $value)
    {
        if (!self::_optionObjSetup()) {
            return false;
        }

        return self::$_optObj->setOption($name, $value);
    }

    /**
     * Sets an array of options found in $_optionDefinitions
     * Public interface to talk with with protected option methods
     *
     * @param array $use_options Array with Options
     *
     * @return boolean
     */
    static public function setOptions($use_options)
    {
        if (!self::_optionObjSetup()) {
            return false;
        }

        return self::$_optObj->setOptions($use_options);
    }

    /**
     * Shortcut for getOption & setOption
     *
     * @param string $name Option to set or get
     *
     * @return mixed
     */
    static public function opt($name)
    {
        $args = func_get_args();
        if (count($args) > 1) {
            return self::setOption($name, $args[1]);
        } else {
            return self::getOption($name);
        }
    }


    /**
     * Gets any option found in $_optionDefinitions
     * Public interface to talk with with protected option methods
     *
     * @param string $name Name of the Option
     *
     * @return mixed
     */
    static public function getOption($name)
    {
        if (!self::_optionObjSetup()) {
            return false;
        }

        return self::$_optObj->getOption($name);
    }

    /**
     * Gets an array of options found
     *
     * @return array
     */
    static public function getOptions()
    {
        if (!self::_optionObjSetup()) {
            return false;
        }

        return self::$_optObj->getOptions();
    }

    /**
     * Catches PHP Errors and forwards them to log function
     *
     * @param integer $errno   Level
     * @param string  $errstr  Error
     * @param string  $errfile File
     * @param integer $errline Line
     *
     * @return boolean
     */
    static public function phpErrors ($errno, $errstr, $errfile, $errline)
    {
        // Ignore suppressed errors (prefixed by '@')
        if (error_reporting() == 0) {
            return;
        }

        // Map PHP error level to System_Daemon log level
        if (!isset(self::$_logPhpMapping[$errno][0])) {
            self::warning('Unknown PHP errorno: %s', $errno);
            $phpLvl = self::LOG_ERR;
        } else {
            list($logLvl, $phpLvl) = self::$_logPhpMapping[$errno];
        }

        // Log it
        // No shortcuts this time!
        self::log(
            $logLvl, '[PHP ' . $phpLvl . '] '.$errstr, $errfile, __CLASS__,
            __FUNCTION__, $errline
        );

        return true;
    }

    /**
     * Abbreviate a string. e.g: Kevin van zonneveld -> Kevin van Z...
     *
     * @param string  $str    Data
     * @param integer $cutAt  Where to cut
     * @param string  $suffix Suffix with something?
     *
     * @return string
     */
    static public function abbr($str, $cutAt = 30, $suffix = '...')
    {
        if (strlen($str) <= 30) {
            return $str;
        }

        $canBe = $cutAt - strlen($suffix);

        return substr($str, 0, $canBe). $suffix;
    }

    /**
     * Tries to return the most significant information as a string
     * based on any given argument.
     *
     * @param mixed $arguments Any type of variable
     *
     * @return string
     */
    static public function semantify($arguments)
    {
        if (is_object($arguments)) {
            return get_class($arguments);
        }
        if (!is_array($arguments)) {
            if (!is_numeric($arguments) && !is_bool($arguments)) {
                $arguments = '\''.$arguments.'\'';
            }
            return $arguments;
        }
        $arr = array();
        foreach ($arguments as $key=>$val) {
            if (is_array($val)) {
                $val = json_encode($val);
            } elseif (!is_numeric($val) && !is_bool($val)) {
                $val = '\''.$val.'\'';
            }

            $val = self::abbr($val);

            $arr[] = $key.': '.$val;
        }
        return join(', ', $arr);
    }

    /**
     * Logging shortcut
     *
     * @return boolean
     */
    public static function emerg()
    {
        $arguments = func_get_args(); array_unshift($arguments, __FUNCTION__);
        call_user_func_array(array('System_Daemon', '_ilog'), $arguments);
        return false;
    }

    /**
     * Logging shortcut
     *
     * @return boolean
     */
    public static function alert()
    {
        $arguments = func_get_args(); array_unshift($arguments, __FUNCTION__);
        call_user_func_array(array('System_Daemon', '_ilog'), $arguments);
        return false;
    }

    /**
     * Logging shortcut
     *
     * @return boolean
     */
    public static function crit()
    {
        $arguments = func_get_args(); array_unshift($arguments, __FUNCTION__);
        call_user_func_array(array('System_Daemon', '_ilog'), $arguments);
        return false;
    }

    /**
     * Logging shortcut
     *
     * @return boolean
     */
    public static function err()
    {
        $arguments = func_get_args(); array_unshift($arguments, __FUNCTION__);
        call_user_func_array(array('System_Daemon', '_ilog'), $arguments);
        return false;
    }

    /**
     * Logging shortcut
     *
     * @return boolean
     */
    public static function warning()
    {
        $arguments = func_get_args(); array_unshift($arguments, __FUNCTION__);
        call_user_func_array(array('System_Daemon', '_ilog'), $arguments);
        return false;
    }

    /**
     * Logging shortcut
     *
     * @return boolean
     */
    public static function notice()
    {
        $arguments = func_get_args(); array_unshift($arguments, __FUNCTION__);
        call_user_func_array(array('System_Daemon', '_ilog'), $arguments);
        return true;
    }

    /**
     * Logging shortcut
     *
     * @return boolean
     */
    public static function info()
    {
        $arguments = func_get_args(); array_unshift($arguments, __FUNCTION__);
        call_user_func_array(array('System_Daemon', '_ilog'), $arguments);
        return true;
    }

    /**
     * Logging shortcut
     *
     * @return boolean
     */
    public static function debug()
    {
        $arguments = func_get_args(); array_unshift($arguments, __FUNCTION__);
        call_user_func_array(array('System_Daemon', '_ilog'), $arguments);
        return true;
    }

    /**
     * Internal logging function. Bridge between shortcuts like:
     * err(), warning(), info() and the actual log() function
     *
     * @param mixed $level As string or constant
     * @param mixed $str   Message
     *
     * @return boolean
     */
    protected static function _ilog($level, $str)
    {
        $arguments = func_get_args();
        $level     = $arguments[0];
        $format    = $arguments[1];

        if (is_string($level)) {
            if (false === ($l = array_search($level, self::$_logLevels))) {
                self::log(LOG_EMERG, 'No such loglevel: '. $level);
            } else {
                $level = $l;
            }
        }

        unset($arguments[0]);
        unset($arguments[1]);

        $str = $format;
        if (count($arguments)) {
            foreach ($arguments as $k => $v) {
                $arguments[$k] = self::semantify($v);
            }
            $str = vsprintf($str, $arguments);
        }

        self::_optionObjSetup();
        $str = preg_replace_callback(
            '/\{([^\{\}]+)\}/is',
            array(self::$_optObj, 'replaceVars'),
            $str
        );


        $history  = 2;
        $dbg_bt   = @debug_backtrace();
        $class    = (string)@$dbg_bt[($history-1)]['class'];
        $function = (string)@$dbg_bt[($history-1)]['function'];
        $file     = (string)@$dbg_bt[$history]['file'];
        $line     = (string)@$dbg_bt[$history]['line'];
        return self::log($level, $str, $file, $class, $function, $line);
    }

    /**
     * Almost every deamon requires a log file, this function can
     * facilitate that. Also handles class-generated errors, chooses
     * either PEAR handling or PEAR-independant handling, depending on:
     * self::opt('usePEAR').
     * Also supports PEAR_Log if you referenc to a valid instance of it
     * in self::opt('usePEARLogInstance').
     *
     * It logs a string according to error levels specified in array:
     * self::$_logLevels (0 is fatal and handles daemon's death)
     *
     * @param integer $level    What function the log record is from
     * @param string  $str      The log record
     * @param string  $file     What code file the log record is from
     * @param string  $class    What class the log record is from
     * @param string  $function What function the log record is from
     * @param integer $line     What code line the log record is from
     *
     * @throws System_Daemon_Exception
     * @return boolean
     * @see _logLevels
     * @see logLocation
     */
    static public function log ($level, $str, $file = false, $class = false,
    $function = false, $line = false) {
        // If verbosity level is not matched, don't do anything
        if (null === self::opt('logVerbosity')
            || false === self::opt('logVerbosity')
        ) {
            // Somebody is calling log before launching daemon..
            // fair enough, but we have to init some log options
            self::_optionsInit(true);
        }
        if (!self::opt('appName')) {
            // Not logging for anything without a name
            return false;
        }

        if ($level > self::opt('logVerbosity')) {
            return true;
        }

        // Make the tail of log massage.
        $log_tail = '';
        if ($level < self::LOG_NOTICE) {
            if (self::opt('logFilePosition')) {
                if (self::opt('logTrimAppDir')) {
                    $file = substr($file, strlen(self::opt('appDir')));
                }

                $log_tail .= ' [f:'.$file.']';
            }
            if (self::opt('logLinePosition')) {
                $log_tail .= ' [l:'.$line.']';
            }
        }

        // Make use of a PEAR_Log() instance
        if (self::opt('usePEARLogInstance') !== false) {
            self::opt('usePEARLogInstance')->log($str . $log_tail, $level);
            return true;
        }
        if (false !== ($cb = self::opt('useCustomLogHandler'))) {
            if (!is_callable($cb)) {
                throw new System_Daemon_Exception('Your "useCustomLogHandler" ' .
                    ' is not callable');
            }
            call_user_func($cb, $str . $log_tail, $level);
            return true;
        }

        // Save resources if arguments are passed.
        // But by falling back to debug_backtrace() it still works
        // if someone forgets to pass them.
        if (function_exists('debug_backtrace') && (!$file || !$line)) {
            $dbg_bt   = @debug_backtrace();
            $class    = (isset($dbg_bt[1]['class'])?$dbg_bt[1]['class']:'');
            $function = (isset($dbg_bt[1]['function'])?$dbg_bt[1]['function']:'');
            $file     = $dbg_bt[0]['file'];
            $line     = $dbg_bt[0]['line'];
        }

        // Determine what process the log is originating from and forge a logline
        //$str_ident = '@'.substr(self::_whatIAm(), 0, 1).'-'.posix_getpid();
        $str_date  = '[' . date('M d H:i:s') . ']';
        $str_level = str_pad(self::$_logLevels[$level] . '', 8, ' ', STR_PAD_LEFT);
        $log_line  = $str_date . ' ' . $str_level . ': ' . $str . $log_tail; // $str_ident

        $non_debug      = ($level < self::LOG_DEBUG);
        $log_succeeded = true;
        $log_echoed     = false;

        if (!self::isInBackground() && $non_debug && !$log_echoed) {
            // It's okay to echo if you're running as a foreground process.
            // Maybe the command to write an init.d file was issued.
            // In such a case it's important to echo failures to the
            // STDOUT
            echo $log_line . "\n";
            $log_echoed = true;
            // but still try to also log to file for future reference
        }

        if (!self::opt('logLocation')) {
            throw new System_Daemon_Exception('Either use PEAR Log or specify '.
                'a logLocation');
        }

        // 'Touch' logfile
        if (!file_exists(self::opt('logLocation'))) {
            file_put_contents(self::opt('logLocation'), '');
        }

        // Not writable even after touch? Allowed to echo again!!
        if (!is_writable(self::opt('logLocation'))
            && $non_debug && !$log_echoed
        ) {
            echo $log_line . "\n";
            $log_echoed    = true;
            $log_succeeded = false;
        }

        // Append to logfile
        $f = file_put_contents(
            self::opt('logLocation'),
            $log_line . "\n",
            FILE_APPEND
        );
        if (!$f) {
            $log_succeeded = false;
        }

        // These are pretty serious errors
        if ($level < self::LOG_ERR) {
            // An emergency logentry is reason for the deamon to
            // die immediately
            if ($level === self::LOG_EMERG) {
                self::_die();
            }
        }

        return $log_succeeded;
    }

    /**
     * Uses OS class to write an: 'init.d' script on the filesystem
     *
     * @param boolean $overwrite May the existing init.d file be overwritten?
     *
     * @return boolean
     */
    static public function writeAutoRun($overwrite=false)
    {
        // Init Options (needed for properties of init.d script)
        if (false === self::_optionsInit(false)) {
            return false;
        }

        // Init OS Object
        if (!self::_osObjSetup()) {
            return false;
        }

        // Get daemon properties
        $options = self::getOptions();

        // Try to write init.d
        $res = self::$_osObj->writeAutoRun($options, $overwrite);
        if (false === $res) {
            if (is_array(self::$_osObj->errors)) {
                foreach (self::$_osObj->errors as $error) {
                    self::notice($error);
                }
            }
            return self::warning('Unable to create startup file');
        }

        if ($res === true) {
            self::notice('Startup was already written');
            return true;
        } else {
            self::notice('Startup written to %s', $res);
        }

        return $res;
    }

    /**
     * Default signal handler.
     * You can overrule various signals with the
     * setSigHandler() method
     *
     * @param integer $signo The posix signal received.
     *
     * @return void
     * @see setSigHandler()
     * @see $_sigHandlers
     */
    static public function defaultSigHandler($signo)
    {
        // Must be public or else will throw a
        // fatal error: Call to protected method
        self::debug('Received signal: %s', $signo);

        switch ($signo) {
        case SIGTERM:
            // Handle shutdown tasks
            if (self::isInBackground()) {
                self::_die();
            } else {
                exit;
            }
            break;
        case SIGHUP:
            // Handle restart tasks
            self::debug('Received signal: restart');
            break;
        case SIGCHLD:
            // A child process has died
            self::debug('Received signal: child');
            while (pcntl_wait($status, WNOHANG OR WUNTRACED) > 0) {
                usleep(1000);
            }
            break;
        default:
            // Handle all other signals
            break;
        }
    }

    /**
     * Whether the class is already running in the background
     *
     * @return boolean
     */
    static public function isInBackground()
    {
        return self::$_processIsChild && self::isRunning();
    }

    /**
     * Whether the our daemon is being killed, you might
     * want to include this in your loop
     *
     * @return boolean
     */
    static public function isDying()
    {
        return self::$_isDying;
    }

    /**
     * Check if a previous process with same pidfile was already running
     *
     * @return boolean
     */
    static public function isRunning()
    {
        $appPidLocation = self::opt('appPidLocation');

        if (!file_exists($appPidLocation)) {
            unset($appPidLocation);
            return false;
        }

        $pid = self::fileread($appPidLocation);
        if (!$pid) {
            return false;
        }

        // Ping app
        if (!posix_kill(intval($pid), 0)) {
            // Not responding so unlink pidfile
            @unlink($appPidLocation);
            return self::warning(
                'Orphaned pidfile found and removed: ' .
                '{appPidLocation}. Previous process crashed?'
            );
        }

        return true;
    }



    /**
     * Put the running script in background
     *
     * @return void
     */
    static protected function _summon()
    {
        if (self::opt('usePEARLogInstance')) {
            $logLoc = '(PEAR Log)';
        } else if (self::opt('useCustomLogHandler')) {
            $logLoc = '(Custom log handler)';
        } else {
            $logLoc = self::opt('logLocation');
        }

        self::notice('Starting {appName} daemon, output in: %s', $logLoc);

        // Allowed?
        if (self::isRunning()) {
            return self::emerg('{appName} daemon is still running. Exiting');
        }

        // Reset Process Information
        self::$_safeMode       = !!@ini_get('safe_mode');
        self::$_processId      = 0;
        self::$_processIsChild = false;

        // Fork process!
        if (!self::_fork()) {
            return self::emerg('Unable to fork');
        }

        // Additional PID succeeded check
        if (!is_numeric(self::$_processId) || self::$_processId < 1) {
            return self::emerg('No valid pid: %s', self::$_processId);
        }

        // Change umask
        @umask(0);

        // Write pidfile
        $p = self::_writePid(self::opt('appPidLocation'), self::$_processId);
        if (false === $p) {
            return self::emerg('Unable to write pid file {appPidLocation}');
        }

        // Change identity. maybe
        $c = self::_changeIdentity(
            self::opt('appRunAsGID'),
            self::opt('appRunAsUID')
        );
        if (false === $c) {
            self::crit('Unable to change identity');
            if (self::opt('appDieOnIdentityCrisis')) {
                self::emerg('Cannot continue after this');
            }
        }

        // Important for daemons
        // See http://www.php.net/manual/en/function.pcntl-signal.php
        declare(ticks = 1);

        // Setup signal handlers
        // Handlers for individual signals can be overrulled with
        // setSigHandler()
        foreach (self::$_sigHandlers as $signal => $handler) {
            if (!is_callable($handler) && $handler != SIG_IGN && $handler != SIG_DFL) {
                return self::emerg(
                    'You want to assign signal %s to handler %s but ' .
                    'it\'s not callable',
                    $signal,
                    $handler
                );
            } else if (!pcntl_signal($signal, $handler)) {
                return self::emerg(
                    'Unable to reroute signal handler: %s',
                    $signal
                );
            }
        }

        // Change dir
        @chdir(self::opt('appDir'));

        return true;
    }

    /**
     * Determine whether pidfilelocation is valid
     *
     * @param string  $pidFilePath Pid location
     * @param boolean $log         Allow this function to log directly on error
     *
     * @return boolean
     */
    static protected function _isValidPidLocation($pidFilePath, $log = true)
    {
        if (empty($pidFilePath)) {
            return self::err(
                '{appName} daemon encountered an empty appPidLocation'
            );
        }

        $pidDirPath = dirname($pidFilePath);
        $parts      = explode('/', $pidDirPath);
        if (count($parts) <= 3 || end($parts) != self::opt('appName')) {
            // like: /var/run/x.pid
            return self::err(
                'Since version 0.6.3, the pidfile needs to be ' .
                'in it\'s own subdirectory like: %s/{appName}/{appName}.pid'
            );
        }

        return true;
    }

    /**
     * Creates pid dir and writes process id to pid file
     *
     * @param string  $pidFilePath PID File path
     * @param integer $pid         PID
     *
     * @return boolean
     */
    static protected function _writePid($pidFilePath = null, $pid = null)
    {
        if (empty($pid)) {
            return self::err('{appName} daemon encountered an empty PID');
        }

        if (!self::_isValidPidLocation($pidFilePath, true)) {
            return false;
        }

        $pidDirPath = dirname($pidFilePath);

        if (!self::_mkdirr($pidDirPath, 0755)) {
            return self::err('Unable to create directory: %s', $pidDirPath);
        }

        if (!file_put_contents($pidFilePath, $pid)) {
            return self::err('Unable to write pidfile: %s', $pidFilePath);
        }

        if (!chmod($pidFilePath, 0644)) {
            return self::err('Unable to chmod pidfile: %s', $pidFilePath);;
        }

        return true;
    }

    /**
     * Read a file. file_get_contents() leaks memory! (#18031 for more info)
     *
     * @param string $filepath
     *
     * @return string
     */
    static public function fileread ($filepath) {
        $f = fopen($filepath, 'r');
        if (!$f) {
            return false;
        }
        $data = fread($f, filesize($filepath));
        fclose($f);
        return $data;
    }

    /**
     * Recursive alternative to mkdir
     *
     * @param string  $dirPath Directory to create
     * @param integer $mode    Umask
     *
     * @return boolean
     */
    static protected function _mkdirr($dirPath, $mode)
    {
        is_dir(dirname($dirPath)) || self::_mkdirr(dirname($dirPath), $mode);
        return is_dir($dirPath) || @mkdir($dirPath, $mode);
    }

    /**
     * Change identity of process & resources if needed.
     *
     * @param integer $gid Group identifier (number)
     * @param integer $uid User identifier (number)
     *
     * @return boolean
     */
    static protected function _changeIdentity($gid = 0, $uid = 0)
    {
        // What files need to be chowned?
        $chownFiles = array();
        if (self::_isValidPidLocation(self::opt('appPidLocation'), true)) {
            $chownFiles[] = dirname(self::opt('appPidLocation'));
        }
        $chownFiles[] = self::opt('appPidLocation');
        if (!is_object(self::opt('usePEARLogInstance'))) {
            $chownFiles[] = self::opt('logLocation');
        }

        // Chown pid- & log file
        // We have to change owner in case of identity change.
        // This way we can modify the files even after we're not root anymore
        foreach ($chownFiles as $filePath) {
            if (!file_exists($filePath)) {
                continue;
            }
            
            // Change File GID
            $doGid = (filegroup($filePath) != $gid ? $gid : false);
            if (false !== $doGid && !@chgrp($filePath, intval($gid))) {
                return self::err(
                    'Unable to change group of file %s to %s',
                    $filePath,
                    $gid
                );
            }

            // Change File UID
            $doUid = (fileowner($filePath) != $uid ? $uid : false);
            if (false !== $doUid && !@chown($filePath, intval($uid))) {
                return self::err(
                    'Unable to change user of file %s to %s',
                    $filePath,
                    $uid
                );
            }

			// Export correct homedir
			if (($info = posix_getpwuid($uid)) && is_dir($info['dir'])) {
				system('export HOME="' . $info['dir'] . '"');
			}
        }

        // Change Process GID
        $doGid = (posix_getgid() !== $gid ? $gid : false);
        if (false !== $doGid && !@posix_setgid($gid)) {
            return self::err('Unable to change group of process to %s', $gid);
        }

        // Change Process UID
        $doUid = (posix_getuid() !== $uid ? $uid : false);
        if (false !== $doUid && !@posix_setuid($uid)) {
            return self::err('Unable to change user of process to %s', $uid);
        }

        $group = posix_getgrgid($gid);
        $user  = posix_getpwuid($uid);

        return self::info(
            'Changed identify to %s:%s',
            $group['name'],
            $user['name']
        );
    }

    /**
     * Fork process and kill parent process, the heart of the 'daemonization'
     *
     * @return boolean
     */
    static protected function _fork()
    {
        self::debug('forking {appName} daemon');
        $pid = pcntl_fork();
        if ($pid === -1) {
            // Error
            return self::warning('Process could not be forked');
        } else if ($pid) {
            // Parent
            self::debug('Ending {appName} parent process');
            // Die without attracting attention
            exit();
        } else {
            // Child
            self::$_processIsChild = true;
            self::$_isDying        = false;
            self::$_processId      = posix_getpid();
            return true;
        }
    }

    /**
     * Return what the current process is: child or parent
     *
     * @return string
     */
    static protected function _whatIAm()
    {
        return (self::isInBackground() ? 'child' : 'parent');
    }

    /**
     * Sytem_Daemon::_die()
     * Kill the daemon
     * Keep this function as independent from complex logic as possible
     *
     * @param boolean $restart Whether to restart after die
     *
     * @return void
     */
    static protected function _die($restart = false)
    {
        if (self::isDying()) {
            return null;
        }

        self::$_isDying = true;
        // Following caused a bug if pid couldn't be written because of
        // privileges
        // || !file_exists(self::opt('appPidLocation'))
        if (!self::isInBackground()) {
            self::info(
                'Process was not daemonized yet, ' .
                'just halting current process'
            );
            die();
        }

        $pid = file_get_contents(
            System_Daemon::getOption('appPidLocation')
        );
        @unlink(self::opt('appPidLocation'));

        if ($restart) {
            // So instead we should:
            die(exec(join(' ', $GLOBALS['argv']) . ' > /dev/null &'));
        } else {
            passthru('kill -9 ' . $pid);
            die();
        }
    }


    /**
     * Sets up OS instance
     *
     * @return boolean
     */
    static protected function _osObjSetup()
    {
        // Create Option Object if nescessary
        if (!self::$_osObj) {
            self::$_osObj = System_Daemon_OS::factory();
        }

        // Still false? This was an error!
        if (!self::$_osObj) {
            return self::emerg('Unable to setup OS object');
        }

        return true;
    }

    /**
     * Sets up Option Object instance
     *
     * @return boolean
     */
    static protected function _optionObjSetup()
    {
        // Create Option Object if nescessary
        if (!self::$_optObj) {
            self::$_optObj = new System_Daemon_Options(self::$_optionDefinitions);
        }

        // Still false? This was an error!
        if (!self::$_optObj) {
            return self::emerg('Unable to setup Options object. ');
        }

        return true;
    }

    /**
     * Checks if all the required options are set.
     * Initializes, sanitizes & defaults unset variables
     *
     * @param boolean $premature Whether to do a premature option init
     *
     * @return mixed integer or boolean
     */
    static protected function _optionsInit($premature=false)
    {
        if (!self::_optionObjSetup()) {
            return false;
        }

        return self::$_optObj->init($premature);
    }
}