*/ require_once ("domframework/fork.php"); require_once ("domframework/file.php"); /** Manage a daemon. * Allow to start, stop, get status of a callable method */ class daemon { /** Set the directory to store the PID of the daemon */ private $runDir = "/var/run"; /** The constructor check if /proc is mounted */ public function __construct () { if (! file_exists ("/proc")) throw new \Exception ("Can't manage daemon: /proc doesn't exists", 500); } /** Get/set the directory to store the PID of the daemon * @param string|null $val The directory */ public function runDir ($val = null) { if ($val === null) return $this->runDir; if (! is_string ($val)) throw new \Exception ("Can not set daemon runDir: not a string", 500); $this->runDir = "$val"; } /** Start the callable method. The terminal is closed. All the displayed * messages from the child are silently dropped. * If some parameters are provided, the called child method will receive them * This function fork and return the child PID * @param string $name The name of daemon to start. * @param callable $callable The callback method to use in child * @param mixed|null $params The params to provide to child method * @return The child PID */ public function start ($name, $callable, $params = array ()) { $file = new \file (); if (! $file->is_writeable ($this->runDir)) throw new \Exception (sprintf ("Run Directory '%s' is not writeable", $this->runDir), 500); if (! $file->is_readable ($this->runDir)) throw new \Exception (sprintf ("Run Directory '%s' is not readable", $this->runDir), 500); if ($file->file_exists ($this->runDir."/$name.pid")) { $pid = trim ($file->file_get_contents ($this->runDir."/$name.pid")); if (file_exists ("/proc/$pid")) throw new \Exception (sprintf ( "Can't start the daemon: already running with PID %d", $pid), 500); } $fork = new \fork (); $pid = $fork->startDetachedChild ($name, $callable, $params); file_put_contents ($this->runDir."/$name.pid", $pid); return $pid; } /** Stop a $name daemon * @param string $name The name of daemon to stop. The name must be the same * as the name used in the start method * @param integer $maxWaitStop The waiting maximum time after SIGTERM before * sending SIGKILL * @param integer $maxWaitKill The waiting maximum time after SIGKILL before * raising an error */ public function stop ($name, $maxWaitStop = 3, $maxWaitKill = 3) { $file = new \file (); if (! $file->is_writeable ($this->runDir)) throw new \Exception (sprintf ("Run Directory '%s' is not writeable", $this->runDir), 500); if (! $file->is_readable ($this->runDir)) throw new \Exception (sprintf ("Run Directory '%s' is not readable", $this->runDir), 500); if (! $file->file_exists ($this->runDir."/$name.pid")) throw new \Exception (sprintf ( "PID file %s not found : can't stop the daemon", $this->runDir."/$name.pid"), 500); $pid = $file->file_get_contents ($this->runDir."/$name.pid"); if (! file_exists ("/proc/$pid")) { $file->unlink ($this->runDir."/$name.pid"); throw new \Exception ( "Can't stop daemon $name. There is no process with the PID $pid", 500); } posix_kill ($pid, SIGTERM); usleep (10000); $startWait = time (); while (time () < $startWait + $maxWaitStop) { // If the child is zombie and is connected to me, pcntl_waitpid allow it // to be destroyed to close correctely the parent pcntl_waitpid ($pid, $status, WNOHANG); if (! file_exists ("/proc/$pid")) { $file->unlink ($this->runDir."/$name.pid"); return true; } usleep (100000); echo "."; } posix_kill ($pid, SIGKILL); $startWait = time (); while (time () < $startWait + $maxWaitKill) { if (! file_exists ("/proc/$pid")) { $file->unlink ($this->runDir."/$name.pid"); return true; } usleep (100000); echo "X"; } throw new \Exception ("Can't stop and can't kill the process $pid", 500); } /** Status of a daemon * @param string $name The name of daemon to have status. The name must be * the same as the name used in the start method */ public function status ($name) { $file = new \file (); if (! $file->is_writeable ($this->runDir)) throw new \Exception (sprintf ("Run Directory '%s' is not writeable", $this->runDir), 500); if (! $file->is_readable ($this->runDir)) throw new \Exception (sprintf ("Run Directory '%s' is not readable", $this->runDir), 500); if (! $file->file_exists ($this->runDir."/$name.pid")) return "The daemon is be stopped"; $pid = $file->file_get_contents ($this->runDir."/$name.pid"); if (! file_exists ("/proc/$pid")) { $file->unlink ($this->runDir."/$name.pid"); return "The daemon is stopped, but the PID file was remaining. Delete"; } return "The daemon is running on PID $pid\n"; } /** Send a HUP Signal to the daemon * @param string $name The name of daemon to HUP. The name must be * the same as the name used in the start method */ public function reload ($name) { $file = new \file (); if (! $file->is_writeable ($this->runDir)) throw new \Exception (sprintf ("Run Directory '%s' is not writeable", $this->runDir), 500); if (! $file->is_readable ($this->runDir)) throw new \Exception (sprintf ("Run Directory '%s' is not readable", $this->runDir), 500); if (! $file->file_exists ($this->runDir."/$name.pid")) return "The daemon is be stopped"; $pid = $file->file_get_contents ($this->runDir."/$name.pid"); if (! file_exists ("/proc/$pid")) { $file->unlink ($this->runDir."/$name.pid"); return "The daemon is stopped, but the PID file was remaining. Delete"; } return posix_kill ($pid, SIGHUP); } }