TLPI 第19 章 练习:Monitoring File Events

笔记和练习博客总目录见:开始读TLPI

本章随书示例程序位于目录inotify。

练习 19-1.

编写一个程序,记录在命令行参数中指定的目录下的所有文件创建、删除和重命名操作。程序应监控指定目录下所有子目录的事件。要获取所有这些子目录的列表,需要使用 nftw()(第18.9节)。当树下添加一个新的子目录或删除一个目录时,监控的子目录集合应相应更新。


先看一下inotify(7)。复习一下基本概念。

  • inotify API 提供了一种监控文件系统事件的机制。Inotify 可以用于监控单个文件,也可以用于监控目录。当监控目录时,inotify 会返回该目录本身的事件,以及目录内文件的事件。
  • inotify_init创建监控实例,inotify_add_watch将监控对象关联到监控实例。一个监控实例可以有多个监控对象。
  • inotify_rm_watch从监控实例移除监控对象。
  • 当产生监控事件时,可以用read读取。
  • 监控的事件包括文件被创建,修改,删除,移动,关闭,元数据改变等等。
  • Inotify 监视是基于 inode 的。
  • 可以使用 select(2)、poll(2) 和 epoll(7) 监视 Inotify 文件描述符。当有事件可用时,文件描述符会显示为可读。

这个man page的后半部分还有一个示例程序,演示了如何用poll监控多个文件描述符(标准输入和监控实例)。

c 复制代码
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <string.h>

/* Read all available inotify events from the file descriptor 'fd'.
   wd is the table of watch descriptors for the directories in argv.
   argc is the length of wd and argv.
   argv is the list of watched directories.
   Entry 0 of wd and argv is unused. */

static void
handle_events(int fd, int *wd, int argc, char* argv[])
{
    /* Some systems cannot read integer variables if they are not
       properly aligned. On other systems, incorrect alignment may
       decrease performance. Hence, the buffer used for reading from
       the inotify file descriptor should have the same alignment as
       struct inotify_event. */

    char buf[4096]
    __attribute__ ((aligned(__alignof__(struct inotify_event))));
    const struct inotify_event *event;
    ssize_t len;

    /* Loop while events can be read from inotify file descriptor. */

    for (;;) {

        /* Read some events. */

        len = read(fd, buf, sizeof(buf));
        if (len == -1 && errno != EAGAIN) {
            perror("read");
            exit(EXIT_FAILURE);
        }

        /* If the nonblocking read() found no events to read, then
           it returns -1 with errno set to EAGAIN. In that case,
           we exit the loop. */

        if (len <= 0)
            break;

        /* Loop over all events in the buffer. */

        for (char *ptr = buf; ptr < buf + len;
                ptr += sizeof(struct inotify_event) + event->len) {

            event = (const struct inotify_event *) ptr;

            /* Print event type. */

            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: ");

            /* Print the name of the watched directory. */

            for (size_t i = 1; i < argc; ++i) {
                if (wd[i] == event->wd) {
                    printf("%s/", argv[i]);
                    break;
                }
            }

            /* Print the name of the file. */

            if (event->len)
                printf("%s", event->name);

            /* Print type of filesystem object. */

            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");

    /* Create the file descriptor for accessing the inotify API. */

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

    /* Allocate memory for watch descriptors. */

    wd = calloc(argc, sizeof(int));
    if (wd == NULL) {
        perror("calloc");
        exit(EXIT_FAILURE);
    }

    /* Mark directories for events
       - file was opened
       - file was closed */

    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': %s\n",
                    argv[i], strerror(errno));
            exit(EXIT_FAILURE);
        }
    }

    /* Prepare for polling. */

    nfds = 2;

    fds[0].fd = STDIN_FILENO;       /* Console input */
    fds[0].events = POLLIN;

    fds[1].fd = fd;                 /* Inotify input */
    fds[1].events = POLLIN;

    /* Wait for events and/or terminal input. */

    printf("Listening for events.\n");
    while (1) {
        poll_num = poll(fds, nfds, -1);
        if (poll_num == -1) {
            if (errno == EINTR)
                continue;
            perror("poll");
            exit(EXIT_FAILURE);
        }

        if (poll_num > 0) {

            if (fds[0].revents & POLLIN) {

                /* Console input is available. Empty stdin and quit. */

                while (read(STDIN_FILENO, &buf, 1) > 0 && buf != '\n')
                    continue;
                break;
            }

            if (fds[1].revents & POLLIN) {

                /* Inotify events are available. */

                handle_events(fd, wd, argc, argv);
            }
        }
    }

    printf("Listening for events stopped.\n");

    /* Close inotify file descriptor. */

    close(fd);

    free(wd);
    exit(EXIT_SUCCESS);
}

❓ 监控目录和监控单个文件的区别?

这是我的第一个问题,经过实验,发现: 监控目录时,其下文件的创建,修改,删除,元数据改变均能监控到。那他们之间唯一的区别只能是监控粒度不一样了,也就是如果你只关心这一个文件时。

❓ 监控目录时,其中的哪些变化无法监控到?

通过实验,发现可以监控到子目录的创建,子目录属性的改变;但如果在子目录中创建一个文件则无法监控到。

所以这个练习的意图应该是:

  • nftw遍历目录,如果发现有子目录,则加到监控列表
  • 如果在监控期间有新子目录创建,也要加到监控列表
  • 如果子目录被重新命名?这个行为我还不知道。(由于inotify是基于inode的,所以重命名不会影响监控,后续也证实了)

查看nftw(3),其中也有一个实例程序如下:

c 复制代码
#define _XOPEN_SOURCE 500
#include <ftw.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int
display_info(const char *fpath, const struct stat *sb,
             int tflag, struct FTW *ftwbuf)
{
    printf("%-3s %2d ",
           (tflag == FTW_D) ?   "d"   : (tflag == FTW_DNR) ? "dnr" :
           (tflag == FTW_DP) ?  "dp"  : (tflag == FTW_F) ?   "f" :
           (tflag == FTW_NS) ?  "ns"  : (tflag == FTW_SL) ?  "sl" :
           (tflag == FTW_SLN) ? "sln" : "???",
           ftwbuf->level);

    if (tflag == FTW_NS)
        printf("-------");
    else
        printf("%7jd", (intmax_t) sb->st_size);

    printf("   %-40s %d %s\n",
           fpath, ftwbuf->base, fpath + ftwbuf->base);

    return 0;           /* To tell nftw() to continue */
}

int
main(int argc, char *argv[])
{
    int flags = 0;

    if (argc > 2 && strchr(argv[2], 'd') != NULL)
        flags |= FTW_DEPTH;
    if (argc > 2 && strchr(argv[2], 'p') != NULL)
        flags |= FTW_PHYS;

    if (nftw((argc < 2) ? "." : argv[1], display_info, 20, flags)
            == -1)
    {
        perror("nftw");
        exit(EXIT_FAILURE);
    }

    exit(EXIT_SUCCESS);
}

可用此程序作为主框架,然后监控部分抄inotify/demo_inotify.c的作业即可。

涉及到nftw,还有一个细节,即 FTW_D和 FTW_DP的区别。

FTW_D和 FTW_DP都是目录,但后者是post-order,即深度优先。或者说,是先最底层的子目录,然后逐层上升到顶层目录。

如果和inotify结合,我们还是先监控顶层目录,然后再逐层下沉到子目录。所以用FTW_D更合适。

FTW_D和 FTW_DP是二选一的,不会同时存在。如果指定了FTW_DEPTH标准,则目录为FTW_DP,否则为FTW_D。

以下演示了其区别:

bash 复制代码
# 目录结构
$ tree -d test
test
├── d1
│   └── d1-1
│       └── d1-1-1
└── d2

4 directories

# 没有指定FTW_DEPTH时
inotify: add dir test to watch list.
inotify: add dir test/d1 to watch list.
inotify: add dir test/d1/d1-1 to watch list.
inotify: add dir test/d1/d1-1/d1-1-1 to watch list.
inotify: add dir test/d2 to watch list.

# 指定FTW_DEPTH时
inotify: add dir test/d1/d1-1/d1-1-1 to watch list.
inotify: add dir test/d1/d1-1 to watch list.
inotify: add dir test/d1 to watch list.
inotify: add dir test/d2 to watch list.
inotify: add dir test to watch list.

代码如下:

c 复制代码
$ cat ex19-1.c
#include <ftw.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <limits.h>
#include "tlpi_hdr.h"

static int inotifyFd;


static void             /* Display information from inotify_event structure */
displayInotifyEvent(struct inotify_event *i)
{
    printf("    wd =%2d; ", i->wd);
    if (i->cookie > 0)
        printf("cookie =%4d; ", i->cookie);

    printf("mask = ");
    if (i->mask & IN_ACCESS)        printf("IN_ACCESS ");
    if (i->mask & IN_ATTRIB)        printf("IN_ATTRIB ");
    if (i->mask & IN_CLOSE_NOWRITE) printf("IN_CLOSE_NOWRITE ");
    if (i->mask & IN_CLOSE_WRITE)   printf("IN_CLOSE_WRITE ");
    if (i->mask & IN_CREATE)        printf("IN_CREATE ");
    if (i->mask & IN_DELETE)        printf("IN_DELETE ");
    if (i->mask & IN_DELETE_SELF)   printf("IN_DELETE_SELF ");
    if (i->mask & IN_IGNORED)       printf("IN_IGNORED ");
    if (i->mask & IN_ISDIR)         printf("IN_ISDIR ");
    if (i->mask & IN_MODIFY)        printf("IN_MODIFY ");
    if (i->mask & IN_MOVE_SELF)     printf("IN_MOVE_SELF ");
    if (i->mask & IN_MOVED_FROM)    printf("IN_MOVED_FROM ");
    if (i->mask & IN_MOVED_TO)      printf("IN_MOVED_TO ");
    if (i->mask & IN_OPEN)          printf("IN_OPEN ");
    if (i->mask & IN_Q_OVERFLOW)    printf("IN_Q_OVERFLOW ");
    if (i->mask & IN_UNMOUNT)       printf("IN_UNMOUNT ");
    printf("\n");

    if (i->len > 0)
        printf("        name = %s\n", i->name);

    if ((i->mask & IN_CREATE) && (i->mask & IN_ISDIR)) {
        printf("inotify: add dir %s to watch list.\n", i->name);
        //watch_dir();
    }
}

void watch_dir(const char *dirname);
void watch_dir(const char *dirname)
{
    int wd;

    printf("inotify: add dir %s to watch list.\n", dirname);

    wd = inotify_add_watch(inotifyFd, dirname, IN_ALL_EVENTS);
    if (wd == -1)
        errExit("inotify_add_watch");
}

static int
add_dir_to_watch(const char *fpath, const struct stat *sb,
                 int tflag, struct FTW *ftwbuf)
{

    if (tflag == FTW_DP)
        watch_dir(fpath);

    return 0;           /* To tell nftw() to continue */
}

#define BUF_LEN (10 * (sizeof(struct inotify_event) + NAME_MAX + 1))

int
main(int argc, char *argv[])
{
    int flags = 0;
    char buf[BUF_LEN] __attribute__ ((aligned(8)));
    ssize_t numRead;
    char *p;
    struct inotify_event *event;

    inotifyFd = inotify_init();                 /* Create inotify instance */
    if (inotifyFd == -1)
        errExit("inotify_init");

    flags |= FTW_PHYS;
    flags |= FTW_DEPTH;

    if (nftw((argc < 2) ? "." : argv[1], add_dir_to_watch, 20, flags)
            == -1)
    {
        errExit("nftw");
    }

    for(;;) {

        numRead = read(inotifyFd, buf, BUF_LEN);
        if (numRead == 0)
            fatal("read() from inotify fd returned 0!");

        if (numRead == -1)
            errExit("read");

        printf("Read %ld bytes from inotify fd\n", (long) numRead);

        /* Process all of the events in buffer returned by read() */

        for (p = buf; p < buf + numRead; ) {
            event = (struct inotify_event *) p;
            displayInotifyEvent(event);

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

    exit(EXIT_SUCCESS);
}

这个代码还有一小部分未完成,即创建了新目录时,由于struct inotify_event中的name只是文件名,而非全路径名,因此还只打印了name, 而没有添加到监控列表中。

实现方法可以在inotify_add_watch时,维护一个wd和dirname的映射。

相关推荐
Bert.Cai1 小时前
Linux basename命令详解
linux·运维·服务器
源远流长jerry1 小时前
Linux 本机网络通信机制深度解析:Loopback 设备原理
linux·运维·服务器·网络·tcp/ip·nginx·负载均衡
源远流长jerry2 小时前
Linux 网络性能优化:从应用到内核
linux·运维·服务器·网络·网络协议·性能优化
goyeer2 小时前
【ITIL】指导原则
linux·运维·服务器·数字化·itil
顶点多余2 小时前
自定义协议、序列化、反序列化实现
java·linux·开发语言·c++·tcp/ip
Bruce_kaizy3 小时前
c++ linux环境编程——从应用层到linux内核深入了解文件io的调用机制(爆肝)
linux·c++·c·嵌入式linux·文件io
浪客灿心3 小时前
Linux网络IP协议
linux·网络·tcp/ip
yuanpan3 小时前
Python + psutil 实战:开发一个简易系统监控工具
linux·运维·python
坚持就完事了3 小时前
Linux的ln命令
linux·运维·服务器