Linux inotify 文件监控

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

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

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

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

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

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

cpp 复制代码
/* 创建 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 事件的数据结构如下:

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

inotify 事件 mask 的宏定义:

cpp 复制代码
#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 分别表示目录文件和常规文件。

cpp 复制代码
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 字段记录了打开目录下的子文件的类型

cpp 复制代码
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 字段取值如下:

cpp 复制代码
enum
  {
    DT_UNKNOWN = 0,
# define DT_UNKNOWN            DT_UNKNOWN
    DT_FIFO = 1,
# define DT_FIFO               DT_FIFO
    DT_CHR = 2,
# define DT_CHR                DT_CHR
    DT_DIR = 4,
# define DT_DIR                DT_DIR //目录文件
    DT_BLK = 6,
# define DT_BLK                DT_BLK
    DT_REG = 8,
# define DT_REG                DT_REG //常规文件
    DT_LNK = 10,
# define DT_LNK                DT_LNK
    DT_SOCK = 12,
# define DT_SOCK               DT_SOCK
    DT_WHT = 14
# define DT_WHT                DT_WHT
  };

文件监控 demo:

cpp 复制代码
#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实例,返回与该实例相关的文件描述符 fd
    if (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) //目录文件需要遍历,为目录中的所有文件添加 watch
            watch_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;
}

编译执行:

bash 复制代码
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

相关推荐
HLC++10 小时前
Linux的进程间通信
android·linux·服务器
华清远见IT开放实验室12 小时前
实验室建设案例 | 石家庄科技信息职业学院嵌入式实验室——从底层硬件到系统应用,一所应用型高校的嵌入式人才培养这样落地
linux·arm开发·stm32·嵌入式硬件·高校·实验室建设
groundhappy14 小时前
idalib安装和codex ida-mcp配置
linux·开发语言·python
通信小小昕15 小时前
Ubuntu 26.04 中文输入法安装
linux·运维·ubuntu
张小姐的猫16 小时前
【Linux】网络编程 —— HTTP协议(上)
linux·运维·服务器·网络·http·单例模式·策略模式
栩栩云生16 小时前
AI 写代码犯的错,早被写进了错题集
linux·安全·ai编程
imc.1118 小时前
linux基础IO
linux·运维·服务器
BelongPanda19 小时前
Linux Nginx 纯手动 Let‘s Encrypt 泛域名证书配置教程
linux·nginx
酷可达拉斯20 小时前
Linux操作系统-shell编程(0)
linux·运维·服务器·python·云计算
2301_7779983420 小时前
Linux中断机制:操作系统如何高效运行
linux·运维·服务器