<?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);
}
}
}
?>
|