Skip to content

Instantly share code, notes, and snippets.

@mhamlet
Created June 9, 2012 04:28
Show Gist options
  • Save mhamlet/2899474 to your computer and use it in GitHub Desktop.
Save mhamlet/2899474 to your computer and use it in GitHub Desktop.

Revisions

  1. Hamlet created this gist Jun 9, 2012.
    236 changes: 236 additions & 0 deletions daemon.php
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,236 @@
    <?php
    /*
    Author: Petr Bondarenko
    E-mail: [email protected]
    Date: 31 May 2012
    License: BSD
    Description: Class for create UNIX-daemon
    */

    class DaemonException extends Exception {}

    /**
    * Класс для реализации UNIX-демонов
    * @author Petr Bondarenko
    * @copyright Petr Bondarenko 2012
    * @version 1.0
    * @abstract
    */
    abstract class DaemonPHP {

    protected $_baseDir;
    protected $_chrootDir = null;
    protected $_pid;
    protected $_log;
    protected $_err;

    /**
    * Конструктор класса. Принимает путь к pid-файлу
    * @param string $path Абсолютный путь к PID-файлу
    * @access public
    */
    public function __construct($path=null) {
    $this->_baseDir = dirname(__FILE__);
    $this->_log = $this->_baseDir . '/daemon-php.log';
    $this->_err = $this->_baseDir . '/daemon-php.err';
    if ($path === null) {
    $this->_pid = $this->_baseDir . '/daemon-php.pid';
    } else {
    $this->_pid = $path;
    }
    }

    /**
    * Метод устанавливает путь log-файла
    * @param string $path Абсолютный путь к log-файлу
    * @return DaemonPHP
    * @access public
    */
    final public function setLog($path) {
    $this->_log = $path;
    return $this;
    }

    /**
    * Метод устанавливает путь err-файла
    * @param string $path Абсолютный путь к err-файлу
    * @return DaemonPHP
    * @access public
    */
    final public function setErr($path) {
    $this->_err = $path;
    return $this;
    }

    /**
    * Метод позволяет установить директорию,
    * в которую будет выполнен chroot после старта демона.
    * Данный метод служит для решения проблем безопасности.
    * @param string $path Абсолютный путь chroot-директории
    * @throws DaemonException
    * @access public
    */
    final public function setChroot($path) {
    if (!function_exists('chroot')) {
    throw new DaemonException('Function chroot() has no. Please update you PHP version.');
    }
    $this->_chrootDir = $path;
    return $this;
    }

    /**
    * Метод выполняет демонизацию процесса, через double fork
    * @throws DaemonException
    * @access protected
    */
    final protected function demonize() {
    $pid = pcntl_fork();
    if ($pid == -1) {
    throw new DaemonException('Not fork process!');
    } else if ($pid) {
    exit(0);
    }

    posix_setsid();
    chdir('/');

    $pid = pcntl_fork();
    if ($pid == -1) {
    throw new DaemonException('Not double fork process!');
    } else if ($pid) {
    $fpid = fopen($this->_pid, 'wb');
    fwrite($fpid, $pid);
    fclose($fpid);
    exit(0);
    }

    posix_setsid();
    chdir('/');
    ini_set('error_log', $this->_baseDir . '/php_error.log');

    fclose(STDIN);
    fclose(STDOUT);
    fclose(STDERR);
    $STDIN = fopen('/dev/null', 'r');

    if ($this->_chrootDir !== null) {
    chroot($this->_chrootDir);
    }

    $STDOUT = fopen($this->_log, 'ab');
    if (!is_writable($this->_log))
    throw new DaemonException('LOG-file is not writable!');
    $STDERR = fopen($this->_err, 'ab');
    if (!is_writable($this->_err))
    throw new DaemonException('ERR-file is not writable!');
    $this->run();
    }

    /**
    * Метод возвращает PID процесса
    * @return int PID процесса либо 0
    * @access protected
    */
    final protected function getPID() {
    if (file_exists($this->_pid)) {
    $pid = (int) file_get_contents($this->_pid);
    if (posix_kill($pid, SIG_DFL)) {
    return $pid;
    } else {
    //Если демон не откликается, а PID-файл существует
    unlink($this->_pid);
    return 0;
    }
    } else {
    return 0;
    }
    }

    /**
    * Метод стартует работу и вызывает метод demonize()
    * @access public
    */
    final public function start() {
    if (($pid = $this->getPID()) > 0) {
    echo "Process is running on PID: " . $pid . PHP_EOL;
    } else {
    echo "Starting..." . PHP_EOL;
    $this->demonize();
    }
    }

    /**
    * Метод останавливает демон
    * @access public
    */
    final public function stop() {
    if (($pid = $this->getPID()) > 0) {
    echo "Stopping ... ";
    posix_kill($pid, SIGTERM);
    unlink($this->_pid);
    echo "OK" . PHP_EOL;
    } else {
    echo "Process not running!" . PHP_EOL;
    }
    }

    /**
    * Метод рестартует демон последовательно вызвав stop() и start()
    * @see start()
    * @see stop()
    * @access public
    */
    final public function restart() {
    $this->stop();
    $this->start();
    }

    /**
    * Метод проверяет работу демона
    * @access public
    */
    final public function status() {
    if (($pid = $this->getPID()) > 0) {
    echo "Process is running on PID: " . $pid . PHP_EOL;
    } else {
    echo "Process not running!" . PHP_EOL;
    }
    }

    /**
    * Метод обрабатывает аргументы командной строки
    * @param array $argv Массив с аргументами коммандной строки
    * @access public
    */
    final public function handle($argv) {
    switch ($argv[1]) {
    case 'start':
    $this->start();
    break;
    case 'stop':
    $this->stop();
    break;
    case 'restart':
    $this->restart();
    break;
    case 'status':
    $this->status();
    break;
    case '':

    default:
    echo "Unknown command!" . PHP_EOL .
    "Use: " . $argv[0] . " start|stop|restart|status" . PHP_EOL;
    break;
    }
    }

    /**
    * Основной класс демона, в котором выполняется работа.
    * Его необходимо переопределить
    * @access public
    * @abstract
    */
    abstract public function run();
    }
    ?>