主循环,等待事件基本的等待事件循环如下所示:
清单 7. 等待事件循环1
2
3
4
5
6
7
8
9
10
11
12
| FD_ZERO(&rfds);
FD_SET(fan_fd, &rfds);
select(fan_fd+1, &rfds, NULL, NULL, NULL);
while ((len = read(fan_fd, buf, sizeof(buf))) > 0) {
。。。
while(FAN_EVENT_OK(metadata, len)) {
// 处理 metadata
. . .
metadata = FAN_EVENT_NEXT(metadata, len); // 读取下一个 metadata
}
select(fan_fd+1, &rfds, NULL, NULL, NULL)
}
|
Fanotify_init 返回的 file descriptor 可以适用于所有文件系统调用。因此可以方便地使用 read 来等待时间;同样,也可以调用 select 同时等待多个 fd 上的事件。因此基本的事件等待循环如代码清单 7 所示。
处理 metadata每一个事件都由一个 metadata 数据结构所表示。
应用程序通过调用 fanotify_init 和 fanotify_mark 设置好需要监控的对象和事件之后,便可以不断地调用 read 轮询是否有文件系统事件发生了。每一个文件系统事件,都由下面这个数据结构表示。
清单 8. 数据结构1
2
3
4
5
6
7
8
9
| struct fanotify_event_metadata {
__u32 event_len;
__u8 vers;
__u8 reserved;
__u16 metadata_len;
__aligned_u64 mask;
__s32 fd;
__s32 pid;
};
|
这个数据结构类似于 inotify 中的 inotify_event,其中我们最常使用的数据成员是:
- fd -- 一个 open fd,代表触发事件的文件系统对象
- pid -- 触发文件系统对象的进程 id
- mask -- 事件 mask,通过判断 mask,我们可以获知发生了什么事件,比如 OPEN,ACCESS 等。
不同的事件处理通过分析 metadata,我们可以确切地获知当前发生的事件类型。因此一般地,我们可以用一个分支语句结构对不同的事件进行不同的处理,代码如下:
清单 9. 分支语句结构1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| if (metadata->mask & FAN_ACCESS)
printf(" access");
if (metadata->mask & FAN_OPEN)
printf(" open");
if (metadata->mask & FAN_MODIFY)
printf(" modify");
if (metadata->mask & FAN_CLOSE) {
if (metadata->mask & FAN_CLOSE_WRITE)
printf(" close(writable)");
if (metadata->mask & FAN_CLOSE_NOWRITE)
printf(" close");
}
if (metadata->mask & FAN_OPEN_PERM)
printf(" open_perm");
if (metadata->mask & FAN_ACCESS_PERM)
printf(" access_perm");
if (metadata->mask & FAN_ALL_PERM_EVENTS) {
if (opt_sleep)
sleep(opt_sleep);
if (handle_perm(fan_fd, metadata))
goto fail;
if (metadata->fd >= 0 &&
opt_ignore_perm &&
set_ignored_mask(fan_fd, metadata->fd,
metadata->mask))
goto fail;
}
|
判断 mask 域,如果是 OPEN,则打印 open。看懂了些 if 语句之后,您便可以执行不同于打印的其他您想要的操作了。
Access Descision相比于 inotify,fanotify 还可以进行 Access Decision,当发生 OPEN_PERM/ACCESS_PERM 事件时,监控进程可以通过向内核 fanotify file descriptor 回写一个数据来允许或者拒绝文件操作。该数据为:
1
2
3
4
| struct fanotify_response {
__s32 fd;
__u32 response;
};
|
其中,fd 代表将被打开的文件系统对象的 fd;
response 代表是否允许操作的决定,可以选择的值为:
- FAN_ALLOW,即允许
- FAN_DENY,即拒绝
进行访问控制决策的函数例子代码如下:
清单 10. 访问控制决策的函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
| int handle_perm(int fan_fd, struct fanotify_event_metadata *metadata)
{
struct fanotify_response response_struct;
int ret;
response_struct.fd = metadata->fd;
response_struct.response = FAN_ALLOW;
ret = write(fan_fd, &response_struct, sizeof(response_struct));
if (ret < 0)
return ret;
return 0;
}
|
在 Eric 的例子中,总是允许该文件操作。因此 handle_perm 函数中没有做任何特殊处理,只是回复一个 FAN_ALLOW。下面我将修改这里的逻辑,让我们更清晰地理解访问控制是如何具体工作的吧。 |