Surveiller les événements fichiers sous Linux avec inotify

Ce forum est dédié à apprendre le développement de programmes user mode sur Linux

Moderator: Rick

Post Reply
Hydraxx
Site Admin
Posts: 46
Joined: Mon Jan 12, 2026 4:04 pm
Location: France
Contact:

Surveiller les événements fichiers sous Linux avec inotify

Post by Hydraxx »

Surveiller les événements fichiers sous Linux avec inotify

Objectif du cours :
Comprendre l'API inotify, savoir quelles fonctions retenir, quelles structures utiliser, quels événements surveiller, et comment écrire un petit programme capable de détecter les créations, suppressions, modifications et renommages de fichiers sous Linux.

1. Introduction

Sous Linux, un programme peut demander au noyau d'être prévenu quand quelque chose change dans un fichier ou dans un répertoire.

Par exemple, on peut vouloir détecter :
  • la création d'un fichier ;
  • la suppression d'un fichier ;
  • la modification du contenu d'un fichier ;
  • l'ouverture ou la fermeture d'un fichier ;
  • le renommage d'un fichier ;
  • le déplacement d'un fichier d'un répertoire vers un autre ;
  • la suppression du répertoire surveillé lui-même ;
  • la saturation de la file d'événements.
Pour cela, Linux fournit une API appelée inotify.

Le principe est simple :
  • on crée une instance inotify ;
  • on ajoute un ou plusieurs éléments à surveiller ;
  • le noyau place les événements dans une file ;
  • le programme lit cette file avec read() ;
  • chaque événement est représenté par une structure struct inotify_event.
On peut voir inotify comme l'équivalent Linux de ReadDirectoryChangesW sous Windows, mais avec une logique très Unix : un descripteur de fichier, un buffer, et une lecture avec read().

2. Comparaison rapide avec Windows

Si on connaît déjà Win32, le parallèle est très naturel.

Code: Select all

Linux                         Windows
------------------------------------------------------------
inotify_init()                ouverture d'un handle de surveillance
inotify_add_watch()           choix du dossier/fichier à surveiller
read()                        ReadDirectoryChangesW()
struct inotify_event          FILE_NOTIFY_INFORMATION
IN_CREATE                     FILE_ACTION_ADDED
IN_DELETE                     FILE_ACTION_REMOVED
IN_MODIFY                     FILE_ACTION_MODIFIED
IN_MOVED_FROM                 FILE_ACTION_RENAMED_OLD_NAME
IN_MOVED_TO                   FILE_ACTION_RENAMED_NEW_NAME
La grande différence :
  • Windows peut surveiller récursivement un arbre de répertoires avec bWatchSubtree = TRUE ;
  • inotify ne surveille pas récursivement automatiquement ;
  • sous Linux, il faut ajouter manuellement un watch sur chaque sous-répertoire si on veut une surveillance récursive.
Donc il faut retenir :

Code: Select all

inotify = ReadDirectoryChangesW version Linux,
mais avec fd + read(), et sans récursivité automatique.
3. Les headers nécessaires

Pour utiliser inotify, le header principal est :

Code: Select all

#include <sys/inotify.h>
Dans un vrai programme, on utilise souvent aussi :

Code: Select all

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
Rôle des headers :
  • <sys/inotify.h> : contient les fonctions, constantes et structures de l'API inotify ;
  • <unistd.h> : contient read(), close(), etc. ;
  • <stdint.h> : contient les types comme uint32_t ;
  • <stdio.h> : contient printf(), perror(), etc. ;
  • <stdlib.h> : contient exit(), EXIT_SUCCESS, EXIT_FAILURE ;
  • <string.h> : utile pour manipuler les chaînes ou buffers ;
  • <errno.h> : utile pour comprendre les erreurs système.
4. Vue d'ensemble de l'API

Les fonctions importantes sont :

Code: Select all

inotify_init()
inotify_init1()
inotify_add_watch()
inotify_rm_watch()
read()
close()
Le schéma mental est le suivant :

Code: Select all

1. fd = inotify_init();

2. wd = inotify_add_watch(fd, "chemin", mask);

3. read(fd, buffer, sizeof(buffer));

4. interpréter les struct inotify_event présentes dans le buffer ;

5. inotify_rm_watch(fd, wd);

6. close(fd);
Il faut bien comprendre que inotify utilise des descripteurs de fichier.

L'instance inotify est représentée par un fd.
Le fichier ou dossier surveillé est représenté par un watch descriptor, souvent appelé wd.

5. Créer une instance inotify

La fonction de base est :

Code: Select all

int inotify_init(void);
Elle crée une nouvelle instance inotify et retourne un descripteur de fichier.

Exemple :

Code: Select all

int fd = inotify_init();

if(fd == -1){
    perror("inotify_init");
    exit(EXIT_FAILURE);
}
À retenir :
  • si la fonction réussit, elle retourne un fd ;
  • si elle échoue, elle retourne -1 ;
  • ce fd sera utilisé ensuite avec read() ;
  • quand on ferme ce fd avec close(), tous les watches associés sont supprimés.
6. Variante moderne : inotify_init1()

Il existe une variante plus moderne :

Code: Select all

int inotify_init1(int flags);
Elle permet de créer une instance inotify avec certains flags.

Les deux flags les plus utiles sont :

Code: Select all

IN_NONBLOCK
IN_CLOEXEC
Exemple :

Code: Select all

int fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);

if(fd == -1){
    perror("inotify_init1");
    exit(EXIT_FAILURE);
}
IN_NONBLOCK signifie que read() ne bloquera pas si aucun événement n'est disponible.

IN_CLOEXEC signifie que le fd sera automatiquement fermé lors d'un exec().

C'est utile dans les programmes propres, surtout quand un processus lance d'autres programmes.

7. Ajouter un watch

Pour demander au noyau de surveiller un fichier ou un répertoire, on utilise :

Code: Select all

int inotify_add_watch(int fd, const char *pathname, uint32_t mask);
Paramètres :
  • fd : le descripteur retourné par inotify_init() ou inotify_init1() ;
  • pathname : le chemin du fichier ou dossier à surveiller ;
  • mask : les événements que l'on veut recevoir.
La fonction retourne un watch descriptor.

Exemple :

Code: Select all

int wd = inotify_add_watch(fd, "test", IN_CREATE | IN_DELETE | IN_MODIFY);

if(wd == -1){
    perror("inotify_add_watch");
    close(fd);
    exit(EXIT_FAILURE);
}
wd est très important : il permet de savoir quel élément surveillé a généré l'événement.

Si on surveille plusieurs dossiers avec la même instance inotify, chaque dossier aura son propre wd.

8. Supprimer un watch

Pour arrêter de surveiller un élément, on utilise :

Code: Select all

int inotify_rm_watch(int fd, int wd);
Exemple :

Code: Select all

if(inotify_rm_watch(fd, wd) == -1){
    perror("inotify_rm_watch");
}
Après suppression, le noyau peut générer un événement IN_IGNORED pour signaler que le watch n'est plus actif.

9. Lire les événements

Les événements sont lus avec read().

Code: Select all

ssize_t read(int fd, void *buf, size_t count);
Exemple :

Code: Select all

char buffer[4096];

ssize_t len = read(fd, buffer, sizeof(buffer));

if(len == -1){
    perror("read");
}
Ici, fd est le descripteur retourné par inotify_init().

Le point très important :

Code: Select all

Un seul read() peut retourner plusieurs événements.
Le buffer peut donc contenir plusieurs structures struct inotify_event les unes après les autres.

10. La structure centrale : struct inotify_event

La structure principale est :

Code: Select all

struct inotify_event {
    int      wd;
    uint32_t mask;
    uint32_t cookie;
    uint32_t len;
    char     name[];
};
C'est la structure la plus importante du chapitre.

10.1 Champ wd

Code: Select all

int wd;
Le champ wd indique quel watch a généré l'événement.

Exemple :

Code: Select all

if(event->wd == wd_dir1){
    printf("evenement dans dir1\n");
}

if(event->wd == wd_dir2){
    printf("evenement dans dir2\n");
}
C'est utile quand une seule instance inotify surveille plusieurs chemins.

10.2 Champ mask

Code: Select all

uint32_t mask;
Le champ mask contient le type d'événement.

Exemple :

Code: Select all

if(event->mask & IN_CREATE){
    printf("creation detectee\n");
}

if(event->mask & IN_DELETE){
    printf("suppression detectee\n");
}
Le mask fonctionne avec des bits.
Il faut donc tester avec l'opérateur &.

10.3 Champ cookie

Code: Select all

uint32_t cookie;
Le champ cookie sert surtout pour les événements de déplacement ou renommage.

Quand un fichier est renommé ou déplacé, le noyau peut générer deux événements :

Code: Select all

IN_MOVED_FROM
IN_MOVED_TO
Le cookie permet de relier les deux.

Exemple mental :

Code: Select all

ancien nom : a.txt
nouveau nom : b.txt
Le noyau peut générer :

Code: Select all

IN_MOVED_FROM  name = a.txt  cookie = 1234
IN_MOVED_TO    name = b.txt  cookie = 1234
Le cookie identique indique que les deux événements appartiennent au même déplacement.

10.4 Champ len

Code: Select all

uint32_t len;
Le champ len indique la taille du champ name.

Le champ name n'a pas toujours la même taille, donc il faut utiliser len pour parcourir correctement le buffer.

10.5 Champ name

Code: Select all

char name[];
Le champ name contient le nom du fichier concerné.

Important : il est surtout rempli quand on surveille un répertoire.

Exemple :

Code: Select all

inotify_add_watch(fd, "dir", IN_CREATE);
Si on crée :

Code: Select all

dir/test.txt
Alors l'événement peut contenir :

Code: Select all

event->name = "test.txt";
Le champ name ne contient pas forcément le chemin complet.
Il contient généralement le nom relatif dans le dossier surveillé.

11. Parcourir correctement le buffer

Comme un seul read() peut renvoyer plusieurs événements, il faut parcourir le buffer avec un pointeur.

Le pattern important est :

Code: Select all

char *p = buffer;

while(p < buffer + len){
    struct inotify_event *event = (struct inotify_event *)p;

    /* traiter event ici */

    p += sizeof(struct inotify_event) + event->len;
}
La ligne essentielle est :

Code: Select all

p += sizeof(struct inotify_event) + event->len;
Pourquoi ?

Parce que struct inotify_event a une partie fixe :

Code: Select all

wd
mask
cookie
len
Et une partie variable :

Code: Select all

name[]
Donc pour passer à l'événement suivant, on avance de la taille fixe + la taille du nom.

12. Les événements principaux

12.1 IN_ACCESS

Code: Select all

IN_ACCESS
Déclenché quand un fichier est lu.

Exemple :

Code: Select all

cat fichier.txt
peut générer un événement IN_ACCESS.

12.2 IN_OPEN

Code: Select all

IN_OPEN
Déclenché quand un fichier est ouvert.

Exemple :

Code: Select all

open("fichier.txt", O_RDONLY);
peut produire IN_OPEN.

12.3 IN_MODIFY

Code: Select all

IN_MODIFY
Déclenché quand le contenu d'un fichier est modifié.

Exemple :

Code: Select all

echo "test" >> fichier.txt
peut générer IN_MODIFY.

12.4 IN_ATTRIB

Code: Select all

IN_ATTRIB
Déclenché quand les métadonnées changent.

Cela peut inclure :
  • les permissions ;
  • le propriétaire ;
  • le groupe ;
  • certains timestamps ;
  • certains attributs liés au fichier.
Exemples :

Code: Select all

chmod 777 fichier.txt
chown user fichier.txt
12.5 IN_CLOSE_WRITE

Code: Select all

IN_CLOSE_WRITE
Déclenché quand un fichier ouvert en écriture est fermé.

C'est un événement très utile.

Exemple :

Code: Select all

echo "abc" > fichier.txt
Quand l'écriture est terminée et le fichier fermé, on peut recevoir IN_CLOSE_WRITE.

C'est souvent plus propre que de réagir immédiatement à IN_MODIFY, car IN_MODIFY peut arriver pendant que le fichier est encore en cours d'écriture.

12.6 IN_CLOSE_NOWRITE

Code: Select all

IN_CLOSE_NOWRITE
Déclenché quand un fichier ouvert sans écriture est fermé.

Exemple : un programme ouvre un fichier en lecture puis le ferme.

12.7 IN_CLOSE

Code: Select all

IN_CLOSE
C'est un masque pratique qui regroupe :

Code: Select all

IN_CLOSE_WRITE | IN_CLOSE_NOWRITE
12.8 IN_CREATE

Code: Select all

IN_CREATE
Déclenché quand un fichier ou un sous-répertoire est créé dans le dossier surveillé.

Exemple :

Code: Select all

touch dir/a.txt
mkdir dir/sub
Si dir est surveillé, cela peut générer IN_CREATE.

12.9 IN_DELETE

Code: Select all

IN_DELETE
Déclenché quand un fichier ou sous-répertoire est supprimé dans le dossier surveillé.

Exemple :

Code: Select all

rm dir/a.txt
Si dir est surveillé, on peut recevoir IN_DELETE.

12.10 IN_DELETE_SELF

Code: Select all

IN_DELETE_SELF
Déclenché quand l'objet surveillé lui-même est supprimé.

Différence importante :

Code: Select all

IN_DELETE       = un enfant du dossier surveillé est supprimé
IN_DELETE_SELF  = le fichier/dossier surveillé lui-même est supprimé
Exemple :

Code: Select all

inotify_add_watch(fd, "dir", IN_DELETE_SELF);
rm -r dir
Ici, dir lui-même disparaît, donc IN_DELETE_SELF peut être généré.

12.11 IN_MOVE_SELF

Code: Select all

IN_MOVE_SELF
Déclenché quand le fichier ou dossier surveillé lui-même est déplacé.

Exemple :

Code: Select all

inotify_add_watch(fd, "dir", IN_MOVE_SELF);
mv dir autre_nom
12.12 IN_MOVED_FROM

Code: Select all

IN_MOVED_FROM
Déclenché quand un fichier quitte un dossier surveillé.

Exemple :

Code: Select all

mv dir1/a.txt dir2/a.txt
Si dir1 est surveillé, il peut recevoir IN_MOVED_FROM.

12.13 IN_MOVED_TO

Code: Select all

IN_MOVED_TO
Déclenché quand un fichier arrive dans un dossier surveillé.

Exemple :

Code: Select all

mv dir1/a.txt dir2/a.txt
Si dir2 est surveillé, il peut recevoir IN_MOVED_TO.

12.14 IN_MOVE

Code: Select all

IN_MOVE
C'est un masque pratique qui regroupe :

Code: Select all

IN_MOVED_FROM | IN_MOVED_TO
12.15 IN_IGNORED

Code: Select all

IN_IGNORED
Indique qu'un watch a été supprimé.

Cela peut arriver :
  • après un appel à inotify_rm_watch() ;
  • si le fichier ou dossier surveillé disparaît ;
  • si le noyau retire automatiquement le watch.
12.16 IN_Q_OVERFLOW

Code: Select all

IN_Q_OVERFLOW
Indique que la file d'événements a débordé.

Cela signifie que le programme n'a pas lu les événements assez vite et que certains événements ont été perdus.

C'est très important pour un outil sérieux.

Si on reçoit IN_Q_OVERFLOW, il ne faut pas faire comme si tout était fiable. Il faut considérer que l'état réel du système de fichiers peut être différent de ce que l'on croit.

12.17 IN_UNMOUNT

Code: Select all

IN_UNMOUNT
Indique que le système de fichiers contenant l'objet surveillé a été démonté.

13. Les masks pratiques

Pour surveiller presque tous les événements :

Code: Select all

IN_ALL_EVENTS
Pour surveiller les changements classiques dans un dossier :

Code: Select all

IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO
Pour surveiller les écritures complètes :

Code: Select all

IN_CLOSE_WRITE
Pour surveiller les renommages/déplacements :

Code: Select all

IN_MOVED_FROM | IN_MOVED_TO
Pour surveiller les suppressions importantes :

Code: Select all

IN_DELETE | IN_DELETE_SELF
14. Les flags spéciaux utiles

En plus des événements, il existe des flags qui modifient le comportement du watch.

14.1 IN_ONLYDIR

Code: Select all

IN_ONLYDIR
Permet de dire : je veux ajouter le watch seulement si le chemin est un dossier.

Exemple :

Code: Select all

int wd = inotify_add_watch(fd, "dir", IN_ONLYDIR | IN_CREATE | IN_DELETE);
Si dir n'est pas un dossier, l'appel échoue.

14.2 IN_DONT_FOLLOW

Code: Select all

IN_DONT_FOLLOW
Permet de ne pas suivre les liens symboliques.

C'est utile si on veut éviter qu'un symlink pointe vers une autre cible que celle attendue.

14.3 IN_ONESHOT

Code: Select all

IN_ONESHOT
Le watch se désactive automatiquement après le premier événement.

Exemple :

Code: Select all

int wd = inotify_add_watch(fd, "fichier.txt", IN_ONESHOT | IN_MODIFY);
Après la première modification détectée, le watch est retiré.

14.4 IN_MASK_ADD

Code: Select all

IN_MASK_ADD
Si un watch existe déjà sur le même chemin, ce flag permet d'ajouter des événements au mask existant au lieu de remplacer l'ancien mask.

Sans ce flag, ajouter un watch sur un chemin déjà surveillé peut remplacer le mask précédent.

15. Surveillance d'un fichier vs surveillance d'un dossier

15.1 Surveiller un fichier

Exemple :

Code: Select all

int wd = inotify_add_watch(fd, "a.txt", IN_MODIFY | IN_DELETE_SELF | IN_MOVE_SELF);
Ici, on surveille directement a.txt.

On peut recevoir des événements si :
  • le fichier est modifié ;
  • le fichier est supprimé ;
  • le fichier est déplacé ;
  • ses attributs changent.
15.2 Surveiller un dossier

Exemple :

Code: Select all

int wd = inotify_add_watch(fd, "dir", IN_CREATE | IN_DELETE | IN_MOVED_FROM | IN_MOVED_TO);
Ici, on surveille les événements qui se produisent dans dir.

Si on crée :

Code: Select all

dir/test.txt
Alors event->name peut valoir :

Code: Select all

test.txt
16. inotify n'est pas récursif

C'est un point fondamental.

Si on fait :

Code: Select all

inotify_add_watch(fd, "/home/user/projet", IN_CREATE | IN_DELETE);
On surveille directement :

Code: Select all

/home/user/projet
Mais on ne surveille pas automatiquement :

Code: Select all

/home/user/projet/src
/home/user/projet/src/core
/home/user/projet/include
Pour surveiller tout un arbre, il faut :
  • parcourir récursivement l'arborescence ;
  • ajouter un watch sur chaque sous-dossier ;
  • quand un nouveau sous-dossier est créé, ajouter aussi un watch dessus ;
  • quand un sous-dossier est supprimé, retirer ou oublier son watch.
C'est une différence importante avec Windows, où ReadDirectoryChangesW peut surveiller récursivement avec bWatchSubtree.

17. Exemple minimal : surveiller un dossier

Ce programme surveille un dossier appelé test.

Code: Select all

#include <sys/inotify.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int fd = inotify_init();

    if(fd == -1){
        perror("inotify_init");
        exit(EXIT_FAILURE);
    }

    int wd = inotify_add_watch(fd, "test", IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO);

    if(wd == -1){
        perror("inotify_add_watch");
        close(fd);
        exit(EXIT_FAILURE);
    }

    char buffer[4096];

    while(1){
        ssize_t len = read(fd, buffer, sizeof(buffer));

        if(len == -1){
            perror("read");
            break;
        }

        char *p = buffer;

        while(p < buffer + len){
            struct inotify_event *event = (struct inotify_event *)p;

            if(event->mask & IN_CREATE){
                printf("creation : %s\n", event->len ? event->name : "");
            }

            if(event->mask & IN_DELETE){
                printf("suppression : %s\n", event->len ? event->name : "");
            }

            if(event->mask & IN_MODIFY){
                printf("modification : %s\n", event->len ? event->name : "");
            }

            if(event->mask & IN_MOVED_FROM){
                printf("deplace depuis le dossier : %s\n", event->len ? event->name : "");
            }

            if(event->mask & IN_MOVED_TO){
                printf("deplace vers le dossier : %s\n", event->len ? event->name : "");
            }

            p += sizeof(struct inotify_event) + event->len;
        }
    }

    inotify_rm_watch(fd, wd);
    close(fd);

    return 0;
}
Compilation :

Code: Select all

gcc -Wall -Wextra -o watcher watcher.c
Préparation du test :

Code: Select all

mkdir test
./watcher
Dans un autre terminal :

Code: Select all

touch test/a.txt
echo "hello" > test/a.txt
mv test/a.txt test/b.txt
rm test/b.txt
18. Exemple : afficher proprement le mask

Il est pratique d'écrire une fonction qui affiche les événements présents dans le mask.

Code: Select all

#include <sys/inotify.h>
#include <stdio.h>

void print_mask(uint32_t mask)
{
    if(mask & IN_ACCESS){
        printf("IN_ACCESS ");
    }

    if(mask & IN_ATTRIB){
        printf("IN_ATTRIB ");
    }

    if(mask & IN_CLOSE_WRITE){
        printf("IN_CLOSE_WRITE ");
    }

    if(mask & IN_CLOSE_NOWRITE){
        printf("IN_CLOSE_NOWRITE ");
    }

    if(mask & IN_CREATE){
        printf("IN_CREATE ");
    }

    if(mask & IN_DELETE){
        printf("IN_DELETE ");
    }

    if(mask & IN_DELETE_SELF){
        printf("IN_DELETE_SELF ");
    }

    if(mask & IN_MODIFY){
        printf("IN_MODIFY ");
    }

    if(mask & IN_MOVE_SELF){
        printf("IN_MOVE_SELF ");
    }

    if(mask & IN_MOVED_FROM){
        printf("IN_MOVED_FROM ");
    }

    if(mask & IN_MOVED_TO){
        printf("IN_MOVED_TO ");
    }

    if(mask & IN_OPEN){
        printf("IN_OPEN ");
    }

    if(mask & IN_IGNORED){
        printf("IN_IGNORED ");
    }

    if(mask & IN_Q_OVERFLOW){
        printf("IN_Q_OVERFLOW ");
    }

    if(mask & IN_UNMOUNT){
        printf("IN_UNMOUNT ");
    }

    if(mask & IN_ISDIR){
        printf("IN_ISDIR ");
    }
}
Le flag IN_ISDIR peut être présent dans un événement pour indiquer que l'objet concerné est un répertoire.

19. Exemple plus propre avec une fonction de traitement

Code: Select all

#include <sys/inotify.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

void display_event(struct inotify_event *event)
{
    printf("wd=%d ", event->wd);

    if(event->len > 0){
        printf("name=%s ", event->name);
    }

    printf("mask=");

    if(event->mask & IN_CREATE){
        printf("IN_CREATE ");
    }

    if(event->mask & IN_DELETE){
        printf("IN_DELETE ");
    }

    if(event->mask & IN_MODIFY){
        printf("IN_MODIFY ");
    }

    if(event->mask & IN_MOVED_FROM){
        printf("IN_MOVED_FROM ");
    }

    if(event->mask & IN_MOVED_TO){
        printf("IN_MOVED_TO ");
    }

    if(event->mask & IN_ISDIR){
        printf("IN_ISDIR ");
    }

    if(event->cookie != 0){
        printf("cookie=%u ", event->cookie);
    }

    printf("\n");
}

int main(int argc, char *argv[])
{
    if(argc < 2){
        fprintf(stderr, "usage: %s <directory>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    int fd = inotify_init();

    if(fd == -1){
        perror("inotify_init");
        exit(EXIT_FAILURE);
    }

    int wd = inotify_add_watch(fd, argv[1], IN_ALL_EVENTS);

    if(wd == -1){
        perror("inotify_add_watch");
        close(fd);
        exit(EXIT_FAILURE);
    }

    char buffer[4096];

    while(1){
        ssize_t len = read(fd, buffer, sizeof(buffer));

        if(len == -1){
            perror("read");
            break;
        }

        char *p = buffer;

        while(p < buffer + len){
            struct inotify_event *event = (struct inotify_event *)p;

            display_event(event);

            p += sizeof(struct inotify_event) + event->len;
        }
    }

    inotify_rm_watch(fd, wd);
    close(fd);

    return 0;
}
Compilation :

Code: Select all

gcc -Wall -Wextra -o watch_all watch_all.c
Exécution :

Code: Select all

mkdir demo
./watch_all demo
Tests dans un autre terminal :

Code: Select all

touch demo/a.txt
echo "hello" > demo/a.txt
cat demo/a.txt
mv demo/a.txt demo/b.txt
rm demo/b.txt
20. Comprendre les renommages avec cookie

Un renommage peut produire deux événements.

Exemple :

Code: Select all

mv a.txt b.txt
Si le dossier est surveillé, on peut recevoir :

Code: Select all

IN_MOVED_FROM  name=a.txt  cookie=12345
IN_MOVED_TO    name=b.txt  cookie=12345
Cela veut dire :
  • un fichier nommé a.txt a quitté son ancien nom ;
  • un fichier nommé b.txt est apparu comme destination ;
  • le cookie identique permet de comprendre que c'est la même opération.
Sans cookie, on pourrait confondre deux déplacements indépendants.

21. Les limites système dans /proc

inotify utilise de la mémoire noyau.
Il existe donc des limites.

Elles sont visibles dans :

Code: Select all

/proc/sys/fs/inotify/
Les fichiers importants sont :

Code: Select all

max_queued_events
max_user_instances
max_user_watches
21.1 max_queued_events

Code: Select all

/proc/sys/fs/inotify/max_queued_events
Nombre maximal d'événements qui peuvent être mis en attente dans la file.

Si le programme ne lit pas assez vite, la file peut déborder et le noyau peut générer :

Code: Select all

IN_Q_OVERFLOW
21.2 max_user_instances

Code: Select all

/proc/sys/fs/inotify/max_user_instances
Nombre maximal d'instances inotify qu'un utilisateur peut créer.

Chaque appel à inotify_init() crée une instance.

21.3 max_user_watches

Code: Select all

/proc/sys/fs/inotify/max_user_watches
Nombre maximal de watches qu'un utilisateur peut créer.

C'est une limite importante pour les outils qui surveillent beaucoup de dossiers.

Exemple : un IDE, un outil de synchronisation, un watcher de projet, etc.

22. Lire les valeurs des limites

On peut lire les limites avec cat :

Code: Select all

cat /proc/sys/fs/inotify/max_queued_events
cat /proc/sys/fs/inotify/max_user_instances
cat /proc/sys/fs/inotify/max_user_watches
On peut aussi les modifier temporairement avec sysctl ou en écrivant dans /proc, selon les permissions.

Exemple :

Code: Select all

sudo sysctl fs.inotify.max_user_watches=524288
Attention : modifier ces valeurs doit être fait proprement, surtout sur une machine de production.

23. Erreurs classiques

23.1 Oublier que read() peut retourner plusieurs événements

Mauvais raisonnement :

Code: Select all

read() = un seul événement
Bon raisonnement :

Code: Select all

read() = un buffer contenant un ou plusieurs événements
Il faut donc toujours parcourir le buffer avec :

Code: Select all

p += sizeof(struct inotify_event) + event->len;
23.2 Croire que c'est récursif

Mauvais raisonnement :

Code: Select all

Je surveille /home/user/projet, donc je surveille aussi tous les sous-dossiers.
Bon raisonnement :

Code: Select all

Je surveille seulement le dossier demandé.
Pour surveiller les sous-dossiers, je dois ajouter un watch sur chacun.
23.3 Confondre IN_DELETE et IN_DELETE_SELF

Code: Select all

IN_DELETE      = un enfant du dossier surveillé est supprimé
IN_DELETE_SELF = l'objet surveillé lui-même est supprimé
23.4 Confondre IN_MOVED_FROM et IN_MOVED_TO

Code: Select all

IN_MOVED_FROM = le fichier quitte le dossier surveillé
IN_MOVED_TO   = le fichier arrive dans le dossier surveillé
23.5 Ignorer IN_Q_OVERFLOW

Si IN_Q_OVERFLOW arrive, des événements ont pu être perdus.

Dans un vrai outil, il faut alors resynchroniser l'état réel du dossier surveillé.

24. Cas d'utilisation réels

inotify peut servir à créer :
  • un outil qui surveille un dossier de logs ;
  • un système d'auto-reload de configuration ;
  • un watcher de projet pour recompiler automatiquement ;
  • un outil de synchronisation de fichiers ;
  • un système de détection de fichiers nouvellement créés ;
  • un petit système de monitoring ;
  • un outil qui déclenche une action quand un fichier est modifié ;
  • un service qui surveille les uploads dans un dossier ;
  • un programme qui attend qu'un fichier soit complètement écrit avant de le traiter.
Exemple concret :

Code: Select all

Un programme surveille /var/log.
Quand un fichier de log est modifié, il lit les nouvelles lignes.
Autre exemple :

Code: Select all

Un programme surveille un dossier d'upload.
Quand un fichier reçoit IN_CLOSE_WRITE, cela signifie que l'écriture est probablement terminée.
Le programme peut alors traiter le fichier.
25. dnotify : l'ancien mécanisme

Avant inotify, Linux avait un mécanisme plus ancien appelé dnotify.

Il est aujourd'hui considéré comme dépassé.

Ses limites principales :
  • il repose sur des signaux ;
  • il est moins pratique ;
  • il est moins précis ;
  • il surveille surtout les répertoires ;
  • il nécessite d'ouvrir les répertoires surveillés ;
  • il est plus lourd à utiliser proprement.
Aujourd'hui, pour un programme moderne, il faut retenir inotify, pas dnotify.

26. Résumé des API à retenir

Code: Select all

int inotify_init(void);
Crée une instance inotify.

Code: Select all

int inotify_init1(int flags);
Crée une instance inotify avec des flags comme IN_NONBLOCK ou IN_CLOEXEC.

Code: Select all

int inotify_add_watch(int fd, const char *pathname, uint32_t mask);
Ajoute un fichier ou dossier à surveiller.

Code: Select all

int inotify_rm_watch(int fd, int wd);
Supprime un watch.

Code: Select all

ssize_t read(int fd, void *buf, size_t count);
Lit les événements générés par le noyau.

Code: Select all

int close(int fd);
Ferme l'instance inotify et libère les watches associés.

27. Résumé des structures à retenir

La structure essentielle :

Code: Select all

struct inotify_event {
    int      wd;
    uint32_t mask;
    uint32_t cookie;
    uint32_t len;
    char     name[];
};
Champs à connaître :
  • wd : identifiant du watch qui a généré l'événement ;
  • mask : type d'événement ;
  • cookie : lien entre IN_MOVED_FROM et IN_MOVED_TO ;
  • len : taille du champ name ;
  • name : nom du fichier concerné dans le dossier surveillé.
28. Résumé des événements à retenir par cœur

Code: Select all

IN_ACCESS        fichier lu
IN_OPEN          fichier ouvert
IN_MODIFY        contenu modifié
IN_ATTRIB        attributs changés
IN_CLOSE_WRITE   fichier fermé après écriture
IN_CLOSE_NOWRITE fichier fermé sans écriture
IN_CREATE        fichier créé dans un dossier surveillé
IN_DELETE        fichier supprimé dans un dossier surveillé
IN_DELETE_SELF   objet surveillé lui-même supprimé
IN_MOVE_SELF     objet surveillé lui-même déplacé
IN_MOVED_FROM    fichier sorti du dossier surveillé
IN_MOVED_TO      fichier arrivé dans le dossier surveillé
IN_IGNORED       watch supprimé
IN_Q_OVERFLOW    file d'événements saturée
IN_UNMOUNT       système de fichiers démonté
IN_ISDIR         l'objet concerné est un dossier
29. Résumé final

inotify est l'API Linux moderne pour surveiller les événements du système de fichiers.

Elle permet à un programme de recevoir des notifications quand des fichiers ou dossiers sont ouverts, lus, modifiés, créés, supprimés, renommés ou déplacés.

Le fonctionnement repose sur :
  • une instance inotify représentée par un file descriptor ;
  • des watches ajoutés avec inotify_add_watch() ;
  • une lecture des événements avec read() ;
  • une structure struct inotify_event pour chaque événement ;
  • un mask pour identifier le type d'événement ;
  • un cookie pour relier certains déplacements ;
  • un champ name pour connaître le nom du fichier concerné dans un dossier surveillé.
Ce qu'il faut retenir absolument :

Code: Select all

inotify_init()
inotify_init1()
inotify_add_watch()
inotify_rm_watch()
read()
close()
struct inotify_event
Et surtout :

Code: Select all

read() peut retourner plusieurs événements.
inotify n'est pas récursif.
IN_DELETE et IN_DELETE_SELF sont différents.
IN_MOVED_FROM et IN_MOVED_TO sont reliés par cookie.
IN_Q_OVERFLOW signifie que des événements ont pu être perdus.
Mentalement, si on vient de Windows, on peut retenir :

Code: Select all

inotify est très proche de ReadDirectoryChangesW,
mais dans le style Linux : fd, read(), buffer, structures variables.
30. Mini défi pour s'entraîner

Défi :
Écrire un programme qui prend un dossier en argument et affiche :
  • quand un fichier est créé ;
  • quand un fichier est supprimé ;
  • quand un fichier est modifié ;
  • quand un fichier est renommé ;
  • si l'objet concerné est un dossier ;
  • le cookie pour les événements de déplacement.
Commande attendue :

Code: Select all

./watcher dossier
Exemple de sortie :

Code: Select all

CREATE      name=a.txt
MODIFY      name=a.txt
MOVED_FROM  name=a.txt cookie=3812
MOVED_TO    name=b.txt cookie=3812
DELETE      name=b.txt
Quand ce défi est réussi, on maîtrise la base pratique du chapitre.

Who is online

Users browsing this forum: No registered users and 1 guest