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