inotify監控檔案實作動態更新

Linux kernel中的inotify模組提供了很好的檔案監控支援

組合poll或select可以寫出簡潔高效的程式

但要從inotify的watching list中新增或刪除對象時

就需要先主動從poll的等待狀態中離開

使用固定時間timedout的話,可以在poll時順帶傳入timedout數

缺點是浪費時間,程式複雜

而Signal雖然也能達到中斷效果,但在有多執行緒的程式中

除非為所有執行緒設定signal mask,否則無法保證哪個執行緒會收到Signal

搭配pselect就顯得不可靠

最後決定在程式內使用pipe,並將pipe的read end加入poll中

當有新的檔案需要被監控或移除時,只要往write end寫入資料

就能達到提醒poll的thread要更新watching list的效果

/* 
    `A /home` 
    `R 1` 
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <errno.h>
#include <sys/inotify.h>

void serve(int fd)
{
    struct pollfd fds[2];
    int inotify_fd = inotify_init();

    fds[0].fd = inotify_fd;
    fds[0].events = POLLIN;

    fds[1].fd = fd;
    fds[1].events = POLLIN;

    int rc = 0;
    while((rc = poll(fds, 2, -1)) > 0) // should handle EINTR
    {
        if(fds[1].revents & POLLIN) {
            char line[1024] = {};
            read(fd, line, 1024);
            if(line[0] == 'A') {
                int wd = inotify_add_watch(inotify_fd, line+2, IN_ALL_EVENTS); // should handle error
                if(wd >= 0)
                    printf("%s has been added with wd %d\n", line+2, wd);
                else
                    printf("add failed %s\n", strerror(errno));
            } else if(line[0] == 'R') {
                int wd = atoi(line+2);
                int rc = inotify_rm_watch(inotify_fd, wd); // should handle error
                if(rc < 0)
                {
                    perror("rm_watch");
                    exit(1);
                }
            }
        }
        if(fds[0].revents & POLLIN) { 
            char buff[1024] = {};
            read(inotify_fd, buff, 1024);
            // handle inotify events
        }
    }
}

int main()
{
    pid_t pid;
    int fds[2];
    pipe(fds);
    if(pid = fork()) {
        // parent
        while(1) {
            char line[1024] = {};
            if(!fgets(line, 1024, stdin))break;
            int len = strlen(line);
            line[len-1] = 0;
            write(fds[1], line, len-1);
        }
    } else {
        serve(fds[0]);
    }
    return 0;
}