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

/* 创建 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.mfbz.cn/a/161294.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

小命令,大世界

Linux是一个大系统&#xff0c;功能丰富&#xff0c;好比是一台巨型机器&#xff0c;而命令&#xff0c;就是这台机器的操作台。要想控制好这台机器&#xff0c;用好这台机器&#xff0c;就得会看仪表&#xff0c;会操作各种按钮。《Linux常用命令自学手册》就是介绍如何操作这…

Linux上编译和安装SOFA23.06

前言 你可以直接使用编译安装好的SOFA版本Installing from all-included binaries (v23.06.00)&#xff1a; 如果你想自己编译&#xff0c;可以看我下面写的内容&#xff0c;不过绝大多数是从官网来的&#xff0c;如果和官网有出入&#xff0c;建议还是以官网为准。 在Linux下…

csapp深入理解计算机系统 bomb lab(1)phase_1

实验目的&#xff1a;进一步了解机器级代码&#xff0c;提高汇编语言、调试器和逆向工程等方面原理与技能的掌握。 实验环境&#xff1a;C、linux 实验获取&#xff1a;进入csapp官网&#xff0c;点击linux/x86-64 binary bomb下载实验压缩包。 实验说明&#xff1a;一共有6…

下一代搜索引擎会什么?

现在是北京时间2023年11月18日。聊一聊搜索。 说到搜索&#xff0c;大家首先想到的肯定是谷歌&#xff0c;百度。我把这些定义成上一个时代的搜索引擎。ChatGPT已经火热了有一年的时间了&#xff0c;大家都认为Ai搜索是下一代的搜索。但是AI搜索&#xff0c;需要的是很大算力&a…

Wireshark TS | 应用传输缓慢问题

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

前端JS 使用input完成文件上传操作,并对文件进行类型转换

使用input实现文件上传 // 定义一个用于文件上传的按钮<input type"file" name"upload1" />// accept属性用于定义允许上传的文件类型&#xff0c; onchange用于绑定文件上传之后的相应函数<input type"file" name"upload2"…

【0基础学Java第十课】-- 认识String类

10. 认识String类 10.1 String类的重要性10.2 常用方法10.2.1 字符串构造10.2.2 String对象的比较10.2.3 字符串查找10.2.4 转化10.2.5 字符串替换10.2.6 字符串拆分10.2.7 字符串截取10.2.8 字符串的不可变性10.2.9 字符串修改 10.3 StringBuilder和StringBuffer10.3.1 String…

cadence virtuoso寄生参数提取问题

问题描述&#xff1a; 寄生参数提取的最后一步出现问题 calibre View generation encountered a fatal Error.Please consult the logfile for messages. 解决办法&#xff1a; sudo gedit /etc/profile&#xff08;如果失败就切换到超级用户root&#xff0c;使用su root命令…

装修干货|卧室常见3个软装搭配问题。福州中宅装饰,福州装修

引言 作为一名软装设计师&#xff0c;我对卧室的家具及软装布置颇有心得&#xff0c;现在就给你们带来卧室装修设计一些小技巧&#xff1a; 1. 床&#xff1b;衣柜&#xff1b;床头柜的摆放 床的摆放位置非常重要&#xff0c;一般要放在离窗户稍远的地方&#xff0c;避免直接…

CV计算机视觉每日开源代码Paper with code速览-2023.11.14

点击CV计算机视觉&#xff0c;关注更多CV干货 论文已打包&#xff0c;点击进入—>下载界面 点击加入—>CV计算机视觉交流群 1.【基础网络架构&#xff1a;Transformer】Aggregate, Decompose, and Fine-Tune: A Simple Yet Effective Factor-Tuning Method for Vision…

场景交互与场景漫游-路径漫游(7)

路径漫游 按照指定的路径进行漫游对一个演示是非常重要的。在osgViewer中&#xff0c;当第一次按下小写字母“z”时&#xff0c;开始记录动画路径;待动画录制完毕&#xff0c;按下大写字母“Z”&#xff0c;保存动画路径文件;使用osgViewer读取该动画路径文件时&#xff0c;会回…

招聘小程序源码 人才招聘网源码

招聘小程序源码 人才招聘网源码 求职招聘小程序源码系统是一种基于微信小程序的招聘平台&#xff0c;它可以帮助企业和求职者快速、方便地进行招聘和求职操作。 该系统通常包括以下功能模块&#xff1a; 用户注册和登录&#xff1a;用户可以通过微信小程序注册和登录&#…

世微 降压恒流驱动IC 景观亮化洗墙灯舞台灯汽车灯LED照明 AP5199S

1. 特性 支持高辉调光&#xff0c;调光比 平均电流工作模式 高效率&#xff1a;最高可达 95% 输出电流可调范围 60mA~12A 最大工作频率 1MHz 恒流精度≤3% 支持 PWM 封装&#xff1a;SOP8 2. 应用领域 景观亮化洗墙灯 舞台调光效果灯 汽车照明 3. 说明 AP5199S…

安全框架springSecurity+Jwt+Vue-1(vue环境搭建、动态路由、动态标签页)

一、安装vue环境&#xff0c;并新建Vue项目 ①&#xff1a;安装node.js 官网(https://nodejs.org/zh-cn/) 2.安装完成之后检查下版本信息&#xff1a; ②&#xff1a;创建vue项目 1.接下来&#xff0c;我们安装vue的环境 # 安装淘宝npm npm install -g cnpm --registryhttps:/…

Mybatis学习笔记-映射文件,标签,插件

目录 概述 mybatis做了什么 原生JDBC存在什么问题 MyBatis组成部分 Mybatis工作原理 mybatis和hibernate区别 使用mybatis&#xff08;springboot&#xff09; mybatis核心-sql映射文件 基础标签说明 1.namespace&#xff0c;命名空间 2.select&#xff0c;insert&a…

TensorFlow:GPU的使用

**引言** TensorFlow 是一个由 Google 开发的开源机器学习框架&#xff0c;它提供了丰富的工具和库&#xff0c;支持开发者构建和训练各种深度学习模型。而 GPU 作为一种高性能并行计算设备&#xff0c;能够显著提升训练深度学习模型的速度&#xff0c;从而加快模型迭代和优化…

CorelDRAW2024最新版本的图形设计软件

CorelDRAW2024是Corel公司推出的最新版本的图形设计软件。CorelDRAW是一款功能强大的矢量图形编辑工具&#xff0c;被广泛用于图形设计、插图、页面布局、照片编辑和网页设计等领域。 1. 新增的设计工具&#xff1a;CorelDRAW 2024引入了一些全新的设计工具&#xff0c;使用户能…

Web(5)Burpsuite之文件上传漏洞

1.搭建网站&#xff1a;为网站设置没有用过的端口号 2.中国蚁剑软件的使用 通过一句话木马获得权限 3.形象的比喻&#xff08;风筝&#xff09; 4.实验操作 参考文章&#xff1a; 文件上传之黑名单绕过_文件上传黑名单绕过_pigzlfa的博客-CSDN博客 后端验证特性 与 Window…

再也不用担心忘记密码了!如何在Windows 10或11中重置被遗忘的密码

​如果你忘记了Windows电脑的密码,不要惊慌。Windows 10和Windows 11都允许你重置忘记的密码,无论你使用的是Microsoft帐户还是本地帐户。你所要做的就是回答你的安全问题以重置密码。另一种选择是创建一个密码重置盘,你可以在任何U盘上进行。 除了使用密码之外,你还应该启…

【MySQL】索引与事务

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文录入于《MySQL》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力)打造&a…