fanotify_mark – 文件系统通知标记
函数介绍
fanotify_mark
是一个Linux系统调用,用于在fanotify实例上添加、修改或删除文件系统对象的监视标记。fanotify是Linux的文件系统通知机制,可以监控文件访问和修改事件。
函数原型
#include <sys/fanotify.h>
#include <fcntl.h>
#include <unistd.h>
int fanotify_mark(int fanotify_fd, unsigned int flags,
uint64_t mask, int dirfd, const char *pathname);
功能
在fanotify文件描述符上设置监视标记,用于监控指定文件系统对象的事件。
参数
int fanotify_fd
: fanotify实例的文件描述符(通过fanotify_init获得)unsigned int flags
: 操作标志FAN_MARK_ADD
: 添加标记FAN_MARK_REMOVE
: 删除标记FAN_MARK_FLUSH
: 刷新所有标记FAN_MARK_DONT_FOLLOW
: 不跟随符号链接FAN_MARK_ONLYDIR
: 只监视目录FAN_MARK_MOUNT
: 监视整个挂载点FAN_MARK_FILESYSTEM
: 监视整个文件系统
uint64_t mask
: 事件掩码,指定要监视的事件类型FAN_ACCESS
: 文件被访问FAN_OPEN
: 文件被打开FAN_CLOSE_WRITE
: 可写文件被关闭FAN_CLOSE_NOWRITE
: 只读文件被关闭FAN_MODIFY
: 文件被修改FAN_OPEN_PERM
: 文件打开权限检查FAN_ACCESS_PERM
: 文件访问权限检查
int dirfd
: 目录文件描述符(用于相对路径)AT_FDCWD
: 使用当前工作目录
const char *pathname
: 要监视的文件或目录路径
返回值
- 成功时返回0
- 失败时返回-1,并设置errno
特殊限制
- 需要Linux 2.6.39以上内核支持
- 需要CAP_SYS_ADMIN能力或特定权限
- 某些操作需要root权限
相似函数
fanotify_init()
: 初始化fanotify实例inotify_add_watch()
: inotify监视添加read()
: 读取fanotify事件
示例代码
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/fanotify.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
#include <poll.h>
#include <signal.h>
// 全局变量用于信号处理
volatile sig_atomic_t keep_running = 1;
// 信号处理函数
void signal_handler(int sig) {
printf("\n接收到信号 %d,准备退出...\n", sig);
keep_running = 0;
}
// 事件类型转换为字符串
const char* event_type_to_string(uint64_t mask) {
if (mask & FAN_ACCESS) return "ACCESS";
if (mask & FAN_OPEN) return "OPEN";
if (mask & FAN_CLOSE_WRITE) return "CLOSE_WRITE";
if (mask & FAN_CLOSE_NOWRITE) return "CLOSE_NOWRITE";
if (mask & FAN_MODIFY) return "MODIFY";
if (mask & FAN_OPEN_PERM) return "OPEN_PERM";
if (mask & FAN_ACCESS_PERM) return "ACCESS_PERM";
return "UNKNOWN";
}
int main() {
int fan_fd, fd, result;
char buffer[4096];
printf("=== Fanotify_mark 函数示例 ===\n");
// 设置信号处理
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
// 示例1: 基本使用
printf("\n示例1: 基本使用\n");
// 创建测试文件
fd = open("test_fanotify.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd != -1) {
write(fd, "test content for fanotify", 25);
close(fd);
printf("创建测试文件: test_fanotify.txt\n");
}
// 初始化fanotify实例
fan_fd = fanotify_init(FAN_CLASS_NOTIF, O_RDONLY);
if (fan_fd == -1) {
if (errno == EPERM) {
printf("权限不足初始化fanotify: %s\n", strerror(errno));
printf("说明: 需要CAP_SYS_ADMIN能力或root权限\n");
unlink("test_fanotify.txt");
exit(EXIT_FAILURE);
} else {
perror("fanotify_init失败");
unlink("test_fanotify.txt");
exit(EXIT_FAILURE);
}
}
printf("成功初始化fanotify实例,文件描述符: %d\n", fan_fd);
// 示例2: 添加监视标记
printf("\n示例2: 添加监视标记\n");
// 监视当前目录下的测试文件
result = fanotify_mark(fan_fd, FAN_MARK_ADD,
FAN_OPEN | FAN_CLOSE | FAN_ACCESS | FAN_MODIFY,
AT_FDCWD, "test_fanotify.txt");
if (result == -1) {
perror("添加监视标记失败");
} else {
printf("成功添加监视标记到 test_fanotify.txt\n");
printf("监视事件: OPEN | CLOSE | ACCESS | MODIFY\n");
}
// 示例3: 监视目录
printf("\n示例3: 监视目录\n");
// 创建测试目录
if (mkdir("fanotify_test_dir", 0755) == -1 && errno != EEXIST) {
perror("创建测试目录失败");
} else {
printf("创建测试目录: fanotify_test_dir\n");
// 监视整个目录
result = fanotify_mark(fan_fd, FAN_MARK_ADD,
FAN_OPEN | FAN_CLOSE | FAN_ACCESS,
AT_FDCWD, "fanotify_test_dir");
if (result == 0) {
printf("成功添加目录监视标记\n");
}
// 在目录中创建文件进行测试
fd = open("fanotify_test_dir/test_file.txt", O_CREAT | O_WRONLY, 0644);
if (fd != -1) {
write(fd, "test file in directory", 22);
close(fd);
printf("在目录中创建测试文件\n");
}
}
// 示例4: 不同的标记操作
printf("\n示例4: 不同的标记操作\n");
// FAN_MARK_REMOVE - 删除标记
result = fanotify_mark(fan_fd, FAN_MARK_REMOVE,
FAN_OPEN | FAN_CLOSE,
AT_FDCWD, "test_fanotify.txt");
if (result == 0) {
printf("成功删除部分监视标记\n");
} else {
printf("删除监视标记失败: %s\n", strerror(errno));
}
// FAN_MARK_FLUSH - 刷新所有标记
result = fanotify_mark(fan_fd, FAN_MARK_FLUSH, 0, 0, NULL);
if (result == 0) {
printf("成功刷新所有监视标记\n");
}
// 重新添加标记进行后续测试
fanotify_mark(fan_fd, FAN_MARK_ADD,
FAN_OPEN | FAN_CLOSE | FAN_ACCESS,
AT_FDCWD, "test_fanotify.txt");
// 示例5: 错误处理演示
printf("\n示例5: 错误处理演示\n");
// 使用无效的fanotify文件描述符
result = fanotify_mark(999, FAN_MARK_ADD, FAN_OPEN, AT_FDCWD, "test.txt");
if (result == -1) {
if (errno == EBADF) {
printf("无效fanotify文件描述符错误处理正确: %s\n", strerror(errno));
}
}
// 使用无效的标志
result = fanotify_mark(fan_fd, 0x1000, FAN_OPEN, AT_FDCWD, "test.txt");
if (result == -1) {
if (errno == EINVAL) {
printf("无效标志错误处理正确: %s\n", strerror(errno));
}
}
// 监视不存在的文件
result = fanotify_mark(fan_fd, FAN_MARK_ADD, FAN_OPEN, AT_FDCWD, "nonexistent.txt");
if (result == -1) {
if (errno == ENOENT) {
printf("监视不存在文件错误处理正确: %s\n", strerror(errno));
}
}
// 示例6: 权限相关操作
printf("\n示例6: 权限相关操作\n");
printf("fanotify权限要求:\n");
printf("1. 需要CAP_SYS_ADMIN能力\n");
printf("2. 或者root权限\n");
printf("3. 某些操作可能需要额外权限\n");
printf("4. 文件访问权限仍然适用\n\n");
// 示例7: 实际应用场景演示
printf("示例7: 实际应用场景演示\n");
printf("文件完整性监控系统:\n");
printf("int setup_file_monitoring(const char* path) {\n");
printf(" int fan_fd = fanotify_init(FAN_CLASS_CONTENT, O_RDONLY);\n");
printf(" if (fan_fd == -1) return -1;\n");
printf(" \n");
printf(" // 监视文件修改\n");
printf(" fanotify_mark(fan_fd, FAN_MARK_ADD, \n");
printf(" FAN_CLOSE_WRITE | FAN_MODIFY,\n");
printf(" AT_FDCWD, path);\n");
printf(" \n");
printf(" return fan_fd;\n");
printf("}\n\n");
printf("实时备份系统:\n");
printf("int setup_backup_monitoring(const char* directory) {\n");
printf(" int fan_fd = fanotify_init(FAN_CLASS_CONTENT, O_RDONLY);\n");
printf(" if (fan_fd == -1) return -1;\n");
printf(" \n");
printf(" // 监视目录及其子目录的所有修改\n");
printf(" fanotify_mark(fan_fd, FAN_MARK_ADD | FAN_MARK_MOUNT,\n");
printf(" FAN_CLOSE_WRITE | FAN_MOVED_TO | FAN_CREATE,\n");
printf(" AT_FDCWD, directory);\n");
printf(" \n");
printf(" return fan_fd;\n");
printf("}\n\n");
// 示例8: 事件处理演示
printf("示例8: 事件处理演示\n");
printf("典型的fanotify事件处理循环:\n");
printf("while (keep_running) {\n");
printf(" ssize_t len = read(fan_fd, buffer, sizeof(buffer));\n");
printf(" if (len == -1 && errno != EAGAIN) {\n");
printf(" perror(\"读取fanotify事件失败\");\n");
printf(" break;\n");
printf(" }\n");
printf(" \n");
printf(" struct fanotify_event_metadata *metadata;\n");
printf(" for (metadata = (void*)buffer;\n");
printf(" FAN_EVENT_OK(metadata, len);\n");
printf(" metadata = FAN_EVENT_NEXT(metadata, len)) {\n");
printf(" \n");
printf(" // 处理事件\n");
printf(" printf(\"事件: %%s, 文件描述符: %%d\\n\",\n");
printf(" event_type_to_string(metadata->mask),\n");
printf(" metadata->fd);\n");
printf(" \n");
printf(" // 关闭事件提供的文件描述符\n");
printf(" close(metadata->fd);\n");
printf(" }\n");
printf("}\n\n");
// 示例9: 不同监视类型说明
printf("示例9: 不同监视类型说明\n");
printf("FAN_MARK_ADD:\n");
printf(" - 添加新的监视标记\n");
printf(" - 可以与现有标记共存\n\n");
printf("FAN_MARK_REMOVE:\n");
printf(" - 删除指定的监视标记\n");
printf(" - 只删除指定的事件类型\n\n");
printf("FAN_MARK_FLUSH:\n");
printf(" - 删除所有监视标记\n");
printf(" - 清空整个监视设置\n\n");
printf("FAN_MARK_DONT_FOLLOW:\n");
printf(" - 不跟随符号链接\n");
printf(" - 直接监视符号链接本身\n\n");
printf("FAN_MARK_ONLYDIR:\n");
printf(" - 只监视目录\n");
printf(" - 如果路径不是目录则失败\n\n");
printf("FAN_MARK_MOUNT:\n");
printf(" - 监视整个挂载点\n");
printf(" - 包括挂载点下的所有文件\n\n");
printf("FAN_MARK_FILESYSTEM:\n");
printf(" - 监视整个文件系统\n");
printf(" - 包括所有挂载的实例\n\n");
// 示例10: 事件类型说明
printf("示例10: 事件类型说明\n");
printf("基本事件类型:\n");
printf("FAN_ACCESS: 文件被访问(读取)\n");
printf("FAN_OPEN: 文件被打开\n");
printf("FAN_CLOSE_WRITE: 可写文件被关闭\n");
printf("FAN_CLOSE_NOWRITE: 只读文件被关闭\n");
printf("FAN_MODIFY: 文件内容被修改\n\n");
printf("权限检查事件类型:\n");
printf("FAN_OPEN_PERM: 文件打开权限检查(需要响应)\n");
printf("FAN_ACCESS_PERM: 文件访问权限检查(需要响应)\n\n");
printf("创建和删除事件:\n");
printf("FAN_CREATE: 文件或目录被创建\n");
printf("FAN_DELETE: 文件或目录被删除\n");
printf("FAN_MOVED_FROM: 文件被移出\n");
printf("FAN_MOVED_TO: 文件被移入\n\n");
// 示例11: 性能和资源考虑
printf("示例11: 性能和资源考虑\n");
printf("fanotify性能特点:\n");
printf("1. 内核级别的通知机制\n");
printf("2. 比轮询方式更高效\n");
printf("3. 支持大量文件监视\n");
printf("4. 低延迟事件通知\n\n");
printf("资源使用考虑:\n");
printf("1. 每个监视标记消耗内核资源\n");
printf("2. 事件队列有大小限制\n");
printf("3. 需要及时处理事件\n");
printf("4. 避免监视过多文件\n\n");
printf("优化建议:\n");
printf("1. 合理选择监视的事件类型\n");
printf("2. 及时处理和响应事件\n");
printf("3. 避免不必要的监视标记\n");
printf("4. 使用适当的fanotify类\n");
printf("5. 监控系统资源使用\n\n");
// 示例12: 安全考虑
printf("示例12: 安全考虑\n");
printf("fanotify安全特性:\n");
printf("1. 需要特殊权限才能使用\n");
printf("2. 不能监视没有访问权限的文件\n");
printf("3. 提供权限检查事件类型\n");
printf("4. 事件包含文件描述符便于验证\n\n");
printf("安全使用建议:\n");
printf("1. 验证事件中的文件描述符\n");
printf("2. 谨慎使用权限检查事件\n");
printf("3. 限制监视的文件范围\n");
printf("4. 及时响应权限检查事件\n");
printf("5. 避免泄露敏感文件信息\n\n");
// 清理资源
printf("清理测试资源...\n");
close(fan_fd);
unlink("test_fanotify.txt");
unlink("fanotify_test_dir/test_file.txt");
rmdir("fanotify_test_dir");
printf("\n总结:\n");
printf("fanotify_mark是fanotify文件系统监控的核心函数\n");
printf("支持灵活的监视标记管理和多种监视类型\n");
printf("需要适当的权限才能使用\n");
printf("适用于文件完整性监控、实时备份等场景\n");
printf("是Linux系统管理和安全监控的重要工具\n");
return 0;
}