☰
WinFsp 的 Passthrough-FUSE3 文件系统:源码实现与三种构建方式深度解析
2026/9/26 8:06:37 网站建设 项目流程
  • 存储
  • 驱动开发

【免费下载链接】winfsp

Windows File System Proxy - FUSE for Windows

项目地址:https://gitcode.com/gh_mirrors/wi/winfsp
点击查看免费下载

导读:本文以 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):

回调底层调用说明
getattrlstat/fstat有fi时走 fd 路径,避免 TOCTOU
mkdir/rmdirmkdir/rmdir
unlink/renameunlink/rename
chmod/chownchmod/lchown
truncatetruncate/ftruncate同样支持 fd 路径
open/createopen(path, fi->flags[, mode])透传fi->flags,返回值存入fh
read/writepread/pwrite按off偏移读写,返回实际字节数
statfsstatvfs报告底层文件系统的容量信息
releaseclose关闭 fd
fsyncfsync
setxattr/getxattr/listxattr/removexattrlsetxattr等透传扩展属性
opendir/readdir/releasediropendir/readdir+filler/closedir目录枚举
init—能力协商(见 2.5)
utimensutimensat(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):

  1. 检查命令行最后两个参数是否以-开头,若不满足则视为rootdir mountpoint,用realpath规范化 rootdir,随后把它从参数表中移除,剩下纯 FUSE 选项交给fuse_main;
  2. 若未从位置参数拿到 rootdir(Windows 构建下),则扫描--UNC=与--VolumePrefix=前缀参数,从类似\\passthrough-fuse\C$\Path的形式中提取盘符路径并realpath之;
  3. 两者皆失败则打印用法并退出:
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 StudioWindows + MSVCwinfsp-*.lib/.dll原生 Windows 分发,产物无 Cygwin 依赖
make winfsp-fuse3Cygwin GCCWinFsp 自带fuse3.pc与 DLL喜欢 POSIX 工具链且希望直接对接 WinFsp
make cygfuse3Cygwin GCCCYGFUSE3(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

项目地址:https://gitcode.com/gh_mirrors/wi/winfsp
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询