如何停止 inotify 结果的重复?

How to stop the repetition of inotify result?

在这段代码中,我试图同时监控两条路径。为此,我使用了 while(1)。但我面临的问题是,每当我 运行 代码时,它都会像这样两次给出相同的结果。

给出结果

Pathname1 "file" is modified 
Pathname1 "file" is modified

预期结果

Pathname1 "file" is modified 

我调试了代码。在打破 main 函数并跨过它之后,下一个命令停在这一行 length = read(fd, buffer, EVENT_BUF_LEN )。每当我在这个长度可变的命令之后换行时,程序就会启动,并且在修改文件后,程序会停在这一行 struct inotify_event *event = ( struct inotify_event *)&buffer[i]; 虽然程序不应该中断。

我也用了IN_CLOSE_WRITE代替了IN_MODIFY但是结果没有变化

typedef struct{
    int length, fd, wd1, wd2;
    char buffer[4096] __attribute__ ((aligned(__alignof__(struct inotify_event))));
} notification;
notification inotify;

int getNotified(char *pathname1, char *pathname2){
    inotify.fd = inotify_init();
    inotify.wd1 = inotify_add_watch(inotify.fd, pathname1, IN_MODIFY);
    inotify.wd2 = inotify_add_watch(inotify.fd, pathname2, IN_MODIFY);

    while(1){
        inotify.length = read(inotify.fd, inotify.buffer, EVENT_BUF_LEN); 
        int i = 0;
        while(i < inotify.length){     
            struct inotify_event *event = (struct inotify_event *)&inotify.buffer[i];
            if(event->len){
                if(event->mask & IN_MODIFY){
                    if(event->wd == inotify.wd1){
                        printf("Pathname1 '%s' is modified\n", event->name);
                        break;
                    }
                    if(event->wd == inotify.wd2){
                        printf("Pathname2 '%s' is modified\n", event->name);
                        break;
                    }
                }
            }
            i += EVENT_SIZE + event->len;
        }
    }
    inotify_rm_watch(inotify.fd, inotify.wd1);
    inotify_rm_watch(inotify.fd, inotify.wd2);

    close(inotify.fd);
    exit(0);
}

一些备注:

  • 您不应该在退出内部“while”循环并再次调用 read() 时“中断”
  • IN_MODIFY事件没有填写事件->名称字段(即事件- >len = 0)
  • IN_MODIFY 事件的数量取决于您修改文件的方式(“echo xxx >> file”= 只写 = 1 IN_MODIFY;“echo xxx > file”= 截断 + 写入= 2 IN_MODIFY)

这里是你的程序的命题(有额外的打印):

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

#define EVENT_BUF_LEN 4096
#define EVENT_SIZE sizeof(struct inotify_event)

typedef struct{
  int length, fd, wd1, wd2;
  char buffer[EVENT_BUF_LEN] __attribute__ ((aligned(__alignof__(struct inotify_event))));
} notification;

notification inotify;

int getNotified(char *pathname1, char *pathname2){

  inotify.fd = inotify_init();
  inotify.wd1 = inotify_add_watch(inotify.fd, pathname1, IN_MODIFY);
  printf("wd1 = %d\n", inotify.wd1);
  inotify.wd2 = inotify_add_watch(inotify.fd, pathname2, IN_MODIFY);
  printf("wd2 = %d\n", inotify.wd2);

  while(1){
    inotify.length = read(inotify.fd, inotify.buffer, EVENT_BUF_LEN); 
    int i = 0;
    printf("read() = %d\n", inotify.length);
    while(i < inotify.length){     
      struct inotify_event *event = (struct inotify_event *)&inotify.buffer[i];
      printf("event->len = %u\n", event->len);
      if(event->mask & IN_MODIFY){
        if(event->wd == inotify.wd1){
          printf("Pathname1 is modified\n");
        } else if (event->wd == inotify.wd2){
          printf("Pathname2 is modified\n");
        }
      }
      i += (EVENT_SIZE + event->len);
      printf("i=%d\n", i);
    }
  }
  inotify_rm_watch(inotify.fd, inotify.wd1);
  inotify_rm_watch(inotify.fd, inotify.wd2);

  close(inotify.fd);
  exit(0);
}



int main(void)
{
  getNotified("/tmp/foo", "/tmp/bar");

  return 0;

} // main

这里是一个执行的例子:

$ gcc notif.c -o notif
$ > /tmp/foo
$ > /tmp/bar
$ ./notif 
wd1 = 1
wd2 = 2

====== Upon "> /tmp/foo": 1 event (truncate operation)
read() = 16
event->len = 0
Pathname1 is modified
i=16

====== Upon "echo qwerty > /tmp/foo": 2 events (write operation, one event for truncate operation and one for the write of "qwerty" at the beginning of the file)
read() = 16
event->len = 0
Pathname1 is modified
i=16
read() = 16
event->len = 0
Pathname1 is modified
i=16

====== Upon "echo qwerty >> /tmp/foo": 1 event (write of "qwerty" at the end of the file)
read() = 16
event->len = 0
Pathname1 is modified
i=16

如果两个路径名引用同一个 inode,则 inotify.wd1 == inotify.wd2。在那种情况下,因为你有

    if (event->wd == inotify.wd1) {
        printf("Pathname1 '%s' is modified\n", event->name);
    }
    if (event->wd == inotify.wd2) {
        printf("Pathname2 '%s' is modified\n", event->name);
    }

两具尸体都会被处决;但输出会有所不同(Pathname1 ...Pathname2 ...)。

您确实应该监控文件所在的目录,而不是实际的路径名,这样您也可以捕获重命名事件。 (许多编辑器创建一个临时文件,并重命名或硬链接临时文件覆盖旧文件,以便程序看到旧文件或新文件,而不是两者的混合。)

考虑以下程序,它没有表现出任何不当的事件重复:

// SPDX-License-Identifier: CC0-1.0
#define  _POSIX_C_SOURCE  200809L
#define  _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/inotify.h>
#include <signal.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

static volatile sig_atomic_t  done = 0;

static void handle_done(int signum)
{
    done = signum;
}

static int install_done(int signum)
{
    struct sigaction  act;
    memset(&act, 0, sizeof act);
    sigemptyset(&act.sa_mask);
    act.sa_handler = handle_done;
    act.sa_flags = 0;
    return sigaction(signum, &act, NULL);
}

struct monitor {
    /* Supplied by caller */
    const char  *directory;
    const char  *pathname;
    int        (*modified)(struct monitor *);
    int        (*completed)(struct monitor *);
    /* Reserved for internal use */
    int          dirwatch;
};

int monitor_files(struct monitor *const list, const size_t count)
{
    char   *events_ptr = NULL;
    size_t  events_len = 65536;
    size_t  i;
    int     err;

    /* Verify sane parameters */
    if (count < 1) {
        errno = ENOENT;
        return -1;
    } else
    if (!list) {
        errno = EINVAL;
        return -1;
    }
    for (i = 0; i < count; i++) {
        if (!list[i].directory || !list[i].directory[0]) {
            errno = EINVAL;
            return -1;
        }
        if (!list[i].pathname || !list[i].pathname[0]) {
            errno = EINVAL;
            return -1;
        }
        list[i].dirwatch = -1;
    }

    /* Obtain a descriptor for inotify event queue */
    int  queue = inotify_init1(IN_CLOEXEC);
    if (queue == -1) {
        /* errno set by inotify_init1() */
        return -1;
    }

    /* Use a reasonable dynamically allocated buffer for events */
    events_ptr = malloc(events_len);
    if (!events_ptr) {
        close(queue);
        errno = ENOMEM;
        return -1;
    }

    /* Add a watch for each directory to be watched */
    for (i = 0; i < count; i++) {
        list[i].dirwatch = inotify_add_watch(queue, list[i].directory, IN_CLOSE_WRITE | IN_MOVED_TO | IN_MODIFY);
        if (list[i].dirwatch == -1) {
            err = errno;
            close(queue);
            free(events_ptr);
            errno = err;
            return -1;
        }
    }

    /* inotify event loop */
    err = 0;
    while (!done) {
        ssize_t  len = read(queue, events_ptr, events_len);
        if (len == -1) {
            /* Interrupted due to signal delivery? */
            if (errno == EINTR)
                continue;
            /* Error */
            err = errno;
            break;
        } else
        if (len < -1) {
            /* Should never occur */
            err = EIO;
            break;
        } else
        if (len == 0) {
            /* No events watched anymore */
            err = 0;
            break;
        }

        char *const end = events_ptr + len;
        char       *ptr = events_ptr;
        while (ptr < end) {
            struct inotify_event *event = (struct inotify_event *)ptr;

            /* Advance buffer pointer for next event */
            ptr += sizeof (struct inotify_event) + event->len;
            if (ptr > end) {
                close(queue);
                free(events_ptr);
                errno = EIO;
                return -1;
            }

            /* Call all event handlers, even duplicate ones */
            for (i = 0; i < count; i++) {
                if (event->wd == list[i].dirwatch && !strcmp(event->name, list[i].pathname)) {
                    if ((event->mask & (IN_MOVED_TO | IN_CLOSE_WRITE)) && list[i].completed) {
                        err = list[i].completed(list + i);
                        if (err)
                            break;
                    } else
                    if ((event->mask & IN_MODIFY) && list[i].modified) {
                        err = list[i].modified(list + i);
                        if (err)
                            break;
                    }
                }
            }
            if (err)
                break;
        }
        if (err)
            break;
    }

    close(queue);
    free(events_ptr);

    errno = 0;
    return err;
}

static int report_modified(struct monitor *m)
{
    printf("%s/%s: Modified\n", m->directory, m->pathname);
    fflush(stdout);
    return 0;
}

static int report_completed(struct monitor *m)
{
    printf("%s/%s: Completed\n", m->directory, m->pathname);
    fflush(stdout);
    return 0;
}

int main(void)
{
    struct monitor  watch[2] = {
        { .directory = ".",
          .pathname  = "file1",
          .modified  = report_modified,
          .completed = report_completed },
        { .directory = ".",
          .pathname  = "file2",
          .modified  = report_modified,
          .completed = report_completed }
    };
    int  err;

    if (install_done(SIGINT) == -1 ||
        install_done(SIGHUP) == -1 ||
        install_done(SIGTERM) == -1) {
        fprintf(stderr, "Cannot set signal handlers: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    }

    fprintf(stderr, "To stop this program, press Ctrl+C, or send\n");
    fprintf(stderr, "INT, HUP, or TERM signal (to process %ld).\n", (long)getpid());
    fflush(stderr);

    err = monitor_files(watch, 2);
    if (err == -1) {
        fprintf(stderr, "Error monitoring files: %s.\n", strerror(errno));
        return EXIT_FAILURE;
    } else
    if (err) {
        fprintf(stderr, "Monitoring files failed [%d].\n", err);
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

如果你使用例如编译它gcc -Wall -Wextra -O2 example.c -o example 和 运行 它通过 ./example,它将报告 IN_MODIFY 事件(作为“已修改”)和 IN_CLOSE_WRITE 和 IN_MOVED_TO 事件(作为“ Completed") for file1 and file2 in the same directory (current working directory).要退出程序,请按 Ctrl+C

(请注意,我们可能应该添加第三个事件类型,“已删除”,对应于 IN_DELETEIN_MOVED_FROM 事件。)

如果您在文本编辑器中打开 file1file2,保存文件会生成一个或多个已修改 (IN_MODIFY) 事件,并且只有一个已完成 (IN_CLOSE_WRITE 或 IN_MOVED_TO) 事件。这是因为导致文件被 t运行 分类的每个 open()/t运行cate()/ft运行cate() 系统调用都会生成一个 IN_MODIFY 事件对于那个文件;就像修改文件内容的每个底层 write() 系统调用一样。因此,当某个进程修改文件时,很自然地会收到多个 IN_MODIFY 事件。

如果列表中有多个 struct monitor 条目具有相同的有效目录和相同的路径名,示例程序将为它们提供多个事件。如果你想避免这种情况,只要确保每当 .pathname 匹配时,两者就会不同 .dirwatches.

while() 循环重写为 for() 循环,并展平 if()s


while(1){
    int i ;
    struct inotify_event *event ;

    inotify.length = read(inotify.fd, inotify.buffer, EVENT_BUF_LEN);

    for(i=0; i < inotify.length; i += EVENT_SIZE + event->len ) {
        event = (struct inotify_event *)&inotify.buffer[i];

        if (!event->len) continue;
        if (!(event->mask & IN_MODIFY)) continue;

        if (event->wd == inotify.wd1){
            printf("Pathname1 '%s' is modified\n", event->name);
            continue;
        }

        if (event->wd == inotify.wd2){
            printf("Pathname2 '%s' is modified\n", event->name);
            continue;
        }
    }
}