Voilà un nouveau chapitre à mon aventure d’administrateur réseau-système qui vient de s’écrire, j’ai fait mon premier daemon en perl.

Pourquoi faire un daemon, tout simplement pour avoir un traitement qui est lancé de façon récurrente sur un ordinateur (sous unix/linux), et dont la fréquence d’exécution est trop importante pour tourner sur une crontab.

On va dans ce cas faire un programme avec une belle boucle infinie dans laquelle on va faire le traitement avec un sleep en début ou en fin de traitement pour laisser le temps au système d’exécuter d’autres applications/processus.
Dans mon cas, l’objectif est de déplacer des fichiers entre plusieurs couples de 2 répertoires après un scan par un antivirus. Le but est de permettre de récupérer de façon sécurisée des fichiers en provenance d’internet sur les VLANs n’ayant pas accès à internet.

Le répertoire d’entrée est mis à jour par FTP (en LAN) donc avec des débits suffisamment important et peu de risque de déconnexions intempestives (un check sommaire de la taille de fichier à quelques secondes d’intervalles suffit donc).

Un script existe et rempli actuellement cette tâche, mais plusieurs points sont à revoir :

  • il ne fonctionne que sur un seul et unique couple de répertoire (on m’a demandé d’ajouter des couples de répertoire),
  • il est lancé dans un shell, connecté en root, ouvert en permanence via screen, en cas de reboot de la machine, il faut relancer le script manuellement,
  • plusieurs paramètres sont « hardcodés » dans le script et ne sont pas forcément très lisibles,

Me voilà donc parti avec mon fidèle compagnon (j’ai nommé Google) vers les joies de la réalisation d’un daemon en Perl.

La première étape consiste à utiliser un fichier de configuration afin de pouvoir séparer ce qui est de la configuration pure du reste du script (le chemin vers le binaire de l’antivirus et ses paramètres, les couples de répertoires, s’il faut ou non activer le logging, le chemin vers le logging, etc. …).

Quelques minutes de recherche plus loin, j’en arrive à la conclusion que le module AppConfig est ce dont j’ai besoin.

use strict;
use warnings;
use File::Copy;
use AppConfig qw(:expand :argcount);

Le paramètre argcount permet de récupérer des listes pour un même paramètre, ce qui est intéressant dans mon cas, on utilisera le même nom de variable pour les couples de répertoires.

On va donc créer un fichier de configuration et y mettre le contenu suivant :

#Configuration générale du daemon
#Logging
# 1 - Actif
# 0 - Inactif
logging = 1

# Les 2 paramètres ci-dessous ne sont à modifier que si votre configuration est différente

#Chemin du log
#Le fichier sera logFilePath/playground_scan.log
logFilePath = /var/log
#Chemin du PID
#Le fichier sera pidFilePath/playground_scan.pid
#Ne pas oublier de modifier le script présent dans init.d si vous changez ce paramètre
pidFilePath = /var/run

#Commande pour le scanner
scan = /chemin/du/scanner/av
scanParam = "--parametres=optionnels"

#Couple de repertoires à scanner
#ils doivent être sous la forme :
# InsecureDir:SecureDir
repertoire=/temp/test1:/temp2/test2
repertoire=/temp/test2:/temp3/test3

Le fichier de configuration étant crée, il n’y a plus qu’à aller lire celui-ci.

my $config = AppConfig->new("scan"       => {ARGCOUNT => ARGCOUNT_ONE},
                            "scanParam"  => {ARGCOUNT => ARGCOUNT_ONE},
                            "logging"    => {ARGCOUNT => ARGCOUNT_ONE},
                            "logFilePath"=> {ARGCOUNT => ARGCOUNT_ONE},
                            "pidFilePath"=> {ARGCOUNT => ARGCOUNT_ONE},
                            "repertoire" => {ARGCOUNT => ARGCOUNT_LIST});

#Lecture du fichier de configuration
$config->file('/etc/playground_scan.conf');

Une fois le fichier de configuration lu et chargé en mémoire, on y accède simplement via un $config->clefConfig(); comme vous pouvez le voir dans l’exemple ci-dessous.

#Initialisation des paramètres du daemon
my $logging       = $config->logging();                     # 1= logging is on
my $logFilePath   = $config->logFilePath();                 # log file path
my $logFile       = $logFilePath . "/" . $daemonName . ".log";
my $pidFilePath   = $config->pidFilePath();                 # PID file path
my $pidFile       = $pidFilePath . "/" . $daemonName . ".pid";

Je fais encore une parenthèse sur le fonctionnement de ce module concernant la liste de répertoire qu’on va récupérer dans $config->repertoire();, cette liste se présente simplement sous la forme d’un tableau que l’on va parcourir (dans mon cas via un simple foreach).

my $confReps = $config->repertoire();

foreach $confDirs ( @$confReps )
  {
  #Code ici
  }

Première objectif atteint : permettre une plus grande flexibilité de configuration.

Reprenons Google pour trouver des informations sur le 2ème objectif, à savoir la daemonisation.

J’ai trouvé un chouette article sur un blog avec un beau squelette en perl pour faire un daemon article que je vais m’efforcer de traduire ici.

Tout d’abord on copie le squelette init.d de façon à créer le script qui servira à lancer le daemon.

sudo cp /etc/init.d/skeleton /etc/init.d/playground_scan
sudo chmod +x /etc/init.d/playground_scan

Ensuite, on édite le fichier /etc/init.d/playground_scan

sudo vi /etc/init.d/playground_scan

Et on y modifie les lignes suivantes afin d’être en accord avec les modifications précédentes. Vérifiez bien que la valeur de PIDFILE correspond à celle renseignée dans la configuration.

PATH=/sbin:/usr/sbin:/bin:/usr/bin
DESC="Description of the service"
NAME=playground_scan
DAEMON=/usr/bin/$NAME
DAEMON_ARGS=""
PIDFILE=/var/run/$NAME.pid
SCRIPTNAME=/etc/init.d/$NAME

Puis le template du daemon Perl en lui même, à copier dans le fichier /usr/sbin/playground_scan (disponible en fin de page), n’oubliez pas d’allez mettre votre code metier en dessous de la ligne :

	sleep($sleepMainLoop);

Il faut bien entendu un peu d’adaptation au squelette pour tourner avec le fichier de configuration et non plus avec ses paramètres pré-enregistrés et ne pas oublier de faire un :

sudo chmod +x /usr/bin/mydaemon

Ensuite, on va créer les fichiers de logs :

sudo touch /var/log/playground_scan.log
sudo chmod 640 /var/log/playground_scan.log
sudo chown root:adm /var/log/playground_scan.log

Il ne reste plus qu’à ajouter le script dans la séquence de démarrage automatique

update-rc.d playground_scan defaults 99

Si certains modules sont manquants il peut vous être nécessaire de lancer les commandes suivantes pour les installer.

sudo cpan POSIX
sudo cpan File::Pid
sudo cpan AppConfig

Note : pour ceux qui voudraient mon script tout fait, j’en fait un .tar.gz et je vous le mets à disposition sous peu.

Sources : pour la partie daemon : http://www.andrewault.net/2010/05/27/creating-a-perl-daemon-in-ubuntu/
Pour la partie configuration : http://chl.be/glmf/articles.linuxmag-france.org/lm32/perlconf.html

#!/usr/bin/perl -w
#
# mydaemon.pl by Andrew Ault, www.andrewault.net
#
# Free software. Use this as you wish.
#
# Throughout this template "mydaemon" is used where the name of your daemon should
# be, replace occurrences of "mydaemon" with the name of your daemon.
#
# This name will also be the exact name to give this file (WITHOUT a ".pl" extension).
#
# It is also the exact name to give the start-stop script that will go into the
# /etc/init.d/ directory.
#
# It is also the name of the log file in the /var/log/ directory WITH a ".log"
# file extension.
#
# Replace "# do something" with your super useful code.
#
# Use "# logEntry("log something");" to log whatever your need to see in the log.
#
use strict;
use warnings;
use POSIX;
use File::Pid;

# make "mydaemon.log" file in /var/log/ with "chown root:adm mydaemon"

# TODO: change "mydaemon" to the exact name of your daemon.
my $daemonName    = "mydaemon";
#
my $dieNow        = 0;                                     # used for "infinte loop" construct - allows daemon mode to gracefully exit
my $sleepMainLoop = 120;                                    # number of seconds to wait between "do something" execution after queue is clear
my $logging       = 1;                                     # 1= logging is on
my $logFilePath   = "/var/log/";                           # log file path
my $logFile       = $logFilePath . $daemonName . ".log";
my $pidFilePath   = "/var/run/";                           # PID file path
my $pidFile       = $pidFilePath . $daemonName . ".pid";

# daemonize
use POSIX qw(setsid);
chdir '/';
umask 0;
open STDIN,  '/dev/null'   or die "Can't read /dev/null: $!";
open STDOUT, '>>/dev/null' or die "Can't write to /dev/null: $!";
open STDERR, '>>/dev/null' or die "Can't write to /dev/null: $!";
defined( my $pid = fork ) or die "Can't fork: $!";
exit if $pid;

# dissociate this process from the controlling terminal that started it and stop being part
# of whatever process group this process was a part of.
POSIX::setsid() or die "Can't start a new session.";

# callback signal handler for signals.
$SIG{INT} = $SIG{TERM} = $SIG{HUP} = \&signalHandler;
$SIG{PIPE} = 'ignore';

# create pid file in /var/run/
my $pidfile = File::Pid->new( { file => $pidFile, } );

$pidfile->write or die "Can't write PID file, /dev/null: $!";

# turn on logging
if ($logging) {
	open LOG, ">>$logFile";
	select((select(LOG), $|=1)[0]); # make the log file "hot" - turn off buffering
}

# "infinite" loop where some useful process happens
until ($dieNow) {
	sleep($sleepMainLoop);

	# TODO: put your custom code here!
	# do something

	# logEntry("log something"); # use this to log whatever you need to
}

# add a line to the log file
sub logEntry {
	my ($logText) = @_;
	my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime(time);
	my $dateTime = sprintf "%4d-%02d-%02d %02d:%02d:%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec;
	if ($logging) {
		print LOG "$dateTime $logText\n";
	}
}

# catch signals and end the program if one is caught.
sub signalHandler {
	$dieNow = 1;    # this will cause the "infinite loop" to exit
}

# do this stuff when exit() is called.
END {
	if ($logging) { close LOG }
	$pidfile->remove if defined $pidfile;
}
5 Comments

5 responses to Perl – Réaliser un daemon

  1. Pingback: Creating a Perl Daemon in Ubuntu « andrewault.net

  2. Topologie réseau on octobre 18, 2011 at 22:41

    Bien joué. Pas trop de ressources consommées?

  3. Marie on octobre 19, 2011 at 09:06

    Merci. Je m’en vais essayer ça tout de suite

  4. Duncane on octobre 19, 2011 at 13:30

    Sur le serveur concerné non, j’ai plus les graphs d’occupation des ressources en tête, mais de mémoire c’était quelque chose de tout à fait raisonnable.

  5. Duncane on octobre 19, 2011 at 13:30

    De rien 🙂

Laisser un commentaire