1个不稳定版本
| 0.1.0 | 2022年7月27日 |
|---|
#1043 在 文件系统
5KB
58 行
简介
LDP_FUSE (LD_PRELOAD 用户空间文件系统) 是一个仅包含头文件的C库,用于编写文件系统,利用了 LD_PRELOAD 小技巧。简要来说,你使用LDP_FUSE提供的API编写一个共享库(.so文件),然后运行一个带有你的共享库 LD_PRELOAD 的二进制文件。LDP_FUSE将为你处理几个底层细节(见文档)。
重要提示:此项目主要是一个概念证明。所有当前的限制使其不适合在生产环境中运行。
安装
将include/文件夹下的所有文件包含到您的构建系统中。
为了更轻松地管理LDP_FUSE文件系统,您可以可选地安装一个基于Rust的CLI工具
cargo install ldpfuse
用法
示例
如何编写LDP_FUSE文件系统的示例
#include <stdio.h>
#include "ldpfuse.h"
ssize_t my_read(int fd, void *buf, size_t count, off_t offset,
orig_pread_t pread_fn) {
printf("Read called!\n");
return pread_fn(fd, buf, count, offset);
}
LDPRELOAD_FUSE_MAIN {
struct ldp_fuse_funcs funcs = {.read = my_read };
ldp_fuse_init(&funcs);
}
使用gcc将其编译为共享对象(.so)文件。您必须动态链接libdl,例如通过指定-ldl。
使用CLI
如果您已安装CLI,现在可以像这样运行您的文件系统
ldpfuse -m <mount_path> -s <so_file_path> -- myprogram
其中 <mount_path> 是您的文件系统挂载的路径。LDP_FUSE会忽略没有此挂载路径作为祖先的路径上的文件系统操作,并将其传递给原始函数。 <so_file_path> 是您的LDP_FUSE文件系统共享对象。路径可以是相对的。
完整示例,读取目录内容
ldpfuse -m /tmp/test -s ./my_fs.so -- ls -la /tmp/test
环境变量
与其使用CLI来管理环境变量并运行程序,您也可以自己设置它们
LD_PRELOAD- 必须是您的LDP_FUSE文件系统共享库的绝对路径。LDP_FUSE_PATH- 必须设置为您的文件系统'挂载'下的绝对路径。例如/tmp/ldpfuse。如果没有设置,任何路径都将被视为在文件系统中。
文档
函数、宏和结构
LDPRELOAD_FUSE_MAIN
在此函数宏的作用域内定义任何设置代码。这个宏应在任何LDP_FUSE文件系统中调用,并包含对ldp_fuse_init的调用。
LDPRELOAD_FUSE_MAIN {
// Any one-time setup code...
ldp_fuse_init(&funcs);
}
ldp_fuse_init
void ldp_fuse_init(ldp_fuse_funcs* funcs)
初始化文件系统并将其函数设置为funcs参数中描述的函数。这应在LDPRELOAD_FUSE_MAIN的某个点调用。
ldp_fuse_funcs
一个结构体,用于定义LDP_FUSE的操作。每个成员都是一个指向函数的指针,该函数将替换常规文件系统I/O函数。请参阅覆盖函数概述,以查看可以覆盖的原始函数。
覆盖函数概述
以下是使用LDP_FUSE可以覆盖的文件系统函数概述。类似的系统调用被重定向到单个用户函数——最通用的一个。例如,stat、lstat、fstat和fstatat都被重定向到fstatat。因此,您只需编写一个fstatat实现。
| 原始函数 | LDP_FUSE函数 | 注意 |
|---|---|---|
| access | access | |
| faccessat | access | |
| euidaccess | access | |
| mkdir | mkdir | |
| close | close | |
| close_range | close | 标志参数被忽略。将在范围内的每个fd上调用close一次。 |
| opendir | opendir | |
| creat | open | |
| open | open | |
| openat | openat | |
| truncate | truncate | |
| chmod | chmod | |
| chown | chown | |
| symlink | symlink | |
| rename | rename | |
| link | link | |
| rmdir | rmdir | |
| unlink | unlink | |
| mknod | mknod | |
| readlink | readlink | |
| write | write | |
| pwrite | write | |
| stat | stat | |
| lstat | stat | |
| fstat | stat | |
| fstatat | stat | |
| read | read | |
| pread | read | |
| getxattr | getxattr | |
| lgetxattr | getxattr | 将遵循符号链接,与默认的询问链接本身相反。 |
| fgetxattr | getxattr |
条件编译
您可以指定以下标志之一
-D LDP_FUSE_DEBUG- 将调试输出记录到stderr。-D LDP_FUSE_THREAD_SAFE- 使LDP_FUSE的内部数据结构线程安全。如果LDP_FUSE文件系统将用于运行执行多线程文件I/O的程序,则应包含此标志。如果包含,则必须动态链接pthreads。-D LDP_FUSE_OFT_SIZE <size>- 设置LDP_FUSE的打开文件描述符表的大小(请参阅打开文件描述符表)。默认为200。
打开文件描述符表(OFT)
LDP_FUSE有其自己的打开文件描述符表,用于跟踪读取偏移量,确定路径是否在文件系统中,并在必要时进行读写锁定。库的用户不需要直接与此交互。条目的最大数量在编译时确定(LDP_FUSE_OFT_SIZE),当超出时,应用程序将带错误信息退出。
陷阱
在您的自定义文件系统函数中,请勿使用原始函数。例如,在my_read中,不要调用read。这样做将导致无限循环,因为LDP_FUSE将read重定向到my_read。相反,使用最后一个参数,它是指向原始pread函数的函数指针。
如果使用LDP_FUSE的程序是多线程的,请包含LDP_FUSE_THREAD_SAFE标志(请参阅条件编译)
已知问题与限制
LDP_FUSE可能与某些二进制文件不兼容。以下列举了一些可能的原因,但并不全面。
Mmap
内存映射文件I/O不能通过LD_PRELOAD进行拦截。因此,任何使用mmap来访问文件的程序都无法使用LDP_FUSE。
内联系统调用,静态链接glibc
任何使用内联系统调用(例如直接使用syscall)或静态链接glibc的程序都不能使用LDP_FUSE。
Setuid二进制文件
Linux以安全执行模式执行setuid二进制文件。在此模式下,出于安全考虑,LD_PRELOAD被忽略。因此,您无法使用LDP_FUSE与setuid二进制文件一起使用。
没有glibc包装器的函数
某些文件系统I/O函数没有glibc包装器(例如openat2)。因此,这些函数调用无法被拦截。
没有后端常规文件系统的LDP_FUSE文件系统
LDP_FUSE为每个进程维护一个OFDT。目前还没有ipc或r/w机制可以避免两个进程同时访问文件。这意味着您仍然需要一个常规文件系统作为后端,以便它可以处理这个问题。这个问题可能在将来通过在共享内存中实现单个OFDT来缓解。
鸣谢
此库包含likle的出色cwalk库,用于路径解析。
许可协议
MIT
依赖项
~4.5MB
~95K SLoC