如何拦截失败的文件打开调用,模拟文件读写而不创建实际文件?
文件F拦截与虚拟数据返回实现方案
先明确:inotify满足不了你的需求
- inotify只能被动监听已存在文件/目录的事件,没法拦截
fopen()/open()这类系统调用的执行过程 - 它没有能力修改系统调用的返回结果,做不到“返回默认数据、拦截写入”这种主动干预的操作,所以得换下面两种方案
方案1:LD_PRELOAD 劫持系统调用(轻量化,仅动态链接程序有效)
这是最省事的方案,不用碰内核,靠动态库钩子直接接管目标程序的文件操作函数。
核心逻辑
- 写一个自定义动态库,重写
fopen()、open()、read()、write()这些常用文件操作函数 - 每次目标程序调用这些函数时,先判断是不是操作文件F:
- 要是打开文件F且真实文件不存在:直接返回指向内存默认数据缓冲区的文件指针/描述符
- 读操作:直接从内存返回默认数据,不走真实磁盘IO
- 写操作:直接返回请求的字节数(假装写入成功),啥也不做
- 用
LD_PRELOAD环境变量让目标程序优先加载你的动态库,实现无侵入拦截
简化代码示例
#define _GNU_SOURCE #include <stdio.h> #include <dlfcn.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <stdarg.h> // 预设的默认文件内容 static const char DEFAULT_F_CONTENT[] = "This is the default content of file F\n"; static const size_t DEFAULT_F_LEN = sizeof(DEFAULT_F_CONTENT) - 1; #define TARGET_F "/absolute/path/to/your/file/F" // 保存系统原始函数的指针 static FILE* (*real_fopen)(const char*, const char*) = NULL; static int (*real_open)(const char*, int, ...) = NULL; static ssize_t (*real_read)(int, void*, size_t) = NULL; static ssize_t (*real_write)(int, const void*, size_t) = NULL; // 重写fopen FILE* fopen(const char* path, const char* mode) { if (!real_fopen) real_fopen = dlsym(RTLD_NEXT, "fopen"); if (strcmp(path, TARGET_F) == 0) { // 用fmemopen创建内存文件流,模拟真实文件 FILE* mem_fp = fmemopen((void*)DEFAULT_F_CONTENT, DEFAULT_F_LEN, mode); if (mem_fp) return mem_fp; } return real_fopen(path, mode); } // 重写open(处理直接调用open的情况) int open(const char* path, int flags, ...) { if (!real_open) real_open = dlsym(RTLD_NEXT, "open"); if (strcmp(path, TARGET_F) == 0) { // 返回自定义标记的fd,后续read/write识别它(实际要避免fd冲突) return 0xdeadbeef; } va_list args; va_start(args, flags); mode_t mode = va_arg(args, mode_t); int fd = real_open(path, flags, mode); va_end(args); return fd; } // 重写read,拦截虚拟文件的读请求 ssize_t read(int fd, void* buf, size_t count) { if (!real_read) real_read = dlsym(RTLD_NEXT, "read"); if (fd == 0xdeadbeef) { // 识别虚拟文件的fd size_t copy_size = count > DEFAULT_F_LEN ? DEFAULT_F_LEN : count; memcpy(buf, DEFAULT_F_CONTENT, copy_size); return copy_size; } return real_read(fd, buf, count); } // 重写write,拦截虚拟文件的写请求 ssize_t write(int fd, const void* buf, size_t count) { if (!real_write) real_write = dlsym(RTLD_NEXT, "write"); if (fd == 0xdeadbeef) { // 假装写入成功,返回请求的字节数 return count; } return real_write(fd, buf, count); }
编译&使用
# 编译动态库 gcc -shared -fPIC -o libfhook.so fhook.c -ldl # 让目标程序加载这个库运行 LD_PRELOAD=./libfhook.so ./your_target_program
注意点
- 要覆盖所有可能的文件操作函数:比如
openat()、fread()、fwrite()、close()等,别漏了 - 得维护fd/文件指针和虚拟文件的映射关系,避免误拦截其他文件
- 只对动态链接的程序有效,静态编译的程序不适用
方案2:FUSE 用户态文件系统(通用型,所有程序都有效)
如果要兼容静态编译程序,或者需要拦截整个目录的文件,用FUSE最合适——它在用户态实现一个完整的文件系统,所有访问挂载点的请求都会经过你的代码。
核心逻辑
- 写一个FUSE程序,把包含文件F的“虚拟目录”挂载到系统某个路径
- 当程序访问挂载点下的文件F时,触发你的回调函数:
getattr:返回模拟的文件属性(大小、权限、修改时间等),让程序觉得文件真实存在open:直接返回成功,不管真实文件有没有read:返回预设的默认数据write:直接返回成功,不做任何实际写入
- 其他程序访问挂载点下的F,就像访问真实文件一样,但所有操作都被你拦截处理
简化代码示例(基于libfuse3)
#define FUSE_USE_VERSION 31 #include <fuse.h> #include <stdio.h> #include <string.h> #include <errno.h> static const char DEFAULT_F_CONTENT[] = "Default content for file F"; static const size_t DEFAULT_F_LEN = sizeof(DEFAULT_F_CONTENT) - 1; #define TARGET_F "F" // 挂载点下的目标文件名 // 返回文件/目录的属性 static int fs_getattr(const char* path, struct stat* stbuf) { memset(stbuf, 0, sizeof(struct stat)); if (strcmp(path, "/") == 0) { stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 2; } else if (strcmp(path + 1, TARGET_F) == 0) { stbuf->st_mode = S_IFREG | 0644; stbuf->st_nlink = 1; stbuf->st_size = DEFAULT_F_LEN; stbuf->st_mtime = 1690000000; // 模拟修改时间 } else { return -ENOENT; } return 0; } // 打开文件 static int fs_open(const char* path, struct fuse_file_info* fi) { if (strcmp(path + 1, TARGET_F) != 0) { return -ENOENT; } // 允许任何打开模式(包括写) return 0; } // 读取文件内容 static int fs_read(const char* path, char* buf, size_t size, off_t offset, struct fuse_file_info* fi) { if (strcmp(path + 1, TARGET_F) != 0) { return -ENOENT; } if (offset >= DEFAULT_F_LEN) { return 0; // 返回EOF } size_t copy_size = size > (DEFAULT_F_LEN - offset) ? (DEFAULT_F_LEN - offset) : size; memcpy(buf, DEFAULT_F_CONTENT + offset, copy_size); return copy_size; } // 拦截写入操作 static int fs_write(const char* path, const char* buf, size_t size, off_t offset, struct fuse_file_info* fi) { if (strcmp(path + 1, TARGET_F) != 0) { return -ENOENT; } // 假装写入成功,返回写入的字节数 return size; } // 读取目录内容(让ls能看到文件F) static int fs_readdir(const char* path, void* buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info* fi) { if (strcmp(path, "/") != 0) { return -ENOENT; } filler(buf, ".", NULL, 0); filler(buf, "..", NULL, 0); filler(buf, TARGET_F, NULL, 0); return 0; } // FUSE操作回调集合 static const struct fuse_operations fs_ops = { .getattr = fs_getattr, .open = fs_open, .read = fs_read, .write = fs_write, .readdir = fs_readdir, }; int main(int argc, char* argv[]) { return fuse_main(argc, argv, &fs_ops, NULL); }
编译&使用
# 先安装libfuse3开发包(Debian/Ubuntu) sudo apt install libfuse3-dev # 编译程序 gcc -o virtual_fs virtual_fs.c `pkg-config fuse3 --cflags --libs` # 创建挂载点 mkdir ~/virtual_mount # 挂载虚拟文件系统(普通用户需要修改/etc/fuse.conf添加user_allow_other) ./virtual_fs ~/virtual_mount # 目标程序访问~/virtual_mount/F即可,完全感知不到是虚拟文件
注意点
- 要补全更多FUSE回调(比如
truncate、release等),避免程序出现异常 - 挂载时如果提示权限问题,修改
/etc/fuse.conf里的user_allow_other并重启fuse服务 - 所有程序(包括静态编译的)都能被拦截,适用性拉满
内容的提问来源于stack exchange,提问作者Rafael Santiago Altoé
相关产品推荐
相关产品推荐

