Jouons avec kvm

J’ai codé un petit programme tout simple qui fait clignoter les leds de mon routeur dès qu’un service est par terre. Ma méthode (qui ne doit pas être la meilleure mais je m’en fout un peu) consiste à regarder régulièrement la liste des processus et détecter l’arrêt d’un service quand le processus associé n’existe plus. J’ai commencé avec un script shell, puis étant assez mauvais pour tout ce qui ressemble de près ou de loin à du script j’ai décidé de le faire en C (langage d’excellence n’est ce pas…?).

Je me suis volontairement compliqué la tache en utilisant des fonctions très proches du système, ainsi s’il est possible d’obtenir une liste de processus via ps(1) avec un popen(3) super crade ou encore sysctl(3) j’ai voulu utiliser kvm(3) kernel memory interface qui est en programmation système la méthode la plus appropriée (d’ailleurs ‘ps’ est codé avec ça), c’est juste une interface qui va nous permettre d’accéder au données du kernel (une copie bien entendu), ici la liste des processus en cours.

Le hic c’est que si les fonctions de kvm sont très standard l’implémentation est très différente suivant les systèmes, comme à peu près toute implémentation, l’important c’est que l’interface soit standard. Ainsi à la lecture de la structure qui décrit les processus kinfo_proc de sys/user.h on se dit chouette pleins d’info à disposition, sauf que sur OpenBSD c’est pas du tout la même forme (ni le même fichier d’ailleurs). D’où l’importance d’une interface riche et c’est ce que nous promet kvm.

Assez causé passons au code (je n’explique pas ou peu le code en dehors des fonctions kvm)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <err.h>
#include <libgen.h>
#include <fcntl.h>
#include <limits.h>
#include <paths.h>
#include <kvm.h>
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/wait.h>
#include <sys/user.h>
void
start_leds(void)
{
/* Ici on execute /usr/sbin/gioctl -q gpio0 led3 (on|off)
* suivant la parité du conteur statique 'i'
* 60 fois avec une pause d'une seconde. */
static int i = 1;
switch(fork())
{
case -1:
err(1, "fork");
break;
case 0:
execl("/usr/sbin/gpioctl", "gpioctl", "-q", "gpio0",
"led3", (i%2) ? "on" : "off", (char*)NULL);
err(1, "execl");
break;
default:
wait(NULL);
break;
}
sleep(1);
if (i++ <= 60)
return start_leds();
return;
}
int
main(int argc, char *argv[])
{
kvm_t *kd;
char berr[_POSIX2_LINE_MAX];
struct kinfo_proc *p, *procs;
int n, j, k, found = 0;
char **pargv;
if (argc < 2)
errx(1, "Usage check_proc procs ...");
/* On accède aux données du kernel qui tourne actuellement (NULL) */
if (!(kd = kvm_open(NULL, NULL, NULL, O_RDONLY, berr)))
errx(1, "%s", berr);
/* On demande la liste des processus, kinfo_proc est décrite
* dans sys/sysctl.h sur OpenBSD et sys/user.h sur FreeBSD.
* Il nous la donne dans un tableau continu de pointeurs de
* taille qu'on récupère dans 'n'. */
if (!(procs = kvm_getprocs(kd, KERN_PROC_ALL, 0, &n)))
err(1, "kvm_getprocs: %s", kvm_geterr(kd));
for (k = 1; k < argc; k++)
{
for (j = 0, p = procs; j < n; j++, p++)
{
/* On accède à la liste des arguments du programme qui a
* généré le processus courant. */
pargv = kvm_getargv(kd, p, 0);
if (pargv && pargv[0] &&
strstr(pargv[0], argv[k]))
{
found++;
break;
}
}
}
if (-1 == kvm_close(kd))
err(1, "kvm_close");
if (found != argc-1)
start_leds();
return 0;
}

Et l’exécution, on veut tester si un processus du nom de ‘dhcpd’ tourne sur le système, si ce n’est pas le cas on lance le clignotement de la led pendant une minute :

# gcc -o check_proc check_proc.c -lkvm
# ./check_proc dhcpd

Le mien s’exécute dans un cron toute les minutes :

* * * * * /root/bin/check_proc named dhcpd adsuck ntpd