• 周二. 8月 16th, 2022

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

Windows&Linux文件目录监控

admin

11月 28, 2021

Windows

Windows提供了几种方式对文件和目录进行监控,包括:FindFirstChangeNotification、ReadDirectoryChangesW、变更日志(Change Journal)等。

(1)FindFirstChangeNotification函数,可以监控到目标目录及其子目录中所有文件的变化,但不能监控到具体是哪一个文件发生改变。

(2)ReadDirectoryChangesW 能监控到目标目录下某一文件发生改变,并且可以知道发生变化的是哪一个文件。

注意,FindFirstChangeNotification 和 ReadDirectoryChangesW 是互斥的,不能同时使用。

(3)变更日志(Change Journal)可以跟踪每一个变更的细节,即使你的软件没有运行。很帅的技术,但也相当难用。

本文只对ReadDirectoryChangesW 进行说明。

该函数定义为:

 BOOL WINAPI ReadDirectoryChangesW(

        HANDLE hDirectory,   // 对目录进行监视的句柄
        LPVOID lpBuffer,     // 一个指向FILE_NOTIFY_INFORMATION结构体的缓冲区,其中可以将获取的数据结果将其返回。
        DWORD nBufferLength, // 指lpBuffer的缓冲区的大小值,以字节为单位。
        BOOL bWatchSubtree, // 是否监视子目录. 
        DWORD dwNotifyFilter, // 对文件过滤的方式和标准
       LPDWORD lpBytesReturned, // 将接收的字节数转入lpBuffer参数
       LPOVERLAPPED lpOverlapped, // 一般选择 NULL
      LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine // 一般选择 NULL

 );

注意点:

1)ReadDirectoryChangesW 数据缓冲区中使用的都是宽字节Unicode,字符串不是 NULL 结尾的,所以不能使用 wcscpy。
2)ReadDirectoryChangesW放入while循环中。目的就是要在每次监测到一次变化后,重新发起新的 ReadDirectoryChangesW 调用。(适用情况:被监控的目录会被修改)
3)如果很多文件在短时间内发生变更,则有可能会丢失部分通知。
4)如果缓冲区溢出,整个缓冲区的内容都会被丢弃,BytesReturned会返回0。 5)在MSDN中,FILE_NOTIFY_INFORMATION的文档有一个关键的描述:如果文件既有长文件名,又有短文件名,那么文件会返回其中的一个名字,但不确定是返回哪一个。
大多数时候,在短文件名和长文件名之间转换都很容易,但是如果文件被删除,情况就不一样了。最好的方法是维护一个跟踪文件的列表,同时跟踪长文件名和短文件名。(这种情况目前还没有遇到过)

函数说明参考链接:
https://blog.csdn.net/dropme/article/details/6036777
https://docs.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-file_notify_information

ReadDirectoryChangesW可以监视的文件系统事件包括:
FILE_ACTION_ADDED,  新增文件
FILE_ACTION_REMOVED, 删除文件
FILE_ACTION_MODIFIED, 修改文件
FILE_ACTION_RENAMED_OLD_NAME, 重命名
FILE_ACTION_RENAMED_NEW_NAME, 重命名
void WatchDirectory(const std::string& watchedDir) {
    //#if defined(OS_WIN)
    std::wstring wstrWatchDir = Zeus::CharsetUtils::UTF8ToUnicode(watchedDir);
    HANDLE dirHandle = CreateFileW(wstrWatchDir.c_str(),
                                   GENERIC_READ | GENERIC_WRITE | FILE_LIST_DIRECTORY,
                                   FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                   NULL,
                                   OPEN_EXISTING,
                                   FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                                   NULL);
    if (dirHandle == INVALID_HANDLE_VALUE) {
        LOG_ERROR << "Failed to get handle of directory.path:" << watchedDir
            << " errCode:" << GetLastError();
        return;
    }
 
    TCHAR notify[1024];
    memset(notify, 0, sizeof(notify));
    FILE_NOTIFY_INFORMATION *pNotification = (FILE_NOTIFY_INFORMATION *)notify;
    DWORD BytesReturned = 0;
 
    while (TRUE) {
        ZeroMemory(pNotification, sizeof(notify));
        auto watch_state = ReadDirectoryChangesW(dirHandle,
                                                 ¬ify,
                                                 sizeof(notify),
                                                 TRUE,   //监控子目录
                                                 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_CREATION,
                                                 (LPDWORD)&BytesReturned,
                                                 NULL,
                                                 NULL);
 
        if (GetLastError() == ERROR_INVALID_FUNCTION) {
            LOG_WARN << u8"文件监控,系统不支持! path:" << watchedDir;
            break;
        } else if (watch_state == FALSE) {
            LOG_WARN << u8"文件监控,监控失败! path:" << watchedDir
                << " errCode:" << GetLastError();
            break;
        } else if (GetLastError() == ERROR_NOTIFY_ENUM_DIR) {
            LOG_INFO << u8"文件监控,内存溢出! path:" << watchedDir;
            continue;
        } else {
            //这里主要就是检测返回的信息,(FILE_NOTIFY_INFORMATION)
            std::wstring fileName(pNotification->FileName, pNotification->FileNameLength / sizeof(wchar_t));
            switch (pNotification->Action) {
            case FILE_ACTION_ADDED:
            {
                // to do ...
            }
            break;
            case FILE_ACTION_REMOVED:
            {
                // to do ...
            }
            break;
            case FILE_ACTION_MODIFIED:
            {
                // to do ...
            }
            break;
            default:
                break;
            }
        }
    }
    CloseHandle(dirHandle);
    //#endif //OS_WIN
}



Linux

自内核2.6.13起,Linux开始提供inotify机制,它是一个内核用于通知用户空间程序文件系统变化的机制,以允许应用程序监控文件事件。

类似的监控服务:

  • Hotplug 是一种内核向用户态应用通报关于热插拔设备一些事件发生的机制,桌面系统能够利用它对设备进行有效的管理。
  • udev 动态地维护 /dev 下的设备文件。
  • inotify 是一种文件系统的变化通知机制,如文件增加、删除等事件可以立刻让用户态得知。

注意点:

1)Inotify 不需要对被监视的目标打开文件描述符,而且如果被监视目标在可移动介质上,那么在 umount 该介质上的文件系统后,被监视目标对应的 watch 将被自动删除,并且会产生一个 umount 事件。
2)Inotify 既可以监视文件,也可以监视目录。当监控目录时,与路径自身及其所含文件相关的事件都会通知给应用程序。
3)Inotify 使用系统调用而非 SIGIO 来通知文件系统事件。
4)Inotify 使用文件描述符作为接口,因而可以使用通常的文件 I / O 操作select 和 poll 来监视文件系统的变化。
5)Inotify 监控机制为非递归。若应用程序有意监控整个目录子树内的事件,则需对该树中的每个目录发起 inotify_add_watch() 调用。

Inotify 可以监视的文件系统事件包括:

IN_ACCESS,即文件被访问
IN_MODIFY,文件被 write
IN_ATTRIB,文件属性被修改,如 chmod、chown、touch 等
IN_CLOSE_WRITE,可写文件被 close
IN_CLOSE_NOWRITE,不可写文件被 close
IN_OPEN,文件被 open
IN_MOVED_FROM,文件被移走,如 mv
IN_MOVED_TO,文件被移来,如 mv、cp
IN_CREATE,创建新文件
IN_DELETE,文件被删除,如 rm
IN_DELETE_SELF,自删除,即一个可执行文件在执行时删除自己
IN_MOVE_SELF,自移动,即一个可执行文件在执行时移动自己
IN_UNMOUNT,宿主文件系统被 umount
IN_CLOSE,文件被关闭,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
IN_MOVE,文件被移动,等同于(IN_MOVED_FROM | IN_MOVED_TO)

inotify API

#include <sys/inotify.h>
 
//Returns file descriptor on success, or -1 on error
int inotify_init(void);
                                 
//Returns watch descriptor on success, or -1 on error
int inotify_add_watch(int fd, const char* pathname, uint32_t mask);
                                 
//Returns 0 on success, or -1 on error
int inotify_rm_watch(int fd, uint32_t wd);

使用步骤:

1、应用程序使用 inotify_init() 来创建一个inotify实例,该系统调用所返回的文件描述符用于在后续操作中指代该实例。   

int fd = inotify_init ();

2、应用程序使用 inotify_add_watch() 向inotify实例的监控列表添加条目,藉此告诉内核哪些文件是自己的兴趣所在。每个监控项都包含一个路径名以及相关的位掩码。位掩码针对路径名指明了所要监控的事件集合。作为函数结果,inotify_add_watch()将返回一监控描述符,用于在后续操作中指代该监控项(系统调用 inotify_rm_watch() 执行其逆向操作,将之前添加入 inotify 实例的监控项移除)。

int wd = inotify_add_watch (fd, path, mask);

3、为获得事件通知,应用程序需针对 inotify 文件描述符执行 read() 操作。每次对 read() 的成功调用,都会返回一个或多个 inotify_event 结构,其中各自记录了处于 inotify 实例监控之下的某一路径名所发生的事件。

length = read(fd, buffer, EVENT_BUF_LEN);

struct inotify_event* event = (struct inotify_event*) & buffer[i];

注意:

1) read()函数。读常规文件是不会阻塞的,不管读多少字节,read一定会在有限的时间内返回。从终端设备或网络读则不一定,如果从终端输入的数据没有换行符,调用read读终端设备就会阻塞,如果网络上没有接收到数据包,调用read从网络读就会阻塞,至于会阻塞多长时间也是不确定的,如果一直没有数据到达就一直阻塞在那里。同样,写常规文件是不会阻塞的,而向终端设备或网络写则不一定。

参考链接:https://blog.csdn.net/u012317833/article/details/39343915

2)inotify_event结构体

struct inotify_event {
    __s32           wd;             /* 被监视目标的 watch 描述符 */
    __u32           mask;           /* 事件掩码 */
    __u32           cookie;         /* cookie to synchronize two events */
    __u32           len;            /* name字符串的长度 */
    char            name[0];        /* 被监视目标的路径名 */
};

inotify-tools工具链接:https://developer.aliyun.com/article/611376

4、应用程序在结束监控时会关闭 inotify 文件描述符。这会自动清除与 inotify 实例相关的所有监控项。

int ret = inotify_rm_watch (fd, wd);
using namespace std;
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/inotify.h>
 
#include <atomic>
 
atomic<bool> g_IsExit{ false };
 
#define EVENT_SIZE  ( sizeof (struct inotify_event) )
#define EVENT_BUF_LEN     ( 1024 * ( EVENT_SIZE + 16 ) )
 
int main() {
    int length, i = 0;
    int fd;
    int wd;
    char buffer[EVENT_BUF_LEN];
 
    /*creating the INOTIFY instance*/
    fd = inotify_init();
 
    /*checking for error*/
    if (fd < 0) {
        perror("inotify_init");
    }
 
 
    std::string watchBasePath = "/tmp";
    std::string watchPath = "/tmp/dir1";
 
    /*adding the “/tmp” directory into watch list. Here, the suggestion is to validate the existence of the directory before adding into monitoring list.*/
    wd = inotify_add_watch(fd, watchBasePath.data(), IN_ONESHOT);
 
    //添加了一个子目录进行监听,默认是不会监听子目录的
    inotify_add_watch(fd, watchPath.data(), IN_CREATE| IN_DELETE); 
    /*read to determine the event change happens on “/tmp” directory. Actually this read blocks until the change event occurs*/
 
    // 在外面加了一个循环,防止监听完一个事件后就退出了
    while (1) 
    {
        i = 0;
        length = read(fd, buffer, EVENT_BUF_LEN);
 
        /*checking for error*/
        if (length < 0) {
            perror("error to read file.");
            break;
        }
 
        if (g_IsExit) {
            break;
        }
 
        /*actually read return the list of change events happens. Here, read the change event one by one and process it accordingly.*/
        while (i < length) {
            struct inotify_event* event = (struct inotify_event*) & buffer[i];
            if (event->len) {
                if (event->mask & IN_CREATE) {
                    if (event->mask & IN_ISDIR) {
                        printf("New directory %s created.
", event->name);
                    } else {
                        auto name = event->name;
                        printf("New file %s created.
", event->name);
                        break;
                    }
                } else if (event->mask & IN_DELETE) {
                    if (event->mask & IN_ISDIR) {
                        printf("Directory %s deleted.
", event->name);
                    } else {
                        auto name = event->name;
                        printf("File %s deleted.
", event->name);
                    }
                }
            }
            i += EVENT_SIZE + event->len;
        }
 
    }
    /*removing the “/tmp” directory from the watch list.*/
    inotify_rm_watch(fd, wd);
    /*closing the INOTIFY instance*/
    close(fd);
    return 0;
}

 






发表回复

您的电子邮箱地址不会被公开。