- 存储
- 驱动开发
【免费下载链接】winfsp
Windows File System Proxy - FUSE for Windows
本篇技术指南围绕 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.vcxproj | Visual Studio 工程 |
| Makefile | Cygwin 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 操作 | 底层实现 | 底层系统调用 |
|---|---|---|
getattr | ptfs_getattr | lstat |
mkdir/unlink/rmdir/rename | 同名回调 | mkdir/unlink/rmdir/rename |
chmod/chown | ptfs_chmod/ptfs_chown | chmod/lchown |
truncate/ftruncate | ptfs_truncate/ptfs_ftruncate | truncate/ftruncate |
open/create | ptfs_open/ptfs_create | open(path, fi->flags[, mode]) |
read/write | ptfs_read/ptfs_write | pread/pwrite(按 offset 定位) |
release/fsync | 同名回调 | close/fsync |
statfs | ptfs_statfs | statvfs |
setxattr/getxattr/listxattr/removexattr | 同名回调 | lsetxattr/lgetxattr/llistxattr/lremovexattr |
opendir/readdir/releasedir | 同名回调 | opendir/readdir(+filler)/closedir |
init | ptfs_init | 能力协商(见 3.5) |
fgetattr | ptfs_fgetattr | fstat |
utimens | ptfs_utimens(由PTFS_UTIMENS宏控制,否则退化为utime) | utimensat(AT_FDCWD, path, tv, AT_SYMLINK_NOFOLLOW) |
| Windows 扩展 | getpath/setcrtime/chflags | getpath/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 mountpointmain()的解析逻辑(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,其执行序列是:
fsp_fuse_parse_cmdline:解析 FUSE 选项与挂载点(fuse_main.c);fsp_fuse_mount(mountpoint, args):创建 WinFsp 卷并挂载;fsp_fuse_new(ch, args, ops, opsize, data):创建 FUSE 会话,注册回调表;env->daemonize(foreground):按前台/后台模式运行;env->set_signal_handlers(f):安装信号处理;multithreaded ? fsp_fuse_loop_mt(env, f) : fsp_fuse_loop(env, f):进入事件循环;- 退出时依次
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$\Path6.2 测试覆盖
WinFsp 的测试套件 tst/winfsp-tests/fuse-test.c 等针对 FUSE 兼容层进行系统性测试;仓库构建流程(appveyor.yml、tools/run-tests.bat)也会编译并运行这些示例。passthrough-fuse 同时作为 WinFsp 教程 涉及的样例之一,是验证 FUSE 文件系统行为是否符合 Windows 语义的参考载体。
七、从 passthrough-fuse 到你自己的文件系统
passthrough-fuse 的工程价值在于它是一份"操作覆盖最全、逻辑最简单"的参考实现:
- 学习 FUSE 编程模型:对照 inc/fuse/fuse.h 与 inc/fuse/fuse_common.h 中
fuse_operations、fuse_file_info(含fh、flags)、fuse_conn_info的定义,结合本示例逐回调理解语义; - 作为新文件系统的起点:复制
passthrough-fuse.c的结构,替换回调中的底层调用即可实现映射型/虚拟型文件系统(如仓库中的 memfs-fuse 就是"直通→内存"的变体); - 验证构建链路:三种构建方式(VS / WinFsp-FUSE / CYGFUSE)可用来验证目标环境的工具链配置,其中 CYGFUSE 实现位于 opt/cygfuse/fuse/cygfuse.c;
- 了解 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
相关推荐
本文内容总结
本文内容总结 这篇文章详细介绍了 s3fs fuse 工具的核心原理、架构设计、关键组件实现以及性能优化策略,帮助用户理解如何通过 s3fs fuse 将AWS
后端对象存储存储告别跨平台壁垒:WinFsp FUSE桥接实现Linux文件系统无缝迁移Windows
告别跨平台壁垒:WinFsp FUSE桥接实现Linux文件系统无缝迁移Windows 你是否在Windows环境下开发时,因Linux特有的文件系统功能(如F
存储驱动开发从零开始理解s3fs-fuse:深入解析FUSE接口实现的核心机制
从零开始理解s3fs fuse:深入解析FUSE接口实现的核心机制 s3fs fuse是一款基于FUSE(用户空间文件系统)技术的工具,它能够将Amazon S
后端对象存储存储
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考