Linux inotify 文件监控

news2024/9/25 23:18:51

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 事件的数据结构如下:

/* 创建 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_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:

#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;
}

编译执行:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1223587.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【从入门到起飞】JavaSE—IO流(1)字节输入流字符输出流

&#x1f38a;专栏【JavaSE】 &#x1f354;喜欢的诗句&#xff1a;天行健&#xff0c;君子以自强不息。 &#x1f386;音乐分享【如愿】 &#x1f384;欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f33a;概述&#x1f33a;作用&#x1f33a;分类&#x1f33…

如何去开发一个springboot starter

如何去开发一个springboot starter 我们在平时用 Java 开发的时候&#xff0c;在 pom.xml 文件中引入一个依赖就可以很方便的使用了&#xff0c;但是你们知道这是如何实现的吗。 现在我们就来解决这一个问题&#xff01; 创建 SpringBoot 项目 首先我们要做的就是把你想要给别…

Wireshark TS | 应用传输缓慢问题

问题背景 沿用之前文章的开头说明&#xff0c;应用传输慢是一种比较常见的问题&#xff0c;慢在哪&#xff0c;为什么慢&#xff0c;有时候光从网络数据包分析方面很难回答的一清二楚&#xff0c;毕竟不同的技术方向专业性太强&#xff0c;全栈大佬只能仰望&#xff0c;而我们…

【Spring篇】使用注解进行开发

&#x1f38a;专栏【Spring】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【如愿】 &#x1f970;欢迎并且感谢大家指出小吉的问题 文章目录 &#x1f33a;原代码&#xff08;无注解&#xff09;&#x1f384;加上注解⭐两个注…

20231117在ubuntu20.04下使用ZIP命令压缩文件夹

20231117在ubuntu20.04下使用ZIP命令压缩文件夹 2023/11/17 17:01 百度搜索&#xff1a;Ubuntu zip 压缩 https://blog.51cto.com/u_64214/7641253 Ubuntu压缩文件夹zip命令 原创 chenglei1208 2023-09-28 17:21:58博主文章分类&#xff1a;LINUX 小工具 文章标签命令行压缩包U…

打不开github网页解决方法

问题&#xff1a; 1、composer更新包总是失败 2、github打不开&#xff0c;访问不了 解决方法&#xff1a;下载一个Watt Toolkit工具&#xff0c;勾选上&#xff0c;一键加速就可以打开了。 下载步骤&#xff1a; 1、打开网址&#xff1a; Watt Toolkit 2、点击【下载wind…

Python (十一) 迭代器与生成器

迭代器 迭代器是访问集合元素的一种方式&#xff0c;可以记住遍历的位置的对象 迭代器有两个基本的方法&#xff1a;iter() 和 next() 字符串&#xff0c;列表或元组对象都可用于创建迭代器 字符串迭代 str1 Python str_iter iter(str1) print(next(str_iter)) print(next(st…

原型网络Prototypical Network的python代码逐行解释,新手小白也可学会!!-----系列2

文章目录 一、原始代码二、每一行代码的详细解释 一、原始代码 labels_trainData ,labels_testData load_data() wide labels_trainData[0][0].shape[0] length labels_trainData[0][0].shape[1] for label in labels_trainData.keys():labels_trainData[label] np.reshap…

FastJsonAPI

maven项目 pom.xml <dependencies><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.26</version></dependency><dependency><groupId>junit</groupId>&l…

vmware17 虚拟机拷贝、备份、复制使用

可以在虚拟机运行的情况下进行拷贝 查看新安装的虚拟机位置 跳转到上一级目录 复制虚拟机 复制虚拟机整个目录 删除lck文件&#xff0c;不然开机的时候会报错 用vmware 打开新复制的虚拟机 lck文件全部删除 点击开机 开机成功

软磁交流测试仪系统磁参量指标

1. 主要应用 2. 软磁交流测试仪磁参量指标 被测参数 最佳测量不确定度 ( k 2 ) 1 kHz 最佳测量重复性 主要动态磁特性参数 Ps 2.0% 1.0% μa 3.0% 1.0% Bm 1.0% 0.5% Hm 1.0% 0.5% δ 5.0% 1.5% 其他磁特性参数供参考 Br 2.0% 1.0% Hc 3.0% 1.0% μ…

振南技术干货集:比萨斜塔要倒了,倾斜传感器快来!(6)

注解目录 1、倾斜传感器的那些基础干货 1.1 典型应用场景 &#xff08;危楼、边坡、古建筑都是对倾斜敏感的。&#xff09; 1.2 倾斜传感器的原理 1.2.1 滚珠式倾斜开关 1.2.2 加速度式倾斜传感器 1)直接输出倾角 2)加速度计算倾角 3)倾角精度的提高 &#xff08;如果…

微积分在神经网络中的本质

calculus 在一个神经网络中我们通常将每一层的输出结果表示为&#xff1a; a [ l ] a^{[l]} a[l] 为了方便记录&#xff0c;将神经网络第一层记为&#xff1a; [ 1 ] [1] [1] 对应的计算记录为为&#xff1a; a [ l ] &#xff1a; 第 l 层 a [ j ] &#xff1a; 第 j 个神经…

How to import dgl-cu113 如何导入 dgl-cu113

参考这个 从How to import dgl-cu113 如何导入 dgl-cu113https://discuss.dgl.ai/t/how-to-import-dgl-cu113/3381https://discuss.dgl.ai/t/how-to-import-dgl-cu113/3381

vscode 推送本地新项目到gitee

一、gitee新建仓库 1、填好相关信息后点击创建 2、创建完成后复制 https&#xff0c;稍后要将本地项目与此关联 3、选择添加远程存储库 4、输入仓库地址&#xff0c;选择从URL添加远程存储仓库 5、输入仓库名称&#xff0c;确保仓库名一致

Redis:新的3种数据类型Bitmaps、HyperLoglog、Geographic

目录 Bitmaps简介常用命令bitmaps与set比较 HyperLoglog简介命令 Geographic简介命令 Bitmaps 简介 位操作字符串。 现代计算机使用二进制&#xff08;位&#xff09;作为信息的基本单位&#xff0c;1个字节等于8位&#xff0c;例如“abc”字符串是有3个字节组成&#xff0c…

开发一款回合制游戏,需要注意什么?

随着游戏行业的蓬勃发展&#xff0c;回合制游戏因其深度的策略性和令人着迷的游戏机制而受到玩家们的热烈欢迎。如果你计划投身回合制游戏的开发领域&#xff0c;本文将为你提供一份详细的指南&#xff0c;从游戏设计到发布&#xff0c;助你成功打造一款引人入胜的游戏。 1. 游…

记一次用jlink调试正常,不进入调试就不能运行的情况

一、概述 我开机会闪烁所有指示灯&#xff0c;但是重新上电时&#xff0c;指示灯并没有闪烁&#xff0c;就像"卡死"了一样。 使用jlink的swd接口进行调试&#xff0c;需要多点几次运行才能跳转到main函数里面。 调试模式第一次点击运行&#xff0c;暂停查看函数堆栈…

开源与闭源:创新与安全的平衡

目录 一、开源和闭源的优劣势比较 一、开源软件的优劣势 优势 劣势 二、闭源软件的优劣势 优势 劣势 二、开源和闭源对大模型技术发展的影响 一、机器学习领域 二、自然语言处理领域 三、数据共享、算法创新与业务拓展的差异 三、开源与闭源的商业模式比较 一、盈…