Linux 内核 2.6.13 以后,引入了 inotify 文件系统监控功能,通过 inotify 可以对敏感目录设置事件监听。这样的功能被也被包装成了一个文件监控神器 inotify-tools。
使用 inotify 进行文件监控的过程:
- 创建 inotify 实例,获取 inotify 事件队列文件描述符
- 为监控的文件逐一添加 watch,绑定 inotify 事件队列文件描述符,确定监控事件
- 使用 inotify 事件队列文件描述符读取产生的监控事件
- 完成以上操作后,关闭inotify事件队列文件描述符
除了以上的核心过程,一个文件监控系统还需要包含:监控文件的获取、监控事件的解析和数据补充。
inotify 文件事件监控核心部分所涉及的 API 如下(包含在 <sys/inotify.h> 中):
read 每次通过文件描述符读取的 inotify 事件队列中一个事件,事件的 mask 标记了文件发生的事件。inotify 事件的数据结构如下:
/* 创建 inotify 实例,获取文件描述符 fd */
int inotify_init(void);//初始化一个新的 inotify 实例,返回一个与新的 inotify 事件队列关联的文件描述符
int inotify_init1(int flags);//如果flags为0,功能与inotify_init()相同/* 添加 watch */
int inotify_add_watch(int fd, const char *pathname, uint32_t mask); //对于在pathname 中指定位置的文件,添加一个新的 watch,或者修改一个现有的 watch
int inotify_rm_watch(int fd, int wd);//从 inotify 中删除现有 watch 实例/* 读取文件事件 */
ssize_t read(int fd, void *buf, size_t count);//尝试从inotify 事件队列关联的文件描述符fd读取多达count个字节到从buf开始的缓冲区中。成功时,返回读取的字节数(零表示文件结尾),文件位置按此数字前进。/* 关闭文件描述符 */
int close(int fd);//关闭一个inotify 事件队列关联的文件描述符,使其不再引用任何文件
read 每次通过文件描述符读取的 inotify 事件队列中一个事件,事件的 mask 标记了文件发生的事件。inotify 事件的数据结构如下:
struct inotify_event {int wd; /* 文件的监控描述符 */uint32_t mask; /* 文件事件的掩码 */uint32_t cookie; /* 重命名事件相关的唯一整数。对于所有其他事件类型,cookie 设置为 0 */uint32_t len; /* 文件名称的长度 */char name[]; /* 被监控的文件名称 */
};
inotify 事件 mask 的宏定义:
#define IN_ACCESS 0x00000001 /* File was accessed. */
#define IN_MODIFY 0x00000002 /* File was modified. */
#define IN_ATTRIB 0x00000004 /* Metadata changed. */
#define IN_CLOSE_WRITE 0x00000008 /* Writtable file was closed. */
#define IN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed. */
#define IN_OPEN 0x00000020 /* File was opened. */
#define IN_MOVED_FROM 0x00000040 /* File was moved from X. */
#define IN_MOVED_TO 0x00000080 /* File was moved to Y. */
#define IN_CREATE 0x00000100 /* Subfile was created. */
#define IN_DELETE 0x00000200 /* Subfile was deleted. */
#define IN_DELETE_SELF 0x00000400 /* Self was deleted. */
#define IN_MOVE_SELF 0x00000800 /* Self was moved. */
inotify 没有实现对目录的递归监控,需要自己添加这部分的功能,因此要判断文件类型,对于常规文件和目录文件分别进行处理。
Linux 下的文件元信息,可以通过 stat() 读取,st_mode 字段记录了文件的类型,取值 S_IFDIR、S_IFREG 分别表示目录文件和常规文件。
struct stat {dev_t st_dev; /* ID of device containing file */ino_t st_ino; /* Inode number */mode_t st_mode; /* File type and mode */nlink_t st_nlink; /* Number of hard links *//* 此处省略部分数据 */
};
Linux 下 使用 readdir 打开目录获取目录信息,此函数返回一个 dirent 结构体,它的 d_type 字段记录了打开目录下的子文件的类型
struct dirent {ino_t d_ino; /* Inode number */off_t d_off; /* Not an offset; see below */unsigned short d_reclen; /* Length of this record */unsigned char d_type; /* Type of file; not supported by all filesystem types */char d_name[256]; /* Null-terminated filename */
};
d_type 字段取值如下:
enum{DT_UNKNOWN = 0,
# define DT_UNKNOWN DT_UNKNOWNDT_FIFO = 1,
# define DT_FIFO DT_FIFODT_CHR = 2,
# define DT_CHR DT_CHRDT_DIR = 4,
# define DT_DIR DT_DIR //目录文件DT_BLK = 6,
# define DT_BLK DT_BLKDT_REG = 8,
# define DT_REG DT_REG //常规文件DT_LNK = 10,
# define DT_LNK DT_LNKDT_SOCK = 12,
# define DT_SOCK DT_SOCKDT_WHT = 14
# define DT_WHT DT_WHT};
文件监控 demo:
#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include<iostream>using std::string;string event_str[12] =
{ "IN_ACCESS", //文件被访问"IN_MODIFY", //文件修改"IN_ATTRIB", //文件元数据修改"IN_CLOSE_WRITE","IN_CLOSE_NOWRITE","IN_OPEN","IN_MOVED_FROM", //文件移动from"IN_MOVED_TO", //文件移动to"IN_CREATE", //文件创建"IN_DELETE", //文件删除"IN_DELETE_SELF","IN_MOVE_SELF"
};class FileMonitor
{
public:void start_watch(int size, char *file_list[]);int watch_dir(const char *dir_path);FileMonitor();~FileMonitor();
private:int fd;
};FileMonitor::FileMonitor()
{fd = inotify_init1(IN_NONBLOCK);//创建inotify实例,返回与该实例相关的文件描述符 fdif (fd == -1) {std::cerr<<"Error: inotifiy initial failed !"<<std::endl;exit(EXIT_FAILURE);}
}FileMonitor::~FileMonitor()
{if (fd > 0)close(fd);
}void FileMonitor::start_watch(int size, char *file_list[])
{ struct stat file_info;int wd, file_type, event_list_len;struct inotify_event *event;char buf[8192] __attribute__ ((aligned(__alignof__(struct inotify_event))));for (int i=1; i < size; i++){stat(file_list[i], &file_info);if (file_info.st_mode & S_IFREG)//普通文件直接添加 watch{wd = inotify_add_watch(fd, file_list[i], IN_ALL_EVENTS);if (wd == -1){std::cerr<<"Error: cannot watch "<<file_list[i]<<" !"<<std::endl;exit(EXIT_FAILURE);}}if (file_info.st_mode & S_IFDIR) //目录文件需要遍历,为目录中的所有文件添加 watchwatch_dir(file_list[i]);}//std::cout<<"start listening for events"<<std::endl;int event_len = sizeof(struct inotify_event);//读取inotify事件队列中的事件while (1){if ((event_list_len = read(fd, buf, sizeof(buf))) > 0)for (char *ptr = buf; ptr < buf+event_list_len; ptr += event_len + event->len){event = (struct inotify_event *)ptr;//解析文件事件for (int i = 1; i < 12; i++){if ((event->mask >> i) & 1){std::cout<<event->name<<": "<<event_str[i-1]<<std::endl;/** event->name获取的是相对路径,获取绝对路径需要额外进行路径存储** 这里可以针对敏感文件设置告警*/}}}}
}int FileMonitor::watch_dir(const char *dir_path)
{DIR *dir;//打开的目录struct dirent *dir_container;//打开目录内容int wd, path_len, count = 0;string path = dir_path, path_str, prnt_path[1000];//当前目录、临时变量、子目录数组if ((dir = opendir(dir_path)) == NULL){std::cerr<<"Error: cannot open directory '"<<dir_path<<"' !"<<std::endl;return -1;}wd = inotify_add_watch(fd, dir_path, IN_ALL_EVENTS);//为目录添加监控if (wd == -1){std::cerr<<"Error: cannot watch '"<<dir_path<<"' !"<<std::endl;return -1;}while((dir_container = readdir(dir)) != NULL){path_len = path.length();path_str = path[path_len-1];if (path_str.compare( "/") != 0)path += "/";path_str = path + (string)dir_container->d_name;//子文件绝对路径//std::cout<<"path: "<<path_str<<std::endl;if (dir_container->d_type == DT_REG){inotify_add_watch(fd, (char *)path_str.c_str(), IN_ALL_EVENTS);//常规文件直接添加监控continue;}if (dir_container->d_type == DT_DIR&& strcmp(".", dir_container->d_name) != 0&& strcmp(dir_container->d_name,"..") != 0){//目录文件加入子目录数组,等待递归遍历prnt_path[count] = path_str;count++;}}closedir(dir);while (count > 0){count--;watch_dir(prnt_path[count].c_str());//递归遍历目录,添加监控}return 0;
}int main(int argc, char* argv[])
{if (argc < 2){std::cerr<<"Error: no watching file!"<<std::endl;exit(1);}FileMonitor monitor;monitor.start_watch(argc, argv);return 0;
}
编译执行:
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