文章目录
fanotify实现原理源码分析
fanotify是一种文件监控的技术,一种比较主流的应用场景就是杀毒软件的文件扫描(或者其他安全软件对于文档的监控),当文件修改或者创建的时候收到相关事件。如下是一个fanotify的基本使用示例:
#include <stdio.h>
#include <fcntl.h>
#include <poll.h>
#include <limits.h>
#include <sys/fanotify.h>
#include <errno.h>
#include <unistd.h>
static void handle_events(int fd)
{
char buffer[4096] = { 0 };
const struct fanotify_event_metadata* metadata = NULL;
size_t len = 0;
while (1)
{
len = read(fd, buffer, sizeof(buffer));
if (len == (size_t)-1)
{
break;
}
if (len == 0)
{
break;
}
metadata = (const struct fanotify_event_metadata*)(buffer);
while (FAN_EVENT_OK(metadata, len))
{
if (metadata->vers != FANOTIFY_METADATA_VERSION)
{
break;
}
if (metadata->fd >= 0)
{
char file_path[PATH_MAX] = { 0 };
char fd_path[PATH_MAX] = { 0 };
if (metadata->mask & FAN_OPEN_PERM)
{
struct fanotify_response response;
response.fd = metadata->fd;
response.response = FAN_ALLOW;
write(fd, &response, sizeof(response));
printf("FAN_OPEN_PERM: ");
}
if (metadata->mask & FAN_CLOSE_WRITE)
{
printf("FAN_CLOSE_WRITE: ");
}
snprintf(fd_path, sizeof(fd_path), "/proc/self/fd/%d", metadata->fd);
(void)readlink(fd_path, file_path, sizeof(file_path) - 1);
printf("%s.\n", file_path);
close(metadata->fd);
}
metadata = FAN_EVENT_NEXT(metadata, len);
}
}
}
int main(int argc, char* argv[])
{
int fd = -1;
int ret = 0;
struct pollfd fds[1] = { 0 };
if (argc != 2)
{
return -1;
}
fd = fanotify_init(FAN_CLASS_CONTENT | FAN_CLOEXEC | FAN_NONBLOCK, O_RDONLY);
if (-1 == fd)
{
return -1;
}
ret = fanotify_mark(fd, FAN_MARK_ADD | FAN_MARK_MOUNT, FAN_OPEN_PERM | FAN_CLOSE_WRITE, AT_FDCWD, argv[1]);
if (ret != 0)
{
close(fd);
return -1;
}
fds[0].fd = fd;
fds[0].events = POLLIN;
while (1)
{
int poll_num = poll(fds, 1, -1);
if (poll_num == -1)
{
break;
}
if (poll_num > 0)
{
if (fds[0].events & POLLIN)
{
handle_events(fd);
}
}
}
close(fd);
fd = -1;
return ret;
}
从上面我们可以发现,fanotify的使用是非常简单的,那么fanotify具体是怎么被实现的呢?下面通过源码来看一下fanotify的实现原理。
1. fanotify_init
1.1 使用
fanotify_init
函数的声明如下:
int fanotify_init(unsigned int flags, unsigned int event_f_flags);
这个函数返回一个fsnotify_group
的文件描述符,这个函数有两个参数:
flags
用来设置fsnotify_group
的相关属性。event_f_flags
这个是对通用的文件描述符的标志属性。
因此flags
才是我们熟悉fanotify_init
使用的关键,这个参数分为两组:
fanotify_init
,fanotify_init
和fanotify_init
表示event(文件消息)的通知时机。FAN_CLOEXEC
,FAN_NONBLOCK
,FAN_UNLIMITED_QUEUE
和FAN_UNLIMITED_MARKS
是其他的附加标记。
对于每个成员的取值含义,可以参加帮助文件,fanotify_init
的使用如下:
fan_fd = fanotify_init(FAN_CLASS_CONTENT | FAN_CLOEXEC | FAN_NONBLOCK, O_RDONLY);
1.2 fanotify_init
fanotify_init
在Linux中的函数响应为:
SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
这个函数的实现如下:
SYSCALL_DEFINE2(fanotify_init, unsigned int, flags, unsigned int, event_f_flags)
{
struct fsnotify_group *group;
int f_flags, fd;
struct user_struct *user;
struct fanotify_event *oevent;
/*
1. 是否拥有管理员权限
2. 从这里可以发现fanotify_init的调用,需要管理员权限的支持
*/
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
/*
1. flags所支持的位判断。
2. CONFIG_AUDITSYSCALL可以发现,审计信息支持配置是否支持
3. event_f_flags所支持位的判断
*/
#ifdef CONFIG_AUDITSYSCALL
if (flags & ~(FANOTIFY_INIT_FLAGS | FAN_ENABLE_AUDIT))
#else
if (flags & ~FANOTIFY_INIT_FLAGS)
#endif
return -EINVAL;
if (event_f_flags & ~FANOTIFY_INIT_ALL_EVENT_F_BITS)
return -EINVAL;
switch (event_f_flags & O_ACCMODE) {
case O_RDONLY:
case O_RDWR:
case O_WRONLY:
break;
default:
return -EINVAL;
}
if ((flags & FAN_REPORT_FID) &&
(flags & FANOTIFY_CLASS_BITS) != FAN_CLASS_NOTIF)
return -EINVAL;
/*
1. 判断每个用户注册的listeners是否超过了FANOTIFY_DEFAULT_MAX_LISTENERS
*/
user = get_current_user();
if (atomic_read(&user->fanotify_listeners) > FANOTIFY_DEFAULT_MAX_LISTENERS) {
free_uid(user);
return -EMFILE;
}
/*
1. fd的标记
*/
f_flags = O_RDWR | FMODE_NONOTIFY;
if (flags & FAN_CLOEXEC)
f_flags |= O_CLOEXEC;
if (flags & FAN_NONBLOCK)
f_flags |= O_NONBLOCK;
/*
1. 使用fsnotify_alloc_group分配一个struct fsnotify_group内存。
2. 并且做简单的初始化
*/
group = fsnotify_alloc_group(&fanotify_fsnotify_ops);
if (IS_ERR(group)) {
free_uid(user);
return PTR_ERR(group);
}
/*
1. fsnotify_group赋予给用户
2. 给fsnotify_group一个独立的mem_cgroup。
3. 做其他相关初始化
*/
group->fanotify_data.user = user;
group->fanotify_data.flags = flags;
atomic_inc(&user->fanotify_listeners);
group->memcg = get_mem_cgroup_from_mm(current->mm);
oevent = fanotify_alloc_event(group, NULL, FS_Q_OVERFLOW, NULL,
FSNOTIFY_EVENT_NONE, NULL);
if (unlikely(!oevent)) {
fd = -ENOMEM;
goto out_destroy_group;
}
group->overflow_event = &oevent->fse;
if (force_o_largefile())
event_f_flags |= O_LARGEFILE;
group->fanotify_data.f_flags = event_f_flags;
init_waitqueue_head(&group->fanotify_data.access_waitq);
INIT_LIST_HEAD(&group->fanotify_data.access_list);
/*
1. 设置好通知的级别
*/
switch (flags & FANOTIFY_CLASS_BITS) {
case FAN_CLASS_NOTIF:
group->priority = FS_PRIO_0;
break;
case FAN_CLASS_CONTENT:
group->priority = FS_PRIO_1;
break;
case FAN_CLASS_PRE_CONTENT:
group->priority = FS_PRIO_2;
break;
default:
fd = -EINVAL;
goto out_destroy_group;
}
/*
1. event队列大小和mark的大小
*/
if (flags & FAN_UNLIMITED_QUEUE) {
fd = -EPERM;
if (!capable(CAP_SYS_ADMIN))
goto out_destroy_group;
group->max_events = UINT_MAX;
}
else {
group->max_events = FANOTIFY_DEFAULT_MAX_EVENTS;
}
if (flags & FAN_UNLIMITED_MARKS) {
fd = -EPERM;
if (!capable(CAP_SYS_ADMIN))
goto out_destroy_group;
group->fanotify_data.max_marks = UINT_MAX;
}
else {
group->fanotify_data.max_marks = FANOTIFY_DEFAULT_MAX_MARKS;
}
if (flags & FAN_ENABLE_AUDIT) {
fd = -EPERM;
if (!capable(CAP_AUDIT_WRITE))
goto out_destroy_group;
}
//分配一个fd
fd = anon_inode_getfd("[fanotify]", &fanotify_fops, group, f_flags);
if (fd < 0)
goto out_destroy_group;
return fd;
out_destroy_group:
fsnotify_destroy_group(group);
return fd;
}
这个函数的主要流程为:
- 简要相关权限以及各种参数。
- 分配
struct fsnotify_group
结构体,并初始化。 - 使用
anon_inode_getfd
创建一个匿名的文件描述符。
例如我们可以看到如下的文件描述符:
$ sudo ls -al /proc/3797/fd | grep anon
lrwx------. 1 root root 64 Jun 20 22:04 3 -> anon_inode:[fanotify]
2. fanotify_mark
2.1 使用
fanotify_mark
函数的原型声明如下:
int fanotify_mark(int fanotify_fd, unsigned int flags,
uint64_t mask, int dirfd, const char *pathname);
对于这个函数的参数如下:
fanotify_fd
为使用fanotify_init
创建的文件描述符,表示这个函数操作的struct fsnotify_group
结构体对象。flags
表示怎么操作,有两种参数选择:FAN_MARK_ADD
,FAN_MARK_REMOVE
以及FAN_MARK_FLUSH
表示怎么操作(增加,移除,还是清理所有规则)。FAN_MARK_DONT_FOLLOW
,FAN_MARK_ONLYDIR
,FAN_MARK_MOUNT
以及FAN_MARK_IGNORED_MASK
,对于这些选项的意义可以参考帮助文档。
mask
表示对文件事件的监控,例如:FAN_ACCESS
,FAN_MODIFY
以及FAN_CLOSE_WRITE
等等。dirfd
和pathname
表示着文件的路径。
2.2 do_fanotify_mark
fanotify_mark
在内核中的系统调用如下:
SYSCALL_DEFINE5(fanotify_mark, int, fanotify_fd, unsigned int, flags,
__u64, mask, int, dfd,
const char __user *, pathname)
{
return do_fanotify_mark(fanotify_fd, flags, mask, dfd, pathname);
}
fanotify_mark
系统调用主要通过do_fanotify_mark
来实现,这个函数实现大致如下:
static int do_fanotify_mark(int fanotify_fd, unsigned int flags, __u64 mask,
int dfd, const char __user *pathname)
{
struct inode *inode = NULL;
struct vfsmount *mnt = NULL;
struct fsnotify_group *group;
struct fd f;
struct path path;
__kernel_fsid_t __fsid, *fsid = NULL;
u32 valid_mask = FANOTIFY_EVENTS | FANOTIFY_EVENT_FLAGS;
unsigned int mark_type = flags & FANOTIFY_MARK_TYPE_BITS;
int ret;
/*
1. 校验falgs和mask的值是否合法。
*/
if (mask & ((__u64)0xffffffff << 32))
return -EINVAL;
if (flags & ~FANOTIFY_MARK_FLAGS)
return -EINVAL;
switch (mark_type) {
case FAN_MARK_INODE:
case FAN_MARK_MOUNT:
case FAN_MARK_FILESYSTEM:
break;
default:
return -EINVAL;
}
switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE | FAN_MARK_FLUSH)) {
case FAN_MARK_ADD:
case FAN_MARK_REMOVE:
if (!mask)
return -EINVAL;
break;
case FAN_MARK_FLUSH:
if (flags & ~(FANOTIFY_MARK_TYPE_BITS | FAN_MARK_FLUSH))
return -EINVAL;
break;
default:
return -EINVAL;
}
if (IS_ENABLED(CONFIG_FANOTIFY_ACCESS_PERMISSIONS))
valid_mask |= FANOTIFY_PERM_EVENTS;
if (mask & ~valid_mask)
return -EINVAL;
f = fdget(fanotify_fd);
if (unlikely(!f.file))
return -EBADF;
/*
1. 获取文件描述符对应的file
2. 从file中获取fsnotify_group
*/
ret = -EINVAL;
if (unlikely(f.file->f_op != &fanotify_fops))
goto fput_and_out;
group = f.file->private_data;
ret = -EINVAL;
if (mask & FANOTIFY_PERM_EVENTS &&
group->priority == FS_PRIO_0)
goto fput_and_out;
if (mask & FANOTIFY_INODE_EVENTS &&
(!FAN_GROUP_FLAG(group, FAN_REPORT_FID) ||
mark_type == FAN_MARK_MOUNT))
goto fput_and_out;
/*
1. 如果使用FAN_MARK_FLUSH清理标记,直接操作即可(无需文件路径)。
2. 使用fsnotify_clear_完成清理
*/
if (flags & FAN_MARK_FLUSH) {
ret = 0;
if (mark_type == FAN_MARK_MOUNT)
fsnotify_clear_vfsmount_marks_by_group(group);
else if (mark_type == FAN_MARK_FILESYSTEM)
fsnotify_clear_sb_marks_by_group(group);
else
fsnotify_clear_inode_marks_by_group(group);
goto fput_and_out;
}
//获取文件路径
ret = fanotify_find_path(dfd, pathname, &path, flags);
if (ret)
goto fput_and_out;
if (FAN_GROUP_FLAG(group, FAN_REPORT_FID)) {
ret = fanotify_test_fid(&path, &__fsid);
if (ret)
goto path_put_and_out;
fsid = &__fsid;
}
if (mark_type == FAN_MARK_INODE)
inode = path.dentry->d_inode;
else
mnt = path.mnt;
/*
1. 开始做各种mark操作
2. 对于FAN_MARK_ADD添加目录使用fanotify_add_xxx来添加路径信息。
3. 对于FAN_MARK_REMOVE移除目录,使用fanotify_remove_xxx来移除标记
*/
switch (flags & (FAN_MARK_ADD | FAN_MARK_REMOVE)) {
case FAN_MARK_ADD:
if (mark_type == FAN_MARK_MOUNT)
ret = fanotify_add_vfsmount_mark(group, mnt, mask,
flags, fsid);
else if (mark_type == FAN_MARK_FILESYSTEM)
ret = fanotify_add_sb_mark(group, mnt->mnt_sb, mask,
flags, fsid);
else
ret = fanotify_add_inode_mark(group, inode, mask,
flags, fsid);
break;
case FAN_MARK_REMOVE:
if (mark_type == FAN_MARK_MOUNT)
ret = fanotify_remove_vfsmount_mark(group, mnt, mask,
flags);
else if (mark_type == FAN_MARK_FILESYSTEM)
ret = fanotify_remove_sb_mark(group, mnt->mnt_sb, mask,
flags);
else
ret = fanotify_remove_inode_mark(group, inode, mask,
flags);
break;
default:
ret = -EINVAL;
}
path_put_and_out:
path_put(&path);
fput_and_out:
fdput(f);
return ret;
}
这个函数所作的事情主要有3个:
- 校验
flags
和mask
各种标记的合法性。 - 通过fd获取file结构体,然后获取
fsnotify_group
. - 对于不同类型的操作,进行不同处理:
FAN_MARK_FLUSH
使用fsnotify_clear_xxxx
进程清理所有mark的路径。FAN_MARK_ADD
使用fanotify_add_xxxx
将路径加入到fsnotify_group
中。FAN_MARK_REMOVE
使用fanotify_remove_xxxx
来将路径从fsnotify_group
中移除。
2.3 fanotify_add_mark
fanotify_add_mark
是对应FAN_MARK_ADD
标记的操作,主要有如下三种:
FAN_MARK_MOUNT
针对挂载点。FAN_MARK_FILESYSTEM
针对文件系统。- 默认,是针对文件的inode。
分别对应如下三种函数操作:
static int fanotify_add_vfsmount_mark(struct fsnotify_group *group,
struct vfsmount *mnt, __u32 mask,
unsigned int flags, __kernel_fsid_t *fsid)
{
return fanotify_add_mark(group, &real_mount(mnt)->mnt_fsnotify_marks,
FSNOTIFY_OBJ_TYPE_VFSMOUNT, mask, flags, fsid);
}
static int fanotify_add_sb_mark(struct fsnotify_group *group,
struct super_block *sb, __u32 mask,
unsigned int flags, __kernel_fsid_t *fsid)
{
return fanotify_add_mark(group, &sb->s_fsnotify_marks,
FSNOTIFY_OBJ_TYPE_SB, mask, flags, fsid);
}
static int fanotify_add_inode_mark(struct fsnotify_group *group,
struct inode *inode, __u32 mask,
unsigned int flags, __kernel_fsid_t *fsid)
{
if ((flags & FAN_MARK_IGNORED_MASK) &&
!(flags & FAN_MARK_IGNORED_SURV_MODIFY) &&
inode_is_open_for_write(inode))
return 0;
return fanotify_add_mark(group, &inode->i_fsnotify_marks,
FSNOTIFY_OBJ_TYPE_INODE, mask, flags, fsid);
}
在上述的struct vfsmount
, struct super_block
以及struct inode
结构中,都存在struct fsnotify_mark_connector
表示各自结构的fsnotify_mark
哈希表,都使用fanotify_add_mark
来进行操作,如下:
static int fanotify_add_mark(struct fsnotify_group *group,
fsnotify_connp_t *connp, unsigned int type,
__u32 mask, unsigned int flags,
__kernel_fsid_t *fsid)
{
struct fsnotify_mark *fsn_mark;
__u32 added;
/*
1. mutex_lock获取struct fsnotify_group的锁。
2. fsnotify_find_mark查找当前的struct fsnotify_mark_connector是否有注册好了当前的fsnotify_group。
3. 如果不存在,那么分配一个新的fanotify_add_new_mark(第一次注册该fsnotify_group)
4. fanotify_mark_add_to_mask将mask和flags设置到fsnotify_mark中
*/
mutex_lock(&group->mark_mutex);
fsn_mark = fsnotify_find_mark(connp, group);
if (!fsn_mark) {
fsn_mark = fanotify_add_new_mark(group, connp, type, fsid);
if (IS_ERR(fsn_mark)) {
mutex_unlock(&group->mark_mutex);
return PTR_ERR(fsn_mark);
}
}
added = fanotify_mark_add_to_mask(fsn_mark, mask, flags);
if (added & ~fsnotify_conn_mask(fsn_mark->connector))
fsnotify_recalc_mask(fsn_mark->connector);
mutex_unlock(&group->mark_mutex);
fsnotify_put_mark(fsn_mark);
return 0;
}
/*
1. 遍历所有哈希表。
2. 比较mark->group == group,是否已经注册过。
*/
struct fsnotify_mark *fsnotify_find_mark(fsnotify_connp_t *connp,
struct fsnotify_group *group)
{
struct fsnotify_mark_connector *conn;
struct fsnotify_mark *mark;
conn = fsnotify_grab_connector(connp);
if (!conn)
return NULL;
hlist_for_each_entry(mark, &conn->list, obj_list) {
if (mark->group == group &&
(mark->flags & FSNOTIFY_MARK_FLAG_ATTACHED)) {
fsnotify_get_mark(mark);
spin_unlock(&conn->lock);
return mark;
}
}
spin_unlock(&conn->lock);
return NULL;
}
/*
1. 设置好fsnotify_mark的标记.
*/
static __u32 fanotify_mark_add_to_mask(struct fsnotify_mark *fsn_mark,
__u32 mask,
unsigned int flags)
{
__u32 oldmask = -1;
spin_lock(&fsn_mark->lock);
if (!(flags & FAN_MARK_IGNORED_MASK)) {
oldmask = fsn_mark->mask;
fsn_mark->mask |= mask;
} else {
fsn_mark->ignored_mask |= mask;
if (flags & FAN_MARK_IGNORED_SURV_MODIFY)
fsn_mark->flags |= FSNOTIFY_MARK_FLAG_IGNORED_SURV_MODIFY;
}
spin_unlock(&fsn_mark->lock);
return mask & ~oldmask;
}
总结: 让我们来看一下相关数据结构
struct mount {
struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
};
struct super_block {
struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
};
struct vfsmount {
struct dentry *mnt_root;
struct super_block *mnt_sb;
int mnt_flags;
} __randomize_layout;
struct inode {
struct fsnotify_mark_connector __rcu *i_fsnotify_marks;
};
//list链接向fsnotify_mark的obj_list
struct fsnotify_mark_connector {
struct hlist_head list;
};
struct fsnotify_mark {
__u32 mask;
refcount_t refcnt;
struct fsnotify_group *group;
struct list_head g_list; //链接到fsnotify_group
spinlock_t lock;
struct hlist_node obj_list; //链接到fsnotify_mark_connector
__u32 ignored_mask;
unsigned int flags;
};
从这里看,我们调用fanotify_mark
添加一个路径的时候,并不是往struct fsnotify_group
结构中添加数据,而是向路径的mount, vfsmount或者inode中添加一个fsnotify_mark
成员。
2.4 fanotify_remove_mark
同样fanotify_remove_mark
是对应FAN_MARK_REMOVE
标记的操作,主要有如下三种:
FAN_MARK_MOUNT
针对挂载点。FAN_MARK_FILESYSTEM
针对文件系统。- 默认,是针对文件的inode。
fanotify_remove_mark
函数的实现如下:
static int fanotify_remove_mark(struct fsnotify_group *group,
fsnotify_connp_t *connp, __u32 mask,
unsigned int flags)
{
struct fsnotify_mark *fsn_mark = NULL;
__u32 removed;
int destroy_mark;
mutex_lock(&group->mark_mutex);
fsn_mark = fsnotify_find_mark(connp, group);
if (!fsn_mark) {
mutex_unlock(&group->mark_mutex);
return -ENOENT;
}
removed = fanotify_mark_remove_from_mask(fsn_mark, mask, flags,
&destroy_mark);
if (removed & fsnotify_conn_mask(fsn_mark->connector))
fsnotify_recalc_mask(fsn_mark->connector);
if (destroy_mark)
fsnotify_detach_mark(fsn_mark);
mutex_unlock(&group->mark_mutex);
if (destroy_mark)
fsnotify_free_mark(fsn_mark);
fsnotify_put_mark(fsn_mark);
return 0;
}
2.5 fsnotify_clear_marks_by_group
我们知道add和remove之后,clear就很简单了,因为struct fsnotify_group
有一个list保存了所有的struct fsnotify_mark
,因此fsnotify_clear_marks_by_group
这个函数就比较简单了,就是遍历struct fsnotify_group
,然后删除指定类型的struct fsnotify_mark
,如下:
void fsnotify_clear_marks_by_group(struct fsnotify_group *group,
unsigned int type_mask)
{
struct fsnotify_mark *lmark, *mark;
LIST_HEAD(to_free);
struct list_head *head = &to_free;
if (type_mask == FSNOTIFY_OBJ_ALL_TYPES_MASK) {
head = &group->marks_list;
goto clear;
}
mutex_lock_nested(&group->mark_mutex, SINGLE_DEPTH_NESTING);
list_for_each_entry_safe(mark, lmark, &group->marks_list, g_list) {
if ((1U << mark->connector->type) & type_mask)
list_move(&mark->g_list, &to_free);
}
mutex_unlock(&group->mark_mutex);
clear:
while (1) {
mutex_lock_nested(&group->mark_mutex, SINGLE_DEPTH_NESTING);
if (list_empty(head)) {
mutex_unlock(&group->mark_mutex);
break;
}
mark = list_first_entry(head, struct fsnotify_mark, g_list);
fsnotify_get_mark(mark);
fsnotify_detach_mark(mark);
mutex_unlock(&group->mark_mutex);
fsnotify_free_mark(mark);
fsnotify_put_mark(mark);
}
}
3. fsnotify
什么时候会导致fsnotify事件的产生,已经产生的事件怎么分发呢?我们通过open操作来看具体过程,在do_sys_open函数中,如果文件打开成功,那么调用fsnotify_open
这个函数实现如下:
static inline void fsnotify_open(struct file *file)
{
const struct path *path = &file->f_path;
struct inode *inode = file_inode(file);
__u32 mask = FS_OPEN;
if (S_ISDIR(inode->i_mode))
mask |= FS_ISDIR;
if (file->f_flags & __FMODE_EXEC)
mask |= FS_OPEN_EXEC;
fsnotify_path(inode, path, mask);
}
static inline int fsnotify_path(struct inode *inode, const struct path *path,
__u32 mask)
{
//...
return fsnotify(inode, mask, path, FSNOTIFY_EVENT_PATH, NULL, 0);
}
int fsnotify(struct inode *to_tell, __u32 mask, const void *data, int data_is,
const struct qstr *file_name, u32 cookie)
{
//...
send_to_group(to_tell, mask, data, data_is, cookie,
file_name, &iter_info);
//...
}
static int send_to_group(struct inode *to_tell,
__u32 mask, const void *data,
int data_is, u32 cookie,
const struct qstr *file_name,
struct fsnotify_iter_info *iter_info)
{
//...
return group->ops->handle_event(group, to_tell, mask, data, data_is,
file_name, cookie, iter_info);
}
group->ops->handle_event
调用的函数为fanotify_handle_event
。
3.1 fanotify_handle_event
fanotify_handle_event
这个函数对于普通event和perm_event分别进行不同逻辑处理,如下:
static int fanotify_handle_event(struct fsnotify_group *group,
struct inode *inode,
u32 mask, const void *data, int data_type,
const struct qstr *file_name, u32 cookie,
struct fsnotify_iter_info *iter_info)
{
int ret = 0;
struct fanotify_event *event;
struct fsnotify_event *fsn_event;
__kernel_fsid_t fsid = {};
/*
1. 是否事件需要上报
*/
mask = fanotify_group_event_mask(group, iter_info, mask, data,
data_type);
if (!mask)
return 0;
/*
1. 是否是perm_event
2. perm_event需要等待返回结果
*/
if (fanotify_is_perm_event(mask)) {
if (!fsnotify_prepare_user_wait(iter_info))
return 0;
}
if (FAN_GROUP_FLAG(group, FAN_REPORT_FID)) {
fsid = fanotify_get_fsid(iter_info);
if (!fsid.val[0] && !fsid.val[1])
return 0;
}
/*
1. 分配一个fanotify_event
*/
event = fanotify_alloc_event(group, inode, mask, data, data_type,
&fsid);
ret = -ENOMEM;
if (unlikely(!event)) {
if (!fanotify_is_perm_event(mask))
fsnotify_queue_overflow(group);
goto finish;
}
/*
1. 放入到队列中
2. 如果是perm_event,获取返回结果
*/
fsn_event = &event->fse;
ret = fsnotify_add_event(group, fsn_event, fanotify_merge);
if (ret) {
BUG_ON(ret == 1 && mask & FANOTIFY_PERM_EVENTS);
fsnotify_destroy_event(group, fsn_event);
ret = 0;
}
else if (fanotify_is_perm_event(mask)) {
ret = fanotify_get_response(group, FANOTIFY_PE(fsn_event),
iter_info);
}
finish:
if (fanotify_is_perm_event(mask))
fsnotify_finish_user_wait(iter_info);
return ret;
}
/*
1. 将event放入到队列group->notification_list中
*/
int fsnotify_add_event(struct fsnotify_group *group,
struct fsnotify_event *event,
int(*merge)(struct list_head *,
struct fsnotify_event *))
{
int ret = 0;
struct list_head *list = &group->notification_list;
spin_lock(&group->notification_lock);
if (group->shutdown) {
spin_unlock(&group->notification_lock);
return 2;
}
if (event == group->overflow_event ||
group->q_len >= group->max_events) {
ret = 2;
if (!list_empty(&group->overflow_event->list)) {
spin_unlock(&group->notification_lock);
return ret;
}
event = group->overflow_event;
goto queue;
}
//是否进行合并
if (!list_empty(list) && merge) {
ret = merge(list, event);
if (ret) {
spin_unlock(&group->notification_lock);
return ret;
}
}
queue:
//入队
group->q_len++;
list_add_tail(&event->list, list);
spin_unlock(&group->notification_lock);
//唤醒等待
wake_up(&group->notification_waitq);
kill_fasync(&group->fsn_fa, SIGIO, POLL_IN);
return ret;
}
从上面我们可以发现,event插入对了group->notification_list
中了,后续我们的read就是通过fanotify_read
从group->notification_list
读取第一个节点,如下:
struct fsnotify_event *fsnotify_peek_first_event(struct fsnotify_group *group)
{
assert_spin_locked(&group->notification_lock);
return list_first_entry(&group->notification_list,
struct fsnotify_event, list);
}
4. 总结
fanotify主要使用struct fsnotify_group
来进行管理,对于文件系统,文件和挂载点的mark标记,分别注册在文件系统,文件和挂载点的结构中,示意图如下: