FANOTIFY(7)

NOM
fanotify - Surveiller les événements des systèmes de fichiers

DESCRIPTION
L’interface de programmation fanotify permet d’être notifié et d’intercepter les événements de systèmes de fichiers. La recherche de virus et la gestion de stockage hiérarchisé font partie des cas d’utilisation. Actuellement, seul un ensemble limité d’événements est pris en charge. En particulier, les événements de création, suppression ou plus ne sont pas pris en charge (consultez inotify(7) pour plus de précisions sur une interface de programmation qui permet d’être notifié de ces événements). La capacité de surveiller tous les objets d’un système de fichiers monté, la capacité de décider des droits d’accès et la possibilité de lire ou modifier les fichiers avant qu’ils ne soient accédé par d’autres applications font partie des capacités supplémentaires à l’interface de programmation inotify(7). Les appels système suivants sont utilisés avec cette interface de programmation : fanotify_init(2), fanotify_mark(2), read(2), write(2) et close(2).

  fanotify_init(), fanotify_mark() et groupes de notification
L’appel système fanotify_init(2) créée et initialise un groupe de notifications fanotify et renvoie un descripteur de fichier le référençant.

Un groupe de notifications fanotify est un objet interne au noyau qui contient une liste de fichiers, répertoires et points de montages pour lesquels des événements seront créés.

Pour chaque entrée dans un groupe de notifications fanotify, deux masques binaires sont présents : le masque mark et le masque ignore. Le masque mark définit les activités de fichier pour lesquelles un événement doit être créé. Le masque ignore définit les activités pour lesquelles aucun événement ne doit être créé. Avoir ces deux types de masque permet à un point de montage ou à un répertoire d’être marqué pour recevoir des événements, tout en ignorant en même temps les événements pour des objets spécifiques dans ce point de montage ou répertoire.

L’appel système fanotify_mark(2) ajoute un fichier, répertoire ou point de montage à un groupe de notifications et indique les événements qui doivent être signalés (ou ignorés), ou supprime ou modifie une telle entrée.

Le masque ignore peut servir pour un cache de fichier. Les événements intéressants pour un cache de fichier sont la modification et la fermeture d’un fichier. Ainsi, le répertoire ou point de montage en cache va être marqué pour recevoir ces événements. Après la réception du premier événement informant qu’un fichier a été modifié, l’entrée correspondante du cache sera désactivée. Aucun autre événement de modification pour ce fichier ne sera utile jusqu’à sa fermeture. Ainsi, l’événement de modification peut être ajouté au masque ignore. Lors de la réception d’un événement de fermeture, l’événement de modification peut être supprimé du masque ignore et l’entrée de cache de fichier peut être mise à jour.

Les entrées des groupes de notification fanotify font référence aux fichiers et répertoires à l’aide de leur numéro d’inœud et aux montages à l’aide de leur identifiant de montage. Si les fichiers ou répertoires sont renommés ou déplacés, les entrées correspondantes survivent. Si les fichiers ou répertoires sont supprimés ou si les montages sont démontés, les entrées correspondante sont supprimées.

  La file d’événements
Comme les événements surviennent sur les objets de système de fichiers surveillés par un groupe de notifications, le système fanotify génère les événements qui sont collectés dans une file. Ces événements peuvent être lus (en utilisant read(2) ou similaire) à partir du descripteur de fichier fanotify renvoyé par fanotify_init(2). Deux types d’événements sont créés : les événements de notification et les événements de permission. Les événements de notification sont surtout informatifs et ne nécessitent pas d’action à prendre par l’action qui les reçoit à part pour la fermeture du descripteur de fichier passé dans l’événement (voir ci-dessous). Les événements de permission sont des demandes à l’application qui les reçoit pour décider si les droits d’accès à un fichier doivent être attribués. Pour ces événements, le destinataire doit écrire une réponse qui décide d’attribuer l’accès ou non. Un événement est supprimé de la file d’événements du groupe fanotify quand il a été lu. Les événements de permission qui ont été lus sont gardés dans une liste interne du groupe fanotify jusqu’à ce qu’une décision d’attribution de droits ait été prise en écrivant dans le descripteur de fichier fanotify ou que le descripteur de fichier fanotify soit fermé.

  Lecture d’événements fanotify
Appeler read(2) pour le descripteur de fichier renvoyé par fanotify_init(2) bloque (si l’attribut FAN_NONBLOCK n’est pas indiqué dans l’appel de fanotify_init(2)) jusqu’à ce qu’un événement de fichier survienne ou que l’appel soit interrompu par un signal (consultez signal(7)). Après une lecture (avec read(2)) réussie, le tampon de lecture contient une ou plusieurs des structures suivantes :
struct fanotify_event_metadata {
    __u32 event_len;
    __u8 vers;
    __u8 reserved;
    __u16 metadata_len;
    __aligned_u64 mask;
    __s32 fd;
    __s32 pid;
};

Pour des raisons de performances, une grande taille de tampon (par exemple 4096 octets) est conseillée pour que plusieurs événements puissent être récupérés en une seul lecture. La valeur de retour de read(2) est le nombre d’octets placés dans le tampon, ou -1 en cas d’erreur (mais consultez BOGUES). Les champs de la structure fanotify_event_metadata sont les suivants.

event_len
    C’est la taille des données pour l’événement actuel et la position du prochain événement dans le tampon. Dans l’implémentation actuelle, la valeur d’event_len est toujours FAN_EVENT_METADATA_LEN. Cependant, l’interface de programmation est conçue pour permettre de renvoyer des structures de taille variable à l’avenir.

vers
    Ce champ contient un numéro de version pour la structure. Il doit être comparé à FANOTIFY_METADATA_VERSION pour vérifier que la structure renvoyée pendant l’exécution correspond aux structures définies à la compilation. En cas d’erreur de correspondance, l’application devrait arrêter d’essayer d’utiliser le descripteur de fichier fanotify.

reserved
    Ce champ n’est pas utilisé.

metadata_len
    C’est la taille de la structure. Le champ a été introduit pour faciliter l’implémentation d’en-têtes facultatifs par type d’événements. Aucun en-tête facultatif n’existe dans l’implémentation actuelle.

mask
    C’est un masque binaire décrivant l’événement (voir ci-dessous).

fd  C’est un descripteur de fichier ouvert pour l’objet actuellement accédé ou FAN_NOFD si un dépassement de file est survenu. Le descripteur de fichier peut être utilisé pour accéder au contenu du fichier ou répertoire surveillé. L’application qui lit est responsable de la fermeture de ce descripteur de fichier.

    Lors d’un appel de fanotify_init(2), l’appelant pourrait indiquer (à l’aide de l’argument event_f_flags) plusieurs attributs d’état de fichier à définir dans la description de fichier ouverte qui correspond à ce descripteur de fichier. De plus, l’attribut d’état de fichier FMODE_NONOTIFY (interne au noyau) est défini dans la description de fichier ouverte. L’attribut supprime la création d’événement fanotify. Ainsi, quand le destinataire de l’événement fanotify accède au fichier ou répertoire notifiés en utilisant ce descripteur de fichier, aucun événement supplémentaire n’est créé.

pid C’est l’identifiant de processus qui a provoqué l’événement. Un programme écoutant les événements fanotify peut comparer ce PID au PID renvoyé par getpid(2), pour déterminer si l’événement est provoqué par l’écoutant lui-même ou par un autre processus accédant au fichier.

Le masque binaire de mask indique les événements survenus pour un seul objet de système de fichiers. Plusieurs bits pourraient être définis dans ce masque si plus d’un événement est survenu pour l’objet de système de fichiers surveillé. En particulier, les événements consécutifs pour le même objet de système de fichiers et originaire du même processus pourraient être fusionnés dans un seul événement, mais deux événements de permission ne sont jamais fusionnés dans une entrée de file.

Les bits pouvant apparaître dans mask sont les suivants.

FAN_ACCESS
    Un fichier ou un répertoire (mais consultez BOGUES) a été accédé (en lecture).

FAN_OPEN
    Un fichier ou un répertoire a été ouvert.

FAN_MODIFY
    Un fichier a été modifié.

FAN_CLOSE_WRITE
    Un fichier qui était ouvert en écriture (O_WRONLY ou O_RDWR) a été fermé.

FAN_CLOSE_NOWRITE
    Soit un fichier, soit un répertoire, qui était ouvert en lecture seule (O_RDONLY), a été fermé.

FAN_Q_OVERFLOW
    La file d’événements a dépassé la limite de 16384 entrées. Cette limite peut être écrasée en indiquant l’attribut FAN_UNLIMITED_QUEUE lors de l’appel de fanotify_init(2).

FAN_ACCESS_PERM
    Une application veut lire un fichier ou répertoire, par exemple en utilisant read(2) ou readdir(2). Le lecteur doit écrire une réponse (telle que décrite ci-dessous) qui détermine si le droit d’accès à l’objet de système de fichiers sera attribué.

FAN_OPEN_PERM
    Une application veut ouvrir un fichier ou un répertoire. Le lecteur doit écrire une réponse qui détermine si le droit d’ouvrir l’objet de système de fichiers sera attribué.

Pour vérifier tous les événements fermés, le masque binaire suivant pourrait être utilisé :

FAN_CLOSE
    Un fichier a été fermé. C’est un synonyme de :
        FAN_CLOSE_WRITE | FAN_CLOSE_NOWRITE

Les macros suivantes sont fournies pour itérer sur un tampon contenant les métadonnées d’événement fanotify renvoyées par read(2) à partir d’un descripteur de fichier fanotify.

FAN_EVENT_OK(meta, len)
    Cette macro compare la taille restante len du tampon meta à la taille de la structure de métadonnées et au champ event_len de la première structure de métadonnées du tampon.

FAN_EVENT_NEXT(meta, len)
    Cette macro utilise la taille indiquée dans le champ event_len de la structure de métadonnées pointé par meta pour calculer l’adresse de la prochaine structure de métadonnées qui suit meta. len est le nombre d’octets de métadonnées qui restent actuellement dans le tampon. La macro renvoie un pointeur vers la prochaine structure de métadonnées qui suit meta et réduit len du nombre d’octets dans la structure de métadonnées qui a été sautée (c’est-à-dire qu’elle soustrait meta->event_len de len).

De plus, il y a :

FAN_EVENT_METADATA_LEN
    Cette macro renvoie la taille (en octet) de la structure fanotify_event_metadata. C’est la taille minimale (et actuellement la seule taille) de métadonnées d’événements.

  Surveiller un descripteur de fichier fanotify pour les événements
Quand un événement fanotify survient, le descripteur de fichier fanotify est indiqué comme lisible lorsque passé à epoll(7), poll(2) ou select(2).

  Traitement des événements de permission
Pour les événements de permission, l’application doit écrire (avec write(2)) une structure de la forme suivant sur le descripteur de fichier fanotify :
struct fanotify_response {
    __s32 fd;
    __u32 response;
};

Les champs de cette structure sont les suivants.

fd  C’est le descripteur de fichier de la structure fanotify_event_metadata.

response
    Ce champ indique si les droits doivent être attribués ou non. Cette valeur doit être soit FAN_ALLOW pour permettre l’opération de fichier, soit FAN_DENY pour refuser l’opération de fichier.

Si l’accès est refusé, l’appel d’application faisant la demande recevra une erreur EPERM.

  Fermeture du descripteur de fichier fanotify

Quand tous les descripteurs de fichier se référant au groupe de notifications fanotify sont fermés, le groupe fanotify est libéré et ses ressources sont libérés pour être réutilisés par le noyau. Lors de l’appel de close(2), les événements de permission restants seront définis à permis.

  /proc/[pid]/fdinfo
Le fichier /proc/[pid]/fdinfo/[fd] contient des renseignements sur les marques fanotify pour le descripteur de fichier fd du processus pid. Consultez le fichier Documentation/filesystems/proc.txt des sources du noyau pour plus de précisions.

ERREURS
En plus des erreurs habituelles de read(2), les erreurs suivantes peuvent survenir lors de la lecture d’un descripteur de fichier fanotify.

EINVAL
    Le tampon est trop petit pour contenir l’événement.

EMFILE
    La limite par processus du nombre de fichiers ouverts a été atteinte. Consultez la description de RLIMIT_NOFILE dans getrlimit(2).

ENFILE
    La limite du nombre de fichiers ouverts sur le système a été atteinte. Consultez /proc/sys/fs/file-max dans proc(5).

ETXTBSY
    Cette erreur est renvoyée par read(2) si O_RDWR ou O_WRONLY ont été indiqués dans l’argument event_f_flags lors de l’appel fanotify_init(2) et qu’un événements est survenu pour un fichier surveillé actuellement en cours d’exécution.

En plus des erreurs habituelles de write(2), les erreurs suivantes peuvent survenir lors de l’écriture sur un descripteur de fichier fanotify.

EINVAL
    Les droits d’accès fanotify ne sont pas activés dans la configuration du noyau ou la valeur de response dans la structure de réponse n’est pas valable.

ENOENT
    Le descripteur de fichier fd dans la structure de réponse n’est pas valable. Cela pourrait survenir quand une réponse pour l’événement de permission a déjà été écrit.

VERSIONS
L’interface de programmation fanotify a été introduite dans la version 2.6.36 du noyau Linux et activée dans la version 2.6.37. La prise en charge de fdinfo a été ajoutée dans la version 3.8.

CONFORMITÉ
L’interface de programmation fanotify est spécifique à Linux.

NOTES
L’interface de programmation fanotify n’est disponible que si le noyau a été construit avec l’option de configuration CONFIG_FANOTIFY activée. De plus, le traitement de permission fanotify n’est disponible que si l’option de configuration CONFIG_FANOTIFY_ACCESS_PERMISSIONS est activée.

  Limites et réserves
Fanotify ne signale que les événements déclenchés par un programme en espace utilisateur à l’aide d’une interface de programmation de système de fichiers. Par conséquent, elle n’intercepte pas les événements qui surviennent sur les systèmes de fichiers en réseau.

L'interface fanotify ne signale pas les accès ni les modifications de fichier qui pourraient survenir à cause de mmap(2), msync(2) ou munmap(2).

Les événements pour répertoires ne sont créés que si le répertoire lui-même est ouvert, lu et fermé. Ajouter, supprimer ou modifier les enfants d’un répertoire marqué ne crée pas d’événement pour le répertoire surveillé lui-même.

La surveillance fanotify des répertoires n'est pas récursive : pour surveiller les sous-répertoires, des marques supplémentaires doivent être créées (mais remarquez que l’interface de programmation fanotify ne fournit aucun moyen de détecter quand un répertoire a été créé dans un répertoire marqué, ce qui rend difficile la surveillance récursive). Les montages surveillés offrent la capacité de surveiller une arborescence complète de répertoires.

La file d'événements peut déborder. Dans ce cas, les événements sont perdus.

BOGUES
Dans Linux 3.15, les bogues suivants existent :

-   quand un événement est créé, aucune vérification n’est effectuée pour voir si l’identifiant utilisateur du processus recevant a le droit de lire ou écrire le fichier avant de passer un descripteur de fichier pour ce fichier. Cela pose un risque de sécurité quand la capacité CAP_SYS_ADMIN est définie pour un programme exécuté par les utilisateurs ordinaires ;

-   si un appel de read(2) traite plusieurs événements de la file fanotify et qu’une erreur survient, la valeur de retour sera la taille totale des événements copiés correctement dans le tampon d’espace utilisateur avant que l’erreur ne survienne. La valeur de retour ne sera pas -1 et errno ne sera pas définie. Ainsi, l’application lisant n’a aucun moyen de détecter l’erreur.

EXEMPLE
Le programme suivant montre l’utilisation de l’interface de programmation fanotify. Il marque le point de montage passé en argument de ligne de commande et attend les événements de type FAN_PERM_OPEN et FAN_CLOSE_WRITE. Quand un événement de permission survient, une réponse FAN_ALLOW est donnée.

La sortie suivante a été enregistrée lors de la modification du fichier /home/utilisateur/temp/notes. Avant d’ouvrir le fichier, un événement FAN_OPEN_PERM est survenu. Après la fermeture du fichier, un événement FAN_CLOSE_WRITE est survenu. L’exécution du programme se termine quand l’utilisateur appuie sur la touche Entrée.

  Exemple de sortie
# ./fanotify_exemple /home
Appuyer sur la touche Entrée pour quitter.
En écoute d’événements.
FAN_OPEN_PERM : Fichier /home/utilisateur/temp/notes
FAN_CLOSE_WRITE : Fichier /home/utilisateur/temp/notes
Arrêt de l’écoute d’événements.

  Source du programme
/* Nécessaire pour obtenir la définition de O_LARGEFILE */
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/fanotify.h>
#include <unistd.h>
/* Lire tous les événements fanotify disponibles à partir du descripteur
   de fichier « fd » */
static void
handle_events(int fd)
{
    const struct fanotify_event_metadata *metadata;
    struct fanotify_event_metadata buf[200];
    ssize_t len;
    char path[PATH_MAX];
    ssize_t path_len;
    char procfd_path[PATH_MAX];
    struct fanotify_response response;
    /* Boucler tant que les événements peuvent être lus à partir du
       descripteur de fichier fanotify */
    for(;;) {
        /* Lire certains événements */
        len = read(fd, (void *) &buf, sizeof(buf));
        if (len == -1 && errno != EAGAIN) {
            perror("read");
            exit(EXIT_FAILURE);
        }
        /* Vérifier si la fin des données disponibles est atteinte */
        if (len <= 0)
            break;
        /* Pointer vers le premier événement du tampon */
        metadata = buf;
        /* Boucler sur tous les événements du tampon */
        while (FAN_EVENT_OK(metadata, len)) {
            /* Vérifier que les structures au moment de l’exécution et
               de la compilation correspondent */
            if (metadata->vers != FANOTIFY_METADATA_VERSION) {
                fprintf(stderr,
            "Non correspondance de version de métadonnées fanotify.\n");
                exit(EXIT_FAILURE);
            }
            /* metadata->fd contient soit FAN_NOFD, indiquant un
               dépassement de file, soit un descripteur de fichier
               (un entier positif).
               Ici, le dépassement de file est simplement ignoré. */
            if (metadata->fd >= 0) {
                /* Traiter l’événement de permission d’ouverture */
                if (metadata->mask & FAN_OPEN_PERM) {
                    printf("FAN_OPEN_PERM : ");
                    /* Permettre d’ouvrir le fichier */
                    response.fd = metadata->fd;
                    response.response = FAN_ALLOW;
                    write(fd, &response,
                          sizeof(struct fanotify_response));
                }
                /* Traiter l’événement de fermeture de fichier ouvert
                   en écriture */
                if (metadata->mask & FAN_CLOSE_WRITE)
                    printf("FAN_CLOSE_WRITE : ");
                /* Récupérer et afficher le chemin du fichier accédé */
                snprintf(procfd_path, sizeof(procfd_path),
                         "/proc/self/fd/%d", metadata->fd);
                path_len = readlink(procfd_path, path,
                                    sizeof(path) - 1);
                if (path_len == -1) {
                    perror("readlink");
                    exit(EXIT_FAILURE);
                }
                path[path_len] = '\0';
                printf("Fichier %s\n", path);
                /* Fermer le descripteur de fichier de l’événement */
                close(metadata->fd);
            }
            /* Avancer au prochain événement */
            metadata = FAN_EVENT_NEXT(metadata, len);
        }
    }
}
int
main(int argc, char *argv[])
{
    char buf;
    int fd, poll_num;
    nfds_t nfds;
    struct pollfd fds[2];
    /* Vérifier qu’un point de montage est fourni */
    if (argc != 2) {
        fprintf(stderr, "Utilisation : %s MONTAGE\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    printf("Appuyer sur la touche Entrée pour quitter.\n");
    /* Créer le descripteur de fichier pour accéder à l’interface de
       programmation fanotify */
    fd = fanotify_init(FAN_CLOEXEC | FAN_CLASS_CONTENT | FAN_NONBLOCK,
                       O_RDONLY | O_LARGEFILE);
    if (fd == -1) {
        perror("fanotify_init");
        exit(EXIT_FAILURE);
    }
    /* Marquer le montage pour :
       - les événements de permission avant d’ouvrir les fichiers ;
       - les événements de notification après fermeture de descripteur
         de fichier ouvert en écriture. */
    if (fanotify_mark(fd, FAN_MARK_ADD | FAN_MARK_MOUNT,
                      FAN_OPEN_PERM | FAN_CLOSE_WRITE, -1,
                      argv[1]) == -1) {
        perror("fanotify_mark");
        exit(EXIT_FAILURE);
    }
    /* Préparer pour la scrutation (polling) */
    nfds = 2;
    /* Entrée de console */
    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN;
    /* Entrée de fanotify */
    fds[1].fd = fd;
    fds[1].events = POLLIN;
    /* Boucle en attente d’arrivée d’événements */
    printf("En écoute d’événements.\n");
    while (1) {
        poll_num = poll(fds, nfds, -1);
        if (poll_num == -1) {
            if (errno == EINTR)     /* Interrompu par un signal */
                continue;           /* Redémarrage de poll() */
            perror("poll");         /* Erreur inattendue */
            exit(EXIT_FAILURE);
        }
        if (poll_num > 0) {
            if (fds[0].revents & POLLIN) {
                /* Entrée de console disponible :
                   vider l’entrée standard et quitter */
                while (read(STDIN_FILENO, &buf, 1) > 0 && buf != '\n')
                    continue;
                break;
            }
            if (fds[1].revents & POLLIN) {
                /* Des événements fanotify sont disponibles */
                handle_events(fd);
            }
        }
    }
    printf("Arrêt de l’écoute d’événements.\n");
    exit(EXIT_SUCCESS);
}

VOIR AUSSI
fanotify_init(2), fanotify_mark(2), inotify(7)

COLOPHON
Cette page fait partie de la publication 3.70 du projet man-pages Linux. Une description du projet et des instructions pour signaler des anomalies peuvent être trouvées à l'adresse http://www.kernel.org/doc/man-pages/.

TRADUCTION
Depuis 2010, cette traduction est maintenue à l'aide de l'outil po4a <http://po4a.alioth.debian.org/> par l'équipe de traduction francophone au sein du projet perkamon <http://perkamon.alioth.debian.org/>.

Veuillez signaler toute erreur de traduction en écrivant à <perkamon-fr@traduc.org>.

Vous pouvez toujours avoir accès à la version anglaise de ce document en utilisant la commande « LC_ALL=C man <section> <page_de_man> ».