Compare commits

..

58 Commits

Author SHA1 Message Date
8ba8d31e50 changelog: v1.10B4 update 2021-11-26 10:34:33 +00:00
c1ab78e8e1 changelog: v1.10B4 update 2021-11-26 10:33:29 +00:00
ed404ee579 inc: winfsp.h: FspCleanupDelete: POSIX semantics 2021-11-25 12:18:43 +00:00
620ebd9e72 inc: winfsp.h: FspCleanupDelete: POSIX semantics 2021-11-25 12:14:48 +00:00
fc8d18e4de doc: revert the Delete redesign 2021-11-25 11:51:29 +00:00
00219f29cf sys: POSIX rename improvements 2021-11-25 10:35:22 +00:00
c4f994f8f6 sys: FspFsvolSetDispositionInformation
return STATUS_FILE_DELETED if file is already deleted
2021-11-24 18:31:48 +00:00
91211f6ccb sys: reimplement POSIX unlink 2021-11-24 16:03:31 +00:00
666561bfa1 dll: revert the Delete redesign 2021-11-22 18:26:45 +00:00
4e94991221 dll: fuse: revert the Delete redesign 2021-11-22 14:46:57 +00:00
826a514fe3 dll: dotnet: revert the Delete redesign 2021-11-22 13:55:32 +00:00
a9d90acd71 tst: winfsp-tests: exec_delete_test 2021-11-20 10:53:57 +00:00
d72fe2ee33 sys: FspFsvolSetDispositionInformation: ignore FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK flag 2021-11-19 17:16:36 +00:00
e09042c028 changelog: update with v1.10B4 notes 2021-11-18 16:04:44 +00:00
6e13825dcc build: bump version to WinFsp 2022 Beta4 2021-11-18 15:52:39 +00:00
cc90b5dd80 changelog: fix notes accidentally magnled in previous release 2021-11-18 15:51:19 +00:00
4b6d9b70d7 tools: run-tests: FUSE sample testing
- exclude reparse_symlink* on AppVeyor
2021-11-18 10:34:48 +00:00
1f68eb0f3d tst: memfs-fuse: remove initializer designators
- allows this project to be built with VS2015
2021-11-17 22:17:07 +00:00
61c48ab417 tools: run-tests: fix ignored ERRORLEVEL 2021-11-17 15:19:29 +00:00
d7e49dfb20 tools: run-tests: add memfs-fuse testing 2021-11-17 13:51:18 +00:00
10c5fa6301 tools: run-tests: add memfs-fuse testing 2021-11-17 13:46:09 +00:00
368855676a tools: run-tests: fix !time! retrieval 2021-11-14 13:01:56 +00:00
6fbe73ce4d tools: run-tests.bat: fix parens escaping 2021-11-14 12:45:13 +00:00
e1763bcd10 tools: run-tests: add test duration 2021-11-14 11:35:20 +00:00
378beb55eb dll: fuse: CheckSymlinkDirectory: optimization 2021-11-14 10:48:17 +00:00
14b212f9af tst: winfsp-tests: remove symbol SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 2021-11-14 00:09:46 +00:00
cca8b32128 installer: add memfs-fuse sample 2021-11-13 23:37:34 +00:00
0b94e8bc6a dll: fuse: multiple improvements and fixes
- Symlinks: Now supports conventing a directory into a symlink reparse
point.

- Symlinks: The determination of whether a symlink is a file or
directory is now possible for file systems that do not support slashdot
(/.) queries.

- EA: Now allows the removal of non-existant EA without error (this is
allowed on Windows).
2021-11-13 23:27:47 +00:00
ec3386c2b3 tst: winfsp-tests: add --fuse-external option 2021-11-13 10:15:12 +00:00
d67a917c6f tst: memfs-fuse: add FUSE reference file system 2021-11-13 10:11:45 +00:00
2a86cd2c90 doc: WinFsp-Delete-Redesign: fix IRP_MJ_CLOSE doc link 2021-11-08 16:57:35 +00:00
67c4011263 update README with reference to Windows 11 2021-11-08 16:20:16 +00:00
90d67bc07d tools: build: sign cab file using SHA-256 2021-11-08 15:20:54 +00:00
70643f40a0 update changelog 2021-11-08 13:38:02 +00:00
366a9c562f art: update with Win11 colors 2021-11-08 13:17:35 +00:00
d7a8b0d9fb launcher: SvcInstanceStartWithArgvCopy: properly handle timeout condition 2021-10-31 12:35:30 +00:00
52dd6f7478 dll: fuse: FSP_FUSE_CAP_DELETE_ACCESS 2021-10-26 13:21:57 +01:00
73359d682b dll: fuse: convert EPERM/EACCES from unlink/rmdir to STATUS_CANNOT_DELETE 2021-10-25 13:58:52 +01:00
2a3f1a3990 doc: update NTFS compatibility 2021-10-22 15:22:08 +01:00
e823103334 doc: update API documentation 2021-10-22 14:47:58 +01:00
1208c6c652 doc: fix code in WinFsp Delete Redesign document 2021-10-22 12:22:33 +01:00
b13b24e0b1 doc: add WinFsp Delete Redesign document 2021-10-22 12:15:16 +01:00
91aa0ac2d0 inc: winfsp.h: fix source doc for Delete 2021-10-22 11:39:05 +01:00
dcce0d44a7 build: bump version to 2021.1 Beta3 2021-10-21 21:52:18 +01:00
af257d4bff tst: winfsp-tests: eliminate use of symbol FILE_SUPPORTS_POSIX_UNLINK_RENAME 2021-10-21 15:56:58 +01:00
9eaaefd154 sys,dll: debug support for DispositionEx, RenameEx 2021-10-21 15:54:38 +01:00
bb3e92df6c sys, dll: implement POSIX semantics for Rename 2021-10-21 15:39:25 +01:00
76bfa395a8 dotnet: implement new Delete design and POSIX semantics 2021-10-20 12:23:14 +01:00
81248f3899 dotnet: FileSystemHost: zero-init allocations made using Marshal.AllocHGlobal 2021-10-20 09:04:20 +01:00
a4d7aee6f5 dll: fuse: implement new Delete design and POSIX semantics 2021-10-19 17:54:01 +01:00
19823d84de sys,dll,inc: implement new Delete design and POSIX semantics 2021-10-19 15:23:15 +01:00
3e66082f11 sys: FspFsvolQueryDirectoryCopy: fix issue #380 2021-10-13 16:35:02 +01:00
490d021b22 dll: mount: MountDoNotUseLauncher registry setting 2021-08-11 16:27:29 +01:00
2d41693f3c src: dotnet: fix newlines 2021-06-20 20:44:06 +03:00
068270fa7f update changelog 2021-06-13 12:40:41 -07:00
f51bdef534 dll: fuse: fix path arg to release on Create error
When the kernel sends a `Create` message the WinFsp-FUSE layer creates
and opens the file (as per Windows semantics). Sometimes an additional
operation needs to be performed after the file has been opened, which
may fail. In this case the just opened file must be released.

In this particular case the WinFsp-FUSE layer used to call `release`
with an uninitialized path. This commit fixes the problem.

This problem was originally reported in cgofuse (billziss-gh/cgofuse#58)
2021-06-09 10:17:59 -07:00
63f91cc667 build: bump version 2021-06-09 10:13:41 -07:00
6e3e469fcb doc: update known file systems 2021-06-08 12:22:26 -07:00
66 changed files with 2901 additions and 261 deletions

View File

@ -1,6 +1,107 @@
= Changelog
v1.10B4 (2022 Beta4)::
+
--
Changes since v1.10B3:
* The Delete functionality introduced in release 2021.1 Beta3 (v1.10B3) has been reverted. For more information see this thread: https://groups.google.com/g/winfsp/c/1VYgTp1JpSI
Changes since v1.9:
* [NEW] Official Windows 11 support.
* [NEW] Support for `FileDispositionInformationEx` and `FileRenameInformationEx` has been added.
* [NEW] WinFsp now supports rebranding of the build products. This is primarily useful to commercial licensees. See https://github.com/billziss-gh/winfsp/wiki/WinFsp-Rebranding for more information.
* [NEW] WinFsp-FUSE has a new capability `FSP_FUSE_CAP_DELETE_ACCESS`. If this capability is specified WinFsp will call the FUSE `access` method with the WinFsp-specific flag `FSP_FUSE_DELETE_OK` to check whether the deletion should be allowed. This can be used to disallow deletion of files and directories that would normally be allowed under the FUSE/POSIX model.
* [NEW] WinFsp-FUSE has a new method for determining file access in the case where the user's primary SID (Windows Security Identifier) and group SID are the same (for example, this can happen when someone uses a Microsoft account as their primary login).
** Previously when the user SID and group SID were the same WinFsp-FUSE looked at the UNIX permissions for the owner and the UNIX permissions for the group and used the MOST restrictive permissions, which often resulted in inadvertent "access denied" errors. (For example, if the owner permission was `rw-` and the group permission was `---` the result was `---` and therefore access denied).
** Going forward this behavior will change. The user and group mode permissions will not be considered together even in the case where the user SID and group SID are the same. This will resolve the access denied errors.
** However to preserve backward compatibility (there might be some file systems that rely on the old behavior) we will do so in stages. For release v1.10 (2021.1) there is a new registry setting under `HKLM\SOFTWARE\WinFsp` (or `HKLM\SOFTWARE\WOW6432Node\WinFsp` on a 64-bit system) called `DistinctPermsForSameOwnerGroup`, which if set to 1 will direct WinFsp-FUSE to use the new behavior. The default value is 0 which directs WinFsp-FUSE to use the old behavior. This default will change in a future release.
* [NEW] A new registry setting under `HKLM\SOFTWARE\WinFsp` (or `HKLM\SOFTWARE\WOW6432Node\WinFsp` on a 64-bit system) called `MountDoNotUseLauncher` has been introduced, which if set to 1 will disable the use of the Launcher during mounting. The default value is 0 which allows the use of the Launcher during mounting in those rare cases when necessary. (In general the Launcher is not necessary for mounting. However when running a file system in the Windows Service context (session 0) under an account that is not LocalSystem (e.g. `NT AUTHORITY\NETWORK SERVICE`), the Launcher is used to create global drives.)
* [NEW] A new sample memfs-fuse file system written in C++ has been added. This file system uses all FUSE2 features offered by WinFsp-FUSE, passes all file system tests (that can be passed by a FUSE2 file system) and will act as the reference WinFsp-FUSE file system going forward.
* [FIX] File share access when overwriting a file (e.g. when using `TRUNCATE_EXISTING`) is now done in a manner compatible with NTFS (previously there were cases when overwriting a file where behavior diverged from the NTFS one). (See GitHub issue #364.)
* [FIX] The FSD will now report a file's reparse tag in the `EaSize` field of `FILE_FULL_DIR_INFORMATION` and friends. This fixes problems such as `cmd.exe` not recognizing symlinks in a `dir` command. (See GitHub issue #380.)
* [FIX] Fixed a problem in the file system shutdown protocol which resolves an occasional access violation in the user mode file system process. Previously it was possible for a file system to crash when stopping itself using `FspFileSystemStopDispatcher`; this problem has been rectified. (See GitHub issue #369.)
* [FIX] Improved symlink support for FUSE file systems. Notably:
** FUSE file systems now support converting a directory to a symlink reparse point.
** The determination of whether a symlink is a file or directory (necessary because the Windows file system makes this distinction) is now possible for file systems that do not support slashdot (`/.`) queries. (A slashdot query is one like `getattr("/PATH/.")`; such queries are not normally expected to be resolved by FUSE file systems.)
* [FIX] Fixed a problem in the FUSE layer where in some rare circumstances the `release` operation could be called with an uninitialized `path` argument. (See GitHub issue billziss-gh/cgofuse#58 and commit f51bdef53427d1bba688fb6c768792fdc22ffc7b).
* [FIX] Fixed a potential problem when launching file system instances using the Launcher. (See GitHub issue #356.)
* [FIX] The `winfsp.h` header no longer defines `FILE_FULL_EA_INFORMATION` when compiled under mingw. This is because the mingw tool chain already includes a definition of this type. (GitHub PR #371. Thanks @lemourin.)
--
v1.10B3 (2021.1 Beta3)::
Changes since v1.9:
* [NEW] Official Windows 11 support.
* [NEW] WinFsp has had its Delete functionality redesigned in release 2021.1 Beta3. This redesign unifies all Windows file deletion semantics under a single file system operation that also supports the new POSIX semantics introduced in Windows 10 Redstone 1. The new Delete design is recommended for new file systems, however existing file systems will continue to work without any changes. See https://github.com/billziss-gh/winfsp/wiki/WinFsp-Delete-Redesign for more information.
* [NEW] Support for `FileDispositionInformationEx` and `FileRenameInformationEx` has been added (see above).
* [NEW] WinFsp now supports rebranding of the build products. This is primarily useful to commercial licensees. See https://github.com/billziss-gh/winfsp/wiki/WinFsp-Rebranding for more information.
* [NEW] WinFsp-FUSE has a new capability `FSP_FUSE_CAP_DELETE_ACCESS`. If this capability is specified WinFsp will call the FUSE `access` method with the WinFsp-specific flag `FSP_FUSE_DELETE_OK` to check whether the deletion should be allowed. This can be used to disallow deletion of files and directories that would normally be allowed under the FUSE/POSIX model.
* [NEW] WinFsp-FUSE has a new method for determining file access in the case where the user's primary SID (Windows Security Identifier) and group SID are the same (for example, this can happen when someone uses a Microsoft account as their primary login).
** Previously when the user SID and group SID were the same WinFsp-FUSE looked at the UNIX permissions for the owner and the UNIX permissions for the group and used the MOST restrictive permissions, which often resulted in inadvertent "access denied" errors. (For example, if the owner permission was `rw-` and the group permission was `---` the result was `---` and therefore access denied).
** Going forward this behavior will change. The user and group mode permissions will not be considered together even in the case where the user SID and group SID are the same. This will resolve the access denied errors.
** However to preserve backward compatibility (there might be some file systems that rely on the old behavior) we will do so in stages. For release v1.10 (2021.1) there is a new registry setting under `HKLM\SOFTWARE\WinFsp` (or `HKLM\SOFTWARE\WOW6432Node\WinFsp` on a 64-bit system) called `DistinctPermsForSameOwnerGroup`, which if set to 1 will direct WinFsp-FUSE to use the new behavior. The default value is 0 which directs WinFsp-FUSE to use the old behavior. This default will change in a future release.
* [NEW] A new registry setting under `HKLM\SOFTWARE\WinFsp` (or `HKLM\SOFTWARE\WOW6432Node\WinFsp` on a 64-bit system) called `MountDoNotUseLauncher` has been introduced, which if set to 1 will disable the use of the Launcher during mounting. The default value is 0 which allows the use of the Launcher during mounting in those rare cases when necessary. (In general the Launcher is not necessary for mounting. However when running a file system in the Windows Service context (session 0) under an account that is not LocalSystem (e.g. `NT AUTHORITY\NETWORK SERVICE`), the Launcher is used to create global drives.)
* [FIX] File share access when overwriting a file (e.g. when using `TRUNCATE_EXISTING`) is now done in a manner compatible with NTFS (previously there were cases when overwriting a file where behavior diverged from the NTFS one). (See GitHub issue #364.)
* [FIX] The FSD will now report a file's reparse tag in the `EaSize` field of `FILE_FULL_DIR_INFORMATION` and friends. This fixes problems such as `cmd.exe` not recognizing symlinks in a `dir` command. (See GitHub issue #380.)
* [FIX] Fixed a problem in the file system shutdown protocol which resolves an occasional access violation in the user mode file system process. Previously it was possible for a file system to crash when stopping itself using `FspFileSystemStopDispatcher`; this problem has been rectified. (See GitHub issue #369.)
* [FIX] Fixed a problem in the FUSE layer where in some rare circumstances the `release` operation could be called with an uninitialized `path` argument. (See GitHub issue billziss-gh/cgofuse#58 and commit f51bdef53427d1bba688fb6c768792fdc22ffc7b).
* [FIX] Fixed a potential problem when launching file system instances using the Launcher. (See GitHub issue #356.)
* [FIX] The `winfsp.h` header no longer defines `FILE_FULL_EA_INFORMATION` when compiled under mingw. This is because the mingw tool chain already includes a definition of this type. (GitHub PR #371. Thanks @lemourin.)
v1.10B2 (2021.1 Beta2)::
Changes since v1.9:
* [NEW] WinFsp now supports rebranding of the build products. This is primarily useful to commercial licensees. See https://github.com/billziss-gh/winfsp/wiki/WinFsp-Rebranding for more information.
* [NEW] WinFsp-FUSE has a new method for determining file access in the case where the user's primary SID (Windows Security Identifier) and group SID are the same (for example, this can happen when someone uses a Microsoft account as their primary login).
** Previously when the user SID and group SID were the same WinFsp-FUSE looked at the UNIX permissions for the owner and the UNIX permissions for the group and used the MOST restrictive permissions, which often resulted in inadvertent "access denied" errors. (For example, if the owner permission was `rw-` and the group permission was `---` the result was `---` and therefore access denied).
** Going forward this behavior will change. The user and group mode permissions will not be considered together even in the case where the user SID and group SID are the same. This will resolve the access denied errors.
** However to preserve backward compatibility (there might be some file systems that rely on the old behavior) we will do so in stages. For release v1.10 (2021.1) there is a new registry setting under `HKLM\SOFTWARE\WinFsp` (or `HKLM\SOFTWARE\WOW6432Node\WinFsp` on a 64-bit system) called `DistinctPermsForSameOwnerGroup`, which if set to 1 will direct WinFsp-FUSE to use the new behavior. The default value is 0 which directs WinFsp-FUSE to use the old behavior. This default will change in a future release.
* [NEW] A new registry setting under `HKLM\SOFTWARE\WinFsp` (or `HKLM\SOFTWARE\WOW6432Node\WinFsp` on a 64-bit system) called `MountDoNotUseLauncher` has been introduced, which if set to 1 will disable the use of the Launcher during mounting. The default value is 0 which allows the use of the Launcher during mounting in those rare cases when necessary. (In general the Launcher is not necessary for mounting. However when running a file system in the Windows Service context (session 0) under an account that is not LocalSystem (e.g. `NT AUTHORITY\NETWORK SERVICE`), the Launcher is used to create global drives.)
* [FIX] File share access when overwriting a file (e.g. when using `TRUNCATE_EXISTING`) is now done in a manner compatible with NTFS (previously there were cases when overwriting a file where behavior diverged from the NTFS one). (See GitHub issue #364.)
* [FIX] Fixed a problem in the file system shutdown protocol which resolves an occasional access violation in the user mode file system process. Previously it was possible for a file system to crash when stopping itself using `FspFileSystemStopDispatcher`; this problem has been rectified. (See GitHub issue #369.)
* [FIX] Fixed a problem in the FUSE layer where in some rare circumstances the `release` operation could be called with an uninitialized `path` argument. (See GitHub issue billziss-gh/cgofuse#58 and commit f51bdef53427d1bba688fb6c768792fdc22ffc7b).
* [FIX] The `winfsp.h` header no longer defines `FILE_FULL_EA_INFORMATION` when compiled under mingw. This is because the mingw tool chain already includes a definition of this type. (GitHub PR #371. Thanks @lemourin.)
v1.10B1 (2021.1 Beta1)::
Changes since v1.9:

View File

@ -117,6 +117,7 @@ WinFsp is designed to run on Windows 7 and above. It has been tested on the foll
* Windows Server 2012
* Windows 10 Pro
* Windows Server 2016
* Windows 11 Pro
## How to Help

View File

@ -42,8 +42,8 @@ install:
build_script:
- appveyor AddMessage "Reboot complete" -Category Information
# build cygfuse
- C:\cygwin64\setup-x86_64.exe -qnNd -P cygport
- C:\cygwin64\bin\bash --login -c "make -C '%CD%\opt\cygfuse' dist"
#- C:\cygwin64\setup-x86_64.exe -qnNd -P cygport
#- C:\cygwin64\bin\bash --login -c "make -C '%CD%\opt\cygfuse' dist"
#- C:\cygwin\setup-x86.exe -qnNd -P cygport
#- C:\cygwin\bin\bash --login -c "make -C '%CD%\opt\cygfuse' dist"
# build winfsp

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -399,6 +399,29 @@
<File Name="memfs-main.c" KeyPath="yes" />
</Component>
</Directory>
<Directory Id="SMPDIR.memfs_fuse" Name="memfs-fuse">
<Component Id="C.memfs_fuse.cpp">
<File Name="memfs-fuse.cpp" KeyPath="yes" />
</Component>
<Component Id="C.memfs_fuse.compat.h">
<File Id="F.memfs_fuse.compat.h" Name="compat.h" KeyPath="yes" />
</Component>
<Component Id="C.memfs_fuse.sln">
<File Name="memfs-fuse.sln" KeyPath="yes" />
</Component>
<Component Id="C.memfs_fuse.vcxproj">
<File Name="memfs-fuse.vcxproj" KeyPath="yes" />
</Component>
<Component Id="C.memfs_fuse.vcxproj.filters">
<File Name="memfs-fuse.vcxproj.filters" KeyPath="yes" />
</Component>
<Component Id="C.memfs_fuse.Makefile">
<File Id="F.memfs_fuse.Makefile" Name="Makefile" KeyPath="yes" />
</Component>
<Component Id="C.memfs_fuse.README.md">
<File Id="F.memfsx_fuse.README.md" Name="README.md" KeyPath="yes" />
</Component>
</Directory>
<Directory Id="SMPDIR.memfs_fuse3" Name="memfs-fuse3">
<Component Id="C.memfs_fuse3.cpp">
<File Name="memfs-fuse3.cpp" KeyPath="yes" />
@ -662,6 +685,13 @@
<ComponentRef Id="C.memfs.h" />
<ComponentRef Id="C.memfs.cpp" />
<ComponentRef Id="C.memfs_main.c" />
<ComponentRef Id="C.memfs_fuse.cpp" />
<ComponentRef Id="C.memfs_fuse.compat.h" />
<ComponentRef Id="C.memfs_fuse.sln" />
<ComponentRef Id="C.memfs_fuse.vcxproj" />
<ComponentRef Id="C.memfs_fuse.vcxproj.filters" />
<ComponentRef Id="C.memfs_fuse.Makefile" />
<ComponentRef Id="C.memfs_fuse.README.md" />
<ComponentRef Id="C.memfs_fuse3.cpp" />
<ComponentRef Id="C.memfs_fuse3.compat.h" />
<ComponentRef Id="C.memfs_fuse3.sln" />

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 KiB

After

Width:  |  Height:  |  Size: 451 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 KiB

After

Width:  |  Height:  |  Size: 451 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 KiB

After

Width:  |  Height:  |  Size: 451 KiB

View File

@ -19,7 +19,7 @@
<MyCanonicalVersion>1.10</MyCanonicalVersion>
<MyProductVersion>2021.1 Beta1</MyProductVersion>
<MyProductVersion>2022 Beta4</MyProductVersion>
<MyProductStage>Beta</MyProductStage>
<MyVersion>$(MyCanonicalVersion).$(MyBuildNumber)</MyVersion>

View File

@ -1,6 +1,6 @@
= Known File Systems and File System Libraries
This document contains a list of known file systems and file system libraries that run on WinFsp. Please contact the WinFsp project to have your file system solution added to this list.
This document contains a list of known open-source file systems and file system libraries that run on WinFsp. Please contact the WinFsp project to have your file system solution added to this list.
== File Systems
@ -9,13 +9,16 @@ This document contains a list of known file systems and file system libraries th
- https://github.com/lowleveldesign/fsmemfs[fsmemfs] - Memory File System written in F#
- https://github.com/ihaveamac/fuse-3ds[fuse-3ds] - FUSE Filesystem Python scripts for Nintendo 3DS files
- https://github.com/sganis/golddrive[golddrive] - Windows ssh network drive
- https://github.com/billziss-gh/hubfs[hubfs] - File system for GitHub
- https://github.com/FrKaram/KS2.Drive[KS2.Drive] - Mount a webDAV/AOS server as a local drive
- https://github.com/billziss-gh/nfs-win[nfs-win] - NFS for Windows
- https://github.com/billziss-gh/objfs[objfs] - Object Storage File System
- https://github.com/ncw/rclone[rclone] - rsync for cloud storage
- https://github.com/hasse69/rar2fs[rar2fs] - FUSE file system for reading RAR archives
- https://github.com/billziss-gh/redditfs[redditfs] - ls -l /r/programming
- https://github.com/netheril96/securefs[securefs] - Filesystem in userspace (FUSE) with transparent authenticated encryption
- https://github.com/billziss-gh/sshfs-win[sshfs-win] - SSHFS for Windows
- https://github.com/printpagestopdf/WordpressDrive[WordpressDrive] - Windows Userspace Filesystem based on WinFsp that presents a Wordpress Site as a Windows Drive
- https://github.com/emoose/xbox-winfsp[xbox-winfsp] - Adds native support to Windows for the FATX, STFS & GDFX (aka XGD/XDVDFS) Xbox filesystems.
- https://github.com/UtrechtUniversity/YodaDrive[YodaDrive] - Mount a Yoda drive as a local drive

View File

@ -17,13 +17,14 @@ WinFsp supports the following NTFS features:
- Opportunistic locks.
- Open, create, close, delete, query named streams.
- Reparse points with special support for symbolic links.
- Extended attributes.
- Traditional Windows and POSIX semantics for delete and rename.
== Unsupported features
WinFsp does not support the following NTFS features:
- Hard links. Rather rarely used on Windows. However it might be worthwhile to implement them for WinFsp.
- Extended attributes. Although popular with POSIX file systems, they are severely hampered and rarely used on Windows. They are also not exposed via the Win32 API.
- Short file names. Short file names are a relic of the past. WinFsp made a conscious decision not to support them.
- Paging files. Providing paging file support via a user mode file system is impossible for a number of reasons.
- Object ID's. Opening files by ID (+FILE_OPEN_BY_FILE_ID+) is not supported.

View File

@ -397,11 +397,13 @@ typedef struct _FSP_LAUNCH_REG_RECORD {
PWSTR RunAs;
PWSTR Security;
PWSTR AuthPackage;
PVOID Reserved0[5];
PWSTR Stderr;
PVOID Reserved0[4];
ULONG JobControl;
ULONG Credentials;
ULONG AuthPackageId;
ULONG Reserved1[5];
ULONG Recovery;
ULONG Reserved1[4];
UINT8 Buffer[];
} FSP_LAUNCH_REG_RECORD;
```
@ -414,7 +416,7 @@ typedef struct _FSP_LAUNCH_REG_RECORD {
<br/>
<p align="center">
<sub>
Copyright © 2015-2020 Bill Zissimopoulos
Copyright © 2015-2021 Bill Zissimopoulos
<br/>
Generated with <a href="https://github.com/billziss-gh/prettydoc">prettydoc</a>
</sub>

View File

@ -148,7 +148,11 @@ a file or directory in Windows is a three-stage process where the file is first
tested to see if the delete can proceed and if the answer is positive the file is then
deleted during Cleanup.
When this flag is set, this is the last outstanding cleanup for this particular file node.
If the file system supports POSIX unlink (FSP\_FSCTL\_VOLUME\_PARAMS ::
SupportsPosixUnlinkRename), then a Cleanup / FspCleanupDelete operation may arrive while
there are other open file handles for this particular file node. If the file system does not
support POSIX unlink, then a Cleanup / FspCleanupDelete operation will always be the last
outstanding cleanup for this particular file node.
- FspCleanupSetAllocationSize -
@ -1504,6 +1508,47 @@ This is a helper for implementing the GetEa operation.
- GetEa
</blockquote>
</details>
<details>
<summary>
<b>FspFileSystemAddNotifyInfo</b> - Add notify information to a buffer.
</summary>
<blockquote>
<br/>
```c
FSP_API BOOLEAN FspFileSystemAddNotifyInfo(
FSP_FSCTL_NOTIFY_INFO *NotifyInfo,
PVOID Buffer,
ULONG Length,
PULONG PBytesTransferred);
```
**Parameters**
- _NotifyInfo_ \- The notify information to add.
- _Buffer_ \- Pointer to a buffer that will receive the notify information.
- _Length_ \- Length of buffer.
- _PBytesTransferred_ \- [out]
Pointer to a memory location that will receive the actual number of bytes stored. This should
be initialized to 0 prior to the first call to FspFileSystemAddNotifyInfo for a particular
buffer.
**Return Value**
TRUE if the notify information was added, FALSE if there was not enough space to add it.
**Discussion**
This is a helper for filling a buffer to use with FspFileSystemNotify.
**See Also**
- FspFileSystemNotify
</blockquote>
</details>
@ -1851,6 +1896,123 @@ The current operation context is stored in thread local storage. It allows acces
Request and Response associated with this operation.
</blockquote>
</details>
<details>
<summary>
<b>FspFileSystemNotify</b> - Notify Windows that the file system has file changes.
</summary>
<blockquote>
<br/>
```c
FSP_API NTSTATUS FspFileSystemNotify(
FSP_FILE_SYSTEM *FileSystem,
FSP_FSCTL_NOTIFY_INFO *NotifyInfo,
SIZE_T Size);
```
**Parameters**
- _FileSystem_ \- The file system object.
- _NotifyInfo_ \- Buffer containing information about file changes.
- _Size_ \- Size of buffer.
**Return Value**
STATUS\_SUCCESS or error code.
**Discussion**
A file system that wishes to notify Windows about file changes must
first issue an FspFileSystemBegin call, followed by 0 or more
FspFileSystemNotify calls, followed by an FspFileSystemNotifyEnd call.
Note that FspFileSystemNotify requires file names to be normalized. A
normalized file name is one that contains the correct case of all characters
in the file name.
For case-sensitive file systems all file names are normalized by definition.
For case-insensitive file systems that implement file name normalization,
a normalized file name is the one that the file system specifies in the
response to Create or Open (see also FspFileSystemGetOpenFileInfo). For
case-insensitive file systems that do not implement file name normalization
a normalized file name is the upper case version of the file name used
to open the file.
</blockquote>
</details>
<details>
<summary>
<b>FspFileSystemNotifyBegin</b> - Begin notifying Windows that the file system has file changes.
</summary>
<blockquote>
<br/>
```c
FSP_API NTSTATUS FspFileSystemNotifyBegin(
FSP_FILE_SYSTEM *FileSystem,
ULONG Timeout);
```
**Parameters**
- _FileSystem_ \- The file system object.
**Return Value**
STATUS\_SUCCESS or error code. The error code STATUS\_CANT\_WAIT means that
a file rename operation is currently in progress and the operation must be
retried at a later time.
**Discussion**
A file system that wishes to notify Windows about file changes must
first issue an FspFileSystemBegin call, followed by 0 or more
FspFileSystemNotify calls, followed by an FspFileSystemNotifyEnd call.
This operation blocks concurrent file rename operations. File rename
operations may interfere with file notification, because a file being
notified may also be concurrently renamed. After all file change
notifications have been issued, you must make sure to call
FspFileSystemNotifyEnd to allow file rename operations to proceed.
</blockquote>
</details>
<details>
<summary>
<b>FspFileSystemNotifyEnd</b> - End notifying Windows that the file system has file changes.
</summary>
<blockquote>
<br/>
```c
FSP_API NTSTATUS FspFileSystemNotifyEnd(
FSP_FILE_SYSTEM *FileSystem);
```
**Parameters**
- _FileSystem_ \- The file system object.
**Return Value**
STATUS\_SUCCESS or error code.
**Discussion**
A file system that wishes to notify Windows about file changes must
first issue an FspFileSystemBegin call, followed by 0 or more
FspFileSystemNotify calls, followed by an FspFileSystemNotifyEnd call.
This operation allows any blocked file rename operations to proceed.
</blockquote>
</details>
@ -2613,7 +2775,7 @@ in a clean manner by calling this function.
<br/>
<p align="center">
<sub>
Copyright © 2015-2020 Bill Zissimopoulos
Copyright © 2015-2021 Bill Zissimopoulos
<br/>
Generated with <a href="https://github.com/billziss-gh/prettydoc">prettydoc</a>
</sub>

View File

@ -0,0 +1,126 @@
*INVALID: The information presented in this document is invalid and should not be used.*
= WinFsp Delete Redesign
WinFsp has had its Delete functionality redesigned in release 2021.1 Beta3. This redesign unifies all Windows file deletion semantics under a single file system operation that also supports the new POSIX semantics introduced in Windows 10 Redstone 1. The new Delete design is recommended for new file systems, however existing file systems will continue to work without any changes.
== Background
In this section we discuss how file deletion worked in Windows traditionally as well as the changes introduced in recent versions of Windows 10.
=== Traditional File Deletion
The traditional method for deleting a file or directory on Windows involves the following steps:
- Open the file using https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntopenfile[`NtOpenFile`] (or equivalent) with `DELETE` access.
- Set the "disposition" flag on the file handle using https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntsetinformationfile[`NtSetInformationFile`] with `FileDispositionInformation`. This only marks the file for deletion and does not delete the file.
- Close the file using https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntclose[`NtClose`] (or equivalent). Provided that there are no other open handles to the file, the file is actually deleted at this stage.
This is the method that https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-deletefilew[`DeleteFileW`] and https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-removedirectoryw[`RemoveDirectoryW`] use to delete a file or directory.
An alternative method involves the `FILE_DELETE_ON_CLOSE` flag:
- Open the file using https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntopenfile[`NtOpenFile`] (or equivalent) with the `FILE_DELETE_ON_CLOSE` option.
- Close the file using https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-ntclose[`NtClose`] (or equivalent). Provided that there are no other open handles to the file, the file is actually deleted at this stage.
This alternative method does not set the disposition flag and therefore does not have chance to perform any associated checks. An important disposition flag check is whether a directory is empty: attempting to remove a non-empty directory using `FILE_DELETE_ON_CLOSE` will fail silently, because there is no way to communicate a file deletion error from `NtClose`.
In order to better understand those scenarios let's examine what happens within the kernel and the file system driver (FSD).
When the kernel receives a file API call such as `NtOpenFile`, it packages the call arguments into a data structure called an "I/O Request Packet" (IRP) and forwards it to the appropriate FSD. Each IRP contains a field that describes its function, for example, `IRP_MJ_CREATE` for `NtOpenFile` and `IRP_MJ_SET_INFORMATION` for `NtSetInformationFile`.
With this knowledge we can now examine what happens in the `DeleteFileW` / `RemoveDirectoryW` scenario:
- Open the file using `NtOpenFile`: The kernel creates an https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/irp-mj-create[`IRP_MJ_CREATE`] IRP, places inside it a newly created https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_file_object[`FILE_OBJECT`] and forwards it to the FSD. If opening the file succeeds, the kernel will also create a `HANDLE` that is used to refer to this `FILE_OBJECT`; if opening the file fails, this `FILE_OBJECT` will be destroyed.
- Set the "disposition" flag on the file handle using `NtSetInformationFile` with `FileDispositionInformation`: The kernel creates an https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/irp-mj-set-information[`IRP_MJ_SET_INFORMATION`] IRP and passes the `FileDispositionInformation` information in it. The FSD performs some checks (e.g. if a directory is empty) and if they succeed it marks the file for deletion.
- Close the file using `NtClose`: The kernel creates an https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/irp-mj-cleanup[`IRP_MJ_CLEANUP`] IRP, which denotes that all ``HANDLE``s that refer to a `FILE_OBJECT` are closed. (It is possible to have multiple ``HANDLE``s to the same `FILE_OBJECT` by using an API such as https://docs.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-duplicatehandle[`DuplicateHandle`].) The FSD checks and if this is the last `FILE_OBJECT` cleaned up for the file and if the file is marked for deletion, it deletes the file. Traditionally there was no way to return an error from `IRP_MJ_CLEANUP`.
- Notice that while the file is closed from the perspective of user mode, it is not closed from the perspective of kernel mode. The kernel and the FSD maintain both a handle count and a reference count for the `FILE_OBJECT`. When the handle count goes to `0` then an `IRP_MJ_CLEANUP` IRP is issued (see above). When the reference count goes to `0` then a different https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/irp-mj-close[`IRP_MJ_CLOSE`] IRP is issued to the FSD. This signifies to the FSD that the `FILE_OBJECT` is going away and the file is fully closed (including from the kernel perspective). There is no way to return an error from `IRP_MJ_CLOSE`.
The situation is similar in the `FILE_DELETE_ON_CLOSE` scenario, with the important difference that the FSD marks the file for deletion immediately upon receiving the `IRP_MJ_CREATE` IRP and that it never receives the `IRP_MJ_SET_INFORMATION` IRP. As before the actual deletion happens in `IRP_MJ_CLEANUP` and only when the last `HANDLE` to the file is closed.
Some important takeaways:
- It is possible for a file to already be open when a `DeleteFileW` / `RemoveDirectoryW` (or equivalent sequence of `NtOpenFile`, `NtSetInformationFile`, `NtClose`, etc.) is executed. This means that the file may NOT be deleted upon return from the `DeleteFileW` / `RemoveDirectoryW` call even though these API's report success. **Traditionally a successful return from `DeleteFileW` / `RemoveDirectory` signifies only that the file or directory has been successfully marked for deletion and not that it has been deleted!**
- The `NtClose` call does not return error codes from `IRP_MJ_CLEANUP`. This means that it is impossible for user mode to know whether a file marked for deletion was deleted or not.
- The `FILE_OBJECT` remains valid even after a file has been deleted in `IRP_MJ_CLEANUP`. It is therefore possible to receive additional I/O (e.g. read/write) on the file. Many Windows file systems (including at least some versions of NTFS) do not handle this case very well.
=== File Deletion in Recent Versions of Windows 10
In Windows 10 Redstone 1 Microsoft introduced the `FileDispositionInformationEx` information class. This new information class can be used to request POSIX semantics for file deletion during the `NtSetInformationFile` call. POSIX semantics for file deletion mean that when a file is deleted any open handles to it remain valid and can be used for I/O such as read/write.
Some time later (unclear exactly when) Microsoft changed the `DeleteFileW` / `RemoveDirectoryW` API's to use the `FileDispositionInformationEx` information class and only if this fails (e.g. because the file system is not capable) fall back to the old `FileDispositionInformation` information class. With this change `DeleteFileW` and `RemoveDirectoryW` actually delete the file or directory rather than simply mark it for deletion. (This change is in general a good thing, but can create incompatibility problems for applications that expect the traditional behavior.)
Let's examine the `DeleteFileW` / `RemoveDirectoryW` scenario again:
- Open the file using `NtOpenFile`: The kernel creates an https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/irp-mj-create[`IRP_MJ_CREATE`] IRP, places inside it a newly created https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_file_object[`FILE_OBJECT`] and forwards it to the FSD. If opening the file succeeds, the kernel will also create a `HANDLE` that is used to refer to this `FILE_OBJECT`; if opening the file fails, this `FILE_OBJECT` will be destroyed.
- Intruct the file system to delete the file with POSIX semantics using `NtSetInformationFile` with `FileDispositionInformationEx`. The kernel creates an https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/irp-mj-set-information[`IRP_MJ_SET_INFORMATION`] IRP and passes the `FileDispositionInformationEx` information in it. The FSD performs some checks (e.g. if a directory is empty) and if they succeed it deletes the file or directory (as opposed to simply mark it for deletion).
- Close the file using `NtClose`. The kernel creates an https://docs.microsoft.com/en-us/windows-hardware/drivers/ifs/irp-mj-cleanup[`IRP_MJ_CLEANUP`] IRP, which denotes that all ``HANDLE``s that refer to a `FILE_OBJECT` are closed. The FSD has already deleted the file and does not need to do anything else.
- As before an `IRP_MJ_CLOSE` IRP will also be sent to the FSD eventually.
Notice that the actual file deletion happens during `NtSetInformationFile` and the return code from this API reports on the success or failure of the file deletion. Thus we no longer have the problems discussed earlier and `DeleteFileW` / `RemoveDirectoryW` correctly report whether the file was deleted or not.
== WinFsp Support for POSIX Unlink
WinFsp gained support for POSIX Unlink (`FileDispositionInformationEx`) and POSIX Rename (`FileRenameInformationEx`) in release 2021.1 Beta3. To enable this support a native or .NET file system must set the `SupportsPosixUnlinkRename` flag when the file system is created. FUSE file systems have this flag enabled by default (but can be disabled with the command line option `-o LegacyUnlinkRename`).
The POSIX Unlink support spurred some changes in the WinFsp native and .NET API's with regards to file deletion. The WinFsp FUSE layer transparently supports these changes.
Prior to release 2021.1 Beta3, user mode file systems handled file deletion by implementing `CanDelete` / `SetDelete` to check the file disposition flag and `Cleanup` with the `FspCleanupDelete` flag to perform the actual file deletion. From release 2021.1 Beta3 forward the recommended method is to use the new `Delete` file system operation to handle all aspects of file deletion.
The new `Delete` operation follows the general pattern below:
[source,c]
----
NTSTATUS Delete(FSP_FILE_SYSTEM *FileSystem,
PVOID FileContext, PWSTR FileName, ULONG Flags)
{
switch (Flags)
{
case FILE_DISPOSITION_DO_NOT_DELETE:
// set file disposition flag: do not delete file at Cleanup time
case FILE_DISPOSITION_DELETE:
// set file disposition flag: delete file at Cleanup time
case FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS:
// delete file now; open handles to file remain valid
case -1:
// delete file now; called during Cleanup time
default:
return STATUS_INVALID_PARAMETER;
}
}
----
As can be seen the `Delete` operation handles marking (`FILE_DISPOSITION_DELETE`) and unmarking (`FILE_DISPOSITION_DO_NOT_DELETE`) a file for deletion, performing file deletion with POSIX semantics (`FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS`) and performing file deletion with traditional Windows semantics (`-1`). If the Delete operation is defined it is used instead of `CanDelete` / `SetDelete` and `Cleanup` with the `FspCleanupDelete` flag, even if these operations are also defined.
A sensible implementation of `Delete` might look something similar to the following:
[source,c]
----
NTSTATUS Delete(FSP_FILE_SYSTEM *FileSystem,
PVOID FileContext, PWSTR FileName, ULONG Flags)
{
switch (Flags)
{
case FILE_DISPOSITION_DO_NOT_DELETE:
return STATUS_SUCCESS;
case FILE_DISPOSITION_DELETE:
if (IsNotEmptyDirectory(FileSystem, FileContext))
return STATUS_DIRECTORY_NOT_EMPTY;
return STATUS_SUCCESS;
case FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS:
case -1:
if (IsNotEmptyDirectory(FileSystem, FileContext))
return STATUS_DIRECTORY_NOT_EMPTY;
return RealDeleteFileOrDirectory(FileSystem, FileContext);
default:
return STATUS_INVALID_PARAMETER;
}
}
----

View File

@ -79,7 +79,7 @@ struct fuse_operations
/* S */ int (*fsyncdir)(const char *path, int datasync, struct fuse_file_info *fi);
/* S */ void *(*init)(struct fuse_conn_info *conn);
/* S */ void (*destroy)(void *data);
/* _ */ int (*access)(const char *path, int mask);
/* S */ int (*access)(const char *path, int mask);
/* S */ int (*create)(const char *path, fuse_mode_t mode, struct fuse_file_info *fi);
/* S */ int (*ftruncate)(const char *path, fuse_off_t off, struct fuse_file_info *fi);
/* S */ int (*fgetattr)(const char *path, struct fuse_stat *stbuf, struct fuse_file_info *fi);

View File

@ -54,6 +54,7 @@ extern "C" {
#define FSP_FUSE_CAP_READDIR_PLUS (1 << 21) /* file system supports enhanced readdir */
#define FSP_FUSE_CAP_READ_ONLY (1 << 22) /* file system is marked read-only */
#define FSP_FUSE_CAP_STAT_EX (1 << 23) /* file system supports fuse_stat_ex */
#define FSP_FUSE_CAP_DELETE_ACCESS (1 << 24) /* file system supports access with DELETE_OK */
#define FSP_FUSE_CAP_CASE_INSENSITIVE FUSE_CAP_CASE_INSENSITIVE
#define FUSE_IOCTL_COMPAT (1 << 0)
@ -79,6 +80,9 @@ extern "C" {
#define UF_ARCHIVE FSP_FUSE_UF_ARCHIVE
#endif
/* delete access */
#define FSP_FUSE_DELETE_OK 0x40000000
/* notify extension */
#define FSP_FUSE_NOTIFY_MKDIR 0x0001
#define FSP_FUSE_NOTIFY_RMDIR 0x0002

View File

@ -197,14 +197,16 @@ enum
/* user-mode flags */\
UINT32 UmFileContextIsUserContext2:1; /* user mode: FileContext parameter is UserContext2 */\
UINT32 UmFileContextIsFullContext:1; /* user mode: FileContext parameter is FullContext */\
UINT32 UmReservedFlags:6;\
UINT32 UmNoReparsePointsDirCheck:1; /* user mode: no dir option check for reparse points */\
UINT32 UmReservedFlags:5;\
/* additional kernel-mode flags */\
UINT32 AllowOpenInKernelMode:1; /* allow kernel mode to open files when possible */\
UINT32 CasePreservedExtendedAttributes:1; /* preserve case of EA (default is UPPERCASE) */\
UINT32 WslFeatures:1; /* support features required for WSLinux */\
UINT32 DirectoryMarkerAsNextOffset:1; /* directory marker is next offset instead of last name */\
UINT32 RejectIrpPriorToTransact0:1; /* reject IRP's prior to FspFsctlTransact with 0 buffers */\
UINT32 KmReservedFlags:3;\
UINT32 SupportsPosixUnlinkRename:1; /* file system supports POSIX-style unlink and rename */\
UINT32 KmReservedFlags:2;\
WCHAR Prefix[FSP_FSCTL_VOLUME_PREFIX_SIZE / sizeof(WCHAR)]; /* UNC prefix (\Server\Share) */\
WCHAR FileSystemName[FSP_FSCTL_VOLUME_FSNAME_SIZE / sizeof(WCHAR)];
#define FSP_FSCTL_VOLUME_PARAMS_V1_FIELD_DEFN\
@ -413,6 +415,10 @@ typedef struct
UINT32 Delete:1;
} Disposition;
struct
{
UINT32 Flags;
} DispositionEx;
struct
{
UINT64 FileSize;
} EndOfFile;
@ -421,6 +427,12 @@ typedef struct
FSP_FSCTL_TRANSACT_BUF NewFileName;
UINT64 AccessToken; /* request access token (PID,HANDLE) */
} Rename;
struct
{
FSP_FSCTL_TRANSACT_BUF NewFileName;
UINT64 AccessToken; /* request access token (PID,HANDLE) */
UINT32 Flags;
} RenameEx;
} Info;
} SetInformation;
struct

View File

@ -367,7 +367,11 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE
* tested to see if the delete can proceed and if the answer is positive the file is then
* deleted during Cleanup.
*
* When this flag is set, this is the last outstanding cleanup for this particular file node.
* If the file system supports POSIX unlink (FSP_FSCTL_VOLUME_PARAMS ::
* SupportsPosixUnlinkRename), then a Cleanup / FspCleanupDelete operation may arrive while
* there are other open file handles for this particular file node. If the file system does not
* support POSIX unlink, then a Cleanup / FspCleanupDelete operation will always be the last
* outstanding cleanup for this particular file node.
* </li>
* <li>FspCleanupSetAllocationSize -
* The NTFS and FAT file systems reset a file's allocation size when they receive the last
@ -1041,11 +1045,13 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE
PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength,
FSP_FSCTL_FILE_INFO *FileInfo);
NTSTATUS (*Obsolete0)(VOID);
/*
* This ensures that this interface will always contain 64 function pointers.
* Please update when changing the interface as it is important for future compatibility.
*/
NTSTATUS (*Reserved[33])();
NTSTATUS (*Reserved[32])();
} FSP_FILE_SYSTEM_INTERFACE;
FSP_FSCTL_STATIC_ASSERT(sizeof(FSP_FILE_SYSTEM_INTERFACE) == 64 * sizeof(NTSTATUS (*)()),
"FSP_FILE_SYSTEM_INTERFACE must have 64 entries.");
@ -1067,7 +1073,13 @@ typedef struct _FSP_FILE_SYSTEM
FSP_FILE_SYSTEM_OPERATION_GUARD_STRATEGY OpGuardStrategy;
SRWLOCK OpGuardLock;
BOOLEAN UmFileContextIsUserContext2, UmFileContextIsFullContext;
UINT16 UmNoReparsePointsDirCheck:1;
UINT16 UmReservedFlags:15;
} FSP_FILE_SYSTEM;
FSP_FSCTL_STATIC_ASSERT(
(4 == sizeof(PVOID) && 660 == sizeof(FSP_FILE_SYSTEM)) ||
(8 == sizeof(PVOID) && 792 == sizeof(FSP_FILE_SYSTEM)),
"sizeof(FSP_FILE_SYSTEM) must be exactly 660 in 32-bit and 792 in 64-bit.");
typedef struct _FSP_FILE_SYSTEM_OPERATION_CONTEXT
{
FSP_FSCTL_TRANSACT_REQ *Request;
@ -1383,7 +1395,8 @@ UINT32 FspFileSystemOperationProcessId(VOID)
case FspFsctlTransactCreateKind:
return FSP_FSCTL_TRANSACT_REQ_TOKEN_PID(Request->Req.Create.AccessToken);
case FspFsctlTransactSetInformationKind:
if (10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass)
if (10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass ||
65/*FileRenameInformationEx*/ == Request->Req.SetInformation.FileInformationClass)
return FSP_FSCTL_TRANSACT_REQ_TOKEN_PID(Request->Req.SetInformation.Info.Rename.AccessToken);
/* fall through! */
default:

View File

@ -477,6 +477,18 @@ FSP_API VOID FspDebugLogRequest(FSP_FSCTL_TRANSACT_REQ *Request)
UserContextBuf),
Request->Req.SetInformation.Info.Disposition.Delete ? "Delete" : "Undelete");
break;
case 64/*FileDispositionInformationEx*/:
FspDebugLog("%S[TID=%04lx]: %p: >>SetInformation [DispositionEx] %s%S%s%s, "
"Flags=%lx\n",
FspDiagIdent(), GetCurrentThreadId(), (PVOID)Request->Hint,
Request->FileName.Size ? "\"" : "",
Request->FileName.Size ? (PWSTR)Request->Buffer : L"",
Request->FileName.Size ? "\", " : "",
FspDebugLogUserContextString(
Request->Req.SetInformation.UserContext, Request->Req.SetInformation.UserContext2,
UserContextBuf),
Request->Req.SetInformation.Info.DispositionEx.Flags);
break;
case 10/*FileRenameInformation*/:
FspDebugLog("%S[TID=%04lx]: %p: >>SetInformation [Rename] %s%S%s%s, "
"NewFileName=\"%S\", AccessToken=%p[PID=%lx]\n",
@ -491,6 +503,21 @@ FSP_API VOID FspDebugLogRequest(FSP_FSCTL_TRANSACT_REQ *Request)
FSP_FSCTL_TRANSACT_REQ_TOKEN_HANDLE(Request->Req.SetInformation.Info.Rename.AccessToken),
FSP_FSCTL_TRANSACT_REQ_TOKEN_PID(Request->Req.SetInformation.Info.Rename.AccessToken));
break;
case 65/*FileRenameInformationEx*/:
FspDebugLog("%S[TID=%04lx]: %p: >>SetInformation [RenameEx] %s%S%s%s, "
"NewFileName=\"%S\", AccessToken=%p[PID=%lx], Flags=%lx\n",
FspDiagIdent(), GetCurrentThreadId(), (PVOID)Request->Hint,
Request->FileName.Size ? "\"" : "",
Request->FileName.Size ? (PWSTR)Request->Buffer : L"",
Request->FileName.Size ? "\", " : "",
FspDebugLogUserContextString(
Request->Req.SetInformation.UserContext, Request->Req.SetInformation.UserContext2,
UserContextBuf),
(PWSTR)(Request->Buffer + Request->Req.SetInformation.Info.Rename.NewFileName.Offset),
FSP_FSCTL_TRANSACT_REQ_TOKEN_HANDLE(Request->Req.SetInformation.Info.Rename.AccessToken),
FSP_FSCTL_TRANSACT_REQ_TOKEN_PID(Request->Req.SetInformation.Info.Rename.AccessToken),
Request->Req.SetInformation.Info.RenameEx.Flags);
break;
default:
FspDebugLog("%S[TID=%04lx]: %p: >>SetInformation [INVALID] %s%S%s%s\n",
FspDiagIdent(), GetCurrentThreadId(), (PVOID)Request->Hint,

View File

@ -161,6 +161,7 @@ FSP_API NTSTATUS FspFileSystemCreate(PWSTR DevicePath,
FileSystem->UmFileContextIsUserContext2 = !!VolumeParams->UmFileContextIsUserContext2;
FileSystem->UmFileContextIsFullContext = !!VolumeParams->UmFileContextIsFullContext;
FileSystem->UmNoReparsePointsDirCheck = VolumeParams->UmNoReparsePointsDirCheck;
*PFileSystem = FileSystem;

View File

@ -56,7 +56,8 @@ FSP_API NTSTATUS FspFileSystemOpEnter(FSP_FILE_SYSTEM *FileSystem,
(FspFsctlTransactCleanupKind == Request->Kind &&
Request->Req.Cleanup.Delete) ||
(FspFsctlTransactSetInformationKind == Request->Kind &&
10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass) ||
(10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass ||
65/*FileRenameInformationEx*/ == Request->Req.SetInformation.FileInformationClass)) ||
FspFsctlTransactSetVolumeInformationKind == Request->Kind ||
(FspFsctlTransactFlushBuffersKind == Request->Kind &&
0 == Request->Req.FlushBuffers.UserContext &&
@ -67,7 +68,8 @@ FSP_API NTSTATUS FspFileSystemOpEnter(FSP_FILE_SYSTEM *FileSystem,
else
if (FspFsctlTransactCreateKind == Request->Kind ||
(FspFsctlTransactSetInformationKind == Request->Kind &&
13/*FileDispositionInformation*/ == Request->Req.SetInformation.FileInformationClass) ||
(13/*FileDispositionInformation*/ == Request->Req.SetInformation.FileInformationClass ||
64/*FileDispositionInformationEx*/ == Request->Req.SetInformation.FileInformationClass)) ||
FspFsctlTransactQueryDirectoryKind == Request->Kind ||
FspFsctlTransactQueryVolumeInformationKind == Request->Kind)
{
@ -95,7 +97,8 @@ FSP_API NTSTATUS FspFileSystemOpLeave(FSP_FILE_SYSTEM *FileSystem,
(FspFsctlTransactCleanupKind == Request->Kind &&
Request->Req.Cleanup.Delete) ||
(FspFsctlTransactSetInformationKind == Request->Kind &&
10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass) ||
(10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass ||
65/*FileRenameInformationEx*/ == Request->Req.SetInformation.FileInformationClass)) ||
FspFsctlTransactSetVolumeInformationKind == Request->Kind ||
(FspFsctlTransactFlushBuffersKind == Request->Kind &&
0 == Request->Req.FlushBuffers.UserContext &&
@ -106,7 +109,8 @@ FSP_API NTSTATUS FspFileSystemOpLeave(FSP_FILE_SYSTEM *FileSystem,
else
if (FspFsctlTransactCreateKind == Request->Kind ||
(FspFsctlTransactSetInformationKind == Request->Kind &&
13/*FileDispositionInformation*/ == Request->Req.SetInformation.FileInformationClass) ||
(13/*FileDispositionInformation*/ == Request->Req.SetInformation.FileInformationClass ||
64/*FileDispositionInformationEx*/ == Request->Req.SetInformation.FileInformationClass)) ||
FspFsctlTransactQueryDirectoryKind == Request->Kind ||
FspFsctlTransactQueryVolumeInformationKind == Request->Kind)
{
@ -373,7 +377,10 @@ NTSTATUS FspFileSystemRenameCheck(FSP_FILE_SYSTEM *FileSystem,
Request->Req.SetInformation.Info.Rename.NewFileName.Size;
CreateRequest->Kind = FspFsctlTransactCreateKind;
CreateRequest->Req.Create.CreateOptions =
FILE_DELETE_ON_CLOSE | /* force read-only check! */
(65/*FileRenameInformationEx*/ == Request->Req.SetInformation.FileInformationClass &&
0 != (0x40/*IGNORE_READONLY_ATTRIBUTE*/ & Request->Req.SetInformation.Info.RenameEx.Flags) ?
0 :
FILE_DELETE_ON_CLOSE) | /* force read-only check! */
FILE_OPEN_REPARSE_POINT; /* allow rename over reparse point */
CreateRequest->Req.Create.AccessToken = Request->Req.SetInformation.Info.Rename.AccessToken;
CreateRequest->Req.Create.UserMode = TRUE;
@ -1134,7 +1141,9 @@ FSP_API NTSTATUS FspFileSystemOpSetInformation(FSP_FILE_SYSTEM *FileSystem,
&FileInfo);
break;
case 13/*FileDispositionInformation*/:
if (0 != FileSystem->Interface->GetFileInfo)
case 64/*FileDispositionInformationEx*/:
if (0 == (0x10/*IGNORE_READONLY_ATTRIBUTE*/ & Request->Req.SetInformation.Info.DispositionEx.Flags) &&
0 != FileSystem->Interface->GetFileInfo)
{
Result = FileSystem->Interface->GetFileInfo(FileSystem,
(PVOID)ValOfFileContext(Request->Req.SetInformation), &FileInfo);
@ -1149,11 +1158,11 @@ FSP_API NTSTATUS FspFileSystemOpSetInformation(FSP_FILE_SYSTEM *FileSystem,
Result = FileSystem->Interface->SetDelete(FileSystem,
(PVOID)ValOfFileContext(Request->Req.SetInformation),
(PWSTR)Request->Buffer,
Request->Req.SetInformation.Info.Disposition.Delete);
0 != (1/*DELETE*/ & Request->Req.SetInformation.Info.DispositionEx.Flags));
}
else if (0 != FileSystem->Interface->CanDelete)
{
if (Request->Req.SetInformation.Info.Disposition.Delete)
if (0 != (1/*DELETE*/ & Request->Req.SetInformation.Info.DispositionEx.Flags))
Result = FileSystem->Interface->CanDelete(FileSystem,
(PVOID)ValOfFileContext(Request->Req.SetInformation),
(PWSTR)Request->Buffer);
@ -1162,6 +1171,7 @@ FSP_API NTSTATUS FspFileSystemOpSetInformation(FSP_FILE_SYSTEM *FileSystem,
}
break;
case 10/*FileRenameInformation*/:
case 65/*FileRenameInformationEx*/:
if (0 != FileSystem->Interface->Rename)
{
if (0 != Request->Req.SetInformation.Info.Rename.AccessToken)

View File

@ -96,6 +96,7 @@ static struct fuse_opt fsp_fuse_core_opts[] =
FSP_FUSE_CORE_OPT("VolumeInfoTimeout=", set_VolumeInfoTimeout, 1),
FSP_FUSE_CORE_OPT("VolumeInfoTimeout=%d", VolumeParams.VolumeInfoTimeout, 0),
FSP_FUSE_CORE_OPT("KeepFileCache=", set_KeepFileCache, 1),
FSP_FUSE_CORE_OPT("LegacyUnlinkRename=", set_LegacyUnlinkRename, 1),
FSP_FUSE_CORE_OPT("ThreadCount=%u", ThreadCount, 0),
FUSE_OPT_KEY("UNC=", 'U'),
FUSE_OPT_KEY("--UNC=", 'U'),
@ -421,6 +422,7 @@ static int fsp_fuse_core_opt_proc(void *opt_data0, const char *arg, int key,
" -o EaTimeout=N extended attribute timeout (millis)\n"
" -o VolumeInfoTimeout=N volume info timeout (millis)\n"
" -o KeepFileCache do not discard cache when files are closed\n"
" -o LegacyUnlinkRename do not support new POSIX unlink/rename\n"
" -o ThreadCount number of file system dispatcher threads\n"
);
opt_data->help = 1;
@ -563,6 +565,7 @@ FSP_FUSE_API struct fuse *fsp_fuse_new(struct fsp_fuse_env *env,
opt_data.VolumeParams.Version = sizeof(FSP_FSCTL_VOLUME_PARAMS);
opt_data.VolumeParams.FileInfoTimeout = 1000;
opt_data.VolumeParams.FlushAndPurgeOnCleanup = TRUE;
opt_data.VolumeParams.SupportsPosixUnlinkRename = TRUE;
if (-1 == fsp_fuse_core_opt_parse(env, args, &opt_data, /*help=*/1))
{
@ -623,6 +626,8 @@ FSP_FUSE_API struct fuse *fsp_fuse_new(struct fsp_fuse_env *env,
opt_data.VolumeParams.VolumeInfoTimeoutValid = 1;
if (opt_data.set_KeepFileCache)
opt_data.VolumeParams.FlushAndPurgeOnCleanup = FALSE;
if (opt_data.set_LegacyUnlinkRename)
opt_data.VolumeParams.SupportsPosixUnlinkRename = FALSE;
opt_data.VolumeParams.CaseSensitiveSearch = TRUE;
opt_data.VolumeParams.CasePreservedNames = TRUE;
opt_data.VolumeParams.PersistentAcls = TRUE;
@ -637,6 +642,7 @@ FSP_FUSE_API struct fuse *fsp_fuse_new(struct fsp_fuse_env *env,
opt_data.VolumeParams.RejectIrpPriorToTransact0 = TRUE;
#endif
opt_data.VolumeParams.UmFileContextIsUserContext2 = TRUE;
opt_data.VolumeParams.UmNoReparsePointsDirCheck = TRUE;
if (L'\0' == opt_data.VolumeParams.FileSystemName[0])
memcpy(opt_data.VolumeParams.FileSystemName, L"FUSE", 5 * sizeof(WCHAR));

View File

@ -21,6 +21,13 @@
#include <dll/fuse/library.h>
static NTSTATUS fsp_fuse_intf_GetReparsePointByName(
FSP_FILE_SYSTEM *FileSystem, PVOID Context,
PWSTR FileName, BOOLEAN IsDirectory, PVOID Buffer, PSIZE_T PSize);
static NTSTATUS fsp_fuse_intf_SetEaEntry(
FSP_FILE_SYSTEM *FileSystem, PVOID Context,
PFILE_FULL_EA_INFORMATION SingleEa);
static inline
VOID fsp_fuse_op_enter_lock(FSP_FILE_SYSTEM *FileSystem,
FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response)
@ -34,7 +41,8 @@ VOID fsp_fuse_op_enter_lock(FSP_FILE_SYSTEM *FileSystem,
(FspFsctlTransactCleanupKind == Request->Kind &&
Request->Req.Cleanup.Delete) ||
(FspFsctlTransactSetInformationKind == Request->Kind &&
10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass) ||
(10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass ||
65/*FileRenameInformationEx*/ == Request->Req.SetInformation.FileInformationClass)) ||
FspFsctlTransactSetVolumeInformationKind == Request->Kind ||
(FspFsctlTransactFlushBuffersKind == Request->Kind &&
0 == Request->Req.FlushBuffers.UserContext &&
@ -48,7 +56,8 @@ VOID fsp_fuse_op_enter_lock(FSP_FILE_SYSTEM *FileSystem,
else
if (FspFsctlTransactCreateKind == Request->Kind ||
(FspFsctlTransactSetInformationKind == Request->Kind &&
13/*FileDispositionInformation*/ == Request->Req.SetInformation.FileInformationClass) ||
(13/*FileDispositionInformation*/ == Request->Req.SetInformation.FileInformationClass ||
64/*FileDispositionInformationEx*/ == Request->Req.SetInformation.FileInformationClass)) ||
FspFsctlTransactQueryDirectoryKind == Request->Kind ||
FspFsctlTransactQueryVolumeInformationKind == Request->Kind ||
/* FSCTL_GET_REPARSE_POINT may access namespace */
@ -78,7 +87,8 @@ VOID fsp_fuse_op_leave_unlock(FSP_FILE_SYSTEM *FileSystem,
(FspFsctlTransactCleanupKind == Request->Kind &&
Request->Req.Cleanup.Delete) ||
(FspFsctlTransactSetInformationKind == Request->Kind &&
10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass) ||
(10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass ||
65/*FileRenameInformationEx*/ == Request->Req.SetInformation.FileInformationClass)) ||
FspFsctlTransactSetVolumeInformationKind == Request->Kind ||
(FspFsctlTransactFlushBuffersKind == Request->Kind &&
0 == Request->Req.FlushBuffers.UserContext &&
@ -92,7 +102,8 @@ VOID fsp_fuse_op_leave_unlock(FSP_FILE_SYSTEM *FileSystem,
else
if (FspFsctlTransactCreateKind == Request->Kind ||
(FspFsctlTransactSetInformationKind == Request->Kind &&
13/*FileDispositionInformation*/ == Request->Req.SetInformation.FileInformationClass) ||
(13/*FileDispositionInformation*/ == Request->Req.SetInformation.FileInformationClass ||
64/*FileDispositionInformationEx*/ == Request->Req.SetInformation.FileInformationClass)) ||
FspFsctlTransactQueryDirectoryKind == Request->Kind ||
FspFsctlTransactQueryVolumeInformationKind == Request->Kind ||
/* FSCTL_GET_REPARSE_POINT may access namespace */
@ -131,7 +142,8 @@ NTSTATUS fsp_fuse_op_enter(FSP_FILE_SYSTEM *FileSystem,
AccessToken = Request->Req.Create.AccessToken;
}
else if (FspFsctlTransactSetInformationKind == Request->Kind &&
10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass)
(10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass ||
65/*FileRenameInformationEx*/ == Request->Req.SetInformation.FileInformationClass))
{
FileName = (PWSTR)(Request->Buffer + Request->Req.SetInformation.Info.Rename.NewFileName.Offset);
AccessToken = Request->Req.SetInformation.Info.Rename.AccessToken;
@ -293,33 +305,111 @@ static BOOLEAN fsp_fuse_intf_CheckSymlinkDirectory(FSP_FILE_SYSTEM *FileSystem,
const char *PosixPath)
{
struct fuse *f = FileSystem->UserContext;
char *PosixDotPath = 0;
size_t Length;
struct fuse_stat_ex stbuf;
int err;
BOOLEAN Result = FALSE;
Length = lstrlenA(PosixPath);
PosixDotPath = MemAlloc(Length + 3);
if (0 != PosixDotPath)
if (FSP_FUSE_HAS_SLASHDOT(f))
{
memcpy(PosixDotPath, PosixPath, Length);
PosixDotPath[Length + 0] = '/';
PosixDotPath[Length + 1] = '.';
PosixDotPath[Length + 2] = '\0';
char *PosixDotPath = 0;
size_t Length;
struct fuse_stat_ex stbuf;
int err;
BOOLEAN Result = FALSE;
Length = lstrlenA(PosixPath);
PosixDotPath = MemAlloc(Length + 3);
if (0 != PosixDotPath)
{
memcpy(PosixDotPath, PosixPath, Length);
PosixDotPath[Length + 0] = '/';
PosixDotPath[Length + 1] = '.';
PosixDotPath[Length + 2] = '\0';
memset(&stbuf, 0, sizeof stbuf);
if (0 != f->ops.getattr)
err = f->ops.getattr(PosixDotPath, (void *)&stbuf);
else
err = -ENOSYS_(f->env);
MemFree(PosixDotPath);
Result = 0 == err && 0040000 == (stbuf.st_mode & 0170000);
}
return Result;
}
else
{
PWSTR WindowsPath = 0, P;
char *PosixResolvedPath = 0;
UINT32 ReparsePointIndex;
UINT32 ResolveFileAttributes[2] = { FILE_ATTRIBUTE_REPARSE_POINT, -1 };
IO_STATUS_BLOCK IoStatus;
union
{
REPARSE_DATA_BUFFER V;
UINT8 B[FIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) +
FSP_FSCTL_TRANSACT_PATH_SIZEMAX + sizeof(WCHAR)/* add space for term-0 */];
} ReparseDataBuf;
SIZE_T ReparseDataSize;
struct fuse_stat_ex stbuf;
int err;
NTSTATUS Result;
Result = FspPosixMapPosixToWindowsPath(PosixPath, &WindowsPath);
if (!NT_SUCCESS(Result))
goto exit;
ReparsePointIndex = 0;
for (P = WindowsPath; '\0' != *P; P++)
if (L'\\' == *P)
ReparsePointIndex = (UINT32)(P + 1 - WindowsPath);
ReparseDataSize = sizeof ReparseDataBuf - sizeof(WCHAR)/* leave space for term-0 */;
Result = FspFileSystemResolveReparsePoints(FileSystem,
fsp_fuse_intf_GetReparsePointByName, ResolveFileAttributes,
WindowsPath, ReparsePointIndex, TRUE,
&IoStatus, &ReparseDataBuf,
&ReparseDataSize);
if (!NT_SUCCESS(Result))
goto exit;
if (IO_REPARSE_TAG_SYMLINK != ReparseDataBuf.V.ReparseTag)
{
Result = STATUS_UNSUCCESSFUL;
goto exit;
}
if (-1 != ResolveFileAttributes[1])
{
Result = (FILE_ATTRIBUTE_DIRECTORY & ResolveFileAttributes[1]) ?
STATUS_SUCCESS : STATUS_UNSUCCESSFUL;
goto exit;
}
P = (PWSTR)(ReparseDataBuf.V.SymbolicLinkReparseBuffer.PathBuffer +
ReparseDataBuf.V.SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
P[ReparseDataBuf.V.SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR)] = L'\0';
Result = FspPosixMapWindowsToPosixPath(P, &PosixResolvedPath);
if (!NT_SUCCESS(Result))
goto exit;
memset(&stbuf, 0, sizeof stbuf);
if (0 != f->ops.getattr)
err = f->ops.getattr(PosixDotPath, (void *)&stbuf);
err = f->ops.getattr(PosixResolvedPath, (void *)&stbuf);
else
err = -ENOSYS_(f->env);
MemFree(PosixDotPath);
Result = 0 == err && 0040000 == (stbuf.st_mode & 0170000) ?
STATUS_SUCCESS : STATUS_UNSUCCESSFUL;
Result = 0 == err && 0040000 == (stbuf.st_mode & 0170000);
exit:
if (0 != PosixResolvedPath)
FspPosixDeletePath(PosixResolvedPath);
if (0 != WindowsPath)
FspPosixDeletePath(WindowsPath);
return NT_SUCCESS(Result);
}
return Result;
}
static inline uint32_t fsp_fuse_intf_MapFileAttributesToFlags(UINT32 FileAttributes)
@ -356,10 +446,11 @@ static inline UINT32 fsp_fuse_intf_MapFlagsToFileAttributes(uint32_t flags)
#define FUSE_FILE_INFO(IsDirectory, fi) ((IsDirectory) ? 0 : (fi))
#define fsp_fuse_intf_GetFileInfoEx(FileSystem, PosixPath, fi, PUid, PGid, PMode, FileInfo)\
fsp_fuse_intf_GetFileInfoFunnel(FileSystem, PosixPath, fi, 0, PUid, PGid, PMode, 0, FileInfo)
fsp_fuse_intf_GetFileInfoFunnel(FileSystem, PosixPath, fi, 0, PUid, PGid, PMode, 0, TRUE, FileInfo)
static NTSTATUS fsp_fuse_intf_GetFileInfoFunnel(FSP_FILE_SYSTEM *FileSystem,
const char *PosixPath, struct fuse_file_info *fi, const void *stbufp,
PUINT32 PUid, PUINT32 PGid, PUINT32 PMode, PUINT32 PDev,
BOOLEAN CheckSymlinkDirectory,
FSP_FSCTL_FILE_INFO *FileInfo)
{
struct fuse *f = FileSystem->UserContext;
@ -418,7 +509,7 @@ static NTSTATUS fsp_fuse_intf_GetFileInfoFunnel(FSP_FILE_SYSTEM *FileSystem,
{
FileInfo->FileAttributes = FILE_ATTRIBUTE_REPARSE_POINT;
FileInfo->ReparseTag = IO_REPARSE_TAG_SYMLINK;
if (fsp_fuse_intf_CheckSymlinkDirectory(FileSystem, PosixPath))
if (CheckSymlinkDirectory && fsp_fuse_intf_CheckSymlinkDirectory(FileSystem, PosixPath))
FileInfo->FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
break;
}
@ -561,7 +652,7 @@ exit:
static NTSTATUS fsp_fuse_intf_GetReparsePointEx(FSP_FILE_SYSTEM *FileSystem,
const char *PosixPath, struct fuse_file_info *fi,
PVOID Buffer, PSIZE_T PSize)
PVOID Buffer, PSIZE_T PSize, PUINT32 PResolveFileAttributes)
{
struct fuse *f = FileSystem->UserContext;
UINT32 Uid, Gid, Mode, Dev;
@ -571,14 +662,29 @@ static NTSTATUS fsp_fuse_intf_GetReparsePointEx(FSP_FILE_SYSTEM *FileSystem,
SIZE_T Size;
NTSTATUS Result;
if (0 != PResolveFileAttributes && FILE_ATTRIBUTE_REPARSE_POINT == PResolveFileAttributes[0])
{
Mode = 0120000;
memset(&FileInfo, 0, sizeof FileInfo);
FileInfo.FileAttributes = PResolveFileAttributes[0];
FileInfo.ReparseTag = IO_REPARSE_TAG_SYMLINK;
PResolveFileAttributes[0] = 0;
goto skip_getattr;
}
Result = fsp_fuse_intf_GetFileInfoFunnel(FileSystem, PosixPath, fi, 0,
&Uid, &Gid, &Mode, &Dev, &FileInfo);
&Uid, &Gid, &Mode, &Dev, FALSE, &FileInfo);
if (!NT_SUCCESS(Result))
return Result;
if (0 == (FILE_ATTRIBUTE_REPARSE_POINT & FileInfo.FileAttributes))
{
if (0 != PResolveFileAttributes)
PResolveFileAttributes[1] = FileInfo.FileAttributes;
return STATUS_NOT_A_REPARSE_POINT;
}
skip_getattr:
if (0 == Buffer)
return STATUS_SUCCESS;
@ -677,13 +783,6 @@ static NTSTATUS fsp_fuse_intf_GetReparsePointEx(FSP_FILE_SYSTEM *FileSystem,
return STATUS_SUCCESS;
}
static NTSTATUS fsp_fuse_intf_GetReparsePointByName(
FSP_FILE_SYSTEM *FileSystem, PVOID Context,
PWSTR FileName, BOOLEAN IsDirectory, PVOID Buffer, PSIZE_T PSize);
static NTSTATUS fsp_fuse_intf_SetEaEntry(
FSP_FILE_SYSTEM *FileSystem, PVOID Context,
PFILE_FULL_EA_INFORMATION SingleEa);
static NTSTATUS fsp_fuse_intf_GetVolumeInfo(FSP_FILE_SYSTEM *FileSystem,
FSP_FSCTL_VOLUME_INFO *VolumeInfo)
{
@ -951,12 +1050,12 @@ exit:
if (CreateOptions & FILE_DIRECTORY_FILE)
{
if (0 != f->ops.releasedir)
f->ops.releasedir(filedesc->PosixPath, &fi);
f->ops.releasedir(contexthdr->PosixPath, &fi);
}
else
{
if (0 != f->ops.release)
f->ops.release(filedesc->PosixPath, &fi);
f->ops.release(contexthdr->PosixPath, &fi);
}
}
@ -980,6 +1079,19 @@ static NTSTATUS fsp_fuse_intf_Open(FSP_FILE_SYSTEM *FileSystem,
int err;
NTSTATUS Result;
if (0 != (CreateOptions & FILE_DELETE_ON_CLOSE) &&
0 != (f->conn_want & FSP_FUSE_CAP_DELETE_ACCESS) && 0 != f->ops.access)
{
err = f->ops.access(contexthdr->PosixPath, FSP_FUSE_DELETE_OK);
Result = fsp_fuse_ntstatus_from_errno(f->env, err);
if (!NT_SUCCESS(Result) && STATUS_INVALID_DEVICE_REQUEST != Result)
{
if (STATUS_ACCESS_DENIED == Result)
Result = STATUS_CANNOT_DELETE;
goto exit;
}
}
Result = fsp_fuse_intf_GetFileInfoEx(FileSystem, contexthdr->PosixPath, 0,
&Uid, &Gid, &Mode, &FileInfoBuf);
if (!NT_SUCCESS(Result))
@ -1007,7 +1119,12 @@ static NTSTATUS fsp_fuse_intf_Open(FSP_FILE_SYSTEM *FileSystem,
break;
}
if (FileInfoBuf.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
if (FileInfoBuf.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
{
fi.fh = -1;
Result = STATUS_SUCCESS;
}
else if (FileInfoBuf.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (0 != f->ops.opendir)
{
@ -1020,11 +1137,6 @@ static NTSTATUS fsp_fuse_intf_Open(FSP_FILE_SYSTEM *FileSystem,
Result = STATUS_SUCCESS;
}
}
else if (FileInfoBuf.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
{
fi.fh = -1;
Result = STATUS_SUCCESS;
}
else
{
/*
@ -1178,6 +1290,14 @@ static VOID fsp_fuse_intf_Cleanup(FSP_FILE_SYSTEM *FileSystem,
* than ours. [Even if there is a concurrent Open of this file, the FSD will fail it with
* STATUS_DELETE_PENDING.] This means that we do not need to worry about the hard_remove
* FUSE option and can safely remove the file at this time.
*
*
* NOTE:
*
* Since WinFsp 2022 Beta4 (v1.10B4) it is possible to handle handles open other than ours
* because of the new POSIX unlink semantics. Although we still do not provide the hard_remove
* option, file systems that would need the hard_remove option can instead use the
* LegacyUnlinkRename option to opt out of the POSIX unlink semantics.
*/
if (Flags & FspCleanupDelete)
@ -1204,15 +1324,15 @@ static VOID fsp_fuse_intf_Close(FSP_FILE_SYSTEM *FileSystem,
fi.flags = filedesc->OpenFlags;
fi.fh = filedesc->FileHandle;
if (filedesc->IsDirectory)
if (filedesc->IsReparsePoint)
{
/* reparse points are not opened, nothing to do! */
}
else if (filedesc->IsDirectory)
{
if (0 != f->ops.releasedir)
f->ops.releasedir(filedesc->PosixPath, &fi);
}
else if (filedesc->IsReparsePoint)
{
/* reparse points are not opened, nothing to do! */
}
else
{
if (0 != f->ops.flush)
@ -1344,7 +1464,9 @@ static NTSTATUS fsp_fuse_intf_Flush(FSP_FILE_SYSTEM *FileSystem,
fi.fh = filedesc->FileHandle;
Result = STATUS_SUCCESS; /* just say success, if fs does not support fsync */
if (filedesc->IsDirectory)
if (filedesc->IsReparsePoint)
Result = STATUS_ACCESS_DENIED;
else if (filedesc->IsDirectory)
{
if (0 != f->ops.fsyncdir)
{
@ -1352,8 +1474,6 @@ static NTSTATUS fsp_fuse_intf_Flush(FSP_FILE_SYSTEM *FileSystem,
Result = fsp_fuse_ntstatus_from_errno(f->env, err);
}
}
else if (filedesc->IsReparsePoint)
Result = STATUS_ACCESS_DENIED;
else
{
if (0 != f->ops.fsync)
@ -1575,6 +1695,19 @@ static NTSTATUS fsp_fuse_intf_CanDelete(FSP_FILE_SYSTEM *FileSystem,
struct fuse_dirhandle dh;
int err;
if (0 != (f->conn_want & FSP_FUSE_CAP_DELETE_ACCESS) && 0 != f->ops.access)
{
NTSTATUS Result;
err = f->ops.access(filedesc->PosixPath, FSP_FUSE_DELETE_OK);
Result = fsp_fuse_ntstatus_from_errno(f->env, err);
if (!NT_SUCCESS(Result) && STATUS_INVALID_DEVICE_REQUEST != Result)
{
if (STATUS_ACCESS_DENIED == Result)
Result = STATUS_CANNOT_DELETE;
return Result;
}
}
if (filedesc->IsDirectory && !filedesc->IsReparsePoint)
{
/* check that directory is empty! */
@ -1800,7 +1933,7 @@ int fsp_fuse_intf_AddDirInfo(void *buf, const char *name,
NTSTATUS Result0;
Result0 = fsp_fuse_intf_GetFileInfoFunnel(dh->FileSystem, name, 0, stbuf,
&Uid, &Gid, &Mode, 0, &DirInfo->FileInfo);
&Uid, &Gid, &Mode, 0, TRUE, &DirInfo->FileInfo);
if (NT_SUCCESS(Result0))
DirInfo->Padding[0] = 1; /* HACK: remember that the FileInfo is valid */
}
@ -1922,6 +2055,9 @@ static NTSTATUS fsp_fuse_intf_ReadDirectory(FSP_FILE_SYSTEM *FileSystem,
int err;
NTSTATUS Result;
if (!filedesc->IsDirectory || filedesc->IsReparsePoint)
return STATUS_ACCESS_DENIED;
if (FspFileSystemAcquireDirectoryBuffer(&filedesc->DirBuffer, 0 == Marker, &Result))
{
memset(&dh, 0, sizeof dh);
@ -1978,6 +2114,9 @@ static NTSTATUS fsp_fuse_intf_GetDirInfoByName(FSP_FILE_SYSTEM *FileSystem,
UINT32 Uid, Gid, Mode;
NTSTATUS Result;
if (!filedesc->IsDirectory || filedesc->IsReparsePoint)
return STATUS_ACCESS_DENIED;
Result = FspPosixMapWindowsToPosixPath(FileName, &PosixName);
if (!NT_SUCCESS(Result))
{
@ -2044,7 +2183,7 @@ static NTSTATUS fsp_fuse_intf_GetReparsePointByName(
if (!NT_SUCCESS(Result))
goto exit;
Result = fsp_fuse_intf_GetReparsePointEx(FileSystem, PosixPath, 0, Buffer, PSize);
Result = fsp_fuse_intf_GetReparsePointEx(FileSystem, PosixPath, 0, Buffer, PSize, Context);
exit:
if (0 != PosixPath)
@ -2066,7 +2205,7 @@ static NTSTATUS fsp_fuse_intf_GetReparsePoint(FSP_FILE_SYSTEM *FileSystem,
return fsp_fuse_intf_GetReparsePointEx(FileSystem, filedesc->PosixPath,
FUSE_FILE_INFO(filedesc->IsDirectory, &fi),
Buffer, PSize);
Buffer, PSize, 0);
}
static NTSTATUS fsp_fuse_intf_SetReparsePoint(FSP_FILE_SYSTEM *FileSystem,
@ -2105,23 +2244,25 @@ static NTSTATUS fsp_fuse_intf_SetReparsePoint(FSP_FILE_SYSTEM *FileSystem,
* Note that this will also result in a change of the inode number for the reparse point!
*/
if (0 == f->ops.rename || 0 == f->ops.unlink)
return STATUS_INVALID_DEVICE_REQUEST;
ReparseData = (PREPARSE_DATA_BUFFER)Buffer;
if (IO_REPARSE_TAG_SYMLINK == ReparseData->ReparseTag || (
IO_REPARSE_TAG_NFS == ReparseData->ReparseTag &&
NFS_SPECFILE_LNK == *(PUINT64)ReparseData->GenericReparseBuffer.DataBuffer))
{
if (0 == f->ops.symlink)
if (filedesc->IsDirectory && 0 == f->ops.rmdir)
return STATUS_INVALID_DEVICE_REQUEST;
if (0 == f->ops.symlink || 0 == f->ops.rename || 0 == f->ops.unlink)
return STATUS_INVALID_DEVICE_REQUEST;
IsSymlink = TRUE;
}
else if (IO_REPARSE_TAG_NFS == ReparseData->ReparseTag)
{
if (0 == f->ops.mknod)
/* FUSE cannot make a directory into an NFS reparse point */
if (filedesc->IsDirectory)
return STATUS_ACCESS_DENIED;
if (0 == f->ops.mknod || 0 == f->ops.rename || 0 == f->ops.unlink)
return STATUS_INVALID_DEVICE_REQUEST;
IsSymlink = FALSE;
@ -2129,10 +2270,6 @@ static NTSTATUS fsp_fuse_intf_SetReparsePoint(FSP_FILE_SYSTEM *FileSystem,
else
return STATUS_IO_REPARSE_TAG_MISMATCH;
/* FUSE cannot make a directory into a reparse point */
if (filedesc->IsDirectory)
return STATUS_ACCESS_DENIED;
memset(&fi, 0, sizeof fi);
fi.flags = filedesc->OpenFlags;
fi.fh = filedesc->FileHandle;
@ -2243,17 +2380,33 @@ static NTSTATUS fsp_fuse_intf_SetReparsePoint(FSP_FILE_SYSTEM *FileSystem,
}
}
err = f->ops.rename(PosixHiddenPath, filedesc->PosixPath);
if (filedesc->IsDirectory)
{
err = f->ops.rmdir(filedesc->PosixPath);
if (0 == err)
err = f->ops.rename(PosixHiddenPath, filedesc->PosixPath);
}
else
err = f->ops.rename(PosixHiddenPath, filedesc->PosixPath);
if (0 != err)
{
/* on failure unlink "hidden" symlink */
f->ops.unlink(PosixHiddenPath);
Result = fsp_fuse_ntstatus_from_errno(f->env, err);
goto exit;
}
if (filedesc->IsDirectory)
{
if (0 != f->ops.releasedir)
f->ops.releasedir(filedesc->PosixPath, &fi);
}
else
{
if (0 != f->ops.release)
f->ops.release(filedesc->PosixPath, &fi);
}
filedesc->IsReparsePoint = TRUE;
filedesc->FileHandle = -1;
Result = STATUS_SUCCESS;
@ -2379,13 +2532,17 @@ static NTSTATUS fsp_fuse_intf_SetEaEntry(
int err;
if (0 != SingleEa->EaValueLength)
err = f->ops.setxattr(PosixPath,
SingleEa->EaName, SingleEa->EaName + SingleEa->EaNameLength + 1, SingleEa->EaValueLength, 0);
{
err = f->ops.setxattr(PosixPath, SingleEa->EaName,
SingleEa->EaName + SingleEa->EaNameLength + 1, SingleEa->EaValueLength, 0);
return fsp_fuse_ntstatus_from_errno(f->env, err);
}
else
err = f->ops.removexattr(PosixPath,
SingleEa->EaName);
return fsp_fuse_ntstatus_from_errno(f->env, err);
{
err = f->ops.removexattr(PosixPath, SingleEa->EaName);
/* ignore errors */
return STATUS_SUCCESS;
}
}
static NTSTATUS fsp_fuse_intf_SetEa(FSP_FILE_SYSTEM *FileSystem,

View File

@ -88,6 +88,7 @@ static NTSTATUS fsp_fuse_loop_start(struct fuse *f)
FSP_FUSE_CAP_READDIR_PLUS |
FSP_FUSE_CAP_READ_ONLY |
FSP_FUSE_CAP_STAT_EX |
FSP_FUSE_CAP_DELETE_ACCESS |
FSP_FUSE_CAP_CASE_INSENSITIVE;
if (0 != f->ops.init)
{
@ -159,6 +160,29 @@ static NTSTATUS fsp_fuse_loop_start(struct fuse *f)
/* this should always fail with ENOSYS or EINVAL */
err = f->ops.readlink("/", buf, sizeof buf);
f->has_symlinks = -ENOSYS_(f->env) != err;
if (f->has_symlinks)
{
/*
* Determine if the file system supports "/." queries.
*
* Symlinks on Windows are differentiated as "file" symlinks or "directory" symlinks.
* When we need to make the distinction we can follow one of two techniques:
*
* - Slashdot technique: We issue a getattr(path + "/.") and check the stat result.
* In general this is not a getattr() query that FUSE file systems are expected
* to handle. For this reason we issue a getattr("/.") below to determine
* if the file system handles this kind of query against the root directory.
*
* - Resolve technique: If the file system cannot handle slashdot queries, we resolve
* the path using readlink on each path component, then issue getattr on the resolved
* path and check the stat result.
*/
struct fuse_stat_ex stbuf;
memset(&stbuf, 0, sizeof stbuf);
err = f->ops.getattr("/.", (void *)&stbuf);
f->has_slashdot = 0 == err && 0040000 == (stbuf.st_mode & 0170000);
}
}
if (0 != f->ops.listxattr && 0 != f->ops.getxattr &&
0 != f->ops.setxattr && 0 != f->ops.removexattr)

View File

@ -34,6 +34,7 @@
((struct fuse_context *)((PUINT8)(h) + sizeof(struct fsp_fuse_context_header)))
#define FSP_FUSE_HAS_SYMLINKS(f) ((f)->has_symlinks)
#define FSP_FUSE_HAS_SLASHDOT(f) ((f)->has_slashdot)
#define ENOSYS_(env) ('C' == (env)->environment ? 88 : 40)
@ -61,7 +62,7 @@ struct fuse
void *data;
unsigned conn_want;
BOOLEAN fsinit;
BOOLEAN has_symlinks;
BOOLEAN has_symlinks, has_slashdot;
UINT32 DebugLog;
FSP_FILE_SYSTEM_OPERATION_GUARD_STRATEGY OpGuardStrategy;
FSP_FSCTL_VOLUME_PARAMS VolumeParams;
@ -153,7 +154,8 @@ struct fsp_fuse_core_opt_data
set_DirInfoTimeout,
set_EaTimeout,
set_VolumeInfoTimeout,
set_KeepFileCache;
set_KeepFileCache,
set_LegacyUnlinkRename;
unsigned ThreadCount;
FSP_FSCTL_VOLUME_PARAMS VolumeParams;
UINT16 VolumeLabelLength;

View File

@ -28,6 +28,29 @@ static NTSTATUS (NTAPI *FspNtMakeTemporaryObject)(
HANDLE Handle);
static NTSTATUS (NTAPI *FspNtClose)(
HANDLE Handle);
static BOOLEAN FspMountDoNotUseLauncher;
static VOID FspMountInitializeFromRegistry(VOID)
{
HKEY RegKey;
LONG Result;
DWORD Size;
DWORD MountDoNotUseLauncher;
MountDoNotUseLauncher = 0;
Result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software\\" FSP_FSCTL_PRODUCT_NAME,
0, KEY_READ | KEY_WOW64_32KEY, &RegKey);
if (ERROR_SUCCESS == Result)
{
Size = sizeof MountDoNotUseLauncher;
Result = RegGetValueW(RegKey, 0, L"MountDoNotUseLauncher",
RRF_RT_REG_DWORD, 0, &MountDoNotUseLauncher, &Size);
RegCloseKey(RegKey);
}
FspMountDoNotUseLauncher = !!MountDoNotUseLauncher;
}
static BOOL WINAPI FspMountInitialize(
PINIT_ONCE InitOnce, PVOID Parameter, PVOID *Context)
@ -49,6 +72,8 @@ static BOOL WINAPI FspMountInitialize(
}
}
FspMountInitializeFromRegistry();
return TRUE;
}
@ -366,7 +391,7 @@ static NTSTATUS FspMountSet_Drive(PWSTR VolumeName, PWSTR MountPoint, PHANDLE PM
Result = FspServiceContextCheck(0, &IsLocalSystem);
IsServiceContext = NT_SUCCESS(Result) && !IsLocalSystem;
if (IsServiceContext)
if (IsServiceContext && !FspMountDoNotUseLauncher)
{
/*
* If the current process is in the service context but not LocalSystem,

View File

@ -325,6 +325,13 @@ FSP_API NTSTATUS FspAccessCheckEx(FSP_FILE_SYSTEM *FileSystem,
goto exit;
}
/*
* We allow some file systems (notably FUSE) to open reparse points
* regardless of the FILE_DIRECTORY_FILE / FILE_NON_DIRECTORY_FILE options.
*/
if (0 != (FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) && FileSystem->UmNoReparsePointsDirCheck)
goto skip_reparse_dir_check;
if ((Request->Req.Create.CreateOptions & FILE_DIRECTORY_FILE) &&
0 == (FileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
@ -337,6 +344,9 @@ FSP_API NTSTATUS FspAccessCheckEx(FSP_FILE_SYSTEM *FileSystem,
Result = STATUS_FILE_IS_A_DIRECTORY;
goto exit;
}
skip_reparse_dir_check:
;
}
if (Request->Req.Create.UserMode)

View File

@ -603,7 +603,7 @@ namespace Fsp
/// directories, etc.
/// </para><para>
/// This function should <b>NEVER</b> delete the file or directory in question. Deletion should
/// happen during Cleanup with the FspCleanupDelete flag set.
/// happen during Cleanup with the CleanupDelete flag set.
/// </para><para>
/// This function gets called when Win32 API's such as DeleteFile or RemoveDirectory are used.
/// It does not get called when a file or directory is opened with FILE_DELETE_ON_CLOSE.
@ -1041,7 +1041,7 @@ namespace Fsp
/// performs tasks such as check for empty directories, etc.
/// </para><para>
/// This function should <b>NEVER</b> delete the file or directory in question. Deletion should
/// happen during Cleanup with the FspCleanupDelete flag set.
/// happen during Cleanup with the CleanupDelete flag set.
/// </para><para>
/// This function gets called when Win32 API's such as DeleteFile or RemoveDirectory are used.
/// It does not get called when a file or directory is opened with FILE_DELETE_ON_CLOSE.

View File

@ -312,6 +312,11 @@ namespace Fsp
get { return 0 != (_VolumeParams.Flags & VolumeParams.RejectIrpPriorToTransact0); }
set { _VolumeParams.Flags |= (value ? VolumeParams.RejectIrpPriorToTransact0 : 0); }
}
public Boolean SupportsPosixUnlinkRename
{
get { return 0 != (_VolumeParams.Flags & VolumeParams.SupportsPosixUnlinkRename); }
set { _VolumeParams.Flags |= (value ? VolumeParams.SupportsPosixUnlinkRename : 0); }
}
/// <summary>
/// Gets or sets the prefix for a network file system.
/// </summary>
@ -1448,6 +1453,9 @@ namespace Fsp
_FileSystemInterface.SetEa = SetEa;
_FileSystemInterfacePtr = Marshal.AllocHGlobal(FileSystemInterface.Size);
/* Marshal.AllocHGlobal does not zero memory; we must do it ourselves! */
for (int Offset = 0; FileSystemInterface.Size > Offset; Offset += IntPtr.Size)
Marshal.WriteIntPtr(_FileSystemInterfacePtr, Offset, IntPtr.Zero);
Marshal.StructureToPtr(_FileSystemInterface, _FileSystemInterfacePtr, false);
}

View File

@ -55,6 +55,7 @@ namespace Fsp.Interop
internal const UInt32 CasePreservedExtendedAttributes = 0x02000000;
internal const UInt32 WslFeatures = 0x04000000;
internal const UInt32 RejectIrpPriorToTransact0 = 0x10000000;
internal const UInt32 SupportsPosixUnlinkRename = 0x20000000;
internal const int PrefixSize = 192;
internal const int FileSystemNameSize = 16;
@ -741,6 +742,8 @@ namespace Fsp.Interop
IntPtr Ea,
UInt32 EaLength,
out FileInfo FileInfo);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate Int32 Obsolete0();
}
internal static int Size = IntPtr.Size * 64;
@ -776,6 +779,7 @@ namespace Fsp.Interop
internal Proto.OverwriteEx OverwriteEx;
internal Proto.GetEa GetEa;
internal Proto.SetEa SetEa;
internal Proto.Obsolete0 Obsolete0;
/* NTSTATUS (*Reserved[33])(); */
}

View File

@ -1321,7 +1321,7 @@ exit:
SvcInstance->StdioHandles[2] = INVALID_HANDLE_VALUE;
}
if (NT_SUCCESS(Result))
if (NT_SUCCESS(Result) && STATUS_TIMEOUT != Result)
{
SvcInstance->Argc = Argc;
SvcInstance->Argv = Argv;

View File

@ -82,21 +82,21 @@ static NTSTATUS FspFsvolCleanup(
FSP_FILE_DESC *FileDesc = FileObject->FsContext2;
FSP_FSCTL_TRANSACT_REQ *Request;
ULONG CleanupFlags;
BOOLEAN DeletePending, SetAllocationSize, FileModified;
BOOLEAN Delete, SetAllocationSize, FileModified;
ASSERT(FileNode == FileDesc->FileNode);
FspFileNodeAcquireExclusive(FileNode, Main);
FspFileNodeCleanup(FileNode, FileObject, &CleanupFlags);
DeletePending = CleanupFlags & 1;
Delete = CleanupFlags & 1;
SetAllocationSize = !!(CleanupFlags & 2);
FileModified = BooleanFlagOn(FileObject->Flags, FO_FILE_MODIFIED);
/* if this is a directory inform the FSRTL Notify mechanism */
if (FileNode->IsDirectory)
{
if (DeletePending)
if (Delete)
FspNotifyDeletePending(
FsvolDeviceExtension->NotifySync, &FsvolDeviceExtension->NotifyList, FileNode);
@ -108,12 +108,12 @@ static NTSTATUS FspFsvolCleanup(
FspFileNodeUnlockAll(FileNode, FileObject, IoGetRequestorProcess(Irp));
/* create the user-mode file system request; MustSucceed because IRP_MJ_CLEANUP cannot fail */
FspIopCreateRequestMustSucceedEx(Irp, DeletePending ? &FileNode->FileName : 0, 0,
FspIopCreateRequestMustSucceedEx(Irp, Delete ? &FileNode->FileName : 0, 0,
FspFsvolCleanupRequestFini, &Request);
Request->Kind = FspFsctlTransactCleanupKind;
Request->Req.Cleanup.UserContext = FileNode->UserContext;
Request->Req.Cleanup.UserContext2 = FileDesc->UserContext2;
Request->Req.Cleanup.Delete = DeletePending;
Request->Req.Cleanup.Delete = Delete;
Request->Req.Cleanup.SetAllocationSize = SetAllocationSize;
Request->Req.Cleanup.SetArchiveBit = (FileModified || FileDesc->DidSetSecurity) &&
!FileDesc->DidSetFileAttributes;
@ -176,6 +176,11 @@ NTSTATUS FspFsvolCleanupComplete(
FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME;
NotifyAction = FILE_ACTION_REMOVED;
}
else if (FileNode->PosixDelete)
{
NotifyFilter = 0;
NotifyAction = 0;
}
else
{
/* send notification for any metadata changes */
@ -236,7 +241,7 @@ static VOID FspFsvolCleanupRequestFini(FSP_FSCTL_TRANSACT_REQ *Request, PVOID Co
FspFileNodeReleaseOwner(FileNode, Pgio, Request);
FspFileNodeCleanupComplete(FileNode, FileObject);
FspFileNodeCleanupComplete(FileNode, FileObject, !!Request->Req.Cleanup.Delete);
if (!FileNode->IsDirectory)
FspFileNodeOplockCheck(FileNode, Irp);
SetFlag(FileObject->Flags, FO_CLEANUP_COMPLETE);

View File

@ -256,6 +256,9 @@ const char *FileInformationClassSym(FILE_INFORMATION_CLASS FileInformationClass)
SYM(FileReplaceCompletionInformation)
SYM(FileHardLinkFullIdInformation)
SYM(FileIdExtdBothDirectoryInformation)
SYM(FileDispositionInformationEx)
SYM(FileRenameInformationEx)
SYM(FileRenameInformationExBypassAccessCheck)
case 68: return "FileStatInformation";
case 70: return "FileStatLxInformation";
default:

View File

@ -119,6 +119,17 @@ static NTSTATUS FspFsvolQueryDirectoryCopy(
DirInfo->FileInfo.FileAttributes : FILE_ATTRIBUTE_NORMAL;\
__VA_ARGS__\
)
#define FILL_INFO_EASIZE()\
if (!FlagOn(DirInfo->FileInfo.FileAttributes, FILE_ATTRIBUTE_REPARSE_POINT))\
{\
Info->EaSize = ReturnEaSize ? DirInfo->FileInfo.EaSize : 0;\
/* magic computations are courtesy of NTFS */\
if (0 != Info->EaSize)\
Info->EaSize += 4;\
}\
else\
/* Fix GitHub issue #380: turns out that the EaSize field is also used for the reparse tag */\
Info->EaSize = DirInfo->FileInfo.ReparseTag
PAGED_CODE();
@ -240,18 +251,12 @@ static NTSTATUS FspFsvolQueryDirectoryCopy(
break;
case FileFullDirectoryInformation:
FILL_INFO(FILE_FULL_DIR_INFORMATION,
Info->EaSize = ReturnEaSize ? DirInfo->FileInfo.EaSize : 0;
/* magic computations are courtesy of NTFS */
if (0 != Info->EaSize)
Info->EaSize += 4;
FILL_INFO_EASIZE();
);
break;
case FileIdFullDirectoryInformation:
FILL_INFO(FILE_ID_FULL_DIR_INFORMATION,
Info->EaSize = ReturnEaSize ? DirInfo->FileInfo.EaSize : 0;
/* magic computations are courtesy of NTFS */
if (0 != Info->EaSize)
Info->EaSize += 4;
FILL_INFO_EASIZE();
Info->FileId.QuadPart = DirInfo->FileInfo.IndexNumber;
);
break;
@ -260,20 +265,14 @@ static NTSTATUS FspFsvolQueryDirectoryCopy(
break;
case FileBothDirectoryInformation:
FILL_INFO(FILE_BOTH_DIR_INFORMATION,
Info->EaSize = ReturnEaSize ? DirInfo->FileInfo.EaSize : 0;
/* magic computations are courtesy of NTFS */
if (0 != Info->EaSize)
Info->EaSize += 4;
FILL_INFO_EASIZE();
Info->ShortNameLength = 0;
RtlZeroMemory(Info->ShortName, sizeof Info->ShortName);
);
break;
case FileIdBothDirectoryInformation:
FILL_INFO(FILE_ID_BOTH_DIR_INFORMATION,
Info->EaSize = ReturnEaSize ? DirInfo->FileInfo.EaSize : 0;
/* magic computations are courtesy of NTFS */
if (0 != Info->EaSize)
Info->EaSize += 4;
FILL_INFO_EASIZE();
Info->ShortNameLength = 0;
RtlZeroMemory(Info->ShortName, sizeof Info->ShortName);
Info->FileId.QuadPart = DirInfo->FileInfo.IndexNumber;
@ -331,6 +330,7 @@ static NTSTATUS FspFsvolQueryDirectoryCopy(
return Result;
#undef FILL_INFO_EASIZE
#undef FILL_INFO
#undef FILL_INFO_BASE
}

View File

@ -1462,6 +1462,7 @@ typedef struct FSP_FILE_NODE
ULONG EaChangeNumber;
ULONG EaChangeCount;
BOOLEAN TruncateOnClose;
BOOLEAN PosixDelete;
FILE_LOCK FileLock;
#if (NTDDI_VERSION < NTDDI_WIN8)
OPLOCK Oplock;
@ -1491,7 +1492,7 @@ typedef struct
UINT64 UserContext2;
UINT32 GrantedAccess;
UINT32
CaseSensitive:1, HasTraversePrivilege:1, DeleteOnClose:1,
CaseSensitive:1, HasTraversePrivilege:1, DeleteOnClose:1, PosixDelete:1,
DidSetMetadata:1,
DidSetFileAttributes:1, DidSetReparsePoint:1, DidSetSecurity:1,
DidSetCreationTime:1, DidSetLastAccessTime:1, DidSetLastWriteTime:1, DidSetChangeTime:1,
@ -1560,7 +1561,7 @@ NTSTATUS FspFileNodeOpen(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject,
FSP_FILE_NODE **POpenedFileNode, PULONG PSharingViolationReason);
VOID FspFileNodeCleanup(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, PULONG PCleanupFlags);
VOID FspFileNodeCleanupFlush(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject);
VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject);
VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, BOOLEAN Delete);
VOID FspFileNodeClose(FSP_FILE_NODE *FileNode,
PFILE_OBJECT FileObject, /* non-0 to remove share access */
BOOLEAN HandleCleanup); /* TRUE to decrement handle count */
@ -1575,7 +1576,8 @@ NTSTATUS FspFileNodeCheckBatchOplocksOnAllStreams(
PUNICODE_STRING StreamFileName);
NTSTATUS FspFileNodeRenameCheck(PDEVICE_OBJECT FsvolDeviceObject, PIRP OplockIrp,
FSP_FILE_NODE *FileNode, ULONG AcquireFlags,
PUNICODE_STRING FileName, BOOLEAN CheckingOldName);
PUNICODE_STRING FileName, BOOLEAN CheckingOldName,
BOOLEAN PosixRename);
VOID FspFileNodeRename(FSP_FILE_NODE *FileNode, PUNICODE_STRING NewFileName);
VOID FspFileNodeGetFileInfo(FSP_FILE_NODE *FileNode, FSP_FSCTL_FILE_INFO *FileInfo);
BOOLEAN FspFileNodeTryGetFileInfo(FSP_FILE_NODE *FileNode, FSP_FSCTL_FILE_INFO *FileInfo);

View File

@ -42,7 +42,7 @@ NTSTATUS FspFileNodeOpen(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject,
FSP_FILE_NODE **POpenedFileNode, PULONG PSharingViolationReason);
VOID FspFileNodeCleanup(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, PULONG PCleanupFlags);
VOID FspFileNodeCleanupFlush(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject);
VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject);
VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, BOOLEAN Delete);
VOID FspFileNodeClose(FSP_FILE_NODE *FileNode,
PFILE_OBJECT FileObject, /* non-0 to remove share access */
BOOLEAN HandleCleanup); /* TRUE to decrement handle count */
@ -57,7 +57,8 @@ NTSTATUS FspFileNodeCheckBatchOplocksOnAllStreams(
PUNICODE_STRING StreamFileName);
NTSTATUS FspFileNodeRenameCheck(PDEVICE_OBJECT FsvolDeviceObject, PIRP OplockIrp,
FSP_FILE_NODE *FileNode, ULONG AcquireFlags,
PUNICODE_STRING FileName, BOOLEAN CheckingOldName);
PUNICODE_STRING FileName, BOOLEAN CheckingOldName,
BOOLEAN PosixRename);
VOID FspFileNodeRename(FSP_FILE_NODE *FileNode, PUNICODE_STRING NewFileName);
VOID FspFileNodeGetFileInfo(FSP_FILE_NODE *FileNode, FSP_FSCTL_FILE_INFO *FileInfo);
BOOLEAN FspFileNodeTryGetFileInfo(FSP_FILE_NODE *FileNode, FSP_FSCTL_FILE_INFO *FileInfo);
@ -789,7 +790,7 @@ VOID FspFileNodeCleanup(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, PULONG
PDEVICE_OBJECT FsvolDeviceObject = FileNode->FsvolDeviceObject;
FSP_FILE_DESC *FileDesc = FileObject->FsContext2;
BOOLEAN DeletePending, SetAllocationSize, SingleHandle;
BOOLEAN DeletePending, Delete, SetAllocationSize, SingleHandle;
FspFsvolDeviceLockContextTable(FsvolDeviceObject);
@ -804,7 +805,19 @@ VOID FspFileNodeCleanup(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, PULONG
FspFsvolDeviceUnlockContextTable(FsvolDeviceObject);
*PCleanupFlags = SingleHandle ? DeletePending | (SetAllocationSize << 1) : 0;
Delete = FALSE;
if (!FileNode->PosixDelete)
{
if (FileDesc->PosixDelete)
{
FileNode->PosixDelete = TRUE;
Delete = TRUE;
}
else if (SingleHandle)
Delete = DeletePending;
}
*PCleanupFlags = SingleHandle ? Delete | (SetAllocationSize << 1) : Delete;
}
VOID FspFileNodeCleanupFlush(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject)
@ -860,7 +873,7 @@ VOID FspFileNodeCleanupFlush(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject)
}
}
VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject)
VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, BOOLEAN Delete)
{
/*
* Complete the cleanup of a FileNode. Remove its share access and
@ -901,6 +914,52 @@ VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject
IoRemoveShareAccess(FileObject, &FileNode->ShareAccess);
if (Delete)
{
FspFsvolDeviceDeleteContextByName(FsvolDeviceObject, &FileNode->FileName,
&DeletedFromContextTable);
ASSERT(DeletedFromContextTable);
FileNode->OpenCount = 0;
/*
* We now have to deal with the scenario where there are cleaned up,
* but unclosed streams for this file still in the context table.
*/
if (FsvolDeviceExtension->VolumeParams.NamedStreams &&
0 == FileNode->MainFileNode)
{
BOOLEAN StreamDeletedFromContextTable;
USHORT FileNameLength = FileNode->FileName.Length;
GATHER_DESCENDANTS(&FileNode->FileName, FALSE,
if (DescendantFileNode->FileName.Length > FileNameLength &&
L'\\' == DescendantFileNode->FileName.Buffer[FileNameLength / sizeof(WCHAR)])
break;
ASSERT(FileNode != DescendantFileNode);
ASSERT(0 != DescendantFileNode->OpenCount);
);
for (
DescendantFileNodeIndex = 0;
DescendantFileNodeCount > DescendantFileNodeIndex;
DescendantFileNodeIndex++)
{
DescendantFileNode = DescendantFileNodes[DescendantFileNodeIndex];
FspFsvolDeviceDeleteContextByName(FsvolDeviceObject, &DescendantFileNode->FileName,
&StreamDeletedFromContextTable);
if (StreamDeletedFromContextTable)
{
DescendantFileNode->OpenCount = 0;
FspFileNodeDereference(DescendantFileNode);
}
}
SCATTER_DESCENDANTS(FALSE);
}
}
ASSERT(0 < FileNode->HandleCount);
if (0 == --FileNode->HandleCount)
{
@ -910,53 +969,8 @@ VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject
MemoryBarrier();
if (DeletePending)
{
FspFsvolDeviceDeleteContextByName(FsvolDeviceObject, &FileNode->FileName,
&DeletedFromContextTable);
ASSERT(DeletedFromContextTable);
FileNode->OpenCount = 0;
FileNode->Header.FileSize.QuadPart = 0;
/*
* We now have to deal with the scenario where there are cleaned up,
* but unclosed streams for this file still in the context table.
*/
if (FsvolDeviceExtension->VolumeParams.NamedStreams &&
0 == FileNode->MainFileNode)
{
BOOLEAN StreamDeletedFromContextTable;
USHORT FileNameLength = FileNode->FileName.Length;
GATHER_DESCENDANTS(&FileNode->FileName, FALSE,
if (DescendantFileNode->FileName.Length > FileNameLength &&
L'\\' == DescendantFileNode->FileName.Buffer[FileNameLength / sizeof(WCHAR)])
break;
ASSERT(FileNode != DescendantFileNode);
ASSERT(0 != DescendantFileNode->OpenCount);
ASSERT(0 == DescendantFileNode->HandleCount);
);
for (
DescendantFileNodeIndex = 0;
DescendantFileNodeCount > DescendantFileNodeIndex;
DescendantFileNodeIndex++)
{
DescendantFileNode = DescendantFileNodes[DescendantFileNodeIndex];
FspFsvolDeviceDeleteContextByName(FsvolDeviceObject, &DescendantFileNode->FileName,
&StreamDeletedFromContextTable);
if (StreamDeletedFromContextTable)
{
DescendantFileNode->OpenCount = 0;
FspFileNodeDereference(DescendantFileNode);
}
}
SCATTER_DESCENDANTS(FALSE);
}
}
if (DeletePending || FileNode->TruncateOnClose)
{
UINT64 AllocationUnit =
@ -1293,7 +1307,8 @@ NTSTATUS FspFileNodeCheckBatchOplocksOnAllStreams(
NTSTATUS FspFileNodeRenameCheck(PDEVICE_OBJECT FsvolDeviceObject, PIRP OplockIrp,
FSP_FILE_NODE *FileNode, ULONG AcquireFlags,
PUNICODE_STRING FileName, BOOLEAN CheckingOldName)
PUNICODE_STRING FileName, BOOLEAN CheckingOldName,
BOOLEAN PosixRename)
{
PAGED_CODE();
@ -1506,11 +1521,32 @@ NTSTATUS FspFileNodeRenameCheck(PDEVICE_OBJECT FsvolDeviceObject, PIRP OplockIrp
if (DescendantFileNode != FileNode && 0 < DescendantFileNode->HandleCount)
{
/*
* If we are doing a POSIX rename, then it is ok if we have open handles,
* provided that we do not have sharing violations.
*
* Check our share access:
*
* - If all openers are allowing FILE_SHARE_DELETE.
* - And all named streams openers are allowing FILE_SHARE_DELETE.
*
* Then we are good to go.
*
* (WinFsp cannot rename streams and there is no need to check MainFileDenyDeleteCount).
*
* NOTE: These are derived rules. AFAIK there is no documentation on how NTFS
* does this in the case of POSIX rename.
*/
if (PosixRename &&
DescendantFileNode->ShareAccess.OpenCount == DescendantFileNode->ShareAccess.SharedDelete &&
0 == DescendantFileNode->StreamDenyDeleteCount)
continue;
/* release the FileNode and rename lock in case of failure! */
FspFileNodeReleaseF(FileNode, AcquireFlags);
FspFsvolDeviceFileRenameRelease(FsvolDeviceObject);
Result = STATUS_ACCESS_DENIED;
Result = PosixRename ? STATUS_SHARING_VIOLATION : STATUS_ACCESS_DENIED;
break;
}
}
@ -1599,14 +1635,14 @@ VOID FspFileNodeRename(FSP_FILE_NODE *FileNode, PUNICODE_STRING NewFileName)
if (!Inserted)
{
/*
* Handle files that have been Cleanup'ed but not Close'd.
* Handle files that have been replaced after a Rename.
* For example, this can happen when the user has mapped and closed a file
* or immediately after breaking a Batch oplock.
* or immediately after breaking a Batch oplock or
* when doing a POSIX rename.
*/
ASSERT(FspFileNodeIsValid(InsertedFileNode));
ASSERT(DescendantFileNode != InsertedFileNode);
ASSERT(0 == InsertedFileNode->HandleCount);
ASSERT(0 != InsertedFileNode->OpenCount);
InsertedFileNode->OpenCount = 0;

View File

@ -1441,7 +1441,8 @@ static NTSTATUS FspFsvolSetDispositionInformation(
NTSTATUS Result;
PFILE_OBJECT FileObject = IrpSp->FileObject;
PFILE_DISPOSITION_INFORMATION Info = (PFILE_DISPOSITION_INFORMATION)Irp->AssociatedIrp.SystemBuffer;
FILE_INFORMATION_CLASS FileInformationClass = IrpSp->Parameters.SetFile.FileInformationClass;
UINT32 DispositionFlags;
ULONG Length = IrpSp->Parameters.SetFile.Length;
FSP_FILE_NODE *FileNode = FileObject->FsContext;
FSP_FILE_DESC *FileDesc = FileObject->FsContext2;
@ -1449,9 +1450,31 @@ static NTSTATUS FspFsvolSetDispositionInformation(
BOOLEAN Success;
ASSERT(FileNode == FileDesc->FileNode);
ASSERT(
FileDispositionInformation == FileInformationClass ||
FileDispositionInformationEx == FileInformationClass);
if (FileDispositionInformation == FileInformationClass)
{
if (sizeof(FILE_DISPOSITION_INFORMATION) > Length)
return STATUS_INVALID_PARAMETER;
DispositionFlags = !!((PFILE_DISPOSITION_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->DeleteFile;
DispositionFlags |= FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK;
// old-school delete does image section check
}
else
{
if (!FspFsvolDeviceExtension(FsvolDeviceObject)->VolumeParams.SupportsPosixUnlinkRename)
return STATUS_INVALID_PARAMETER;
if (sizeof(FILE_DISPOSITION_INFORMATION_EX) > Length)
return STATUS_INVALID_PARAMETER;
DispositionFlags = ((PFILE_DISPOSITION_INFORMATION_EX)Irp->AssociatedIrp.SystemBuffer)->Flags;
/* WinFsp does not support the FILE_DISPOSITION_ON_CLOSE flag */
if (FlagOn(DispositionFlags, FILE_DISPOSITION_ON_CLOSE))
return STATUS_NOT_SUPPORTED;
}
if (sizeof(FILE_DISPOSITION_INFORMATION) > Length)
return STATUS_INVALID_PARAMETER;
if (FileNode->IsRootDirectory)
/* cannot delete root directory */
return STATUS_CANNOT_DELETE;
@ -1459,7 +1482,13 @@ static NTSTATUS FspFsvolSetDispositionInformation(
retry:
FspFileNodeAcquireExclusive(FileNode, Full);
if (Info->DeleteFile)
if (FileNode->PosixDelete)
{
Result = STATUS_FILE_DELETED;
goto unlock_exit;
}
if (FlagOn(DispositionFlags, FILE_DISPOSITION_DELETE))
{
/*
* Perform oplock check.
@ -1487,15 +1516,57 @@ retry:
if (!NT_SUCCESS(Result))
goto unlock_exit;
/* make sure no process is mapping the file as an image */
/*
* Make sure no process is mapping the file as an image.
*
* NOTE:
* Turns out that NTFS always does this test, even when
* FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK has been specified.
* If MmFlushImageSection fails (e.g. an actively running EXE) then
* it only allows the deletion to go through when a secondary hard
* link is being deleted.
*
* Since WinFsp does not support hard links, we will go ahead and
* ignore the FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK flag and
* always respect the result of MmFlushImageSection.
*/
Success = MmFlushImageSection(FileObject->SectionObjectPointer, MmFlushForDelete);
if (!Success)
{
Result = STATUS_CANNOT_DELETE;
goto unlock_exit;
}
/*
* The documentation states:
* A return value of STATUS_CANNOT_DELETE indicates that either the file is read-only,
* or there's an existing mapped view to the file. Specifying FILE_DISPOSITION_IGNORE_-
* READONLY_ATTRIBUTE avoids this return value due to the file being read-only, provided
* the caller has FILE_WRITE_ATTRIBUTES access to the file (the access that would be
* required to clear the read-only attribute).
*
* This appears to be incorrect with NTFS on Win10 and Win11. See:
* https://github.com/MicrosoftDocs/windows-driver-docs-ddi/issues/1216
*/
#if 0
if (FlagOn(DispositionFlags, FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE))
{
/* if FileDesc does not have FILE_WRITE_ATTRIBUTE access, remove IGNORE_READONLY_ATTRIBUTE */
if (!FlagOn(FileDesc->GrantedAccess, FILE_WRITE_ATTRIBUTES))
DispositionFlags &= ~FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE;
}
#endif
}
if (FlagOn(DispositionFlags, FILE_DISPOSITION_DELETE))
DispositionFlags &=
FILE_DISPOSITION_DO_NOT_DELETE |
FILE_DISPOSITION_DELETE |
FILE_DISPOSITION_POSIX_SEMANTICS |
FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE;
else
DispositionFlags = FILE_DISPOSITION_DO_NOT_DELETE;
Result = FspIopCreateRequestEx(Irp, &FileNode->FileName, 0,
FspFsvolSetInformationRequestFini, &Request);
if (!NT_SUCCESS(Result))
@ -1504,8 +1575,8 @@ retry:
Request->Kind = FspFsctlTransactSetInformationKind;
Request->Req.SetInformation.UserContext = FileNode->UserContext;
Request->Req.SetInformation.UserContext2 = FileDesc->UserContext2;
Request->Req.SetInformation.FileInformationClass = FileDispositionInformation;
Request->Req.SetInformation.Info.Disposition.Delete = Info->DeleteFile;
Request->Req.SetInformation.FileInformationClass = FileInformationClass;
Request->Req.SetInformation.Info.DispositionEx.Flags = DispositionFlags;
FspFileNodeSetOwner(FileNode, Full, Request);
FspIopRequestContext(Request, RequestFileNode) = FileNode;
@ -1525,16 +1596,23 @@ static NTSTATUS FspFsvolSetDispositionInformationSuccess(
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
PFILE_OBJECT FileObject = IrpSp->FileObject;
PFILE_DISPOSITION_INFORMATION Info = (PFILE_DISPOSITION_INFORMATION)Irp->AssociatedIrp.SystemBuffer;
FSP_FILE_NODE *FileNode = FileObject->FsContext;
FSP_FILE_DESC *FileDesc = FileObject->FsContext2;
FSP_FSCTL_TRANSACT_REQ *Request = FspIrpRequest(Irp);
UINT32 DispositionFlags = Request->Req.SetInformation.Info.DispositionEx.Flags;
BOOLEAN Delete = BooleanFlagOn(DispositionFlags, FILE_DISPOSITION_DELETE);
FileNode->DeletePending = Info->DeleteFile;
FileObject->DeletePending = Info->DeleteFile;
FileNode->DeletePending = Delete;
FileObject->DeletePending = Delete;
if (!Delete)
FileDesc->PosixDelete = FALSE;
else if (FlagOn(DispositionFlags, FILE_DISPOSITION_POSIX_SEMANTICS))
FileDesc->PosixDelete = TRUE;
/* fastfat does this, although it seems unnecessary */
#if 1
if (FileNode->IsDirectory && Info->DeleteFile)
if (FileNode->IsDirectory && Delete)
{
FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension =
FspFsvolDeviceExtension(IrpSp->DeviceObject);
@ -1560,7 +1638,9 @@ static NTSTATUS FspFsvolSetRenameInformation(
FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = FspFsvolDeviceExtension(FsvolDeviceObject);
PFILE_OBJECT FileObject = IrpSp->FileObject;
PFILE_OBJECT TargetFileObject = IrpSp->Parameters.SetFile.FileObject;
FILE_INFORMATION_CLASS FileInformationClass = IrpSp->Parameters.SetFile.FileInformationClass;
BOOLEAN ReplaceIfExists = IrpSp->Parameters.SetFile.ReplaceIfExists;
UINT32 RenameFlags = !!ReplaceIfExists;
PFILE_RENAME_INFORMATION Info = (PFILE_RENAME_INFORMATION)Irp->AssociatedIrp.SystemBuffer;
ULONG Length = IrpSp->Parameters.SetFile.Length;
FSP_FILE_NODE *FileNode = FileObject->FsContext;
@ -1575,11 +1655,21 @@ static NTSTATUS FspFsvolSetRenameInformation(
PSECURITY_SUBJECT_CONTEXT SecuritySubjectContext = 0;
ASSERT(FileNode == FileDesc->FileNode);
ASSERT(
FileRenameInformation == FileInformationClass ||
FileRenameInformationEx == FileInformationClass);
if (sizeof(FILE_RENAME_INFORMATION) > Length)
return STATUS_INVALID_PARAMETER;
if (sizeof(WCHAR) > Info->FileNameLength)
return STATUS_INVALID_PARAMETER;
if (FileRenameInformationEx == FileInformationClass)
{
if (!FspFsvolDeviceExtension(FsvolDeviceObject)->VolumeParams.SupportsPosixUnlinkRename)
return STATUS_INVALID_PARAMETER;
RenameFlags |= Info->Flags &
(FILE_RENAME_POSIX_SEMANTICS | FILE_RENAME_IGNORE_READONLY_ATTRIBUTE);
}
if (FileNode->IsRootDirectory)
/* cannot rename root directory */
return STATUS_INVALID_PARAMETER;
@ -1601,6 +1691,12 @@ retry:
FspFsvolDeviceFileRenameAcquireExclusive(FsvolDeviceObject);
FspFileNodeAcquireExclusive(FileNode, Full);
if (FileNode->PosixDelete)
{
Result = STATUS_ACCESS_DENIED;
goto unlock_exit;
}
if (0 == Request)
{
if (0 != TargetFileNode)
@ -1657,13 +1753,14 @@ retry:
Request->Kind = FspFsctlTransactSetInformationKind;
Request->Req.SetInformation.UserContext = FileNode->UserContext;
Request->Req.SetInformation.UserContext2 = FileDesc->UserContext2;
Request->Req.SetInformation.FileInformationClass = FileRenameInformation;
Request->Req.SetInformation.FileInformationClass = FileInformationClass;
Request->Req.SetInformation.Info.Rename.NewFileName.Offset = Request->FileName.Size;
Request->Req.SetInformation.Info.Rename.NewFileName.Size = NewFileName.Length + sizeof(WCHAR);
Request->Req.SetInformation.Info.RenameEx.Flags = RenameFlags;
}
/*
* Special rules for renaming open files:
* Special rules for renaming open files without POSIX semantics:
* - A file cannot be renamed if it has any open handles,
* unless it is only open because of a batch opportunistic lock (oplock)
* and the batch oplock can be broken immediately.
@ -1675,13 +1772,15 @@ retry:
Result = FspFileNodeRenameCheck(FsvolDeviceObject, Irp,
FileNode, FspFileNodeAcquireFull,
&FileNode->FileName, TRUE);
&FileNode->FileName, TRUE,
0 != (FILE_RENAME_POSIX_SEMANTICS & RenameFlags));
/* FspFileNodeRenameCheck releases FileNode and rename lock on failure */
if (STATUS_OPLOCK_BREAK_IN_PROGRESS == Result)
goto retry;
if (!NT_SUCCESS(Result))
{
Result = STATUS_ACCESS_DENIED;
if (STATUS_SHARING_VIOLATION != Result)
Result = STATUS_ACCESS_DENIED;
goto exit;
}
@ -1689,13 +1788,15 @@ retry:
{
Result = FspFileNodeRenameCheck(FsvolDeviceObject, Irp,
FileNode, FspFileNodeAcquireFull,
&NewFileName, FALSE);
&NewFileName, FALSE,
0 != (FILE_RENAME_POSIX_SEMANTICS & RenameFlags));
/* FspFileNodeRenameCheck releases FileNode and rename lock on failure */
if (STATUS_OPLOCK_BREAK_IN_PROGRESS == Result)
goto retry;
if (!NT_SUCCESS(Result))
{
Result = STATUS_ACCESS_DENIED;
if (STATUS_SHARING_VIOLATION != Result)
Result = STATUS_ACCESS_DENIED;
goto exit;
}
}
@ -1796,10 +1897,15 @@ static NTSTATUS FspFsvolSetInformation(
FILE_INFORMATION_CLASS FileInformationClass = IrpSp->Parameters.SetFile.FileInformationClass;
/* special case FileDispositionInformation/FileRenameInformation */
if (FileDispositionInformation == FileInformationClass)
switch (FileInformationClass)
{
case FileDispositionInformation:
case FileDispositionInformationEx:
return FspFsvolSetDispositionInformation(FsvolDeviceObject, Irp, IrpSp);
if (FileRenameInformation == FileInformationClass)
case FileRenameInformation:
case FileRenameInformationEx:
return FspFsvolSetRenameInformation(FsvolDeviceObject, Irp, IrpSp);
}
NTSTATUS Result;
PFILE_OBJECT FileObject = IrpSp->FileObject;
@ -1924,8 +2030,10 @@ NTSTATUS FspFsvolSetInformationPrepare(
PAGED_CODE();
PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
FILE_INFORMATION_CLASS FileInformationClass = IrpSp->Parameters.SetFile.FileInformationClass;
if (FileRenameInformation != IrpSp->Parameters.SetFile.FileInformationClass ||
if ((FileRenameInformation != FileInformationClass &&
FileRenameInformationEx != FileInformationClass) ||
0 == FspIopRequestContext(Request, RequestSubjectContextOrAccessToken))
return STATUS_SUCCESS;
@ -1997,10 +2105,15 @@ NTSTATUS FspFsvolSetInformationComplete(
FILE_INFORMATION_CLASS FileInformationClass = IrpSp->Parameters.SetFile.FileInformationClass;
/* special case FileDispositionInformation/FileRenameInformation */
if (FileDispositionInformation == FileInformationClass)
switch (FileInformationClass)
{
case FileDispositionInformation:
case FileDispositionInformationEx:
FSP_RETURN(Result = FspFsvolSetDispositionInformationSuccess(Irp, Response));
if (FileRenameInformation == FileInformationClass)
case FileRenameInformation:
case FileRenameInformationEx:
FSP_RETURN(Result = FspFsvolSetRenameInformationSuccess(Irp, Response));
}
PFILE_OBJECT FileObject = IrpSp->FileObject;
FSP_FILE_NODE *FileNode = FileObject->FsContext;

View File

@ -101,7 +101,8 @@ static NTSTATUS FspFsvolQueryFsAttributeInformation(
(FsvolDeviceExtension->VolumeParams.NamedStreams ? FILE_NAMED_STREAMS : 0) |
//(FsvolDeviceExtension->VolumeParams.HardLinks ? FILE_SUPPORTS_HARD_LINKS : 0) |
(FsvolDeviceExtension->VolumeParams.ExtendedAttributes ? FILE_SUPPORTS_EXTENDED_ATTRIBUTES : 0) |
(FsvolDeviceExtension->VolumeParams.ReadOnlyVolume ? FILE_READ_ONLY_VOLUME : 0);
(FsvolDeviceExtension->VolumeParams.ReadOnlyVolume ? FILE_READ_ONLY_VOLUME : 0) |
(FsvolDeviceExtension->VolumeParams.SupportsPosixUnlinkRename ? FILE_SUPPORTS_POSIX_UNLINK_RENAME : 0);
Info->MaximumComponentNameLength = FsvolDeviceExtension->VolumeParams.MaxComponentLength;
RtlInitUnicodeString(&FileSystemName, FsvolDeviceExtension->VolumeParams.FileSystemName);

View File

@ -76,7 +76,7 @@ if X%SignedPackage%==X (
echo driver-x86.inf >>driver.ddf
echo %MyProductFileName%-x86.sys >>driver.ddf
makecab /F driver.ddf
signtool sign /ac %CrossCert% /i %Issuer% /n %Subject% /t http://timestamp.digicert.com driver.cab
signtool sign /ac %CrossCert% /i %Issuer% /n %Subject% /fd sha256 /tr http://timestamp.digicert.com /td sha256 driver.cab
if errorlevel 1 set /a signfail=signfail+1
popd
)

View File

@ -32,6 +32,7 @@ set dfl_tests=^
winfsp-tests-x64 ^
winfsp-tests-x64-case-randomize ^
winfsp-tests-x64-flushpurge ^
winfsp-tests-x64-legacy-unlink-rename ^
winfsp-tests-x64-mountpoint-drive ^
winfsp-tests-x64-mountpoint-dir ^
winfsp-tests-x64-mountpoint-dir-case-sensitive ^
@ -42,7 +43,6 @@ set dfl_tests=^
winfsp-tests-x64-external-share ^
fsx-memfs-x64-disk ^
fsx-memfs-x64-net ^
fsx-memfs-x64-slowio ^
standby-memfs-x64-disk ^
standby-memfs-x64-net ^
net-use-memfs-x64 ^
@ -52,6 +52,7 @@ set dfl_tests=^
winfsp-tests-x86 ^
winfsp-tests-x86-case-randomize ^
winfsp-tests-x86-flushpurge ^
winfsp-tests-x86-legacy-unlink-rename ^
winfsp-tests-x86-mountpoint-drive ^
winfsp-tests-x86-mountpoint-dir ^
winfsp-tests-x86-mountpoint-dir-case-sensitive ^
@ -62,7 +63,6 @@ set dfl_tests=^
winfsp-tests-x86-external-share ^
fsx-memfs-x86-disk ^
fsx-memfs-x86-net ^
fsx-memfs-x86-slowio ^
standby-memfs-x86-disk ^
standby-memfs-x86-net ^
net-use-memfs-x86 ^
@ -73,13 +73,16 @@ set dfl_tests=^
winfsp-tests-dotnet-external-share ^
fsx-memfs-dotnet-disk ^
fsx-memfs-dotnet-net ^
fsx-memfs-dotnet-slowio ^
winfstest-memfs-dotnet-disk ^
winfstest-memfs-dotnet-net
set opt_tests=^
ifstest-memfs-x64-disk ^
ifstest-memfs-x86-disk ^
ifstest-memfs-dotnet-disk ^
sample-memfs-fuse-x64 ^
sample-fsx-memfs-fuse-x64 ^
sample-memfs-fuse-x86 ^
sample-fsx-memfs-fuse-x86 ^
sample-memfs-fuse3-x64 ^
sample-fsx-memfs-fuse3-x64 ^
sample-memfs-fuse3-x86 ^
@ -97,6 +100,9 @@ set opt_tests=^
sample-passthrough-fuse3-x86 ^
sample-fsx-passthrough-fuse3-x86 ^
sample-passthrough-dotnet ^
slowio-fsx-memfs-x64 ^
slowio-fsx-memfs-x86 ^
slowio-fsx-memfs-dotnet ^
compat-v1.2-memfs-x64 ^
compat-v1.2-memfs-x86 ^
compat-v1.1-passthrough-fuse-x64 ^
@ -133,24 +139,32 @@ for %%f in (%tests%) do (
)
pushd %cd%
set "begtime=!time: =0!"
call :%%f
set ERRORLEVEL_save=!ERRORLEVEL!
set "endtime=!time: =0!"
popd
if !ERRORLEVEL! neq 0 (
REM see https://stackoverflow.com/a/9935540
set "endtimecalc=!endtime:%time:~8,1%=%%100)*100+1!" & set "begtimecalc=!begtime:%time:~8,1%=%%100)*100+1!"
set /A "duration=((((10!endtimecalc:%time:~2,1%=%%100)*60+1!%%100)-((((10!begtimecalc:%time:~2,1%=%%100)*60+1!%%100), duration-=(duration>>31)*24*60*60*100"
set /A "duration=10*duration"
if !ERRORLEVEL_save! neq 0 (
set /a testfail=testfail+1
echo === Failed %%f
echo === Failed %%f ^(!duration! ms^)
if defined APPVEYOR (
appveyor UpdateTest "%%f" -FileName None -Framework None -Outcome Failed -Duration 0
appveyor UpdateTest "%%f" -FileName None -Framework None -Outcome Failed -Duration !duration!
)
) else (
set /a testpass=testpass+1
echo === Passed %%f
echo === Passed %%f ^(!duration! ms^)
if defined APPVEYOR (
appveyor UpdateTest "%%f" -FileName None -Framework None -Outcome Passed -Duration 0
appveyor UpdateTest "%%f" -FileName None -Framework None -Outcome Passed -Duration !duration!
)
)
echo:
@ -191,6 +205,11 @@ winfsp-tests-x64 --flush-and-purge-on-cleanup * +ea*
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:winfsp-tests-x64-legacy-unlink-rename
winfsp-tests-x64 --legacy-unlink-rename * +ea*
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:winfsp-tests-x64-mountpoint-drive
winfsp-tests-x64 --mountpoint=X: --resilient * +ea*
if !ERRORLEVEL! neq 0 goto fail
@ -236,6 +255,11 @@ winfsp-tests-x86 --flush-and-purge-on-cleanup * +ea*
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:winfsp-tests-x86-legacy-unlink-rename
winfsp-tests-x86 --legacy-unlink-rename * +ea*
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:winfsp-tests-x86-mountpoint-drive
winfsp-tests-x86 --mountpoint=X: --resilient * +ea*
if !ERRORLEVEL! neq 0 goto fail
@ -505,17 +529,17 @@ if !ERRORLEVEL! neq 0 goto fail
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:fsx-memfs-x64-slowio
:slowio-fsx-memfs-x64
call :__run_fsx_memfs_slowio_test memfs64-slowio memfs-x64
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:fsx-memfs-x86-slowio
:slowio-fsx-memfs-x86
call :__run_fsx_memfs_slowio_test memfs32-slowio memfs-x86
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:fsx-memfs-dotnet-slowio
:slowio-fsx-memfs-dotnet
call :__run_fsx_memfs_slowio_test memfs.net-slowio memfs-dotnet-msil
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
@ -691,15 +715,35 @@ for /F "delims=" %%l in ('call "%ProjRoot%\tools\ifstest.bat" %* /v ^| findstr /
if not X!IfsTestFound!==XYES set IfsTestExit=1
exit /b !IfsTestExit!
:sample-memfs-fuse-x64
call :__run_sample_fuse_test memfs-fuse x64 memfs-fuse-x64 winfsp-tests-x64 "+*"
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:sample-memfs-fuse-x86
call :__run_sample_fuse_test memfs-fuse x86 memfs-fuse-x86 winfsp-tests-x86 "+*"
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:sample-fsx-memfs-fuse-x64
call :__run_sample_fsx_fuse_test memfs-fuse x64 memfs-fuse-x64 fsx
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:sample-fsx-memfs-fuse-x86
call :__run_sample_fsx_fuse_test memfs-fuse x86 memfs-fuse-x86 fsx
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:sample-memfs-fuse3-x64
call :__run_sample_fuse_test memfs-fuse3 x64 memfs-fuse3-x64 winfsp-tests-x64 ^
"-create_fileattr_test -create_readonlydir_test -setfileinfo_test"
"+* -create_fileattr_test -create_readonlydir_test -setfileinfo_test -delete_access_test"
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:sample-memfs-fuse3-x86
call :__run_sample_fuse_test memfs-fuse3 x86 memfs-fuse3-x86 winfsp-tests-x86 ^
"-create_fileattr_test -create_readonlydir_test -setfileinfo_test"
"+* -create_fileattr_test -create_readonlydir_test -setfileinfo_test -delete_access_test"
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
@ -750,12 +794,12 @@ if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:sample-passthrough-fuse-x64
call :__run_sample_fuse_test passthrough-fuse x64 passthrough-fuse-x64 winfsp-tests-x64
call :__run_sample_fuse_oldtest passthrough-fuse x64 passthrough-fuse-x64 winfsp-tests-x64
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:sample-passthrough-fuse-x86
call :__run_sample_fuse_test passthrough-fuse x86 passthrough-fuse-x86 winfsp-tests-x86
call :__run_sample_fuse_oldtest passthrough-fuse x86 passthrough-fuse-x86 winfsp-tests-x86
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
@ -770,13 +814,13 @@ if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:sample-passthrough-fuse3-x64
call :__run_sample_fuse_test passthrough-fuse3 x64 passthrough-fuse3-x64 winfsp-tests-x64 ^
call :__run_sample_fuse_oldtest passthrough-fuse3 x64 passthrough-fuse3-x64 winfsp-tests-x64 ^
"-create_fileattr_test -create_readonlydir_test -setfileinfo_test"
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
:sample-passthrough-fuse3-x86
call :__run_sample_fuse_test passthrough-fuse3 x86 passthrough-fuse3-x86 winfsp-tests-x86 ^
call :__run_sample_fuse_oldtest passthrough-fuse3 x86 passthrough-fuse3-x86 winfsp-tests-x86 ^
"-create_fileattr_test -create_readonlydir_test -setfileinfo_test"
if !ERRORLEVEL! neq 0 goto fail
exit /b 0
@ -857,6 +901,38 @@ set RunSampleTestExit=0
call %ProjRoot%\tools\build-sample %Configuration% %2 %1 "%TMP%\%1"
if !ERRORLEVEL! neq 0 goto fail
mkdir "%TMP%\%1\test"
call "%ProjRoot%\tools\fsreg" %1 "%TMP%\%1\build\%Configuration%\%3.exe" ^
"-orellinks,uid=11,gid=65792 --VolumePrefix=%%%%1 %%%%2" "D:P(A;;RPWPLC;;;WD)"
echo net use L: "\\%1\%TMP::=$%\%1\test"
net use L: "\\%1\%TMP::=$%\%1\test"
if !ERRORLEVEL! neq 0 goto fail
echo net use ^| findstr L:
net use | findstr L:
pushd >nul
cd L: >nul 2>nul || (echo Unable to find drive L: >&2 & goto fail)
L:
REM Exclude reparse_symlink* tests as they do not pass on AppVeyor VS2015. They do pass locally.
if defined APPVEYOR (
"%ProjRoot%\build\VStudio\build\%Configuration%\%4.exe" ^
--fuse-external --resilient --case-insensitive-cmp --share-prefix="\%1\%TMP::=$%\%1\test" %~5 ^
-reparse_symlink*
) else (
"%ProjRoot%\build\VStudio\build\%Configuration%\%4.exe" ^
--fuse-external --resilient --case-insensitive-cmp --share-prefix="\%1\%TMP::=$%\%1\test" %~5
)
if !ERRORLEVEL! neq 0 set RunSampleTestExit=1
popd
echo net use L: /delete
net use L: /delete
call "%ProjRoot%\tools\fsreg" -u %1
rmdir /s/q "%TMP%\%1"
exit /b !RunSampleTestExit!
:__run_sample_fuse_oldtest
set RunSampleTestExit=0
call %ProjRoot%\tools\build-sample %Configuration% %2 %1 "%TMP%\%1"
if !ERRORLEVEL! neq 0 goto fail
mkdir "%TMP%\%1\test"
call "%ProjRoot%\tools\fsreg" %1 "%TMP%\%1\build\%Configuration%\%3.exe" ^
"-ouid=11,gid=65792 --VolumePrefix=%%%%1 %%%%2" "D:P(A;;RPWPLC;;;WD)"
echo net use L: "\\%1\%TMP::=$%\%1\test"

View File

@ -286,6 +286,7 @@ namespace memfs
Host.ExtendedAttributes = true;
Host.WslFeatures = true;
Host.RejectIrpPriorToTransact0 = true;
Host.SupportsPosixUnlinkRename = true;
return STATUS_SUCCESS;
}

10
tst/memfs-fuse/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
build
*.ncb
*.suo
*.vcproj.*
*.vcxproj.user
*.VC.db
*.VC.opendb
.vs
*.exe
*.install

18
tst/memfs-fuse/Makefile Normal file
View File

@ -0,0 +1,18 @@
usage:
@echo "make cygfuse|winfsp-fuse" 1>&2
@echo "" 1>&2
@echo " cygfuse Link with CYGFUSE" 1>&2
@echo " winfsp-fuse Link with WinFsp-FUSE" 1>&2
@exit 2
cygfuse: memfs-cygfuse
winfsp-fuse: memfs-winfsp-fuse
memfs-cygfuse: memfs-fuse.cpp
g++ $^ -o $@ -g -Wall -std=gnu++17 `pkg-config fuse --cflags --libs`
memfs-winfsp-fuse: export PKG_CONFIG_PATH=$(PWD)/winfsp.install/lib
memfs-winfsp-fuse: memfs-fuse.cpp
ln -nsf "`regtool --wow32 get '/HKLM/Software/WinFsp/InstallDir' | cygpath -au -f -`" winfsp.install
g++ $^ -o $@ -g -Wall -std=gnu++17 `pkg-config fuse --cflags --libs`

7
tst/memfs-fuse/README.md Normal file
View File

@ -0,0 +1,7 @@
`Memfs-fuse` is an in-memory FUSE file system.
It can be built with the following tools:
- Using Visual Studio (`memfs-fuse.sln`).
- Using Cygwin GCC and linking directly with the WinFsp DLL (`make winfsp-fuse`).
- Using Cygwin GCC and linking to CYGFUSE (`make cygfuse`).

104
tst/memfs-fuse/compat.h Normal file
View File

@ -0,0 +1,104 @@
/**
* @file compat.h
*
* @copyright 2015-2021 Bill Zissimopoulos
*/
/*
* This file is part of WinFsp.
*
* You can redistribute it and/or modify it under the terms of the GNU
* General Public License version 3 as published by the Free Software
* Foundation.
*
* Licensees holding a valid commercial license may use this software
* in accordance with the commercial license agreement provided in
* conjunction with the software. The terms and conditions of any such
* commercial license agreement shall govern, supersede, and render
* ineffective any application of the GPLv3 license to this software,
* notwithstanding of any reference thereto in the software or
* associated repository.
*/
#ifndef COMPAT_H_INCLUDED
#define COMPAT_H_INCLUDED
#if defined(_WIN32) && defined(FSP_FUSE_SYM)
#include <winfsp/winfsp.h>
#undef fuse_main
#define fuse_main(argc, argv, ops, data)\
(FspLoad(0), fuse_main_real(argc, argv, ops, sizeof *(ops), data))
#endif
#if !defined(_WIN32) && !defined(fuse_stat)
#define fuse_uid_t uid_t
#define fuse_gid_t gid_t
#define fuse_pid_t pid_t
#define fuse_dev_t dev_t
#define fuse_mode_t mode_t
#define fuse_nlink_t nlink_t
#define fuse_off_t off_t
#define fuse_fsblkcnt_t fsblkcnt_t
#define fuse_fsfilcnt_t fsfilcnt_t
#define fuse_blksize_t blksize_t
#define fuse_blkcnt_t blkcnt_t
#define fuse_timespec timespec
#define fuse_stat stat
#define fuse_statvfs statvfs
#define fuse_flock flock
#define fuse_iovec iovec
#endif
#if !defined(S_IFMT)
#define S_IFMT 0170000
#endif
#if !defined(S_IFDIR)
#define S_IFDIR 0040000
#endif
#if !defined(S_IFCHR)
#define S_IFCHR 0020000
#endif
#if !defined(S_IFBLK)
#define S_IFBLK 0060000
#endif
#if !defined(S_IFREG)
#define S_IFREG 0100000
#endif
#if !defined(S_IFLNK)
#define S_IFLNK 0120000
#endif
#if !defined(S_IFSOCK)
#define S_IFSOCK 0140000
#endif
#if !defined(S_IFIFO)
#define S_IFIFO 0010000
#endif
#if defined(__APPLE__)
#define st_atim st_atimespec
#define st_ctim st_ctimespec
#define st_mtim st_mtimespec
#endif
#if defined(__APPLE__) || defined(__linux__) || defined(__CYGWIN__)
#include <sys/xattr.h>
#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(_WIN32)
#define XATTR_CREATE 1
#define XATTR_REPLACE 2
#endif
#if !defined(ENOATTR)
#define ENOATTR ENODATA
#elif !defined(ENODATA)
#define ENODATA ENOATTR
#endif
#endif

View File

@ -0,0 +1,693 @@
/**
* @file memfs-fuse.c
*
* @copyright 2015-2021 Bill Zissimopoulos
*/
/*
* This file is part of WinFsp.
*
* You can redistribute it and/or modify it under the terms of the GNU
* General Public License version 3 as published by the Free Software
* Foundation.
*
* Licensees holding a valid commercial license may use this software
* in accordance with the commercial license agreement provided in
* conjunction with the software. The terms and conditions of any such
* commercial license agreement shall govern, supersede, and render
* ineffective any application of the GPLv3 license to this software,
* notwithstanding of any reference thereto in the software or
* associated repository.
*/
#include <cerrno>
#include <chrono>
#include <cstring>
#include <algorithm>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include <fuse.h>
#include "compat.h"
class memfs
{
public:
memfs() : _ino(1), _root(std::make_shared<node_t>(_ino, S_IFDIR | 00777, 0, 0))
{
}
int main(int argc, char *argv[])
{
static fuse_operations ops =
{
getattr,
0, // getdir
readlink,
mknod,
mkdir,
unlink,
rmdir,
symlink,
rename,
link,
chmod,
chown,
truncate,
0, // utime
open,
read,
write,
statfs,
flush,
release,
0, // fsync
setxattr,
getxattr,
listxattr,
removexattr,
opendir,
readdir,
releasedir,
0, // fsyncdir
init,
0, // destroy
0, // access
0, // create
ftruncate,
fgetattr,
0, // lock
utimens,
0, // bmap
0, // flag_nullpath_ok
0, // flag_nopath
0, // flag_utime_omit_ok
0, // flag_reserved
0, // ioctl
0, // poll
0, // write_buf
0, // read_buf
0, // flock
0, // fallocate
0, // reserved00
0, // reserved01
0, // reserved02
0, // statfs_x
0, // setvolname
0, // exchange
0, // getxtimes
0, // setbkuptime
0, // setchgtime
setcrtime,
#if defined(FSP_FUSE_USE_STAT_EX)
chflags,
#else
0, // chflags
#endif
0, // setattr_x
0, // fsetattr_x
};
return fuse_main(argc, argv, &ops, this);
}
private:
struct node_t
{
node_t(fuse_ino_t ino, fuse_mode_t mode, fuse_uid_t uid, fuse_gid_t gid, fuse_dev_t dev = 0)
: stat()
{
stat.st_ino = ino;
stat.st_mode = mode;
stat.st_nlink = 1;
stat.st_uid = uid;
stat.st_gid = gid;
stat.st_rdev = dev;
stat.st_atim = stat.st_mtim = stat.st_ctim = stat.st_birthtim = now();
}
void resize(size_t size, bool capacity)
{
if (capacity)
{
const size_t unit = 64 * 1024;
size_t newcap = (size + unit - 1) / unit * unit;
size_t oldcap = data.capacity();
if (newcap > oldcap)
data.reserve(newcap);
else if (newcap < oldcap)
{
data.resize(newcap);
data.shrink_to_fit();
}
}
data.resize(size);
stat.st_size = size;
}
struct fuse_stat stat;
std::vector<uint8_t> data;
std::unordered_map<std::string, std::shared_ptr<node_t>> childmap;
std::unordered_map<std::string, std::vector<uint8_t>> xattrmap;
};
static fuse_timespec now()
{
using namespace std::chrono;
auto now = system_clock::now();
auto sec = floor<seconds>(now);
auto nsec = floor<nanoseconds>(now) - floor<nanoseconds>(sec);
return fuse_timespec
{
static_cast<decltype(fuse_timespec::tv_sec)>(sec.time_since_epoch().count()),
/* std::chrono epoch is UNIX epoch in C++20 */
static_cast<decltype(fuse_timespec::tv_nsec)>(nsec.count()),
};
}
static memfs *getself()
{
return static_cast<memfs *>(fuse_get_context()->private_data);
}
static int getattr(const char *path, struct fuse_stat *stbuf)
{
return fgetattr(path, stbuf, nullptr);
}
static int fgetattr(const char *path, struct fuse_stat *stbuf, struct fuse_file_info *fi)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto node = self->get_node(path, fi);
if (!node)
return -ENOENT;
*stbuf = node->stat;
return 0;
}
static int readlink(const char *path, char *buf, size_t size)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto node = self->get_node(path);
if (!node)
return -ENOENT;
if (S_IFLNK != (node->stat.st_mode & S_IFMT))
return EINVAL;
size = (std::min)(size - 1, node->data.size());
std::memcpy(buf, node->data.data(), size);
buf[size] = '\0';
return 0;
}
static int mknod(const char *path, fuse_mode_t mode, fuse_dev_t dev)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
return self->make_node(path, mode, dev);
}
static int mkdir(const char *path, fuse_mode_t mode)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
return self->make_node(path, S_IFDIR | (mode & 07777), 0);
}
static int unlink(const char *path)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
return self->remove_node(path, false);
}
static int rmdir(const char *path)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
return self->remove_node(path, true);
}
static int symlink(const char *dstpath, const char *srcpath)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
return self->make_node(srcpath, S_IFLNK | 00777, 0, dstpath);
}
static int rename(const char *oldpath, const char *newpath)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto oldlookup = self->lookup_node(oldpath);
auto oldprnt = std::get<0>(oldlookup);
auto oldname = std::get<1>(oldlookup);
auto oldnode = std::get<2>(oldlookup);
if (!oldnode)
return -ENOENT;
auto newlookup = self->lookup_node(newpath);
auto newprnt = std::get<0>(newlookup);
auto newname = std::get<1>(newlookup);
auto newnode = std::get<2>(newlookup);
if (!newprnt)
return -ENOENT;
if (newname.empty())
// guard against directory loop creation
return -EINVAL;
if (oldprnt == newprnt && oldname == newname)
return 0;
if (newnode)
{
if (int errc = self->remove_node(newpath, S_IFDIR == (oldnode->stat.st_mode & S_IFMT)))
return errc;
}
oldprnt->childmap.erase(oldname);
newprnt->childmap[newname] = oldnode;
return 0;
}
static int link(const char *oldpath, const char *newpath)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto oldlookup = self->lookup_node(oldpath);
auto oldnode = std::get<2>(oldlookup);
if (!oldnode)
return -ENOENT;
auto newlookup = self->lookup_node(newpath);
auto newprnt = std::get<0>(newlookup);
auto newname = std::get<1>(newlookup);
auto newnode = std::get<2>(newlookup);
if (!newprnt)
return -ENOENT;
if (newnode)
return -EEXIST;
oldnode->stat.st_nlink++;
newprnt->childmap[newname] = oldnode;
oldnode->stat.st_ctim = newprnt->stat.st_ctim = newprnt->stat.st_mtim = now();
return 0;
}
static int chmod(const char *path, fuse_mode_t mode)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto node = self->get_node(path);
if (!node)
return -ENOENT;
node->stat.st_mode = (node->stat.st_mode & S_IFMT) | (mode & 07777);
node->stat.st_ctim = now();
return 0;
}
static int chown(const char *path, fuse_uid_t uid, fuse_gid_t gid)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto node = self->get_node(path);
if (!node)
return -ENOENT;
if (-1 != uid)
node->stat.st_uid = uid;
if (-1 != gid)
node->stat.st_gid = gid;
node->stat.st_ctim = now();
return 0;
}
static int truncate(const char *path, fuse_off_t size)
{
return ftruncate(path, size, nullptr);
}
static int ftruncate(const char *path, fuse_off_t size,
struct fuse_file_info *fi)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto node = self->get_node(path, fi);
if (!node)
return -ENOENT;
if (SIZE_MAX < size)
return -EFBIG;
node->resize(static_cast<size_t>(size), true);
node->stat.st_ctim = node->stat.st_mtim = now();
#if defined(FSP_FUSE_USE_STAT_EX)
node->stat.st_flags |= UF_ARCHIVE;
#endif
return 0;
}
static int open(const char *path, struct fuse_file_info *fi)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
return self->open_node(path, false, fi);
}
static int read(const char *path, char *buf, size_t size, fuse_off_t off,
struct fuse_file_info *fi)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto node = self->get_node(path, fi);
if (!node)
return -ENOENT;
fuse_off_t endoff = (std::min)(
off + static_cast<fuse_off_t>(size), static_cast<fuse_off_t>(node->data.size()));
if (off > endoff)
return 0;
std::memcpy(buf, node->data.data() + off, static_cast<int>(endoff - off));
node->stat.st_atim = now();
return static_cast<int>(endoff - off);
}
static int write(const char *path, const char *buf, size_t size, fuse_off_t off,
struct fuse_file_info *fi)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto node = self->get_node(path, fi);
if (!node)
return -ENOENT;
fuse_off_t endoff = off + static_cast<fuse_off_t>(size);
if (SIZE_MAX < endoff)
return -EFBIG;
if (node->data.size() < endoff)
node->resize(static_cast<size_t>(endoff), true);
std::memcpy(node->data.data() + off, buf, static_cast<int>(endoff - off));
node->stat.st_ctim = node->stat.st_mtim = now();
#if defined(FSP_FUSE_USE_STAT_EX)
node->stat.st_flags |= UF_ARCHIVE;
#endif
return static_cast<int>(endoff - off);
}
static int statfs(const char *path, struct fuse_statvfs *stbuf)
{
std::memset(stbuf, 0, sizeof *stbuf);
return 0;
}
static int flush(const char *path, struct fuse_file_info *fi)
{
return -ENOSYS;
}
static int release(const char *path, struct fuse_file_info *fi)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
return self->close_node(fi);
}
static int setxattr(const char *path, const char *name0, const char *value, size_t size,
int flags)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto node = self->get_node(path);
if (!node)
return -ENOENT;
if (0 == std::strcmp("com.apple.ResourceFork", name0))
return -ENOTSUP;
std::string name = name0;
if (XATTR_CREATE == flags)
{
if (node->xattrmap.end() != node->xattrmap.find(name))
return -EEXIST;
}
else if (XATTR_REPLACE == flags)
{
if (node->xattrmap.end() == node->xattrmap.find(name))
return -ENOATTR;
}
node->xattrmap[name].assign(value, value + size);
return 0;
}
static int getxattr(const char *path, const char *name0, char *value, size_t size)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto node = self->get_node(path);
if (!node)
return -ENOENT;
if (0 == std::strcmp("com.apple.ResourceFork", name0))
return -ENOTSUP;
std::string name = name0;
auto iter = node->xattrmap.find(name);
if (node->xattrmap.end() == iter)
return -ENOATTR;
if (0 != size)
{
if (iter->second.size() > size)
return -ERANGE;
std::memcpy(value, iter->second.data(), iter->second.size());
}
return static_cast<int>(iter->second.size());
}
static int listxattr(const char *path, char *namebuf, size_t size)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto node = self->get_node(path);
if (!node)
return -ENOENT;
size_t copysize = 0;
for (auto elem : node->xattrmap)
{
size_t namesize = elem.first.size() + 1;
if (0 != size)
{
if (copysize + namesize > size)
return -ERANGE;
std::memcpy(namebuf + copysize, elem.first.c_str(), namesize);
copysize += namesize;
}
}
return static_cast<int>(copysize);
}
static int removexattr(const char *path, const char *name0)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto node = self->get_node(path);
if (!node)
return -ENOENT;
if (0 == std::strcmp("com.apple.ResourceFork", name0))
return -ENOTSUP;
std::string name = name0;
return node->xattrmap.erase(name) ? 0 : -ENOATTR;
}
static int opendir(const char *path, struct fuse_file_info *fi)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
return self->open_node(path, true, fi);
}
static int readdir(const char *path, void *buf, fuse_fill_dir_t filler, fuse_off_t off,
struct fuse_file_info *fi)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto node = self->get_node(path, fi);
if (!node)
return -ENOENT;
filler(buf, ".", &node->stat, 0);
filler(buf, "..", nullptr, 0);
for (auto elem : node->childmap)
if (0 != filler(buf, elem.first.c_str(), &elem.second->stat, 0))
break;
return 0;
}
static int releasedir(const char *path, struct fuse_file_info *fi)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
return self->close_node(fi);
}
static void *init(struct fuse_conn_info *conn)
{
#if defined(FSP_FUSE_CAP_READDIR_PLUS)
conn->want |= (conn->capable & FSP_FUSE_CAP_READDIR_PLUS);
#endif
#if defined(FSP_FUSE_USE_STAT_EX) && defined(FSP_FUSE_CAP_STAT_EX)
conn->want |= (conn->capable & FSP_FUSE_CAP_STAT_EX);
#endif
return getself();
}
static int utimens(const char *path, const struct fuse_timespec tmsp[2])
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto node = self->get_node(path);
if (!node)
return -ENOENT;
if (tmsp)
{
node->stat.st_ctim = now();
node->stat.st_atim = tmsp[0];
node->stat.st_mtim = tmsp[1];
}
else
node->stat.st_ctim = node->stat.st_atim = node->stat.st_mtim = now();
return 0;
}
static int setcrtime(const char *path, const struct fuse_timespec *tmsp)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto node = self->get_node(path);
if (!node)
return -ENOENT;
if (tmsp)
{
node->stat.st_ctim = now();
node->stat.st_birthtim = tmsp[0];
}
else
node->stat.st_ctim = node->stat.st_birthtim = now();
return 0;
}
#if defined(FSP_FUSE_USE_STAT_EX)
static int chflags(const char *path, uint32_t flags)
{
auto self = getself();
std::lock_guard<std::mutex> lock(self->_mutex);
auto node = self->get_node(path);
if (!node)
return -ENOENT;
node->stat.st_flags = flags;
node->stat.st_ctim = now();
return 0;
}
#endif
std::tuple<std::shared_ptr<node_t>, std::string, std::shared_ptr<node_t>>
lookup_node(const char *path, node_t *ancestor = nullptr)
{
auto prnt = _root;
std::string name;
auto node = prnt;
for (const char *part = path, *p; *part; part = p + !!(*p))
{
for (p = part; *p && '/' != *p; p++)
;
if (part == p)
continue;
prnt = node;
if (!node)
break;
name.assign(part, p);
auto iter = node->childmap.find(name);
node = node->childmap.end() != iter ? iter->second : nullptr;
if (ancestor && node.get() == ancestor)
{
name.assign(""); // special case loop condition
break;
}
}
return std::make_tuple(prnt, name, node);
}
int make_node(const char *path, fuse_mode_t mode, fuse_dev_t dev, const char *data = nullptr)
{
auto lookup = lookup_node(path);
auto prnt = std::get<0>(lookup);
auto name = std::get<1>(lookup);
auto node = std::get<2>(lookup);
if (!prnt)
return -ENOENT;
if (node)
return -EEXIST;
fuse_context *context = fuse_get_context();
node = std::make_shared<node_t>(++_ino, mode, context->uid, context->gid, dev);
#if defined(FSP_FUSE_USE_STAT_EX)
if (S_IFDIR != (mode & S_IFMT))
node->stat.st_flags |= UF_ARCHIVE;
#endif
if (data)
{
node->resize(std::strlen(data), false);
std::memcpy(node->data.data(), data, node->data.size());
}
prnt->childmap[name] = node;
prnt->stat.st_ctim = prnt->stat.st_mtim = node->stat.st_ctim;
return 0;
}
int remove_node(const char *path, bool dir)
{
auto lookup = lookup_node(path);
auto prnt = std::get<0>(lookup);
auto name = std::get<1>(lookup);
auto node = std::get<2>(lookup);
if (!node)
return -ENOENT;
if (!dir && S_IFDIR == (node->stat.st_mode & S_IFMT))
return -EISDIR;
if (dir && S_IFDIR != (node->stat.st_mode & S_IFMT))
return -ENOTDIR;
if (0 < node->childmap.size())
return -ENOTEMPTY;
node->stat.st_nlink--;
prnt->childmap.erase(name);
node->stat.st_ctim = prnt->stat.st_ctim = prnt->stat.st_mtim = now();
return 0;
}
int open_node(const char *path, bool dir, struct fuse_file_info *fi)
{
auto node = std::get<2>(lookup_node(path));
if (!node)
return -ENOENT;
if (!dir && S_IFDIR == (node->stat.st_mode & S_IFMT))
return -EISDIR;
if (dir && S_IFDIR != (node->stat.st_mode & S_IFMT))
return -ENOTDIR;
// A file descriptor is a raw pointer to a shared_ptr.
// This has the effect of incrementing the shared_ptr
// refcount, thus keeping an open node around even
// if the node is unlinked.
fi->fh = (uint64_t)(uintptr_t)new std::shared_ptr<node_t>(node);
return 0;
}
int close_node(struct fuse_file_info *fi)
{
delete (std::shared_ptr<node_t> *)(uintptr_t)fi->fh;
return 0;
}
std::shared_ptr<node_t> get_node(const char *path, struct fuse_file_info *fi = nullptr)
{
if (!fi)
return std::get<2>(lookup_node(path));
else
return *(std::shared_ptr<node_t> *)(uintptr_t)fi->fh;
}
private:
std::mutex _mutex;
fuse_ino_t _ino;
std::shared_ptr<node_t> _root;
};
int main(int argc, char *argv[])
{
return memfs().main(argc, argv);
}

View File

@ -0,0 +1,28 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "memfs-fuse", "memfs-fuse.vcxproj", "{CF538F42-C714-4653-B351-E72FD7B0B217}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CF538F42-C714-4653-B351-E72FD7B0B217}.Debug|x64.ActiveCfg = Debug|x64
{CF538F42-C714-4653-B351-E72FD7B0B217}.Debug|x64.Build.0 = Debug|x64
{CF538F42-C714-4653-B351-E72FD7B0B217}.Debug|x86.ActiveCfg = Debug|Win32
{CF538F42-C714-4653-B351-E72FD7B0B217}.Debug|x86.Build.0 = Debug|Win32
{CF538F42-C714-4653-B351-E72FD7B0B217}.Release|x64.ActiveCfg = Release|x64
{CF538F42-C714-4653-B351-E72FD7B0B217}.Release|x64.Build.0 = Release|x64
{CF538F42-C714-4653-B351-E72FD7B0B217}.Release|x86.ActiveCfg = Release|Win32
{CF538F42-C714-4653-B351-E72FD7B0B217}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,189 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{CF538F42-C714-4653-B351-E72FD7B0B217}</ProjectGuid>
<Keyword>Win32Proj</Keyword>
<RootNamespace>memfsfuse</RootNamespace>
<WindowsTargetPlatformVersion>$(LatestTargetPlatformVersion)</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)build\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\$(ProjectName).build\$(Configuration)\$(PlatformTarget)\</IntDir>
<TargetName>$(ProjectName)-$(PlatformTarget)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LinkIncremental>true</LinkIncremental>
<OutDir>$(SolutionDir)build\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\$(ProjectName).build\$(Configuration)\$(PlatformTarget)\</IntDir>
<TargetName>$(ProjectName)-$(PlatformTarget)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)build\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\$(ProjectName).build\$(Configuration)\$(PlatformTarget)\</IntDir>
<TargetName>$(ProjectName)-$(PlatformTarget)</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<LinkIncremental>false</LinkIncremental>
<OutDir>$(SolutionDir)build\$(Configuration)\</OutDir>
<IntDir>$(SolutionDir)build\$(ProjectName).build\$(Configuration)\$(PlatformTarget)\</IntDir>
<TargetName>$(ProjectName)-$(PlatformTarget)</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>FSP_FUSE_USE_STAT_EX;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(MSBuildProgramFiles32)\WinFsp\inc\fuse;$(MSBuildProgramFiles32)\WinFsp\inc</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<DisableSpecificWarnings>4018</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>$(MSBuildProgramFiles32)\WinFsp\lib\winfsp-$(PlatformTarget).lib;%(AdditionalDependencies)</AdditionalDependencies>
<DelayLoadDLLs>winfsp-$(PlatformTarget).dll</DelayLoadDLLs>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>FSP_FUSE_USE_STAT_EX;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(MSBuildProgramFiles32)\WinFsp\inc\fuse;$(MSBuildProgramFiles32)\WinFsp\inc</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<DisableSpecificWarnings>4018</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>$(MSBuildProgramFiles32)\WinFsp\lib\winfsp-$(PlatformTarget).lib;%(AdditionalDependencies)</AdditionalDependencies>
<DelayLoadDLLs>winfsp-$(PlatformTarget).dll</DelayLoadDLLs>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>FSP_FUSE_USE_STAT_EX;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(MSBuildProgramFiles32)\WinFsp\inc\fuse;$(MSBuildProgramFiles32)\WinFsp\inc</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<DisableSpecificWarnings>4018</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>$(MSBuildProgramFiles32)\WinFsp\lib\winfsp-$(PlatformTarget).lib;%(AdditionalDependencies)</AdditionalDependencies>
<DelayLoadDLLs>winfsp-$(PlatformTarget).dll</DelayLoadDLLs>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<PrecompiledHeader>
</PrecompiledHeader>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>FSP_FUSE_USE_STAT_EX;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<SDLCheck>true</SDLCheck>
<AdditionalIncludeDirectories>$(MSBuildProgramFiles32)\WinFsp\inc\fuse;$(MSBuildProgramFiles32)\WinFsp\inc</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<DisableSpecificWarnings>4018</DisableSpecificWarnings>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>$(MSBuildProgramFiles32)\WinFsp\lib\winfsp-$(PlatformTarget).lib;%(AdditionalDependencies)</AdditionalDependencies>
<DelayLoadDLLs>winfsp-$(PlatformTarget).dll</DelayLoadDLLs>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="compat.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="memfs-fuse.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="compat.h">
<Filter>Source</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="memfs-fuse.cpp">
<Filter>Source</Filter>
</ClCompile>
</ItemGroup>
</Project>

View File

@ -2293,12 +2293,11 @@ static FSP_FILE_SYSTEM_INTERFACE MemfsInterface =
#if defined(MEMFS_EA)
Overwrite,
GetEa,
SetEa
SetEa,
#else
0,
0,
0,
0,
#endif
};
@ -2323,6 +2322,7 @@ NTSTATUS MemfsCreateFunnel(
FSP_FSCTL_VOLUME_PARAMS VolumeParams;
BOOLEAN CaseInsensitive = !!(Flags & MemfsCaseInsensitive);
BOOLEAN FlushAndPurgeOnCleanup = !!(Flags & MemfsFlushAndPurgeOnCleanup);
BOOLEAN SupportsPosixUnlinkRename = !(Flags & MemfsLegacyUnlinkRename);
PWSTR DevicePath = MemfsNet == (Flags & MemfsDeviceMask) ?
L"" FSP_FSCTL_NET_DEVICE_NAME : L"" FSP_FSCTL_DISK_DEVICE_NAME;
UINT64 AllocationUnit;
@ -2404,6 +2404,7 @@ NTSTATUS MemfsCreateFunnel(
#if defined(MEMFS_REJECT_EARLY_IRP)
VolumeParams.RejectIrpPriorToTransact0 = 1;
#endif
VolumeParams.SupportsPosixUnlinkRename = SupportsPosixUnlinkRename;
if (0 != VolumePrefix)
wcscpy_s(VolumeParams.Prefix, sizeof VolumeParams.Prefix / sizeof(WCHAR), VolumePrefix);
wcscpy_s(VolumeParams.FileSystemName, sizeof VolumeParams.FileSystemName / sizeof(WCHAR),

View File

@ -37,9 +37,10 @@ enum
MemfsDeviceMask = 0x0000000f,
MemfsCaseInsensitive = 0x80000000,
MemfsFlushAndPurgeOnCleanup = 0x40000000,
MemfsLegacyUnlinkRename = 0x20000000,
};
#define MemfsCreate(Flags, FileInfoTimeout, MaxFileNodes, MaxFileSize, VolumePrefix, RootSddl, PMemfs)\
#define MemfsCreate(Flags, FileInfoTimeout, MaxFileNodes, MaxFileSize, VolumePrefix, RootSddl, PMemfs)\
MemfsCreateFunnel(\
Flags,\
FileInfoTimeout,\

View File

@ -1370,9 +1370,10 @@ void create_tests(void)
TEST(create_fileattr_test);
TEST(create_readonlydir_test);
TEST(create_related_test);
TEST(create_allocation_test);
if (!OptFuseExternal)
TEST(create_allocation_test);
TEST(create_sd_test);
if (!OptNoTraverseToken && !OptShareName)
if (!OptFuseExternal && !OptNoTraverseToken && !OptShareName)
TEST(create_notraverse_test);
TEST(create_backup_test);
TEST(create_restore_test);

View File

@ -170,7 +170,11 @@ static NTSTATUS ea_check_ea_enumerate(
if (0 == strcmp(SingleEa->EaName, "BNAMETWO"))
{
ASSERT(FILE_NEED_EA == SingleEa->Flags);
if (!OptFuseExternal)
{
/* FUSE has no concept of FILE_NEED_EA */
ASSERT(FILE_NEED_EA == SingleEa->Flags);
}
ASSERT(SingleEa->EaNameLength == (UCHAR)strlen("BNAMETWO"));
ASSERT(SingleEa->EaValueLength == (UCHAR)strlen("second"));
ASSERT(0 == memcmp(SingleEa->EaName + SingleEa->EaNameLength + 1, "second", SingleEa->EaValueLength));
@ -720,12 +724,17 @@ static void ea_create_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout)
ASSERT(STATUS_SUCCESS == Result);
CloseHandle(FileHandle);
Result = NtCreateFile(&FileHandle,
FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE, &Obja, &Iosb,
&LargeZero, FILE_ATTRIBUTE_NORMAL, 0,
FILE_OPEN, FILE_NO_EA_KNOWLEDGE,
0, 0);
ASSERT(STATUS_ACCESS_DENIED == Result);
if (!OptFuseExternal)
{
/* FUSE has no concept of FILE_NEED_EA */
Result = NtCreateFile(&FileHandle,
FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE, &Obja, &Iosb,
&LargeZero, FILE_ATTRIBUTE_NORMAL, 0,
FILE_OPEN, FILE_NO_EA_KNOWLEDGE,
0, 0);
ASSERT(STATUS_ACCESS_DENIED == Result);
}
Result = NtCreateFile(&FileHandle,
FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE, &Obja, &Iosb,

View File

@ -200,6 +200,27 @@ static void exec_delete_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout)
ASSERT(!DeleteFileW(FilePath));
ASSERT(ERROR_ACCESS_DENIED == GetLastError());
{
MY_FILE_DISPOSITION_INFO_EX DispositionInfo;
HANDLE Handle;
BOOLEAN Success;
Handle = CreateFileW(FilePath,
DELETE, FILE_SHARE_DELETE, 0,
OPEN_EXISTING, 0, 0);
if (INVALID_HANDLE_VALUE != Handle)
{
DispositionInfo.Flags = 3/*FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS*/;
Success = SetFileInformationByHandle(Handle,
21/*FileDispositionInfoEx*/, &DispositionInfo, sizeof DispositionInfo);
ASSERT(!Success);
ASSERT(
ERROR_INVALID_PARAMETER == GetLastError() ||
ERROR_ACCESS_DENIED == GetLastError());
Success = CloseHandle(Handle);
ASSERT(Success);
}
}
WaitHelper(Process, 1000);
ASSERT(DeleteFileW(FilePath));

View File

@ -894,6 +894,242 @@ void delete_standby_test(void)
}
}
static void delete_ex_dotest(ULONG Flags, PWSTR VolPrefix, PWSTR Prefix, ULONG FileInfoTimeout)
{
BOOLEAN Success;
DWORD FileSystemFlags;
Success = GetVolumeInformationW(L"C:\\",
0, 0,
0, 0, &FileSystemFlags,
0, 0);
if (!Success || 0 == (FileSystemFlags & 0x400/*FILE_SUPPORTS_POSIX_UNLINK_RENAME*/))
/* skip this test if the system lacks FILE_SUPPORTS_POSIX_UNLINK_RENAME capability */
return;
void *memfs = memfs_start_ex(Flags, FileInfoTimeout);
NTSYSCALLAPI NTSTATUS NTAPI
NtSetInformationFile(
HANDLE FileHandle,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass);
typedef struct
{
ULONG Flags;
} FILE_DISPOSITION_INFORMATION_EX, *PFILE_DISPOSITION_INFORMATION_EX;
HANDLE Handle0, Handle1, Handle2, FindHandle;
WCHAR FilePath[MAX_PATH];
WIN32_FIND_DATAW FindData;
FILE_DISPOSITION_INFORMATION_EX DispositionInfo;
IO_STATUS_BLOCK IoStatus;
StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\",
VolPrefix ? L"" : L"\\\\?\\GLOBALROOT", VolPrefix ? VolPrefix : memfs_volumename(memfs));
Success = GetVolumeInformationW(FilePath,
0, 0,
0, 0, &FileSystemFlags,
0, 0);
ASSERT(Success);
if (0 != (FileSystemFlags & 0x400/*FILE_SUPPORTS_POSIX_UNLINK_RENAME*/))
{
StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\file",
Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs));
/* POSIX Semantics / Ignore Readonly */
Handle0 = CreateFileW(FilePath,
GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0,
CREATE_NEW, FILE_ATTRIBUTE_READONLY, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle0);
Handle1 = CreateFileW(FilePath,
DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0,
OPEN_EXISTING, 0, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle1);
memset(&DispositionInfo, 0, sizeof DispositionInfo);
DispositionInfo.Flags = 3; /* DELETE | POSIX_SEMANTICS */
IoStatus.Status = NtSetInformationFile(
Handle1, &IoStatus,
&DispositionInfo, sizeof DispositionInfo,
64/*FileDispositionInformationEx*/);
ASSERT(STATUS_CANNOT_DELETE == IoStatus.Status);
memset(&DispositionInfo, 0, sizeof DispositionInfo);
DispositionInfo.Flags = 0x13; /* DELETE | POSIX_SEMANTICS | IGNORE_READONLY_ATTRIBUTE */
IoStatus.Status = NtSetInformationFile(
Handle1, &IoStatus,
&DispositionInfo, sizeof DispositionInfo,
64/*FileDispositionInformationEx*/);
ASSERT(0 == IoStatus.Status);
FindHandle = FindFirstFileW(FilePath, &FindData);
ASSERT(INVALID_HANDLE_VALUE != FindHandle);
ASSERT(0 == mywcscmp(FindData.cFileName, 4, L"file", 4));
FindClose(FindHandle);
Handle2 = CreateFileW(FilePath,
0, 0, 0,
OPEN_EXISTING, 0, 0);
ASSERT(INVALID_HANDLE_VALUE == Handle2);
ASSERT(ERROR_ACCESS_DENIED == GetLastError());
CloseHandle(Handle1);
FindHandle = FindFirstFileW(FilePath, &FindData);
ASSERT(INVALID_HANDLE_VALUE == FindHandle);
ASSERT(ERROR_FILE_NOT_FOUND == GetLastError());
Handle2 = CreateFileW(FilePath,
0, 0, 0,
OPEN_EXISTING, 0, 0);
ASSERT(INVALID_HANDLE_VALUE == Handle2);
ASSERT(ERROR_FILE_NOT_FOUND == GetLastError());
memset(&DispositionInfo, 0, sizeof DispositionInfo);
DispositionInfo.Flags = 0; /* DO_NOT_DELETE */
IoStatus.Status = NtSetInformationFile(
Handle0, &IoStatus,
&DispositionInfo, sizeof DispositionInfo,
64/*FileDispositionInformationEx*/);
ASSERT(STATUS_FILE_DELETED == IoStatus.Status);
memset(&DispositionInfo, 0, sizeof DispositionInfo);
DispositionInfo.Flags = 1; /* DELETE */
IoStatus.Status = NtSetInformationFile(
Handle0, &IoStatus,
&DispositionInfo, sizeof DispositionInfo,
64/*FileDispositionInformationEx*/);
ASSERT(STATUS_FILE_DELETED == IoStatus.Status);
CloseHandle(Handle0);
/* POSIX Semantics / Set/Reset */
Handle0 = CreateFileW(FilePath,
GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle0);
Handle1 = CreateFileW(FilePath,
DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0,
OPEN_EXISTING, 0, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle1);
memset(&DispositionInfo, 0, sizeof DispositionInfo);
DispositionInfo.Flags = 3; /* DELETE | POSIX_SEMANTICS */
IoStatus.Status = NtSetInformationFile(
Handle1, &IoStatus,
&DispositionInfo, sizeof DispositionInfo,
64/*FileDispositionInformationEx*/);
ASSERT(STATUS_SUCCESS == IoStatus.Status);
memset(&DispositionInfo, 0, sizeof DispositionInfo);
DispositionInfo.Flags = 0; /* DO_NOT_DELETE */
IoStatus.Status = NtSetInformationFile(
Handle1, &IoStatus,
&DispositionInfo, sizeof DispositionInfo,
64/*FileDispositionInformationEx*/);
ASSERT(STATUS_SUCCESS == IoStatus.Status);
CloseHandle(Handle1);
FindHandle = FindFirstFileW(FilePath, &FindData);
ASSERT(INVALID_HANDLE_VALUE != FindHandle);
ASSERT(0 == mywcscmp(FindData.cFileName, 4, L"file", 4));
FindClose(FindHandle);
Handle1 = CreateFileW(FilePath,
DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0,
OPEN_EXISTING, 0, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle1);
memset(&DispositionInfo, 0, sizeof DispositionInfo);
DispositionInfo.Flags = 3; /* DELETE | POSIX_SEMANTICS */
IoStatus.Status = NtSetInformationFile(
Handle1, &IoStatus,
&DispositionInfo, sizeof DispositionInfo,
64/*FileDispositionInformationEx*/);
ASSERT(STATUS_SUCCESS == IoStatus.Status);
CloseHandle(Handle1);
CloseHandle(Handle0);
#if 0
/* On Close */
Handle0 = CreateFileW(FilePath,
GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle0);
memset(&DispositionInfo, 0, sizeof DispositionInfo);
DispositionInfo.Flags = 8; /* DO_NOT_DELETE | ON_CLOSE */
IoStatus.Status = NtSetInformationFile(
Handle0, &IoStatus,
&DispositionInfo, sizeof DispositionInfo,
64/*FileDispositionInformationEx*/);
ASSERT(0 == IoStatus.Status);
CloseHandle(Handle0);
Handle0 = CreateFileW(FilePath,
DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0,
OPEN_EXISTING, 0, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle0);
memset(&DispositionInfo, 0, sizeof DispositionInfo);
DispositionInfo.Flags = 9; /* DELETE | ON_CLOSE */;
IoStatus.Status = NtSetInformationFile(
Handle0, &IoStatus,
&DispositionInfo, sizeof DispositionInfo,
64/*FileDispositionInformationEx*/);
ASSERT(STATUS_NOT_SUPPORTED == IoStatus.Status);
memset(&DispositionInfo, 0, sizeof DispositionInfo);
DispositionInfo.Flags = 3; /* DELETE | POSIX_SEMANTICS */;
IoStatus.Status = NtSetInformationFile(
Handle0, &IoStatus,
&DispositionInfo, sizeof DispositionInfo,
64/*FileDispositionInformationEx*/);
ASSERT(0 == IoStatus.Status);
CloseHandle(Handle0);
#endif
}
memfs_stop(memfs);
}
void delete_ex_test(void)
{
if (OptLegacyUnlinkRename)
return;
if (NtfsTests)
{
WCHAR DirBuf[MAX_PATH], DriveBuf[3];
GetTestDirectoryAndDrive(DirBuf, DriveBuf);
delete_ex_dotest(-1, DriveBuf, DirBuf, 0);
}
if (WinFspDiskTests)
{
delete_ex_dotest(MemfsDisk, 0, 0, 0);
delete_ex_dotest(MemfsDisk, 0, 0, 1000);
}
if (WinFspNetTests)
{
delete_ex_dotest(MemfsNet, L"\\\\memfs\\share", L"\\\\memfs\\share", 0);
delete_ex_dotest(MemfsNet, L"\\\\memfs\\share", L"\\\\memfs\\share", 1000);
}
}
static void rename_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout)
{
void *memfs = memfs_start_ex(Flags, FileInfoTimeout);
@ -1784,6 +2020,230 @@ void rename_pid_dotest(ULONG Flags, PWSTR Prefix)
ASSERT(0 < rename_pid_Pass);// && 0 == rename_pid_Fail);
}
static void rename_ex_dotest(ULONG Flags, PWSTR VolPrefix, PWSTR Prefix, ULONG FileInfoTimeout)
{
BOOLEAN Success;
DWORD FileSystemFlags;
Success = GetVolumeInformationW(L"C:\\",
0, 0,
0, 0, &FileSystemFlags,
0, 0);
if (!Success || 0 == (FileSystemFlags & 0x400/*FILE_SUPPORTS_POSIX_UNLINK_RENAME*/))
/* skip this test if the system lacks FILE_SUPPORTS_POSIX_UNLINK_RENAME capability */
return;
void *memfs = memfs_start_ex(Flags, FileInfoTimeout);
NTSYSCALLAPI NTSTATUS NTAPI
NtSetInformationFile(
HANDLE FileHandle,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass);
typedef struct
{
ULONG Flags;
HANDLE RootDirectory;
ULONG FileNameLength;
WCHAR FileName[1];
} FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION;
typedef struct
{
ULONG Flags;
} FILE_DISPOSITION_INFORMATION_EX, *PFILE_DISPOSITION_INFORMATION_EX;
HANDLE Handle0, Handle1, Handle2;
WCHAR File0Path[MAX_PATH];
WCHAR File2Path[MAX_PATH];
union
{
FILE_RENAME_INFORMATION I;
UINT8 B[sizeof(FILE_RENAME_INFORMATION) + MAX_PATH * sizeof(WCHAR)];
} RenameInfo;
FILE_DISPOSITION_INFORMATION_EX DispositionInfo;
IO_STATUS_BLOCK IoStatus;
StringCbPrintfW(File0Path, sizeof File0Path, L"%s%s\\",
VolPrefix ? L"" : L"\\\\?\\GLOBALROOT", VolPrefix ? VolPrefix : memfs_volumename(memfs));
Success = GetVolumeInformationW(File0Path,
0, 0,
0, 0, &FileSystemFlags,
0, 0);
ASSERT(Success);
if (0 != (FileSystemFlags & 0x400/*FILE_SUPPORTS_POSIX_UNLINK_RENAME*/))
{
StringCbPrintfW(File0Path, sizeof File0Path, L"%s%s\\file0",
Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs));
StringCbPrintfW(File2Path, sizeof File2Path, L"%s%s\\file2",
Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs));
/* File Test */
Handle0 = CreateFileW(File0Path,
GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle0);
CloseHandle(Handle0);
Handle0 = CreateFileW(File0Path,
DELETE, 0, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle0);
Handle1 = CreateFileW(File0Path,
FILE_READ_ATTRIBUTES, 0, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle1);
memset(&RenameInfo.I, 0, sizeof RenameInfo.I);
RenameInfo.I.Flags = 2/*FILE_RENAME_POSIX_SEMANTICS*/;
RenameInfo.I.FileNameLength = (ULONG)(wcslen(L"file2") * sizeof(WCHAR));
memcpy(RenameInfo.I.FileName, L"file2", RenameInfo.I.FileNameLength);
IoStatus.Status = NtSetInformationFile(
Handle0, &IoStatus,
&RenameInfo.I, FIELD_OFFSET(FILE_RENAME_INFORMATION, FileName) + RenameInfo.I.FileNameLength,
65/*FileRenameInformationEx*/);
ASSERT(0 == IoStatus.Status);
Handle2 = CreateFileW(File2Path,
FILE_READ_ATTRIBUTES, 0, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle2);
CloseHandle(Handle2);
CloseHandle(Handle1);
CloseHandle(Handle0);
Handle0 = CreateFileW(File0Path,
GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle0);
CloseHandle(Handle0);
Handle1 = CreateFileW(File0Path,
GENERIC_READ | GENERIC_WRITE, 0, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle1);
Handle2 = CreateFileW(File2Path,
DELETE, 0, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle0);
memset(&RenameInfo.I, 0, sizeof RenameInfo.I);
RenameInfo.I.Flags = 1/*FILE_RENAME_REPLACE_IF_EXISTS*/;
RenameInfo.I.FileNameLength = (ULONG)(wcslen(L"file0") * sizeof(WCHAR));
memcpy(RenameInfo.I.FileName, L"file0", RenameInfo.I.FileNameLength);
IoStatus.Status = NtSetInformationFile(
Handle2, &IoStatus,
&RenameInfo.I, FIELD_OFFSET(FILE_RENAME_INFORMATION, FileName) + RenameInfo.I.FileNameLength,
10/*FileRenameInformation*/);
ASSERT(STATUS_ACCESS_DENIED == IoStatus.Status);
memset(&RenameInfo.I, 0, sizeof RenameInfo.I);
RenameInfo.I.Flags = 3/*FILE_RENAME_REPLACE_IF_EXISTS|FILE_RENAME_POSIX_SEMANTICS*/;
RenameInfo.I.FileNameLength = (ULONG)(wcslen(L"file0") * sizeof(WCHAR));
memcpy(RenameInfo.I.FileName, L"file0", RenameInfo.I.FileNameLength);
IoStatus.Status = NtSetInformationFile(
Handle2, &IoStatus,
&RenameInfo.I, FIELD_OFFSET(FILE_RENAME_INFORMATION, FileName) + RenameInfo.I.FileNameLength,
65/*FileRenameInformationEx*/);
ASSERT(STATUS_SHARING_VIOLATION == IoStatus.Status);
CloseHandle(Handle2);
CloseHandle(Handle1);
Handle1 = CreateFileW(File0Path,
FILE_READ_ATTRIBUTES, 0, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle1);
Handle2 = CreateFileW(File2Path,
DELETE, 0, 0,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle0);
memset(&RenameInfo.I, 0, sizeof RenameInfo.I);
RenameInfo.I.Flags = 3/*FILE_RENAME_REPLACE_IF_EXISTS|FILE_RENAME_POSIX_SEMANTICS*/;
RenameInfo.I.FileNameLength = (ULONG)(wcslen(L"file0") * sizeof(WCHAR));
memcpy(RenameInfo.I.FileName, L"file0", RenameInfo.I.FileNameLength);
IoStatus.Status = NtSetInformationFile(
Handle2, &IoStatus,
&RenameInfo.I, FIELD_OFFSET(FILE_RENAME_INFORMATION, FileName) + RenameInfo.I.FileNameLength,
65/*FileRenameInformationEx*/);
ASSERT(0 == IoStatus.Status);
CloseHandle(Handle2);
CloseHandle(Handle1);
Success = DeleteFileW(File0Path);
ASSERT(Success);
/* Deleted File Test */
Handle0 = CreateFileW(File0Path,
GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0,
CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle0);
Handle1 = CreateFileW(File0Path,
DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0,
OPEN_EXISTING, 0, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle1);
memset(&DispositionInfo, 0, sizeof DispositionInfo);
DispositionInfo.Flags = 3; /* DELETE | POSIX_SEMANTICS */
IoStatus.Status = NtSetInformationFile(
Handle1, &IoStatus,
&DispositionInfo, sizeof DispositionInfo,
64/*FileDispositionInformationEx*/);
ASSERT(0 == IoStatus.Status);
CloseHandle(Handle1);
memset(&RenameInfo.I, 0, sizeof RenameInfo.I);
RenameInfo.I.Flags = 2/*FILE_RENAME_POSIX_SEMANTICS*/;
RenameInfo.I.FileNameLength = (ULONG)(wcslen(L"file2") * sizeof(WCHAR));
memcpy(RenameInfo.I.FileName, L"file2", RenameInfo.I.FileNameLength);
IoStatus.Status = NtSetInformationFile(
Handle0, &IoStatus,
&RenameInfo.I, FIELD_OFFSET(FILE_RENAME_INFORMATION, FileName) + RenameInfo.I.FileNameLength,
65/*FileRenameInformationEx*/);
ASSERT(STATUS_ACCESS_DENIED == IoStatus.Status);
CloseHandle(Handle0);
}
memfs_stop(memfs);
}
void rename_ex_test(void)
{
if (OptLegacyUnlinkRename)
return;
if (NtfsTests)
{
WCHAR DirBuf[MAX_PATH], DriveBuf[3];
GetTestDirectoryAndDrive(DirBuf, DriveBuf);
rename_ex_dotest(-1, DriveBuf, DirBuf, 0);
}
if (WinFspDiskTests)
{
rename_ex_dotest(MemfsDisk, 0, 0, 0);
rename_ex_dotest(MemfsDisk, 0, 0, 1000);
}
if (WinFspNetTests)
{
rename_ex_dotest(MemfsNet, L"\\\\memfs\\share", L"\\\\memfs\\share", 0);
rename_ex_dotest(MemfsNet, L"\\\\memfs\\share", L"\\\\memfs\\share", 1000);
}
}
void rename_pid_test(void)
{
if (NtfsTests)
@ -2040,10 +2500,11 @@ void query_winfsp_test(void)
void info_tests(void)
{
if (!OptShareName)
if (!OptFuseExternal && !OptShareName)
TEST(getfileattr_test);
TEST(getfileinfo_test);
TEST(getfileinfo_name_test);
if (!OptFuseExternal)
TEST(getfileinfo_name_test);
TEST(setfileinfo_test);
TEST(delete_test);
TEST(delete_access_test);
@ -2051,6 +2512,8 @@ void info_tests(void)
if (!OptShareName)
TEST(delete_mmap_test);
TEST(delete_standby_test);
if (!OptLegacyUnlinkRename)
TEST(delete_ex_test);
TEST(rename_test);
TEST(rename_backslash_test);
TEST(rename_open_test);
@ -2060,6 +2523,8 @@ void info_tests(void)
if (!OptShareName)
TEST(rename_mmap_test);
TEST(rename_standby_test);
if (!OptLegacyUnlinkRename)
TEST(rename_ex_test);
if (!NtfsTests)
TEST(rename_pid_test);
TEST(getvolinfo_test);

View File

@ -43,7 +43,8 @@ void *memfs_start_ex(ULONG Flags, ULONG FileInfoTimeout)
Result = MemfsCreateFunnel(
Flags |
(OptCaseInsensitive ? MemfsCaseInsensitive : 0) |
(OptFlushAndPurgeOnCleanup ? MemfsFlushAndPurgeOnCleanup : 0),
(OptFlushAndPurgeOnCleanup ? MemfsFlushAndPurgeOnCleanup : 0) |
(OptLegacyUnlinkRename ? MemfsLegacyUnlinkRename : 0),
FileInfoTimeout,
1024,
1024 * 1024,

View File

@ -192,20 +192,25 @@ static void reparse_nfs_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout)
0, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, 0);
ASSERT(INVALID_HANDLE_VALUE != Handle);
ReparseDataBuf.D.ReparseDataLength = 0;
if (!OptFuseExternal)
{
/* FUSE cannot delete reparse points */
Success = DeviceIoControl(Handle, FSCTL_DELETE_REPARSE_POINT,
&ReparseDataBuf, REPARSE_DATA_BUFFER_HEADER_SIZE + ReparseDataBuf.D.ReparseDataLength,
0, 0,
&Bytes, 0);
ASSERT(Success);
ReparseDataBuf.D.ReparseDataLength = 0;
Success = DeviceIoControl(Handle, FSCTL_GET_REPARSE_POINT,
0, 0,
&ReparseDataBuf, sizeof ReparseDataBuf,
&Bytes, 0);
ASSERT(!Success);
ASSERT(ERROR_NOT_A_REPARSE_POINT == GetLastError());
Success = DeviceIoControl(Handle, FSCTL_DELETE_REPARSE_POINT,
&ReparseDataBuf, REPARSE_DATA_BUFFER_HEADER_SIZE + ReparseDataBuf.D.ReparseDataLength,
0, 0,
&Bytes, 0);
ASSERT(Success);
Success = DeviceIoControl(Handle, FSCTL_GET_REPARSE_POINT,
0, 0,
&ReparseDataBuf, sizeof ReparseDataBuf,
&Bytes, 0);
ASSERT(!Success);
ASSERT(ERROR_NOT_A_REPARSE_POINT == GetLastError());
}
CloseHandle(Handle);
@ -241,7 +246,7 @@ static void reparse_symlink_dotest0(ULONG Flags, PWSTR Prefix,
PUINT8 NameInfoBuf[sizeof(FILE_NAME_INFO) + MAX_PATH];
PFILE_NAME_INFO PNameInfo = (PVOID)NameInfoBuf;
Success = CreateSymbolicLinkW(LinkPath, TargetPath, 0);
Success = BestEffortCreateSymbolicLinkW(LinkPath, TargetPath, 0);
if (Success)
{
Handle = CreateFileW(FilePath,
@ -401,7 +406,7 @@ static BOOL my_symlink_fn(ULONG Flags, PWSTR Prefix, void *memfs, PWSTR LinkName
StringCbPrintfW(FilePath, sizeof FilePath, L"%s",
FileName);
return CreateSymbolicLinkW(LinkPath,
return BestEffortCreateSymbolicLinkW(LinkPath,
-1 == Flags && L'\\' == FileName[0] ? FilePath + 6 : FilePath,
SymlinkFlags);
}
@ -422,7 +427,57 @@ static BOOL my_namecheck_fn(ULONG Flags, PWSTR Prefix, void *memfs, PWSTR FileNa
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if (INVALID_HANDLE_VALUE == Handle)
return FALSE;
{
/*
* Prior to Windows 11 it used to be the case that NTFS open with FILE_FLAG_BACKUP_SEMANTICS
* did not care about SYMLINK/SYMLINKD difference!
*
* On Windows 11 this no longer appears to be true. In order to keep this test around, we perform
* an alternative name check in this case.
*/
if (-1 == Flags && (ERROR_ACCESS_DENIED == GetLastError() || ERROR_DIRECTORY == GetLastError()))
; /* Windows 11: if NTFS and appropriate error then ignore */
else
return FALSE;
Handle = CreateFileW(FilePath, FILE_READ_ATTRIBUTES,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, 0);
if (INVALID_HANDLE_VALUE == Handle)
return FALSE;
union
{
REPARSE_DATA_BUFFER D;
UINT8 B[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
} ReparseDataBuf;
DWORD Bytes;
BOOL Success;
Success = DeviceIoControl(Handle, FSCTL_GET_REPARSE_POINT,
0, 0,
&ReparseDataBuf, sizeof ReparseDataBuf,
&Bytes, 0);
if (Success)
{
Success = Success &&
ReparseDataBuf.D.ReparseTag == IO_REPARSE_TAG_SYMLINK;
Success = Success &&
ReparseDataBuf.D.SymbolicLinkReparseBuffer.SubstituteNameLength ==
wcslen(ExpectedPath + 6) * sizeof(WCHAR);
Success = Success &&
0 == mywcscmp(
ExpectedPath + 6,
-1,
ReparseDataBuf.D.SymbolicLinkReparseBuffer.PathBuffer +
ReparseDataBuf.D.SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR),
ReparseDataBuf.D.SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR));
}
CloseHandle(Handle);
return Success;
}
if (GetFileInformationByHandleEx(Handle, FileNameInfo, PNameInfo, sizeof NameInfoBuf))
{
@ -500,7 +555,11 @@ static void reparse_symlink_relative_dotest(ULONG Flags, PWSTR Prefix, ULONG Fil
my_failcheck(L"\\loop");
ASSERT(ERROR_CANT_RESOLVE_FILENAME == GetLastError());
/* NTFS open with FILE_FLAG_BACKUP_SEMANTICS does not care about SYMLINK/SYMLINKD difference! */
/*
* NTFS open with FILE_FLAG_BACKUP_SEMANTICS does not care about SYMLINK/SYMLINKD difference!
*
* UPDATE: Appears to no longer be true on Windows 11!
*/
my_namecheck(L"\\lf", L"\\1");
my_namecheck(L"\\ld", L"\\1\\1.1\\1.1.1");
@ -555,7 +614,8 @@ void reparse_symlink_relative_test(void)
void reparse_tests(void)
{
TEST(reparse_guid_test);
if (!OptFuseExternal)
TEST(reparse_guid_test);
TEST(reparse_nfs_test);
TEST(reparse_symlink_test);
TEST(reparse_symlink_relative_test);

View File

@ -333,6 +333,7 @@ void security_stress_meta_test(void)
void security_tests(void)
{
TEST(getsecurity_test);
TEST(setsecurity_test);
if (!OptFuseExternal)
TEST(setsecurity_test);
TEST_OPT(security_stress_meta_test);
}

View File

@ -2287,6 +2287,9 @@ void stream_dirnotify_test(void)
void stream_tests(void)
{
if (OptFuseExternal)
return;
TEST(stream_create_test);
if (!OptOplock)
TEST(stream_create_overwrite_test);

View File

@ -34,11 +34,13 @@ int WinFspDiskTests = 1;
int WinFspNetTests = 1;
BOOLEAN OptExternal = FALSE;
BOOLEAN OptFuseExternal = FALSE;
BOOLEAN OptResilient = FALSE;
BOOLEAN OptCaseInsensitiveCmp = FALSE;
BOOLEAN OptCaseInsensitive = FALSE;
BOOLEAN OptCaseRandomize = FALSE;
BOOLEAN OptFlushAndPurgeOnCleanup = FALSE;
BOOLEAN OptLegacyUnlinkRename = FALSE;
BOOLEAN OptNotify = FALSE;
WCHAR OptOplock = 0;
WCHAR OptMountPointBuf[MAX_PATH], *OptMountPoint;
@ -269,6 +271,15 @@ int main(int argc, char *argv[])
WinFspNetTests = 0;
rmarg(argv, argc, argi);
}
else if (0 == strcmp("--fuse-external", a))
{
OptExternal = TRUE;
OptFuseExternal = TRUE;
NtfsTests = 1;
WinFspDiskTests = 0;
WinFspNetTests = 0;
rmarg(argv, argc, argi);
}
else if (0 == strcmp("--resilient", a))
{
OptResilient = TRUE;
@ -296,6 +307,11 @@ int main(int argc, char *argv[])
OptFlushAndPurgeOnCleanup = TRUE;
rmarg(argv, argc, argi);
}
else if (0 == strcmp("--legacy-unlink-rename", a))
{
OptLegacyUnlinkRename = TRUE;
rmarg(argv, argc, argi);
}
else if (0 == strcmp("--notify", a))
{
OptNotify = TRUE;

View File

@ -132,10 +132,32 @@ BOOL WINAPI ResilientDeleteFileW(
BOOL WINAPI ResilientRemoveDirectoryW(
LPCWSTR lpPathName);
static inline
BOOLEAN BestEffortCreateSymbolicLinkW(
PWSTR SymlinkFileName,
PWSTR TargetFileName,
DWORD Flags)
{
BOOLEAN Success = CreateSymbolicLinkW(
SymlinkFileName,
TargetFileName,
Flags | 2/*SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE*/);
if (!Success && ERROR_INVALID_PARAMETER == GetLastError())
Success = CreateSymbolicLinkW(
SymlinkFileName,
TargetFileName,
Flags & ~2/*SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE*/);
return Success;
}
typedef struct
{
BOOLEAN Disposition;
} MY_FILE_DISPOSITION_INFO;
typedef struct
{
ULONG Flags;
} MY_FILE_DISPOSITION_INFO_EX;
void *memfs_start_ex(ULONG Flags, ULONG FileInfoTimeout);
void *memfs_start(ULONG Flags);
@ -154,11 +176,13 @@ extern int WinFspDiskTests;
extern int WinFspNetTests;
extern BOOLEAN OptExternal;
extern BOOLEAN OptFuseExternal;
extern BOOLEAN OptResilient;
extern BOOLEAN OptCaseInsensitiveCmp;
extern BOOLEAN OptCaseInsensitive;
extern BOOLEAN OptCaseRandomize;
extern BOOLEAN OptFlushAndPurgeOnCleanup;
extern BOOLEAN OptLegacyUnlinkRename;
extern BOOLEAN OptNotify;
extern WCHAR OptOplock;
extern WCHAR OptMountPointBuf[], *OptMountPoint;

View File

@ -184,5 +184,8 @@ static void wsl_stat_test(void)
void wsl_tests(void)
{
if (OptFuseExternal)
return;
TEST_OPT(wsl_stat_test);
}