- 存储
- 驱动开发
【免费下载链接】winfsp
Windows File System Proxy - FUSE for Windows
导读:本文以 WinFsp 仓库中的 tst/passthrough-fuse3/README.md 为骨架,剖析
passthrough-fuse3这一“把全部文件系统操作透传给底层文件系统”的 FUSE3 示例:先讲透其源码级实现原理(路径拼接、文件句柄编码、能力协商),再完整演示 Visual Studio、Cygwin 直连 WinFsp DLL、链接 CYGFUSE3 三种构建方式,最后给出命令行挂载与 WinFsp.Launcher 场景下的--VolumePrefix用法。读完你既能看懂 WinFsp-FUSE3 层如何把 FUSE 回调翻译成 Windows 文件系统请求,也能直接复现、编译并挂载一个可用的透传文件系统。
1. 它是什么:一个"透传"式 FUSE3 文件系统
WinFsp(Windows File System Proxy,FUSE for Windows)提供了一套让用户态程序以 FUSE API 实现 Windows 文件系统的能力。passthrough-fuse3是仓库中随附的示例之一,位于 tst/passthrough-fuse3/,其定位非常朴素:
一个简单的 FUSE3 文件系统,把所有文件系统操作透传给某个底层文件系统(通常是 NTFS 下的一个真实目录)。
换句话说,用户通过这个文件系统看到的目录树,实际就是磁盘上某个真实目录的内容——创建、删除、读写、改名、扩展属性等操作都会被原样转发。它天然是验证 WinFsp-FUSE3 移植层正确性的"最小可用"样例,也是学习 FUSE3 回调接口最直接、最完整的参考实现(同一目录下的 tst/memfs-fuse3/ 则是"内存版"的姊妹示例,与之对照可以理解两种截然不同的数据存放策略)。
原 README 明确指出它可用以下三种工具构建:
- 使用 Visual Studio(
winfsp.sln); - 使用 Cygwin GCC 并直接链接 WinFsp DLL(
make winfsp-fuse3); - 使用 Cygwin GCC 并链接 CYGFUSE3(
make cygfuse3)。
下文将分别展开说明每种方式的原理与命令细节。
2. 源码级剖析:Passthrough 如何被实现
2.1 核心状态:一个 rootdir 就够
整个文件系统只需要记住一件事:底层真实目录在哪里。源码用极简的结构体承载:
typedef struct { const char *rootdir; } PTFS;(见 tst/passthrough-fuse3/passthrough-fuse3.c)。rootdir在main()中通过realpath()解析得到绝对路径,之后所有操作回调都能通过fuse_get_context()->private_data取回该结构体。
2.2 路径拼接宏:把 FUSE 路径映射到底层路径
FUSE 回调收到的path是挂载点内的相对路径(如/dir/file.txt),而透传实现必须把它拼到rootdir之后才能调用 POSIX 系统调用。源码用一个宏完成"拼接 + 越界检查":
#define concat_path(ptfs, fn, fp) \ (sizeof fp > (unsigned)snprintf(fp, sizeof fp, "%s%s", ptfs->rootdir, fn)) #define ptfs_impl_fullpath(n) \ char full ## n[PATH_MAX * 4]; \ if (!concat_path(((PTFS *)fuse_get_context()->private_data), n, full ## n))\ return -ENAMETOOLONG; \ n = full ## n要点:
- 缓冲区大小为
PATH_MAX * 4,是为了容纳 Windows 路径中可能出现的宽字符编码膨胀(在 Cygwin 环境下尤其必要); - 拼接失败(
snprintf返回超长)时直接返回-ENAMETOOLONG,把错误码负值抛回 FUSE 层——FUSE3 的约定就是"返回负 errno"。
2.3 文件句柄编码:文件 fd 与目录 DIR* 共用 fh
透传实现需要在open/read/write/release等回调之间保持一个打开的文件描述符,同时还要在opendir/readdir/releasedir之间保持目录流。FUSE3 的struct fuse_file_info.fh是用户自定义的 64 位值,源码用最高位做"目录标记"区分两类句柄:
#define fi_dirbit (0x8000000000000000ULL) #define fi_fh(fi, MASK) ((fi)->fh & (MASK)) #define fi_setfh(fi, FH, MASK) ((fi)->fh = (intptr_t)(FH) | (MASK)) #define fi_fd(fi) (fi_fh(fi, fi_dirbit) ? \ dirfd((DIR *)(intptr_t)fi_fh(fi, ~fi_dirbit)) : (int)fi_fh(fi, ~fi_dirbit)) #define fi_dirp(fi) ((DIR *)(intptr_t)fi_fh(fi, ~fi_dirbit)) #define fi_setfd(fi, fd) (fi_setfh(fi, fd, 0)) #define fi_setdirp(fi, dirp) (fi_setfh(fi, dirp, fi_dirbit))即:
- 对文件,
fh低位直接存放open()返回的 fd; - 对目录,
fh存放opendir()返回的DIR *指针,并置位最高位fi_dirbit用于区分; - 需要 fd 时,若句柄是目录则通过
dirfd()取出目录对应的 fd(用于fstat、ftruncate、fsync等)。
这种"一位区分类型"的技巧保证了文件与目录操作可以共用一个fuse_file_info通道,无需额外的状态表。
2.4 回调全景:一张表把透传讲完
ptfs_ops(struct fuse_operations)是文件系统的"菜单",源码完整实现了 23 个回调(见 tst/passthrough-fuse3/passthrough-fuse3.c):
| 回调 | 底层调用 | 说明 |
|---|---|---|
getattr | lstat/fstat | 有fi时走 fd 路径,避免 TOCTOU |
mkdir/rmdir | mkdir/rmdir | |
unlink/rename | unlink/rename | |
chmod/chown | chmod/lchown | |
truncate | truncate/ftruncate | 同样支持 fd 路径 |
open/create | open(path, fi->flags[, mode]) | 透传fi->flags,返回值存入fh |
read/write | pread/pwrite | 按off偏移读写,返回实际字节数 |
statfs | statvfs | 报告底层文件系统的容量信息 |
release | close | 关闭 fd |
fsync | fsync | |
setxattr/getxattr/listxattr/removexattr | lsetxattr等 | 透传扩展属性 |
opendir/readdir/releasedir | opendir/readdir+filler/closedir | 目录枚举 |
init | — | 能力协商(见 2.5) |
utimens | utimensat(AT_FDCWD, ..., AT_SYMLINK_NOFOLLOW) | 时间戳更新 |
每个回调的通用模式是:拼接 fullpath → 调用底层系统调用 → 成功返回 0/字节数,失败返回-errno。例如ptfs_open:
static int ptfs_open(const char *path, struct fuse_file_info *fi) { ptfs_impl_fullpath(path); int fd; return -1 != (fd = open(path, fi->flags)) ? (fi_setfd(fi, fd), 0) : -errno; }ptfs_readdir值得单独一提:在 Windows 构建下(_WIN64 || _WIN32)它使用FUSE_FILL_DIR_PLUS标志,把struct dirent中携带的d_stat一并交给filler,从而以一次回调同时返回文件名和属性,配合READDIRPLUS能力可显著减少目录枚举时的属性查询次数。
2.5 init 回调:能力协商
ptfs_init展示了 FUSE3 的"能力协商"机制——文件系统声明希望启用的能力,但只请求内核(这里是 WinFsp-FUSE3 层)已经具备的部分:
static void *ptfs_init(struct fuse_conn_info *conn, struct fuse_config *conf) { conn->want |= (conn->capable & FUSE_CAP_READDIRPLUS); #if defined(FSP_FUSE_CAP_CASE_INSENSITIVE) conn->want |= (conn->capable & FSP_FUSE_CAP_CASE_INSENSITIVE); #endif return fuse_get_context()->private_data; }FUSE_CAP_READDIRPLUS与FSP_FUSE_CAP_CASE_INSENSITIVE均在 inc/fuse3/fuse_common.h 中定义(后者即FUSE_CAP_CASE_INSENSITIVE,是 WinFsp 对 FUSE 能力的 Windows 化扩展,用于向 Windows 文件系统层宣告大小写不敏感语义);- 返回
fuse_get_context()->private_data作为用户数据,即PTFS指针。
2.6 main():参数解析与 Windows 下的 UNC 约定
main()的逻辑(见 tst/passthrough-fuse3/passthrough-fuse3.c):
- 检查命令行最后两个参数是否以
-开头,若不满足则视为rootdir mountpoint,用realpath规范化 rootdir,随后把它从参数表中移除,剩下纯 FUSE 选项交给fuse_main; - 若未从位置参数拿到 rootdir(Windows 构建下),则扫描
--UNC=与--VolumePrefix=前缀参数,从类似\\passthrough-fuse\C$\Path的形式中提取盘符路径并realpath之; - 两者皆失败则打印用法并退出:
usage: passthrough-fuse [FUSE options] rootdir mountpoint第 2 步的意图在源码注释中写得很清楚:让文件系统可以在WinFsp.Launcher下运行,并通过形如
net use z: \\passthrough-fuse\C$\Path的命令把文件系统挂载为 Windows 盘符。这里的C$约定被解析为底层真实路径C:\——也就是说,用户可以通过 UNC 命名空间直接"以 passthrough 视图"访问某个真实磁盘目录。
3. 三种构建方式详解
3.1 方式一:Visual Studio(winfsp.sln)
工程文件 tst/passthrough-fuse3/passthrough-fuse3.sln 提供Debug/Release×x86/x64/ARM64六种配置;tst/passthrough-fuse3/passthrough-fuse3.vcxproj 则定义了具体的编译/链接参数,其中与 WinFsp 集成相关的关键点:
- 头文件搜索路径:
$(MSBuildProgramFiles32)\WinFsp\inc\fuse3;$(MSBuildProgramFiles32)\WinFsp\inc,即使用 WinFsp 安装目录提供的fuse3头(声明在 inc/fuse3/fuse.h 等)和winfsp头; - 链接库:
$(MSBuildProgramFiles32)\WinFsp\lib\winfsp-$(PlatformTarget).lib(x86/x64),ARM64 使用winfsp-a64.lib; - 延迟加载:
DelayLoadDLLs = winfsp-$(PlatformTarget).dll/winfsp-a64.dll,程序启动时才加载 WinFsp 运行时; - 平台宏:x86 配置定义
WIN32,ARM64 配置产物命名为passthrough-fuse3-a64.exe; - 另外,由于 Windows 原生环境没有 POSIX 系统调用,工程把 tst/passthrough-fuse3/winposix.c 一并编入,由 tst/passthrough-fuse3/winposix.h 提供
realpath、pread、pwrite、statvfs、opendir/readdir等 POSIX 符号的 Windows 实现。
因此前提是:本机已安装 WinFsp(含inc与lib目录),然后用 Visual Studio 打开winfsp.sln选择对应配置生成即可。这一路径产出的是原生 Windows 可执行文件,直接调用winfsp-*.dll中的 FUSE3 实现。
3.2 方式二:Cygwin GCC 直连 WinFsp DLL(make winfsp-fuse3)
在 Cygwin 环境中,进入 tst/passthrough-fuse3/ 执行:
make winfsp-fuse3对应的 Makefile 目标(见 tst/passthrough-fuse3/Makefile)展开后是两条核心命令:
passthrough-winfsp-fuse3: export PKG_CONFIG_PATH=$(PWD)/winfsp.install/lib passthrough-winfsp-fuse3: passthrough-fuse3.c ln -nsf "`regtool --wow32 get '/HKLM/Software/WinFsp/InstallDir' | cygpath -au -f -`" winfsp.install gcc $^ -o $@ -g -Wall `pkg-config fuse3 --cflags --libs`其中:
regtool --wow32 get '/HKLM/Software/WinFsp/InstallDir'读取 WinFsp 安装目录注册表键,配合cygpath -au -f -转成 Cygwin 绝对路径,并软链接为本地winfsp.install;- 通过
PKG_CONFIG_PATH指向winfsp.install/lib,使pkg-config fuse3能找到 WinFsp 自带的fuse3.pc,从而解析出正确的头文件路径与库链接参数; - 编译命令为
gcc -g -Wall \pkg-config fuse3 --cflags --libs``。
也就是说,"直连 WinFsp DLL"意味着 Cygwin 的fuse3头文件、导入库和运行时都来自 WinFsp 安装目录(而非独立的 CYGFUSE3 包),可执行文件直接与 WinFsp 的 FUSE3 层绑定。
3.3 方式三:Cygwin GCC 链接 CYGFUSE3(make cygfuse3)
make cygfuse3对应目标:
passthrough-cygfuse3: passthrough-fuse3.c gcc $^ -o $@ -g -Wall `pkg-config fuse3 --cflags --libs`这里不再设置PKG_CONFIG_PATH,pkg-config fuse3解析的是 Cygwin 发行版中的 CYGFUSE3(fuse3.pc),即cygfuse包(参见仓库 opt/cygfuse/fuse3/fuse3.pc.in)。CYGFUSE3 是 WinFsp 与 Cygwin 之间的一套胶水库(opt/cygfuse/fuse3/cygfuse.c),它让 Cygwin 的 FUSE3 应用无需修改即可跑在 WinFsp 之上,同时保留 Cygwin POSIX 语义。
3.4 三种方式的取舍
| 构建方式 | 编译环境 | 链接对象 | 适用场景 |
|---|---|---|---|
| Visual Studio | Windows + MSVC | winfsp-*.lib/.dll | 原生 Windows 分发,产物无 Cygwin 依赖 |
make winfsp-fuse3 | Cygwin GCC | WinFsp 自带fuse3.pc与 DLL | 喜欢 POSIX 工具链且希望直接对接 WinFsp |
make cygfuse3 | Cygwin GCC | CYGFUSE3(cygfuse 包) | 已有 FUSE3 应用的 Cygwin 移植/测试 |
需要注意:winfsp-fuse3目标依赖本机已安装 WinFsp(用于读取注册表与头文件/导入库);而cygfuse3目标依赖 Cygwin 的fuse3开发包。两种make方式均在 Cygwin 环境运行,产物为 Cygwin 可执行文件。
4. 运行与挂载:从命令行到 WinFsp.Launcher
4.1 直接命令行挂载
编译产物支持两种启动形态。最直接的方式是位置参数:
# 把 /mnt/data 的内容通过 passthrough-fuse 暴露在挂载点 /mnt/mirror 下 ./passthrough-fuse /mnt/data /mnt/mirror挂载点参数最终交给fuse_main处理,WinFsp-FUSE3 层会把它转换为 Windows 卷挂载。命令行中还可以混入任意 FUSE 选项(usage提示为passthrough-fuse [FUSE options] rootdir mountpoint)。
4.2 配合 WinFsp.Launcher 与 UNC 命名空间
若要纳入 WinFsp 的服务式管理(WinFsp.Launcher 负责启动与监控文件系统进程),应使用--VolumePrefix=(或等价的--UNC=)指定 UNC 前缀。源码中解析逻辑支持形如\\passthrough-fuse\C$\Path的语法:提取第二个反斜杠后的C$\Path,把C$归一化为真实路径C:\,其余部分作为子路径。之后即可用 Windows 命令挂载:
net use z: \\passthrough-fuse\C$\Path这样Z:盘呈现的将是C:\Path目录树的透传视图。这种用法在 doc/WinFsp-Tutorial.asciidoc 等文档中也有对应的工作流介绍(挂载后的目录操作全部经由 passthrough 回调落到真实磁盘)。
5. 底层支撑:WinFsp-FUSE3 层如何工作
passthrough-fuse3之所以"零 Windows 代码"(源码只包含<fuse.h>与少量条件编译的 POSIX 头),是因为 WinFsp 提供了完整的 FUSE3 兼容层:
- 头文件:inc/fuse3/fuse.h、inc/fuse3/fuse_common.h、inc/fuse3/fuse_opt.h 定义了与 Linux FUSE3 对齐的 API(
fuse_main、fuse_operations、fuse_file_info等); - 实现:src/dll/fuse3/fuse3.c 及 src/dll/fuse3/fuse2to3.c 负责把 FUSE3 回调转换为 WinFsp 的原生 FSDK 调用,再经由 src/dll/fsop.c 等内核代理模块进入 WinFsp 文件系统驱动(src/sys/)与 Windows I/O 管理器交互;
- 兼容层:Windows 原生构建通过
winposix(tst/passthrough-fuse3/winposix.h)补齐 POSIX 系统调用,fuse_main被宏改写为WinFspLoad()+fuse_main_real(...),确保在 Windows 下也能安全地延迟加载 WinFsp 运行时; - 能力协商:
FUSE_CAP_READDIRPLUS、FUSE_CAP_CASE_INSENSITIVE(即FSP_FUSE_CAP_CASE_INSENSITIVE)等能力位在 inc/fuse3/fuse_common.h 中定义,由文件系统在init回调里声明。
因此从调用链看:Windows 应用 → WinFsp FUSE3 层(fuse3.c)→ WinFsp FSDK/用户态代理 → winfsp.sys 内核驱动 → 真实磁盘。passthrough-fuse3恰好落在链路的最上层,是观察整条链路行为的最小观测点。
6. 延伸阅读:与 memfs-fuse3 及测试的对照
- 内存版对照:tst/memfs-fuse3/ 的
memfs-fuse3是同样的 FUSE3 工程骨架(C++ 实现,g++ -std=gnu++17),但数据存于内存而非透传磁盘,可对比"有状态文件系统"与"无状态透传"在回调组织上的差异; - FUSE2 版本:tst/passthrough-fuse/ 提供 FUSE2 API 的对应实现,可对照 FUSE2/FUSE3 的 API 演进;
- 回归测试:仓库的 tst/winfsp-tests/ 覆盖了大量文件系统语义测试(如 tst/winfsp-tests/rdwr-test.c、tst/winfsp-tests/memfs-test.c),可用于验证基于 passthrough 构建的文件系统行为是否符合预期;
- 构建基础设施:仓库根目录的
winfsp.sln会聚合这些示例工程,README.md 给出了整体构建与安装指引。
7. 小结
passthrough-fuse3是 WinFsp 生态中"最短路径"的 FUSE3 参考实现:23 个回调、1 个 rootdir、1 张fuse_operations表,就把透传语义完整表达。它同时示范了 WinFsp 的多工具链支持——Visual Studio 原生构建、Cygwin 直连 WinFsp DLL、CYGFUSE3 胶水库,为不同背景的开发者提供了同一套代码的三种编译入口。无论你是要学习 FUSE3 回调的边界行为,还是要为真实项目搭一个"目录映射"文件系统原型,这份 README 连同其源码都是最合适的起点。
- 存储
- 驱动开发
【免费下载链接】winfsp
Windows File System Proxy - FUSE for Windows
相关推荐
30分钟上手WinFsp:从零实现Passthrough文件系统
30分钟上手WinFsp:从零实现Passthrough文件系统 你是否曾因Windows文件系统开发的复杂性望而却步?面对驱动开发的陡峭学习曲线、内核模式的调
存储驱动开发WinFsp 深入解析:在 Windows 上用 WinFsp 构建用户态文件系统的完整实践指南
WinFsp 深入解析:在 Windows 上用 WinFsp 构建用户态文件系统的完整实践指南 WinFsp(Windows File System Prox
存储驱动开发WinFsp深度解析:重新定义Windows文件系统开发范式
WinFsp深度解析:重新定义Windows文件系统开发范式 在当今数据驱动的时代,传统的文件系统架构已难以满足多样化的存储需求。想象一下,将Git仓库、Red
存储驱动开发
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考