Suite à mon document sur la création de serveurs en PHP, peut-être certains d’entre vous ont dû se poser la question : comment « daemonizer » un script PHP ? En clair, comment faire en sorte qu’il fonctionne en arrière-plan sans user de saletés telles que nohup, comme un véritable serveur tel que Apache, MySQL, Samba, etc ?

Il est clair que la documentation de PHP manque de précision sur ce point, ce qui m’a forcé à aller récupérer quelques morceaux de réponse un peu partout et même à m’inspirer de programmes écrits en C ou en Perl. Je vais récapituler dans ce billet ce que j’ai appris.

Pour « daemonizer » un script PHP, il faut 3 étapes bien distinctes :

  • Le script doit se relancer lui-même en redirigeant tous les flux (STDIN, STDOUT, STDERR) vers /dev/null. Faute de quoi la sortie du script sera envoyée sur votre terminal. De plus, un problème avec OpenSSH se produit également lorsqu’on ne le fait pas.
  • Le processus doit être « forké ». En d’autres termes, on va faire une copie à l’identique du processus. La seule différence est que cette copie sera orpheline, c’est à dire qu’elle sera enfant direct de INIT (le processus racine du système), et non du terminal à partir duquel le démon a été lancé. On fermera ensuite le processus original. Si le fork n’st pas fait, l’administrateur se verra contraint de faire CTRL + C pour détacher le démon, ce qui n’est pas problématique (le processus ne sera pas fermé), mais peu esthétique.
  • Le processus doit être transformé en « chef de session ». Cela n’est pas réellement nécéssaire mais plus propre : ça évitera que le processus ne soit encore rattaché au terminal qui l’a lancé.

Pour programmer ces trois points, nous aurons besoin de 3 fonctions (respectivement) : proc_open(), pcntl_fork() et posix_setsid().

Voici le code :

// On se place dans le même répertoire que le script
chdir(dirname(__FILE__));

// On vérifie que l'extension PCNTL est bien là
if (!is_callable('pcntl_fork'))
{
	die('Error : PCNTL extension not available, recompile PHP with --enable-pcntl'."\n");
}

// Si cela n'est pas déjà été fait...
if (!in_array('/redirected', $argv))
{
	// ... on relance le script en redirigeant les flux

	$desc = array(
		0 => array('file', '/dev/null', 'r'),
		1 => array('file', '/dev/null', 'a'),
		2 => array('file', '/dev/null', 'a')
	);
	// Note : on peut améliorer ceci en repassant les paramètres de ligne de commande
	proc_open('php -f '.basename(__FILE__).' /redirected', $desc, $null);

	// Puis on ferme le processus parent
	die();
}

// On tente de forker
$pid = pcntl_fork();

if ($pid == -1)
{
	// Erreur lors du fork
	die('Error: cannot fork'."\n");
}

if ($pid)
{
	// Fork réalisé : on ferme le processus original
	die();
}
else
{
	// Fork déjà réalisé : on ne fait rien

}

// On devient maître de session
posix_setsid();

// Let's go !

Vous pouvez coder tout ce que vous voulez à la suite, ce sera « daemonizé ».