You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何拦截失败的文件打开调用,模拟文件读写而不创建实际文件?

文件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é

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 09:44:59