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的映射。

相关推荐
lihao lihao2 小时前
软硬链接
linux·运维·服务器
YY&DS2 小时前
Qt 嵌入 CEF 在 Linux 下必须设置 `QT_XCB_GL_INTEGRATION=xcb_egl才能加载网页
linux·开发语言·qt
辰风沐阳2 小时前
ThinkPHP8.1 + think-swoole 4.1 使用指南(保姆级教程)
linux·后端·swoole
mounter6253 小时前
迈向硬件级无缝热升级:Linux 内核 VFIO 与 IOMMU 持久化技术的演进之路
linux·服务器·内存管理·kernel
晚风吹红霞3 小时前
Linux软件包管理器详解 —— yum与apt的使用及软件生态
linux·运维·服务器
曦夜日长3 小时前
Linux系统篇,进程概念(一):计算机体系、操作系统的认识、程序的加载过程
linux·运维·网络
似水এ᭄往昔3 小时前
【Linux网络编程】--Socket编程预备
linux·服务器·网络
皮卡狮4 小时前
环境变量详解
linux
致Great4 小时前
Claude Code 上线 Dynamic Workflows:一句话调度 1000 个子智能体并行干活
java·linux·服务器
满天星83035775 小时前
【Git】原理及使用(三)(分支管理)
linux·git