• Linux inotify 文件监控


    Linux 内核 2.6.13 以后,引入了 inotify 文件系统监控功能,通过 inotify 可以对敏感目录设置事件监听。这样的功能被也被包装成了一个文件监控神器 inotify-tools。

    使用 inotify 进行文件监控的过程:

    1. 创建 inotify 实例,获取 inotify 事件队列文件描述符
    2. 为监控的文件逐一添加 watch,绑定 inotify 事件队列文件描述符,确定监控事件
    3. 使用 inotify 事件队列文件描述符读取产生的监控事件
    4. 完成以上操作后,关闭inotify事件队列文件描述符

    除了以上的核心过程,一个文件监控系统还需要包含:监控文件的获取、监控事件的解析和数据补充。

    inotify 文件事件监控核心部分所涉及的 API 如下(包含在 中):

    read 每次通过文件描述符读取的 inotify 事件队列中一个事件,事件的 mask 标记了文件发生的事件。inotify 事件的数据结构如下:

    1. /* 创建 inotify 实例,获取文件描述符 fd */
    2. int inotify_init(void);//初始化一个新的 inotify 实例,返回一个与新的 inotify 事件队列关联的文件描述符
    3. int inotify_init1(int flags);//如果flags为0,功能与inotify_init()相同
    4. /* 添加 watch */
    5. int inotify_add_watch(int fd, const char *pathname, uint32_t mask); //对于在pathname 中指定位置的文件,添加一个新的 watch,或者修改一个现有的 watch
    6. int inotify_rm_watch(int fd, int wd);//从 inotify 中删除现有 watch 实例
    7. /* 读取文件事件 */
    8. ssize_t read(int fd, void *buf, size_t count);//尝试从inotify 事件队列关联的文件描述符fd读取多达count个字节到从buf开始的缓冲区中。成功时,返回读取的字节数(零表示文件结尾),文件位置按此数字前进。
    9. /* 关闭文件描述符 */
    10. int close(int fd);//关闭一个inotify 事件队列关联的文件描述符,使其不再引用任何文件

    read 每次通过文件描述符读取的 inotify 事件队列中一个事件,事件的 mask 标记了文件发生的事件。inotify 事件的数据结构如下:

    1. struct inotify_event {
    2.     int      wd;       /* 文件的监控描述符 */
    3.     uint32_t mask;     /* 文件事件的掩码 */
    4.     uint32_t cookie;   /* 重命名事件相关的唯一整数。对于所有其他事件类型,cookie 设置为 0 */
    5.     uint32_t len;      /* 文件名称的长度 */
    6.     char     name[];   /* 被监控的文件名称 */
    7. };

    inotify 事件 mask 的宏定义:

    1. #define IN_ACCESS 0x00000001 /* File was accessed. */
    2. #define IN_MODIFY 0x00000002 /* File was modified. */
    3. #define IN_ATTRIB 0x00000004 /* Metadata changed. */
    4. #define IN_CLOSE_WRITE 0x00000008 /* Writtable file was closed. */
    5. #define IN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed. */
    6. #define IN_OPEN 0x00000020 /* File was opened. */
    7. #define IN_MOVED_FROM 0x00000040 /* File was moved from X. */
    8. #define IN_MOVED_TO 0x00000080 /* File was moved to Y. */
    9. #define IN_CREATE 0x00000100 /* Subfile was created. */
    10. #define IN_DELETE 0x00000200 /* Subfile was deleted. */
    11. #define IN_DELETE_SELF 0x00000400 /* Self was deleted. */
    12. #define IN_MOVE_SELF 0x00000800 /* Self was moved. */

    inotify 没有实现对目录的递归监控,需要自己添加这部分的功能,因此要判断文件类型,对于常规文件和目录文件分别进行处理。

    Linux 下的文件元信息,可以通过 stat() 读取,st_mode 字段记录了文件的类型,取值 S_IFDIR、S_IFREG 分别表示目录文件和常规文件。

    1. struct stat {
    2. dev_t st_dev; /* ID of device containing file */
    3. ino_t st_ino; /* Inode number */
    4. mode_t st_mode; /* File type and mode */
    5. nlink_t st_nlink; /* Number of hard links */
    6. /* 此处省略部分数据 */
    7. };

    Linux 下 使用 readdir 打开目录获取目录信息,此函数返回一个 dirent 结构体,它的 d_type 字段记录了打开目录下的子文件的类型

    1. struct dirent {
    2. ino_t d_ino; /* Inode number */
    3. off_t d_off; /* Not an offset; see below */
    4. unsigned short d_reclen; /* Length of this record */
    5. unsigned char d_type; /* Type of file; not supported by all filesystem types */
    6. char d_name[256]; /* Null-terminated filename */
    7. };

    d_type 字段取值如下:

    1. enum
    2. {
    3. DT_UNKNOWN = 0,
    4. # define DT_UNKNOWN DT_UNKNOWN
    5. DT_FIFO = 1,
    6. # define DT_FIFO DT_FIFO
    7. DT_CHR = 2,
    8. # define DT_CHR DT_CHR
    9. DT_DIR = 4,
    10. # define DT_DIR DT_DIR //目录文件
    11. DT_BLK = 6,
    12. # define DT_BLK DT_BLK
    13. DT_REG = 8,
    14. # define DT_REG DT_REG //常规文件
    15. DT_LNK = 10,
    16. # define DT_LNK DT_LNK
    17. DT_SOCK = 12,
    18. # define DT_SOCK DT_SOCK
    19. DT_WHT = 14
    20. # define DT_WHT DT_WHT
    21. };

    文件监控 demo:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. using std::string;
    12. string event_str[12] =
    13. {
    14. "IN_ACCESS", //文件被访问
    15. "IN_MODIFY", //文件修改
    16. "IN_ATTRIB", //文件元数据修改
    17. "IN_CLOSE_WRITE",
    18. "IN_CLOSE_NOWRITE",
    19. "IN_OPEN",
    20. "IN_MOVED_FROM", //文件移动from
    21. "IN_MOVED_TO", //文件移动to
    22. "IN_CREATE", //文件创建
    23. "IN_DELETE", //文件删除
    24. "IN_DELETE_SELF",
    25. "IN_MOVE_SELF"
    26. };
    27. class FileMonitor
    28. {
    29. public:
    30. void start_watch(int size, char *file_list[]);
    31. int watch_dir(const char *dir_path);
    32. FileMonitor();
    33. ~FileMonitor();
    34. private:
    35. int fd;
    36. };
    37. FileMonitor::FileMonitor()
    38. {
    39. fd = inotify_init1(IN_NONBLOCK);//创建inotify实例,返回与该实例相关的文件描述符 fd
    40. if (fd == -1) {
    41. std::cerr<<"Error: inotifiy initial failed !"<
    42. exit(EXIT_FAILURE);
    43. }
    44. }
    45. FileMonitor::~FileMonitor()
    46. {
    47. if (fd > 0)
    48. close(fd);
    49. }
    50. void FileMonitor::start_watch(int size, char *file_list[])
    51. {
    52. struct stat file_info;
    53. int wd, file_type, event_list_len;
    54. struct inotify_event *event;
    55. char buf[8192] __attribute__ ((aligned(__alignof__(struct inotify_event))));
    56. for (int i=1; i < size; i++)
    57. {
    58. stat(file_list[i], &file_info);
    59. if (file_info.st_mode & S_IFREG)//普通文件直接添加 watch
    60. {
    61. wd = inotify_add_watch(fd, file_list[i], IN_ALL_EVENTS);
    62. if (wd == -1)
    63. {
    64. std::cerr<<"Error: cannot watch "<" !"<
    65. exit(EXIT_FAILURE);
    66. }
    67. }
    68. if (file_info.st_mode & S_IFDIR) //目录文件需要遍历,为目录中的所有文件添加 watch
    69. watch_dir(file_list[i]);
    70. }
    71. //std::cout<<"start listening for events"<
    72. int event_len = sizeof(struct inotify_event);
    73. //读取inotify事件队列中的事件
    74. while (1)
    75. {
    76. if ((event_list_len = read(fd, buf, sizeof(buf))) > 0)
    77. for (char *ptr = buf; ptr < buf+event_list_len; ptr += event_len + event->len){
    78. event = (struct inotify_event *)ptr;
    79. //解析文件事件
    80. for (int i = 1; i < 12; i++){
    81. if ((event->mask >> i) & 1){
    82. std::cout<name<<": "<-1]<
    83. /*
    84. * event->name获取的是相对路径,获取绝对路径需要额外进行路径存储
    85. *
    86. * 这里可以针对敏感文件设置告警
    87. */
    88. }
    89. }
    90. }
    91. }
    92. }
    93. int FileMonitor::watch_dir(const char *dir_path)
    94. {
    95. DIR *dir;//打开的目录
    96. struct dirent *dir_container;//打开目录内容
    97. int wd, path_len, count = 0;
    98. string path = dir_path, path_str, prnt_path[1000];//当前目录、临时变量、子目录数组
    99. if ((dir = opendir(dir_path)) == NULL)
    100. {
    101. std::cerr<<"Error: cannot open directory '"<"' !"<
    102. return -1;
    103. }
    104. wd = inotify_add_watch(fd, dir_path, IN_ALL_EVENTS);//为目录添加监控
    105. if (wd == -1)
    106. {
    107. std::cerr<<"Error: cannot watch '"<"' !"<
    108. return -1;
    109. }
    110. while((dir_container = readdir(dir)) != NULL)
    111. {
    112. path_len = path.length();
    113. path_str = path[path_len-1];
    114. if (path_str.compare( "/") != 0)
    115. path += "/";
    116. path_str = path + (string)dir_container->d_name;//子文件绝对路径
    117. //std::cout<<"path: "<
    118. if (dir_container->d_type == DT_REG)
    119. {
    120. inotify_add_watch(fd, (char *)path_str.c_str(), IN_ALL_EVENTS);//常规文件直接添加监控
    121. continue;
    122. }
    123. if (dir_container->d_type == DT_DIR
    124. && strcmp(".", dir_container->d_name) != 0
    125. && strcmp(dir_container->d_name,"..") != 0)
    126. {//目录文件加入子目录数组,等待递归遍历
    127. prnt_path[count] = path_str;
    128. count++;
    129. }
    130. }
    131. closedir(dir);
    132. while (count > 0){
    133. count--;
    134. watch_dir(prnt_path[count].c_str());//递归遍历目录,添加监控
    135. }
    136. return 0;
    137. }
    138. int main(int argc, char* argv[])
    139. {
    140. if (argc < 2){
    141. std::cerr<<"Error: no watching file!"<
    142. exit(1);
    143. }
    144. FileMonitor monitor;
    145. monitor.start_watch(argc, argv);
    146. return 0;
    147. }

    编译执行:

    c++ -o test -std=c++11 test.cpp && ./test /var/log

    参考:

    inotify(7) - Linux manual page

    stat(2) - Linux manual page

    readdir(3) - Linux manual page

    dirent.h

    sys_stat.h(0p) - Linux manual page

  • 相关阅读:
    防水运动蓝牙耳机,分体式蓝牙耳机品牌推荐
    CI/CD简介
    一致性 hash 环
    MongoDB副本集集群搭建
    STM32 Cube MX以及STM32 H750 XBH6新建工程,HAL库,LL库
    计算机毕业论文选题java毕业设计软件源代码strust2+mybatis客户关系系统[包运行成功]
    React组件 - 实现侧滑删除
    各种ui框架的 form校验 validator获取不到value
    EATON XV-440-10TVB-1-13-1工业显示屏模块
    DTC商业模式研报 | 创新DTC策略利于提升业务灵活性和数字化体验
  • 原文地址:https://blog.csdn.net/SHELLCODE_8BIT/article/details/134479361