news 2026/9/25 22:39:13

WinFsp passthrough-fuse 深度解析:用 FUSE 接口在 Windows 上实现文件系统直通(Pass-through)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
WinFsp passthrough-fuse 深度解析:用 FUSE 接口在 Windows 上实现文件系统直通(Pass-through)
  • 存储
  • 驱动开发

【免费下载链接】winfsp

Windows File System Proxy - FUSE for Windows

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

本篇技术指南围绕 WinFsp 仓库中的tst/passthrough-fuse示例展开,剖析一个把所有文件系统操作原样转发(pass-through)给底层文件系统的 FUSE 文件系统:它同时是理解 FUSE for Windows 编程模型的最小可运行范本、以及验证多种构建链路(Visual Studio / Cygwin GCC / CYGFUSE)的样板工程。读完本文,你将掌握 passthrough-fuse 的操作覆盖、fh句柄编码、能力协商、命令行参数解析等实现要点,并能独立把它当作模板改造成自己的 Windows FUSE 文件系统。

一、passthrough-fuse 是什么

关联文档 tst/passthrough-fuse/README.md 对它的定位只有一句话:

Passthrough-fuseis a simple FUSE file system that passes all file system operations to an underlying file system.

即:这是一个"简单 FUSE 文件系统,将全部文件系统操作转发给底层文件系统"。它本身不管理任何数据,只是把 WinFsp 的 FUSE 接口调用映射为对真实目录(rootdir)的 POSIX 风格操作,因此非常适合做 FUSE API 的教学样例和功能验证基准。

1.1 仓库中的文件布局

文件作用
passthrough-fuse.c核心实现,全部 FUSE 操作回调与main()
passthrough-fuse.sln / passthrough-fuse.vcxprojVisual Studio 工程
MakefileCygwin GCC 构建脚本(winfsp-fuse/cygfuse两个目标)
winposix.h / winposix.c面向 Windows 的最小 POSIX 适配层
winposix.c 同目录直通所需的 POSIX 文件 API 的 Windows 实现

仓库中还有同一主题的姊妹工程:passthrough-fuse3(面向 FUSE 3 API 的直通实现)、passthrough-cpp(C++ 版本,直接使用 WinFsp 原生接口)以及 memfs-fuse(内存文件系统)。对比阅读可以清晰看出 FUSE 抽象层对文件系统开发的简化作用。

二、三种构建方式详解

关联文档明确给出了 passthrough-fuse 的三种构建路径,下面结合仓库文件逐一展开。

2.1 方式一:Visual Studio(winfsp.sln)

README 标注为使用winfsp.sln解决方案构建。从 passthrough-fuse.vcxproj 可以看到工程的关键配置:

  • 预处理器定义:FSP_FUSE_USE_STAT_EX(启用增强的 stat 结构,携带st_flags文件属性位)、WIN32、_DEBUG/_CONSOLE等;
  • 头文件搜索路径:$(MSBuildProgramFiles32)\WinFsp\inc\fuse;$(MSBuildProgramFiles32)\WinFsp\inc,即链接的是已安装的 WinFsp SDK中随附的 FUSE 兼容头文件(与仓库内 inc/fuse 同源);
  • 链接库:winfsp-$(PlatformTarget).lib(x64 为winfsp-x64.lib,x86 为winfsp-x86.lib),并配合<DelayLoadDLLs>winfsp-$(PlatformTarget).dll</DelayLoadDLLs>延迟加载;
  • 平台支持:工程覆盖Debug/Release × Win32/x64/ARM64六种配置,ARM64 输出名为passthrough-fuse-a64;
  • 源文件:同时编译 passthrough-fuse.c 与 winposix.c,因此整个实现可以在原生 Windows 上以_WIN64/_WIN32路径编译。

2.2 方式二:Cygwin GCC + WinFsp-FUSE(make winfsp-fuse)

Makefile 中对应目标如下:

passthrough-winfsp-fuse: export PKG_CONFIG_PATH=$(PWD)/winfsp.install/lib passthrough-winfsp-fuse: passthrough-fuse.c ln -nsf "`regtool --wow32 get '/HKLM/Software/WinFsp/InstallDir' | cygpath -au -f -`" winfsp.install gcc $^ -o $@ -g -Wall `pkg-config fuse --cflags --libs`

要点:

  • 通过regtool --wow32 get '/HKLM/Software/WinFsp/InstallDir'读取注册表中 WinFsp 的安装目录,再用cygpath转成 Cygwin 路径,软链接为winfsp.install;
  • 设置PKG_CONFIG_PATH=$(PWD)/winfsp.install/lib后,pkg-config fuse会命中 WinFsp 自带的 fuse.pc(Name: fuse、Version: 2.8,Cflags: -I"${incdir}",Libs: "${implib}",其中 implib 指向bin/winfsp-${arch}.dll),从而直接链接 WinFsp DLL;
  • 编译参数为gcc -g -Wall,产物名为passthrough-winfsp-fuse。

2.3 方式三:Cygwin GCC + CYGFUSE(make cygfuse)

passthrough-cygfuse: passthrough-fuse.c gcc $^ -o $@ -g -Wall `pkg-config fuse --cflags --libs`

该目标不做任何 WinFsp 特定处理,直接使用 Cygwin 环境提供的 FUSE 开发包(CYGFUSE,其实现位于 opt/cygfuse/fuse/cygfuse.c,包含 fuse/fuse3 两套Makefile)。此时源码走#else分支:#include <dirent.h>、#include <unistd.h>,并使用标准 POSIX 系统调用。也就是说,同一份 passthrough-fuse.c 既可编译成原生 Windows 程序(WinFsp FUSE),也可编译成 Cygwin 程序(CYGFUSE),这正是 WinFsp FUSE 兼容 API 设计目标的最好例证。

三、核心实现:把 FUSE 操作"直通"到宿主文件系统

3.1 私有数据结构与路径拼接

passthrough-fuse.c 定义了核心状态:

typedef struct { const char *rootdir; size_t rootlen; } PTFS;

rootdir是被直通的底层目录。所有路径操作的第一步都是把 FUSE 传来的虚拟路径拼接到rootdir之后:

#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 下winposix.h把PATH_MAX定义为 1024(winposix.h),为 UTF-8 多字节文件名预留了足够空间;
  • 拼接失败(缓冲区溢出)时返回-ENAMETOOLONG,遵循 FUSE 回调"返回负 errno"的约定。

rootdir和文件系统私有数据通过fuse_main(argc, argv, &ptfs_ops, &ptfs)的第 4 个参数传入,回调内用fuse_get_context()->private_data取回(见ptfs_init的返回值,passthrough-fuse.c)。

3.2 fuse_operations 注册表:覆盖哪些操作

ptfs_ops 是核心,它把 FUSE 回调一一映射到 POSIX 调用:

FUSE 操作底层实现底层系统调用
getattrptfs_getattrlstat
mkdir/unlink/rmdir/rename同名回调mkdir/unlink/rmdir/rename
chmod/chownptfs_chmod/ptfs_chownchmod/lchown
truncate/ftruncateptfs_truncate/ptfs_ftruncatetruncate/ftruncate
open/createptfs_open/ptfs_createopen(path, fi->flags[, mode])
read/writeptfs_read/ptfs_writepread/pwrite(按 offset 定位)
release/fsync同名回调close/fsync
statfsptfs_statfsstatvfs
setxattr/getxattr/listxattr/removexattr同名回调lsetxattr/lgetxattr/llistxattr/lremovexattr
opendir/readdir/releasedir同名回调opendir/readdir(+filler)/closedir
initptfs_init能力协商(见 3.5)
fgetattrptfs_fgetattrfstat
utimensptfs_utimens(由PTFS_UTIMENS宏控制,否则退化为utime)utimensat(AT_FDCWD, path, tv, AT_SYMLINK_NOFOLLOW)
Windows 扩展getpath/setcrtime/chflagsgetpath/fgetpath/setcrtime/lchflags

从 errno.i 等 WinFsp FUSE 实现可见,每个回调成功返回0/字节数,失败返回-errno,错误码最终由 WinFsp 映射为 NTSTATUS。这是编写任何 WinFsp FUSE 文件系统都必须遵守的约定。

3.3 文件句柄编码:一个fh同时装下 fd 与目录指针

passthrough-fuse.c 用一组宏把 64 位fi->fh的最高位当作"目录位":

#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))
  • 文件打开时fi_setfd(fi, fd):低 63 位存文件描述符;
  • 目录打开时fi_setdirp(fi, dirp):最高位置 1、低 63 位存DIR *;
  • 读取时fi_fd(fi)统一取出 fd;对目录句柄,fi_fd通过dirfd(dirp)拿到 WinFsp FUSE 需要的目录句柄(Windows 下winposix.c的dirfd返回底层CreateFileW得到的 HANDLE 值)。

这种"一字段两用"的技巧避免了为文件/目录维护两张句柄映射表,是直通型文件系统常见的工程手法。

3.4 readdir 直通与 STAT_EX 增强

static int ptfs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, fuse_off_t off, struct fuse_file_info *fi) { DIR *dirp = fi_dirp(fi); struct dirent *de; rewinddir(dirp); for (;;) { errno = 0; if (0 == (de = readdir(dirp))) break; #if defined(_WIN64) || defined(_WIN32) if (0 != filler(buf, de->d_name, &de->d_stat, 0)) #else if (0 != filler(buf, de->d_name, 0, 0)) #endif return -ENOMEM; } return -errno; }

filler每接收一个目录项即向 WinFsp 缓冲区写入一项;若缓冲区满返回非 0,回调返回-ENOMEM。Windows 构建下还会把de->d_stat(由 winposix.c 的readdir用FindFirstFileW/FindNextFileW填充)随目录项传给 FUSE,配合FSP_FUSE_CAP_READDIR_PLUS能力减少后续getattr往返。

3.5 init 回调与能力协商

ptfs_init在挂载初始化时向内核协商增强能力(passthrough-fuse.c):

conn->want |= (conn->capable & FSP_FUSE_CAP_READDIR_PLUS); conn->want |= (conn->capable & FSP_FUSE_CAP_STAT_EX); conn->want |= (conn->capable & FSP_FUSE_CAP_CASE_INSENSITIVE);

对应的能力位定义在 inc/fuse/fuse_common.h:FSP_FUSE_CAP_READDIR_PLUS(1<<21)、FSP_FUSE_CAP_STAT_EX(1<<23),以及FUSE_CAP_CASE_INSENSITIVE(1<<29)(声明文件系统大小写不敏感,贴合 Windows 语义)。conn->want |= conn->capable & ...的写法是标准的"只请求内核支持的能力"协商模式。

3.6 Windows 专有扩展:getpath / setcrtime / chflags

在_WIN64/_WIN32分支下,passthrough-fuse 还注册了 WinFsp FUSE 的扩展回调:

  • getpath(passthrough-fuse.c):根据句柄反查文件的虚拟路径。有fi时用fgetpath(fd),无fi时用getpath(path),再把返回的绝对路径减去ptfs->rootlen前缀,还原成挂载点内的虚拟路径;若结果为空则返回"/"。这是 Windows 文件系统支持"路径查询"(如GetFinalPathNameByHandle场景)所必需的;
  • setcrtime(passthrough-fuse.c):设置文件创建时间(Windows 特有语义);
  • chflags(passthrough-fuse.c):仅在定义FSP_FUSE_USE_STAT_EX时注册,映射到lchflags,对应 Windows 的隐藏/只读/系统/归档属性位(见 winposix.c 中MapFileAttributesToFlags/MapFlagsToFileAttributes的双向映射)。

四、main 入口:参数解析、rootdir 与 WinFsp 加载

4.1 命令行格式

passthrough-fuse.c 定义了用法:

usage: passthrough-fuse [FUSE options] rootdir mountpoint

main()的解析逻辑(passthrough-fuse.c):

if (3 <= argc && '-' != argv[argc - 2][0] && '-' != argv[argc - 1][0]) { ptfs.rootdir = realpath(argv[argc - 2], 0); argv[argc - 2] = argv[argc - 1]; argc--; }

即:最后两个位置参数分别是 rootdir(被直通的真实目录)和 mountpoint(挂载点盘符/目录),且二者不能以-开头。realpath会把 rootdir 规范化为绝对路径。随后调用fuse_main(argc, argv, &ptfs_ops, &ptfs)进入 WinFsp FUSE 的主循环。

4.2 --UNC / --VolumePrefix:配合 WinFsp.Launcher 以服务方式运行

Windows 构建下,如果 rootdir 尚未确定,main()会扫描命令行中的--UNC=与--VolumePrefix=参数(passthrough-fuse.c)。其语法为:

--VolumePrefix=\passthrough-fuse\C$\Path

解析时把C$还原为盘符C:后再realpath。这样文件系统就可以由 WinFsp.Launcher(见 src/launcher/launcher.c)以服务方式启动,并通过 UNC 共享方式挂载:

net use z: \\passthrough-fuse\C$\Path

也就是说,net use的共享名passthrough-fuse就是这里传入的--VolumePrefix前缀。

4.3 fuse_main 内部流程

fuse_main最终落到 WinFsp FUSE 实现的 fsp_fuse_main_real,其执行序列是:

  1. fsp_fuse_parse_cmdline:解析 FUSE 选项与挂载点(fuse_main.c);
  2. fsp_fuse_mount(mountpoint, args):创建 WinFsp 卷并挂载;
  3. fsp_fuse_new(ch, args, ops, opsize, data):创建 FUSE 会话,注册回调表;
  4. env->daemonize(foreground):按前台/后台模式运行;
  5. env->set_signal_handlers(f):安装信号处理;
  6. multithreaded ? fsp_fuse_loop_mt(env, f) : fsp_fuse_loop(env, f):进入事件循环;
  7. 退出时依次fsp_fuse_destroy、fsp_fuse_unmount,并返回!!result(0 成功、1 失败)。

选项解析表(fuse_main.c)定义了 FUSE 标准选项:

选项含义
-h/--help打印帮助
-ho仅打印挂载选项帮助
-d/-o debug开启调试输出(隐含-f前台)
-f前台运行
-s关闭多线程运行
-V/--version打印版本

五、winposix:支撑"直通"语义的 Windows POSIX 层

原生 Windows 没有 POSIX 文件 API,而 passthrough-fuse.c 写的却是标准 POSIX 调用。为此仓库提供了 winposix.c(约 1050 行),其文件头注释明确说明:

This is a very simple Windows POSIX layer. It handles all the POSIX file API's required to implement passthrough-fuse in POSIX, however the API handling is rather unsophisticated. Ways to improve it: use the FspPosix* API's to properly handle file names and security.

也就是说,它是为支撑 passthrough-fuse 而裁剪的最小 POSIX 适配层,并指出可用 WinFsp 的FspPosix*API 进一步完善文件名与安全语义。其主要工作:

  • 句柄抽象:open用CreateFileW返回 HANDLE,强转成 int 作为 fd;pread/pwrite用OVERLAPPED实现带偏移读写;fsync映射FlushFileBuffers(winposix.c);
  • 路径处理:uncpath把 POSIX 路径转换为\\?\前缀的 Windows 绝对路径,并归一化/与\(winposix.c);
  • xattr → NTFS EA:lsetxattr/lgetxattr/llistxattr/lremovexattr基于NtSetEaFile/NtQueryEaFile实现(源码顶部以NTSYSAPI声明了这两个 ntdll 导出函数),并做了名称长度(≤254)、值长度(≤0xffff)等校验(winposix.c);
  • 时间戳转换:用FspPosixFileTimeToUnixTime/FspPosixUnixTimeToFileTime在 Windows FILETIME 与 POSIX timespec 之间转换;
  • 错误映射:maperror把GetLastError()的 Windows 错误码映射为 errno(如ERROR_FILE_NOT_FOUND → ENOENT、ERROR_ACCESS_DENIED → EACCES、ERROR_FILE_EXISTS → EEXIST,见 winposix.c 起的 switch 表);
  • stat 填充:fstat/readdir用GetFileInformationByHandle/WIN32_FIND_DATAW填充fuse_stat(st_mode 固定0777、st_nlink 固定 1,size 由 64 位文件大小拼装,并在FSP_FUSE_USE_STAT_EX下填充st_flags);
  • 安全相关:chmod与lchown直接返回 0(不实现文件安全),符合"直通但简化"的定位。

六、运行与验证

6.1 直接运行

在 Windows 命令行下(以 x64 为例):

passthrough-fuse-x64 C:\real\folder Z:

把真实目录C:\real\folder挂载为盘符Z:,此后对Z:的一切读写都会落到C:\real\folder。调试运行可加-d -f(调试输出 + 前台运行)。

通过 WinFsp.Launcher 以服务方式运行并使用 UNC 挂载:

net use z: \\passthrough-fuse\C$\Path

6.2 测试覆盖

WinFsp 的测试套件 tst/winfsp-tests/fuse-test.c 等针对 FUSE 兼容层进行系统性测试;仓库构建流程(appveyor.yml、tools/run-tests.bat)也会编译并运行这些示例。passthrough-fuse 同时作为 WinFsp 教程 涉及的样例之一,是验证 FUSE 文件系统行为是否符合 Windows 语义的参考载体。

七、从 passthrough-fuse 到你自己的文件系统

passthrough-fuse 的工程价值在于它是一份"操作覆盖最全、逻辑最简单"的参考实现:

  1. 学习 FUSE 编程模型:对照 inc/fuse/fuse.h 与 inc/fuse/fuse_common.h 中fuse_operations、fuse_file_info(含fh、flags)、fuse_conn_info的定义,结合本示例逐回调理解语义;
  2. 作为新文件系统的起点:复制passthrough-fuse.c的结构,替换回调中的底层调用即可实现映射型/虚拟型文件系统(如仓库中的 memfs-fuse 就是"直通→内存"的变体);
  3. 验证构建链路:三种构建方式(VS / WinFsp-FUSE / CYGFUSE)可用来验证目标环境的工具链配置,其中 CYGFUSE 实现位于 opt/cygfuse/fuse/cygfuse.c;
  4. 了解 WinFsp 专有能力:getpath、setcrtime、chflags、READDIR_PLUS、STAT_EX、CASE_INSENSITIVE等扩展展示了 FUSE 抽象如何承载 Windows 特有文件系统语义,这些能力在编写面向 Windows 用户的真实文件系统时几乎必不可少。

如果你需要 FUSE 3 版本,直接对照 passthrough-fuse3 即可看到同一直通逻辑在 fuse3 兼容层下的写法差异——这也是从 FUSE 2 迁移到 FUSE 3 的绝佳对照教材。

  • 存储
  • 驱动开发

【免费下载链接】winfsp

Windows File System Proxy - FUSE for Windows

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 22:35:15

Atlas 300V 24G推理加速卡YOLO实战:从模型转到部署避坑指南

不知道有多少朋友和我一样&#xff0c;第一次听到“Atlas 300V 24G”这个名字的时候&#xff0c;下意识会把它和NVIDIA的A100、4090这类GPU划等号。毕竟名字里带个“V”&#xff0c;参数表里赫然写着24G&#xff0c;怎么看都像是一块“国产大显存显卡”。可真等到你把它插到服务…

作者头像 李华
网站建设 2026/9/25 22:33:28

客服团队CRM选型与落地:通信一体化如何解决客户信息割裂问题?

如果你管过一支坐席团队&#xff0c;大概率经历过这种场景&#xff1a;客户刚打完电话&#xff0c;又在微信上追问一遍&#xff0c;坐席翻着 Excel 回复“我查一下&#xff0c;晚点答复您”。我接手团队客服流程改造时&#xff0c;这种割裂状态每天都在消耗客户耐心&#xff0c…

作者头像 李华
网站建设 2026/9/25 22:19:46

数据库批量删除表:安全方案、踩坑细节与误删恢复

你是不是也遇到过这种场景&#xff1a;某天突然发现测试库里躺着上百张tmp_2024_*临时表&#xff0c;或者分库分表切流之后老表没清理&#xff0c;一眼望过去全是几十个order_2023_*。真正让你头大的不是删除单张表&#xff0c;而是一次要处理几百张表——一张张手点右键删能删…

作者头像 李华
网站建设 2026/9/25 22:12:01

Android Studio拼图游戏开发:兼容API 21+的轻量级期末大作业实现

简介&#xff1a;这是一份面向计算机及相关专业本科生的Android移动应用开发实战资源&#xff0c;专为课程设计、期末大作业及毕业设计前期练手打造。项目基于Android Studio开发&#xff0c;实现经典拼图游戏功能&#xff0c;含完整可运行源码、详细说明文档及发布版APK&#…

作者头像 李华