Linux inotify机制 – 监控文件系统事件


描述信息

在Linux中,inotify API提供了一种机制来监控文件系统的事件信息。Inotify可以监控文件亦或者是目录。当一个目录被监控时,inotify会返回这个目录本身所发生的事件以及目录内的文件所发生的事件信息。
常用的API:

  • inotify_init()函数创建一个inotify的实例,返回一个被inotify实例引用的文件描述符。inotify_init1()函数的功能和inotify_init()一致,只不过多了一个标志参数 ,提供对某些额外功能的访问。
  • inotify_add_watch()函数操纵与inotify实例相关联的“watch list”,即监控列表。每一个在监控列表里的项目都被指定了相应文件或目录的路径,系统就是通过这些路径来监控相应的文件或者目录的事件集。inotify_add_watch()既不新建新的监控项目,也不会修改现有的监控项目。每一个监控项目都有属于自己独一无二的“监控描述符”,这是一个整数值,inotify_add_watch()调用成功后会返回这个值。
  • 当被监控的文件或者目录发生了相应的事件后,这些事件信息可用通过read()函数来读取。
  • inotify_rm_watch()将从监控列表里移除指定的监控项目。
  • 当所有的监控项目都被关闭(使用close()函数),这些项目的资源会被内核释放。

从inotify文件描述符中读取事件信息

想要知道被监控的文件或目录发生了什么,需要用read()函数读取inotify文件描述符。如果在read()函数被调用时尚未发生任何事件,那么read()函数将会一直阻塞等待事件的发生,除非收到了中断信号。read()函数每一次成功的调用,都会返回一个包含着监控事件的结构体:

struct inotify_event {
    int      wd;       /* Watch descriptor */
    uint32_t mask;     /* Mask describing event */
    uint32_t cookie;   /* Unique cookie associating related events (for rename(2)) */
    uint32_t len;      /* Size of name field */
    char     name[];   /* Optional null-terminated name */
};
  • wd即为监控描述符独一无二的编号
  • mask包含着描述事件类型的位信息,下面还会介绍。
  • cookie是连接着相关联的事件的独一无二的一个整数。目前来说,这只能用于重命名事件,允许应用程序连接IN_MOVED_FROMIN_MOVED_TO事件的结果集。除了这两个事件之外,其他的事件的cookie都被设置为0。
  • name字段只会在监控一个目录的事件时才会被提供,如果只是监控一个文件,这个字段将为空。
  • len字段记录着name字段的长度。每一个inotify_event结构体的长度是sizeof(struct
    inotify_event)+len
    ,不要以为len就是inotify_event结构体的长度。

当读取的buffer大小不足时,返回值会根据内核的不同而不同。内核2.6.21以前会返回0,从2.6.21以后会返回EINVALsizeof(struct inotify_event) + NAME_MAX + 1能够保证至少读取一个事件的信息。

inotify events

  • IN_ACCESS:文件被访问
  • IN_ATTRIB:元数据被更改。比如访问权限、访问时间等等。
  • IN_CLOSE_WRITE:已写入的文件被关闭。(原文:File opened for writing was closed.)(可写文件被关闭)
  • IN_CLOSE_NOWRITE:未写入的文件或目录被关闭(原文:File or directory not opened for writing was closed)(文件不可写或目录已经关闭)
  • IN_CREATE:文件或目录被创建
  • IN_DELETE:文件或目录从监控的目录中删除
  • IN_DELETE_SELF:被监控的目录自己被删除
  • IN_MODIFY:文件被修改
  • IN_MOVE_SELF:监控的目录自己被移动
  • IN_MOVED_FROM:Generated for the directory containing the old filename when a file is renamed
  • IN_MOVED_TO:Generated for the directory containing the new filename when a file is renamed
  • IN_OPEN:文件或目录被打开
  • IN_MOVE:相当于IN_MOVED_FROM | IN_MOVED_TO
  • IN_CLOSE:相当于IN_CLOSE_WRITE | IN_CLOSE_NOWRITE
  • IN_DONT_FOLLOW(since Linux 2.6.15):如果是符号链接,不要取消引用路径名
  • IN_EXCL_UNLINK (since Linux 2.6.36):By default, when watching events on the children of a directory, events are generated for children even after they have been unlinked from the directory. This can result in large numbers of uninteresting events for some applications (e.g.,if watching /tmp, in which many applications create temporary files whose names are immediately unlinked). Specifying IN_EXCL_UNLINK changes the default behavior, so that events are not generated for children after they have been unlinked from the watched directory.
  • IN_MASK_ADD:对于某个路径名,如果监控实例已经存在,则将mask中的事件添加到监控项目的mask中,而不是直接替换mask
  • IN_ONESHOT:监控相应的路径,获得一次事件后就从监控列表里移除自己。
  • IN_ONLYDIR(since Linux 2.6.15):只监控目录。

下列的mask标志位可能被read()函数设置的值:
- IN_IGNORED:监控明确的被inotify_rm_watch()函数移除又或者文件已经被删除、文件系统未挂载。
- IN_ISDIR:这个事件的主题是一个目录.
- IN_Q_OVERFLOW:事件队列溢出了。(wd将被置为-1)
- IN_UNMOUNT:被监控的文件系统未挂载。

例子

fd = open("dir/myfile", O_RDWR);
        Generates IN_OPEN events for both dir and dir/myfile.

    read(fd, buf, count);
        Generates IN_ACCESS events for both dir and dir/myfile.

    write(fd, buf, count);
        Generates IN_MODIFY events for both dir and dir/myfile.

    fchmod(fd, mode);
        Generates IN_ATTRIB events for both dir and dir/myfile.

    close(fd);
        Generates IN_CLOSE_WRITE events for both dir and dir/myfile.

    link("dir1/myfile", "dir2/new");
           Generates an IN_ATTRIB event for  myfile  and  an  `IN_CREATE` event for dir2.

    rename("dir1/myfile", "dir2/myfile");
           Generates  an  `IN_MOVED_FROM`  event for dir1, an `IN_MOVED_TO` event for
           dir2, and an `IN_MOVE_SELF` event for  myfile.   The `IN_MOVED_FROM`  and
           `IN_MOVED_TO`  events  will  have the same cookie value.

    unlink("dir2/yy");
           Generates  an IN_ATTRIB event for xx (because its link count
           changes) and an IN_DELETE event for dir2.

    unlink("dir1/xx");
           Generates IN_ATTRIB, IN_DELETE_SELF, and  IN_IGNORED  events
           for xx, and an IN_DELETE event for dir1.

    mkdir("dir/new", mode);
           Generates an IN_CREATE | IN_ISDIR event for dir.

    rmdir("dir/subdir");
           Generates  IN_DELETE_SELF  and IN_IGNORED events for subdir,
           and an IN_DELETE | IN_ISDIR event for dir.

poll函数原型

NAME
       poll, ppoll - wait for some event on a file descriptor

SYNOPSIS
       #include <poll.h>

       int poll(struct pollfd *fds, nfds_t nfds, int timeout);

       #define _GNU_SOURCE /* See feature_test_macros(7) */
       #include <signal.h>
       #include <poll.h>

       int ppoll(struct pollfd *fds, nfds_t nfds,
               const struct timespec *tmo_p, const sigset_t *sigmask);


        struct pollfd {
               int   fd;         /* file descriptor */
               short events;     /* requested events */
               short revents;    /* returned events */
           };

read()函数原型

NAME
       read - read from a file descriptor

SYNOPSIS
       #include <unistd.h>

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

inotify_add_watch()函数原型

NAME
       inotify_add_watch - add a watch to an initialized inotify instance

SYNOPSIS
       #include <sys/inotify.h>

       int inotify_add_watch(int fd, const char *pathname, uint32_t mask);

RETURN VALUE
       On success, inotify_add_watch() returns a nonnegative watch descriptor.
       On error, -1 is returned and errno is set appropriately.

inotify_init()函数原型

NAME
       inotify_init, inotify_init1 - initialize an inotify instance

SYNOPSIS
       #include <sys/inotify.h>

       int inotify_init(void);
       int inotify_init1(int flags);
DESCRIPTION
       For an overview of the inotify API, see inotify(7).

       inotify_init()  initializes  a  new inotify instance and returns a file
       descriptor associated with a new inotify event queue.

       If flags is 0, then inotify_init1() is the same as inotify_init().  The
       following  values  can  be  bitwise  ORed  in flags to obtain different
       behavior:

       IN_NONBLOCK Set the O_NONBLOCK file status flag on the  new  open  file
                   description.  Using this flag saves extra calls to fcntl(2)
                   to achieve the same result.

       IN_CLOEXEC  Set the close-on-exec (FD_CLOEXEC) flag  on  the  new  file
                   descriptor.   See  the description of the O_CLOEXEC flag in
                   open(2) for reasons why this may be useful.

RETURN VALUE
       On success, these system calls return a new file descriptor.  On error,
       -1 is returned, and errno is set to indicate the error.

inotify_rm_watch()函数原型

NAME
       inotify_rm_watch - remove an existing watch from an inotify instance

SYNOPSIS
       #include <sys/inotify.h>

       int inotify_rm_watch(int fd, int wd);

测试程序

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

/*fd - 文件描述符 wd - 参数argv里的所有目录的监控描述符表 argc - 参数argv的个数 argv - 目录列表 */
static void 
handle_events(int fd, int *wd, int argc, char *argv[])
{
    char buf[4096];
    const struct inotify_event *event;
    int i;
    ssize_t len;
    char *ptr;

    /*循环读取事件*/
    for(;;) {
        len = read(fd, buf, sizeof buf);
        if (len == -1 && errno != EAGAIN) {
            perror("read");
            exit(EXIT_FAILURE);
        }
        /*当非阻塞read()发现没有事件可读取,会返回-1且设置errno为EAGAIN,此时退出循环*/
        if (len < 0)
            break;
        /*读取在buffer中的事件并处理*/
        for (ptr = buf; ptr < buf + len;
                ptr += sizeof(struct inotify_event) + event->len) {

            event = (const struct inotify_event *) ptr;
            /*打印事件名字*/
            if (event->mask & IN_OPEN)
                printf("IN_OPEN: ");
            if (event->mask & IN_CLOSE_NOWRITE)
                printf("IN_CLOSE_NOWRITE: ");
            if (event->mask & IN_CLOSE_WRITE)
                printf("IN_CLOSE_WRITE: ");
            /*打印被监测的目录名字*/
            for (i = 1; i < argc; ++i) {
                if (wd[i] == event->wd) {
                    printf("%s/", argv[i]);
                    break;
                }
            }

            /*打印文件名*/
            if (event->len)
                printf("%s", event->name);
            if (event->mask & IN_ISDIR)
                printf(" [directory] \n");
            else 
                printf(" [file] \n");
        }
    }
}

int main(int argc, char* argv[]) {
    char buf;
    int fd, i, poll_num;
    int *wd;
    nfds_t nfds;
    struct pollfd fds[2];
    if (argc < 2) {
        printf("Usage: %s PATH [PATH ...] \n", argv[0]);
        exit(EXIT_FAILURE);
    }
    printf("Press ENTER key to terminate.\n");
    /*非阻塞调用*/
    fd = inotify_init1(IN_NONBLOCK);
    if (fd == -1) {
        perror("inotify_init1");
        exit(EXIT_FAILURE);
    }
    /*为wd申请内存*/
    wd = calloc(argc, sizeof(int));
    if (wd == NULL) {
        perror("calloc");
        exit(EXIT_FAILURE);
    }
    /*监控事件IN_OPEN 、 IN_CLOSE*/
    for (i = 1; i < argc; i++) {
        wd[i] = inotify_add_watch(fd, argv[i], IN_OPEN | IN_CLOSE);
        if (wd[i] == -1) {
            fprintf(stderr, "Cannot watch '%s'\n", argv[i]);
            perror("inotify_add_watch");
            exit(EXIT_FAILURE);
        }
    }
    /*为polling做准备*/
    nfds = 2;
    fds[0].fd = STDIN_FILENO;//控制台输入
    fds[0].events = POLLIN;
    fds[1].fd = fd;//inotify输入
    fds[1].events = POLLIN;

    /*等待事件的发生或者控制台的输入*/
    printf("Listening for events.\n");
    while(1) {
        poll_num = poll(fds, nfds, -1);//-1使poll()一直挂起直到一个指定事件发生
        if (poll_num == -1) {
            if (errno == EINTR)//忽略读写时的中断
                continue;
            perror("poll");
            exit(EXIT_FAILURE);
        }
        if (poll_num > 0) {
            if (fds[0].revents & POLLIN) {
                while(read(STDIN_FILENO, &buf, 1) > 0 && buf != '\n')
                    continue;
                break;
            }
            if (fds[1].revents & POLLIN) {
                handle_events(fd, wd, argc, argv);
            }
        }
    }
    printf("Listening for evnets stopped.\n");
    close(fd);
    free(wd);
    exit(EXIT_SUCCESS);
}