| 
<?php
/**
 * @package binarychoice.system.unix
 * @since 1.0.3
 */
 
 // Log message levels
 define('DLOG_TO_CONSOLE', 1);
 define('DLOG_NOTICE', 2);
 define('DLOG_WARNING', 4);
 define('DLOG_ERROR', 8);
 define('DLOG_CRITICAL', 16);
 
 /**
 * Daemon base class
 *
 * Requirements:
 * Unix like operating system
 * PHP 4 >= 4.3.0 or PHP 5
 * PHP compiled with:
 * --enable-sigchild
 * --enable-pcntl
 *
 * @package binarychoice.system.unix
 * @author Michal 'Seth' Golebiowski <seth at binarychoice dot pl>
 * @copyright Copyright 2005 Seth
 * @since 1.0.3
 */
 class Daemon
 {
 /**#@+
 * @access public
 */
 /**
 * User ID
 *
 * @var int
 * @since 1.0
 */
 var $userID = 99;
 
 /**
 * Group ID
 *
 * @var integer
 * @since 1.0
 */
 var $groupID = 99;
 
 /**
 * Terminate daemon when set identity failure ?
 *
 * @var bool
 * @since 1.0.3
 */
 var $requireSetIdentity = false;
 
 /**
 * Path to PID file
 *
 * @var string
 * @since 1.0.1
 */
 var $pidFileLocation = '/tmp/daemon.pid';
 
 /**
 * Home path
 *
 * @var string
 * @since 1.0
 */
 var $homePath = '/';
 /**#@-*/
 
 
 /**#@+
 * @access protected
 */
 /**
 * Current process ID
 *
 * @var int
 * @since 1.0
 */
 var $_pid = 0;
 
 /**
 * Is this process a children
 *
 * @var boolean
 * @since 1.0
 */
 var $_isChildren = false;
 
 /**
 * Is daemon running
 *
 * @var boolean
 * @since 1.0
 */
 var $_isRunning = false;
 /**#@-*/
 
 
 /**
 * Constructor
 *
 * @access public
 * @since 1.0
 * @return void
 */
 function Daemon()
 {
 error_reporting(0);
 set_time_limit(0);
 ob_implicit_flush();
 
 register_shutdown_function(array(&$this, 'releaseDaemon'));
 }
 
 /**
 * Starts daemon
 *
 * @access public
 * @since 1.0
 * @return bool
 */
 function start()
 {
 $this->_logMessage('Starting daemon');
 
 if (!$this->_daemonize())
 {
 $this->_logMessage('Could not start daemon', DLOG_ERROR);
 
 return false;
 }
 
 
 $this->_logMessage('Running...');
 
 $this->_isRunning = true;
 
 
 while ($this->_isRunning)
 {
 $this->_doTask();
 }
 
 return true;
 }
 
 /**
 * Stops daemon
 *
 * @access public
 * @since 1.0
 * @return void
 */
 function stop()
 {
 $this->_logMessage('Stoping daemon');
 
 $this->_isRunning = false;
 }
 
 /**
 * Do task
 *
 * @access protected
 * @since 1.0
 * @return void
 */
 function _doTask()
 {
 // override this method
 }
 
 /**
 * Logs message
 *
 * @access protected
 * @since 1.0
 * @return void
 */
 function _logMessage($msg, $level = DLOG_NOTICE)
 {
 // override this method
 }
 
 /**
 * Daemonize
 *
 * Several rules or characteristics that most daemons possess:
 * 1) Check is daemon already running
 * 2) Fork child process
 * 3) Sets identity
 * 4) Make current process a session laeder
 * 5) Write process ID to file
 * 6) Change home path
 * 7) umask(0)
 *
 * @access private
 * @since 1.0
 * @return void
 */
 function _daemonize()
 {
 ob_end_flush();
 
 if ($this->_isDaemonRunning())
 {
 // Deamon is already running. Exiting
 return false;
 }
 
 if (!$this->_fork())
 {
 // Coudn't fork. Exiting.
 return false;
 }
 
 if (!$this->_setIdentity() && $this->requireSetIdentity)
 {
 // Required identity set failed. Exiting
 return false;
 }
 
 if (!posix_setsid())
 {
 $this->_logMessage('Could not make the current process a session leader', DLOG_ERROR);
 
 return false;
 }
 
 if (!$fp = @fopen($this->pidFileLocation, 'w'))
 {
 $this->_logMessage('Could not write to PID file', DLOG_ERROR);
 
 return false;
 }
 else
 {
 fputs($fp, $this->_pid);
 fclose($fp);
 }
 
 @chdir($this->homePath);
 umask(0);
 
 declare(ticks = 1);
 
 pcntl_signal(SIGCHLD, array(&$this, 'sigHandler'));
 pcntl_signal(SIGTERM, array(&$this, 'sigHandler'));
 
 return true;
 }
 
 /**
 * Cheks is daemon already running
 *
 * @access private
 * @since 1.0.3
 * @return bool
 */
 function _isDaemonRunning()
 {
 $oldPid = @file_get_contents($this->pidFileLocation);
 
 if ($oldPid !== false && posix_kill(trim($oldPid),0))
 {
 $this->_logMessage('Daemon already running with PID: '.$oldPid, (DLOG_TO_CONSOLE | DLOG_ERROR));
 
 return true;
 }
 else
 {
 return false;
 }
 }
 
 /**
 * Forks process
 *
 * @access private
 * @since 1.0
 * @return bool
 */
 function _fork()
 {
 $this->_logMessage('Forking...');
 
 $pid = pcntl_fork();
 
 if ($pid == -1) // error
 {
 $this->_logMessage('Could not fork', DLOG_ERROR);
 
 return false;
 }
 else if ($pid) // parent
 {
 $this->_logMessage('Killing parent');
 
 exit();
 }
 else // children
 {
 $this->_isChildren = true;
 $this->_pid = posix_getpid();
 
 return true;
 }
 }
 
 /**
 * Sets identity of a daemon and returns result
 *
 * @access private
 * @since 1.0
 * @return bool
 */
 function _setIdentity()
 {
 if (!posix_setgid($this->groupID) || !posix_setuid($this->userID))
 {
 $this->_logMessage('Could not set identity', DLOG_WARNING);
 
 return false;
 }
 else
 {
 return true;
 }
 }
 
 /**
 * Signals handler
 *
 * @access public
 * @since 1.0
 * @return void
 */
 function sigHandler($sigNo)
 {
 switch ($sigNo)
 {
 case SIGTERM:   // Shutdown
 $this->_logMessage('Shutdown signal');
 exit();
 break;
 
 case SIGCHLD:   // Halt
 $this->_logMessage('Halt signal');
 while (pcntl_waitpid(-1, $status, WNOHANG) > 0);
 break;
 }
 }
 
 /**
 * Releases daemon pid file
 * This method is called on exit (destructor like)
 *
 * @access public
 * @since 1.0
 * @return void
 */
 function releaseDaemon()
 {
 if ($this->_isChildren && file_exists($this->pidFileLocation))
 {
 $this->_logMessage('Releasing daemon');
 
 unlink($this->pidFileLocation);
 }
 }
 }
 ?>
 |