diff --git a/appveyor.yml b/appveyor.yml
index 3380730e..ba087bbe 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -11,6 +11,9 @@ environment:
#- CONFIGURATION: Release
# TESTING: Perf
+init:
+#- ps: iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
+
install:
- git submodule update --init --recursive
- appveyor AddMessage "Change boot configuration and reboot" -Category Information
@@ -42,3 +45,4 @@ test_script:
on_finish:
- if exist %SystemRoot%\memory.dmp (7z a memory.dmp.zip %SystemRoot%\memory.dmp && appveyor PushArtifact memory.dmp.zip)
- verifier /query
+#- ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
diff --git a/build/VStudio/testing/winfsp-tests.vcxproj b/build/VStudio/testing/winfsp-tests.vcxproj
index c1d7825e..d4f78a30 100644
--- a/build/VStudio/testing/winfsp-tests.vcxproj
+++ b/build/VStudio/testing/winfsp-tests.vcxproj
@@ -185,6 +185,7 @@
+
diff --git a/build/VStudio/testing/winfsp-tests.vcxproj.filters b/build/VStudio/testing/winfsp-tests.vcxproj.filters
index 493a812a..26eb09a9 100644
--- a/build/VStudio/testing/winfsp-tests.vcxproj.filters
+++ b/build/VStudio/testing/winfsp-tests.vcxproj.filters
@@ -94,6 +94,9 @@
Source
+
+ Source
+
diff --git a/build/VStudio/winfsp.sln b/build/VStudio/winfsp.sln
index d726e30a..288f222c 100644
--- a/build/VStudio/winfsp.sln
+++ b/build/VStudio/winfsp.sln
@@ -60,232 +60,168 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fsptool", "tools\fsptool.vc
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
- Installer.Debug|Any CPU = Installer.Debug|Any CPU
Installer.Debug|x64 = Installer.Debug|x64
Installer.Debug|x86 = Installer.Debug|x86
- Installer.Release|Any CPU = Installer.Release|Any CPU
Installer.Release|x64 = Installer.Release|x64
Installer.Release|x86 = Installer.Release|x86
- Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Debug|Any CPU.ActiveCfg = Debug|Win32
{4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Debug|x64.ActiveCfg = Debug|x64
{4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Debug|x64.Build.0 = Debug|x64
{4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Debug|x86.ActiveCfg = Debug|Win32
{4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Debug|x86.Build.0 = Debug|Win32
- {4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Installer.Debug|Any CPU.ActiveCfg = Release|Win32
{4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Installer.Debug|x64.ActiveCfg = Debug|x64
{4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Installer.Debug|x86.ActiveCfg = Debug|Win32
- {4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Installer.Release|Any CPU.ActiveCfg = Release|Win32
{4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Installer.Release|x64.ActiveCfg = Release|x64
{4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Installer.Release|x86.ActiveCfg = Release|Win32
- {4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Release|Any CPU.ActiveCfg = Release|Win32
{4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Release|x64.ActiveCfg = Release|x64
{4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Release|x64.Build.0 = Release|x64
{4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Release|x86.ActiveCfg = Release|Win32
{4A7C0B21-9E10-4C81-92DE-1493EFCF24EB}.Release|x86.Build.0 = Release|Win32
- {C85C26BA-8C22-4D30-83DA-46C3548E6332}.Debug|Any CPU.ActiveCfg = Debug|Win32
{C85C26BA-8C22-4D30-83DA-46C3548E6332}.Debug|x64.ActiveCfg = Debug|x64
{C85C26BA-8C22-4D30-83DA-46C3548E6332}.Debug|x64.Build.0 = Debug|x64
{C85C26BA-8C22-4D30-83DA-46C3548E6332}.Debug|x86.ActiveCfg = Debug|Win32
{C85C26BA-8C22-4D30-83DA-46C3548E6332}.Debug|x86.Build.0 = Debug|Win32
- {C85C26BA-8C22-4D30-83DA-46C3548E6332}.Installer.Debug|Any CPU.ActiveCfg = Release|Win32
{C85C26BA-8C22-4D30-83DA-46C3548E6332}.Installer.Debug|x64.ActiveCfg = Debug|x64
{C85C26BA-8C22-4D30-83DA-46C3548E6332}.Installer.Debug|x86.ActiveCfg = Debug|Win32
- {C85C26BA-8C22-4D30-83DA-46C3548E6332}.Installer.Release|Any CPU.ActiveCfg = Release|Win32
{C85C26BA-8C22-4D30-83DA-46C3548E6332}.Installer.Release|x64.ActiveCfg = Release|x64
{C85C26BA-8C22-4D30-83DA-46C3548E6332}.Installer.Release|x86.ActiveCfg = Release|Win32
- {C85C26BA-8C22-4D30-83DA-46C3548E6332}.Release|Any CPU.ActiveCfg = Release|Win32
{C85C26BA-8C22-4D30-83DA-46C3548E6332}.Release|x64.ActiveCfg = Release|x64
{C85C26BA-8C22-4D30-83DA-46C3548E6332}.Release|x64.Build.0 = Release|x64
{C85C26BA-8C22-4D30-83DA-46C3548E6332}.Release|x86.ActiveCfg = Release|Win32
{C85C26BA-8C22-4D30-83DA-46C3548E6332}.Release|x86.Build.0 = Release|Win32
- {262DF8CC-E7A8-4460-A22C-683CBA322C32}.Debug|Any CPU.ActiveCfg = Debug|Win32
{262DF8CC-E7A8-4460-A22C-683CBA322C32}.Debug|x64.ActiveCfg = Debug|x64
{262DF8CC-E7A8-4460-A22C-683CBA322C32}.Debug|x64.Build.0 = Debug|x64
{262DF8CC-E7A8-4460-A22C-683CBA322C32}.Debug|x86.ActiveCfg = Debug|Win32
{262DF8CC-E7A8-4460-A22C-683CBA322C32}.Debug|x86.Build.0 = Debug|Win32
- {262DF8CC-E7A8-4460-A22C-683CBA322C32}.Installer.Debug|Any CPU.ActiveCfg = Release|Win32
{262DF8CC-E7A8-4460-A22C-683CBA322C32}.Installer.Debug|x64.ActiveCfg = Debug|x64
{262DF8CC-E7A8-4460-A22C-683CBA322C32}.Installer.Debug|x86.ActiveCfg = Debug|Win32
- {262DF8CC-E7A8-4460-A22C-683CBA322C32}.Installer.Release|Any CPU.ActiveCfg = Release|Win32
{262DF8CC-E7A8-4460-A22C-683CBA322C32}.Installer.Release|x64.ActiveCfg = Release|x64
{262DF8CC-E7A8-4460-A22C-683CBA322C32}.Installer.Release|x86.ActiveCfg = Release|Win32
- {262DF8CC-E7A8-4460-A22C-683CBA322C32}.Release|Any CPU.ActiveCfg = Release|Win32
{262DF8CC-E7A8-4460-A22C-683CBA322C32}.Release|x64.ActiveCfg = Release|x64
{262DF8CC-E7A8-4460-A22C-683CBA322C32}.Release|x64.Build.0 = Release|x64
{262DF8CC-E7A8-4460-A22C-683CBA322C32}.Release|x86.ActiveCfg = Release|Win32
{262DF8CC-E7A8-4460-A22C-683CBA322C32}.Release|x86.Build.0 = Release|Win32
- {AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Debug|Any CPU.ActiveCfg = Debug|Win32
{AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Debug|x64.ActiveCfg = Debug|x64
{AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Debug|x64.Build.0 = Debug|x64
{AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Debug|x86.ActiveCfg = Debug|Win32
{AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Debug|x86.Build.0 = Debug|Win32
- {AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Installer.Debug|Any CPU.ActiveCfg = Release|Win32
{AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Installer.Debug|x64.ActiveCfg = Debug|x64
{AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Installer.Debug|x86.ActiveCfg = Debug|Win32
- {AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Installer.Release|Any CPU.ActiveCfg = Release|Win32
{AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Installer.Release|x64.ActiveCfg = Release|x64
{AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Installer.Release|x86.ActiveCfg = Release|Win32
- {AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Release|Any CPU.ActiveCfg = Release|Win32
{AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Release|x64.ActiveCfg = Release|x64
{AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Release|x64.Build.0 = Release|x64
{AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Release|x86.ActiveCfg = Release|Win32
{AA7190E8-877F-4827-8CDD-E0D85F83C8C1}.Release|x86.Build.0 = Release|Win32
- {D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Debug|Any CPU.ActiveCfg = Debug|x86
{D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Debug|x64.ActiveCfg = Debug|x86
{D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Debug|x86.ActiveCfg = Debug|x86
- {D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Installer.Debug|Any CPU.ActiveCfg = Release|x86
- {D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Installer.Debug|Any CPU.Build.0 = Release|x86
{D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Installer.Debug|x64.ActiveCfg = Release|x86
{D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Installer.Debug|x64.Build.0 = Release|x86
{D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Installer.Debug|x86.ActiveCfg = Debug|x86
{D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Installer.Debug|x86.Build.0 = Debug|x86
- {D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Installer.Release|Any CPU.ActiveCfg = Release|x86
- {D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Installer.Release|Any CPU.Build.0 = Release|x86
{D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Installer.Release|x64.ActiveCfg = Release|x86
{D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Installer.Release|x64.Build.0 = Release|x86
{D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Installer.Release|x86.ActiveCfg = Release|x86
{D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Installer.Release|x86.Build.0 = Release|x86
- {D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Release|Any CPU.ActiveCfg = Release|x86
{D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Release|x64.ActiveCfg = Release|x86
{D53AAC39-4C57-4CA5-A4F3-C2B24888C594}.Release|x86.ActiveCfg = Release|x86
- {95C223E6-B5F1-4FD0-9376-41CDBC824445}.Debug|Any CPU.ActiveCfg = Debug|Win32
{95C223E6-B5F1-4FD0-9376-41CDBC824445}.Debug|x64.ActiveCfg = Debug|Win32
{95C223E6-B5F1-4FD0-9376-41CDBC824445}.Debug|x86.ActiveCfg = Debug|Win32
- {95C223E6-B5F1-4FD0-9376-41CDBC824445}.Installer.Debug|Any CPU.ActiveCfg = Release|Win32
- {95C223E6-B5F1-4FD0-9376-41CDBC824445}.Installer.Debug|Any CPU.Build.0 = Release|Win32
{95C223E6-B5F1-4FD0-9376-41CDBC824445}.Installer.Debug|x64.ActiveCfg = Release|Win32
{95C223E6-B5F1-4FD0-9376-41CDBC824445}.Installer.Debug|x64.Build.0 = Release|Win32
{95C223E6-B5F1-4FD0-9376-41CDBC824445}.Installer.Debug|x86.ActiveCfg = Debug|Win32
{95C223E6-B5F1-4FD0-9376-41CDBC824445}.Installer.Debug|x86.Build.0 = Debug|Win32
- {95C223E6-B5F1-4FD0-9376-41CDBC824445}.Installer.Release|Any CPU.ActiveCfg = Release|Win32
- {95C223E6-B5F1-4FD0-9376-41CDBC824445}.Installer.Release|Any CPU.Build.0 = Release|Win32
{95C223E6-B5F1-4FD0-9376-41CDBC824445}.Installer.Release|x64.ActiveCfg = Release|Win32
{95C223E6-B5F1-4FD0-9376-41CDBC824445}.Installer.Release|x64.Build.0 = Release|Win32
{95C223E6-B5F1-4FD0-9376-41CDBC824445}.Installer.Release|x86.ActiveCfg = Release|Win32
{95C223E6-B5F1-4FD0-9376-41CDBC824445}.Installer.Release|x86.Build.0 = Release|Win32
- {95C223E6-B5F1-4FD0-9376-41CDBC824445}.Release|Any CPU.ActiveCfg = Release|Win32
{95C223E6-B5F1-4FD0-9376-41CDBC824445}.Release|x64.ActiveCfg = Release|Win32
{95C223E6-B5F1-4FD0-9376-41CDBC824445}.Release|x86.ActiveCfg = Release|Win32
- {10757011-749D-4954-873B-AE38D8145472}.Debug|Any CPU.ActiveCfg = Debug|Win32
{10757011-749D-4954-873B-AE38D8145472}.Debug|x64.ActiveCfg = Debug|x64
{10757011-749D-4954-873B-AE38D8145472}.Debug|x64.Build.0 = Debug|x64
{10757011-749D-4954-873B-AE38D8145472}.Debug|x86.ActiveCfg = Debug|Win32
{10757011-749D-4954-873B-AE38D8145472}.Debug|x86.Build.0 = Debug|Win32
- {10757011-749D-4954-873B-AE38D8145472}.Installer.Debug|Any CPU.ActiveCfg = Release|Win32
{10757011-749D-4954-873B-AE38D8145472}.Installer.Debug|x64.ActiveCfg = Debug|x64
{10757011-749D-4954-873B-AE38D8145472}.Installer.Debug|x86.ActiveCfg = Debug|Win32
- {10757011-749D-4954-873B-AE38D8145472}.Installer.Release|Any CPU.ActiveCfg = Release|Win32
{10757011-749D-4954-873B-AE38D8145472}.Installer.Release|x64.ActiveCfg = Release|x64
{10757011-749D-4954-873B-AE38D8145472}.Installer.Release|x86.ActiveCfg = Release|Win32
- {10757011-749D-4954-873B-AE38D8145472}.Release|Any CPU.ActiveCfg = Release|Win32
{10757011-749D-4954-873B-AE38D8145472}.Release|x64.ActiveCfg = Release|x64
{10757011-749D-4954-873B-AE38D8145472}.Release|x64.Build.0 = Release|x64
{10757011-749D-4954-873B-AE38D8145472}.Release|x86.ActiveCfg = Release|Win32
{10757011-749D-4954-873B-AE38D8145472}.Release|x86.Build.0 = Release|Win32
- {C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Debug|Any CPU.ActiveCfg = Debug|Win32
{C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Debug|x64.ActiveCfg = Debug|x64
{C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Debug|x64.Build.0 = Debug|x64
{C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Debug|x86.ActiveCfg = Debug|Win32
{C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Debug|x86.Build.0 = Debug|Win32
- {C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Installer.Debug|Any CPU.ActiveCfg = Release|Win32
{C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Installer.Debug|x64.ActiveCfg = Debug|x64
{C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Installer.Debug|x86.ActiveCfg = Debug|Win32
- {C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Installer.Release|Any CPU.ActiveCfg = Release|Win32
{C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Installer.Release|x64.ActiveCfg = Release|x64
{C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Installer.Release|x86.ActiveCfg = Release|Win32
- {C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Release|Any CPU.ActiveCfg = Release|Win32
{C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Release|x64.ActiveCfg = Release|x64
{C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Release|x64.Build.0 = Release|x64
{C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Release|x86.ActiveCfg = Release|Win32
{C4E1E9E5-0959-488E-8C6A-C327CC81BEFB}.Release|x86.Build.0 = Release|Win32
- {94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Debug|x64.ActiveCfg = Debug|Any CPU
{94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Debug|x64.Build.0 = Debug|Any CPU
{94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Debug|x86.ActiveCfg = Debug|Any CPU
{94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Debug|x86.Build.0 = Debug|Any CPU
- {94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Installer.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Installer.Debug|x64.ActiveCfg = Debug|Any CPU
{94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Installer.Debug|x86.ActiveCfg = Debug|Any CPU
- {94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Installer.Release|Any CPU.ActiveCfg = Release|Any CPU
{94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Installer.Release|x64.ActiveCfg = Release|Any CPU
{94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Installer.Release|x86.ActiveCfg = Release|Any CPU
- {94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Release|Any CPU.Build.0 = Release|Any CPU
{94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Release|x64.ActiveCfg = Release|Any CPU
{94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Release|x64.Build.0 = Release|Any CPU
{94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Release|x86.ActiveCfg = Release|Any CPU
{94580219-CC8D-4FE5-A3BE-437B0B3481E1}.Release|x86.Build.0 = Release|Any CPU
- {4920E350-D496-4652-AE98-6C4208AEC1D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {4920E350-D496-4652-AE98-6C4208AEC1D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4920E350-D496-4652-AE98-6C4208AEC1D8}.Debug|x64.ActiveCfg = Debug|Any CPU
{4920E350-D496-4652-AE98-6C4208AEC1D8}.Debug|x64.Build.0 = Debug|Any CPU
{4920E350-D496-4652-AE98-6C4208AEC1D8}.Debug|x86.ActiveCfg = Debug|Any CPU
{4920E350-D496-4652-AE98-6C4208AEC1D8}.Debug|x86.Build.0 = Debug|Any CPU
- {4920E350-D496-4652-AE98-6C4208AEC1D8}.Installer.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4920E350-D496-4652-AE98-6C4208AEC1D8}.Installer.Debug|x64.ActiveCfg = Debug|Any CPU
{4920E350-D496-4652-AE98-6C4208AEC1D8}.Installer.Debug|x86.ActiveCfg = Debug|Any CPU
- {4920E350-D496-4652-AE98-6C4208AEC1D8}.Installer.Release|Any CPU.ActiveCfg = Release|Any CPU
{4920E350-D496-4652-AE98-6C4208AEC1D8}.Installer.Release|x64.ActiveCfg = Release|Any CPU
{4920E350-D496-4652-AE98-6C4208AEC1D8}.Installer.Release|x86.ActiveCfg = Release|Any CPU
- {4920E350-D496-4652-AE98-6C4208AEC1D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {4920E350-D496-4652-AE98-6C4208AEC1D8}.Release|Any CPU.Build.0 = Release|Any CPU
{4920E350-D496-4652-AE98-6C4208AEC1D8}.Release|x64.ActiveCfg = Release|Any CPU
{4920E350-D496-4652-AE98-6C4208AEC1D8}.Release|x64.Build.0 = Release|Any CPU
{4920E350-D496-4652-AE98-6C4208AEC1D8}.Release|x86.ActiveCfg = Release|Any CPU
{4920E350-D496-4652-AE98-6C4208AEC1D8}.Release|x86.Build.0 = Release|Any CPU
- {6CDF9411-B852-4EAC-822D-8F930675F17B}.Debug|Any CPU.ActiveCfg = Debug|Win32
{6CDF9411-B852-4EAC-822D-8F930675F17B}.Debug|x64.ActiveCfg = Debug|x64
{6CDF9411-B852-4EAC-822D-8F930675F17B}.Debug|x64.Build.0 = Debug|x64
{6CDF9411-B852-4EAC-822D-8F930675F17B}.Debug|x86.ActiveCfg = Debug|Win32
{6CDF9411-B852-4EAC-822D-8F930675F17B}.Debug|x86.Build.0 = Debug|Win32
- {6CDF9411-B852-4EAC-822D-8F930675F17B}.Installer.Debug|Any CPU.ActiveCfg = Release|x64
{6CDF9411-B852-4EAC-822D-8F930675F17B}.Installer.Debug|x64.ActiveCfg = Debug|x64
{6CDF9411-B852-4EAC-822D-8F930675F17B}.Installer.Debug|x86.ActiveCfg = Debug|Win32
- {6CDF9411-B852-4EAC-822D-8F930675F17B}.Installer.Release|Any CPU.ActiveCfg = Release|x64
{6CDF9411-B852-4EAC-822D-8F930675F17B}.Installer.Release|x64.ActiveCfg = Release|x64
{6CDF9411-B852-4EAC-822D-8F930675F17B}.Installer.Release|x86.ActiveCfg = Release|Win32
- {6CDF9411-B852-4EAC-822D-8F930675F17B}.Release|Any CPU.ActiveCfg = Release|Win32
{6CDF9411-B852-4EAC-822D-8F930675F17B}.Release|x64.ActiveCfg = Release|x64
{6CDF9411-B852-4EAC-822D-8F930675F17B}.Release|x64.Build.0 = Release|x64
{6CDF9411-B852-4EAC-822D-8F930675F17B}.Release|x86.ActiveCfg = Release|Win32
{6CDF9411-B852-4EAC-822D-8F930675F17B}.Release|x86.Build.0 = Release|Win32
- {264A5D09-126F-4760-A3F1-4B3B95C925AA}.Debug|Any CPU.ActiveCfg = Debug|Win32
{264A5D09-126F-4760-A3F1-4B3B95C925AA}.Debug|x64.ActiveCfg = Debug|x64
{264A5D09-126F-4760-A3F1-4B3B95C925AA}.Debug|x64.Build.0 = Debug|x64
{264A5D09-126F-4760-A3F1-4B3B95C925AA}.Debug|x86.ActiveCfg = Debug|Win32
{264A5D09-126F-4760-A3F1-4B3B95C925AA}.Debug|x86.Build.0 = Debug|Win32
- {264A5D09-126F-4760-A3F1-4B3B95C925AA}.Installer.Debug|Any CPU.ActiveCfg = Release|x64
{264A5D09-126F-4760-A3F1-4B3B95C925AA}.Installer.Debug|x64.ActiveCfg = Debug|x64
{264A5D09-126F-4760-A3F1-4B3B95C925AA}.Installer.Debug|x86.ActiveCfg = Debug|Win32
- {264A5D09-126F-4760-A3F1-4B3B95C925AA}.Installer.Release|Any CPU.ActiveCfg = Release|x64
{264A5D09-126F-4760-A3F1-4B3B95C925AA}.Installer.Release|x64.ActiveCfg = Release|x64
{264A5D09-126F-4760-A3F1-4B3B95C925AA}.Installer.Release|x86.ActiveCfg = Release|Win32
- {264A5D09-126F-4760-A3F1-4B3B95C925AA}.Release|Any CPU.ActiveCfg = Release|Win32
{264A5D09-126F-4760-A3F1-4B3B95C925AA}.Release|x64.ActiveCfg = Release|x64
{264A5D09-126F-4760-A3F1-4B3B95C925AA}.Release|x64.Build.0 = Release|x64
{264A5D09-126F-4760-A3F1-4B3B95C925AA}.Release|x86.ActiveCfg = Release|Win32
{264A5D09-126F-4760-A3F1-4B3B95C925AA}.Release|x86.Build.0 = Release|Win32
- {1E997BEC-1642-4A5C-B252-852DA094E11E}.Debug|Any CPU.ActiveCfg = Debug|Win32
{1E997BEC-1642-4A5C-B252-852DA094E11E}.Debug|x64.ActiveCfg = Debug|x64
{1E997BEC-1642-4A5C-B252-852DA094E11E}.Debug|x64.Build.0 = Debug|x64
{1E997BEC-1642-4A5C-B252-852DA094E11E}.Debug|x86.ActiveCfg = Debug|Win32
{1E997BEC-1642-4A5C-B252-852DA094E11E}.Debug|x86.Build.0 = Debug|Win32
- {1E997BEC-1642-4A5C-B252-852DA094E11E}.Installer.Debug|Any CPU.ActiveCfg = Release|x64
{1E997BEC-1642-4A5C-B252-852DA094E11E}.Installer.Debug|x64.ActiveCfg = Debug|x64
{1E997BEC-1642-4A5C-B252-852DA094E11E}.Installer.Debug|x86.ActiveCfg = Debug|Win32
- {1E997BEC-1642-4A5C-B252-852DA094E11E}.Installer.Release|Any CPU.ActiveCfg = Release|x64
{1E997BEC-1642-4A5C-B252-852DA094E11E}.Installer.Release|x64.ActiveCfg = Release|x64
{1E997BEC-1642-4A5C-B252-852DA094E11E}.Installer.Release|x86.ActiveCfg = Release|Win32
- {1E997BEC-1642-4A5C-B252-852DA094E11E}.Release|Any CPU.ActiveCfg = Release|Win32
{1E997BEC-1642-4A5C-B252-852DA094E11E}.Release|x64.ActiveCfg = Release|x64
{1E997BEC-1642-4A5C-B252-852DA094E11E}.Release|x64.Build.0 = Release|x64
{1E997BEC-1642-4A5C-B252-852DA094E11E}.Release|x86.ActiveCfg = Release|Win32
diff --git a/inc/winfsp/fsctl.h b/inc/winfsp/fsctl.h
index 67f1a630..6d7d99be 100644
--- a/inc/winfsp/fsctl.h
+++ b/inc/winfsp/fsctl.h
@@ -152,7 +152,7 @@ enum
UINT32 ReparsePointsAccessCheck:1; /* file system performs reparse point access checks */\
UINT32 NamedStreams:1; /* file system supports named streams */\
UINT32 HardLinks:1; /* unimplemented; set to 0 */\
- UINT32 ExtendedAttributes:1; /* unimplemented; set to 0 */\
+ UINT32 ExtendedAttributes:1; /* file system supports extended attributes */\
UINT32 ReadOnlyVolume:1;\
/* kernel-mode flags */\
UINT32 PostCleanupWhenModifiedOnly:1; /* post Cleanup when a file was modified/deleted */\
@@ -167,7 +167,8 @@ enum
UINT32 UmReservedFlags:6;\
/* additional kernel-mode flags */\
UINT32 AllowOpenInKernelMode:1; /* allow kernel mode to open files when possible */\
- UINT32 KmReservedFlags:7;\
+ UINT32 CasePreservedExtendedAttributes:1; /* preserve case of EA (default is UPPERCASE) */\
+ UINT32 KmReservedFlags:6;\
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\
@@ -176,22 +177,28 @@ enum
UINT32 DirInfoTimeoutValid:1; /* DirInfoTimeout field is valid */\
UINT32 SecurityTimeoutValid:1; /* SecurityTimeout field is valid*/\
UINT32 StreamInfoTimeoutValid:1; /* StreamInfoTimeout field is valid */\
- UINT32 KmAdditionalReservedFlags:28;\
+ UINT32 EaTimeoutValid:1; /* EaTimeout field is valid */\
+ UINT32 KmAdditionalReservedFlags:27;\
UINT32 VolumeInfoTimeout; /* volume info timeout (millis); overrides FileInfoTimeout */\
UINT32 DirInfoTimeout; /* dir info timeout (millis); overrides FileInfoTimeout */\
UINT32 SecurityTimeout; /* security info timeout (millis); overrides FileInfoTimeout */\
UINT32 StreamInfoTimeout; /* stream info timeout (millis); overrides FileInfoTimeout */\
- UINT32 Reserved32[3];\
+ UINT32 EaTimeout; /* EA timeout (millis); overrides FileInfoTimeout */\
+ UINT32 Reserved32[2];\
UINT64 Reserved64[2];
typedef struct
{
FSP_FSCTL_VOLUME_PARAMS_V0_FIELD_DEFN
} FSP_FSCTL_VOLUME_PARAMS_V0;
+FSP_FSCTL_STATIC_ASSERT(456 == sizeof(FSP_FSCTL_VOLUME_PARAMS_V0),
+ "sizeof(FSP_FSCTL_VOLUME_PARAMS_V0) must be exactly 456.");
typedef struct
{
FSP_FSCTL_VOLUME_PARAMS_V0_FIELD_DEFN
FSP_FSCTL_VOLUME_PARAMS_V1_FIELD_DEFN
} FSP_FSCTL_VOLUME_PARAMS;
+FSP_FSCTL_STATIC_ASSERT(504 == sizeof(FSP_FSCTL_VOLUME_PARAMS),
+ "sizeof(FSP_FSCTL_VOLUME_PARAMS) is currently 504. Update this assertion check if it changes.");
typedef struct
{
UINT64 TotalSize;
@@ -261,7 +268,7 @@ typedef struct
UINT32 DesiredAccess; /* FILE_{READ_DATA,WRITE_DATA,etc.} */
UINT32 GrantedAccess; /* FILE_{READ_DATA,WRITE_DATA,etc.} */
UINT32 ShareAccess; /* FILE_SHARE_{READ,WRITE,DELETE} */
- FSP_FSCTL_TRANSACT_BUF Ea; /* reserved; not currently implemented */
+ FSP_FSCTL_TRANSACT_BUF Ea; /* extended attributes buffer */
UINT32 UserMode:1; /* request originated in user mode */
UINT32 HasTraversePrivilege:1; /* requestor has TOKEN_HAS_TRAVERSE_PRIVILEGE */
UINT32 HasBackupPrivilege:1; /* requestor has TOKEN_HAS_BACKUP_PRIVILEGE */
@@ -280,6 +287,7 @@ typedef struct
UINT32 FileAttributes; /* file attributes for overwritten/superseded files */
UINT64 AllocationSize; /* allocation size for overwritten/superseded files */
UINT32 Supersede:1; /* 0: FILE_OVERWRITE operation, 1: FILE_SUPERSEDE operation */
+ FSP_FSCTL_TRANSACT_BUF Ea; /* extended attributes buffer */
} Overwrite;
struct
{
@@ -356,6 +364,17 @@ typedef struct
} Info;
} SetInformation;
struct
+ {
+ UINT64 UserContext;
+ UINT64 UserContext2;
+ } QueryEa;
+ struct
+ {
+ UINT64 UserContext;
+ UINT64 UserContext2;
+ FSP_FSCTL_TRANSACT_BUF Ea;
+ } SetEa;
+ struct
{
UINT64 UserContext;
UINT64 UserContext2;
@@ -470,6 +489,14 @@ typedef struct
FSP_FSCTL_FILE_INFO FileInfo; /* valid: File{Allocation,Basic,EndOfFile}Information */
} SetInformation;
struct
+ {
+ FSP_FSCTL_TRANSACT_BUF Ea;
+ } QueryEa;
+ struct
+ {
+ FSP_FSCTL_TRANSACT_BUF Ea; /* Size==0 means no extended atttributed returned */
+ } SetEa;
+ struct
{
FSP_FSCTL_FILE_INFO FileInfo; /* valid when flushing file (not volume) */
} FlushBuffers;
diff --git a/inc/winfsp/winfsp.h b/inc/winfsp/winfsp.h
index 300aa267..2d08893f 100644
--- a/inc/winfsp/winfsp.h
+++ b/inc/winfsp/winfsp.h
@@ -85,6 +85,21 @@ typedef struct _REPARSE_DATA_BUFFER
} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;
#endif
+/*
+ * The FILE_FULL_EA_INFORMATION definitions are missing from the user mode headers.
+ */
+#if !defined(FILE_NEED_EA)
+#define FILE_NEED_EA 0x00000080
+#endif
+typedef struct _FILE_FULL_EA_INFORMATION
+{
+ ULONG NextEntryOffset;
+ UCHAR Flags;
+ UCHAR EaNameLength;
+ USHORT EaValueLength;
+ CHAR EaName[1];
+} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION;
+
/**
* @group File System
*
@@ -894,12 +909,134 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE
*/
NTSTATUS (*SetDelete)(FSP_FILE_SYSTEM *FileSystem,
PVOID FileContext, PWSTR FileName, BOOLEAN DeleteFile);
+ /**
+ * Create new file or directory.
+ *
+ * This function works like Create, except that it also accepts EA (extended attributes).
+ *
+ * NOTE: If both Create and CreateEx are defined, CreateEx takes precedence.
+ *
+ * @param FileSystem
+ * The file system on which this request is posted.
+ * @param FileName
+ * The name of the file or directory to be created.
+ * @param CreateOptions
+ * Create options for this request. This parameter has the same meaning as the
+ * CreateOptions parameter of the NtCreateFile API. User mode file systems should typically
+ * only be concerned with the flag FILE_DIRECTORY_FILE, which is an instruction to create a
+ * directory rather than a file. Some file systems may also want to pay attention to the
+ * FILE_NO_INTERMEDIATE_BUFFERING and FILE_WRITE_THROUGH flags, although these are
+ * typically handled by the FSD component.
+ * @param GrantedAccess
+ * Determines the specific access rights that have been granted for this request. Upon
+ * receiving this call all access checks have been performed and the user mode file system
+ * need not perform any additional checks. However this parameter may be useful to a user
+ * mode file system; for example the WinFsp-FUSE layer uses this parameter to determine
+ * which flags to use in its POSIX open() call.
+ * @param FileAttributes
+ * File attributes to apply to the newly created file or directory.
+ * @param SecurityDescriptor
+ * Security descriptor to apply to the newly created file or directory. This security
+ * descriptor will always be in self-relative format. Its length can be retrieved using the
+ * Windows GetSecurityDescriptorLength API. Will be NULL for named streams.
+ * @param AllocationSize
+ * Allocation size for the newly created file.
+ * @param Ea
+ * Extended attributes buffer.
+ * @param EaLength
+ * Extended attributes buffer length.
+ * @param PFileContext [out]
+ * Pointer that will receive the file context on successful return from this call.
+ * @param FileInfo [out]
+ * Pointer to a structure that will receive the file information on successful return
+ * from this call. This information includes file attributes, file times, etc.
+ * @return
+ * STATUS_SUCCESS or error code.
+ */
+ NTSTATUS (*CreateEx)(FSP_FILE_SYSTEM *FileSystem,
+ PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess,
+ UINT32 FileAttributes, PSECURITY_DESCRIPTOR SecurityDescriptor, UINT64 AllocationSize,
+ PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength,
+ PVOID *PFileContext, FSP_FSCTL_FILE_INFO *FileInfo);
+ /**
+ * Overwrite a file.
+ *
+ * This function works like Overwrite, except that it also accepts EA (extended attributes).
+ *
+ * NOTE: If both Overwrite and OverwriteEx are defined, OverwriteEx takes precedence.
+ *
+ * @param FileSystem
+ * The file system on which this request is posted.
+ * @param FileContext
+ * The file context of the file to overwrite.
+ * @param FileAttributes
+ * File attributes to apply to the overwritten file.
+ * @param ReplaceFileAttributes
+ * When TRUE the existing file attributes should be replaced with the new ones.
+ * When FALSE the existing file attributes should be merged (or'ed) with the new ones.
+ * @param AllocationSize
+ * Allocation size for the overwritten file.
+ * @param Ea
+ * Extended attributes buffer.
+ * @param EaLength
+ * Extended attributes buffer length.
+ * @param FileInfo [out]
+ * Pointer to a structure that will receive the file information on successful return
+ * from this call. This information includes file attributes, file times, etc.
+ * @return
+ * STATUS_SUCCESS or error code.
+ */
+ NTSTATUS (*OverwriteEx)(FSP_FILE_SYSTEM *FileSystem,
+ PVOID FileContext, UINT32 FileAttributes, BOOLEAN ReplaceFileAttributes, UINT64 AllocationSize,
+ PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength,
+ FSP_FSCTL_FILE_INFO *FileInfo);
+ /**
+ * Get extended attributes.
+ *
+ * @param FileSystem
+ * The file system on which this request is posted.
+ * @param FileContext
+ * The file context of the file to get extended attributes for.
+ * @param Ea
+ * Extended attributes buffer.
+ * @param EaLength
+ * Extended attributes buffer length.
+ * @param PBytesTransferred [out]
+ * Pointer to a memory location that will receive the actual number of bytes transferred.
+ * @return
+ * STATUS_SUCCESS or error code.
+ * @see
+ * SetEa
+ * FspFileSystemAddEa
+ */
+ NTSTATUS (*GetEa)(FSP_FILE_SYSTEM *FileSystem,
+ PVOID FileContext,
+ PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, PULONG PBytesTransferred);
+ /**
+ * Set extended attributes.
+ *
+ * @param FileSystem
+ * The file system on which this request is posted.
+ * @param FileContext
+ * The file context of the file to set extended attributes for.
+ * @param Ea
+ * Extended attributes buffer.
+ * @param EaLength
+ * Extended attributes buffer length.
+ * @return
+ * STATUS_SUCCESS or error code.
+ * @see
+ * GetEa
+ */
+ NTSTATUS (*SetEa)(FSP_FILE_SYSTEM *FileSystem,
+ PVOID FileContext,
+ PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength);
/*
* 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[37])();
+ NTSTATUS (*Reserved[33])();
} FSP_FILE_SYSTEM_INTERFACE;
FSP_FSCTL_STATIC_ASSERT(sizeof(FSP_FILE_SYSTEM_INTERFACE) == 64 * sizeof(NTSTATUS (*)()),
"FSP_FILE_SYSTEM_INTERFACE must have 64 entries.");
@@ -1203,6 +1340,10 @@ FSP_API NTSTATUS FspFileSystemOpQueryInformation(FSP_FILE_SYSTEM *FileSystem,
FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response);
FSP_API NTSTATUS FspFileSystemOpSetInformation(FSP_FILE_SYSTEM *FileSystem,
FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response);
+FSP_API NTSTATUS FspFileSystemOpQueryEa(FSP_FILE_SYSTEM *FileSystem,
+ FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response);
+FSP_API NTSTATUS FspFileSystemOpSetEa(FSP_FILE_SYSTEM *FileSystem,
+ FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response);
FSP_API NTSTATUS FspFileSystemOpFlushBuffers(FSP_FILE_SYSTEM *FileSystem,
FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response);
FSP_API NTSTATUS FspFileSystemOpQueryVolumeInformation(FSP_FILE_SYSTEM *FileSystem,
@@ -1433,6 +1574,56 @@ FSP_API NTSTATUS FspFileSystemCanReplaceReparsePoint(
*/
FSP_API BOOLEAN FspFileSystemAddStreamInfo(FSP_FSCTL_STREAM_INFO *StreamInfo,
PVOID Buffer, ULONG Length, PULONG PBytesTransferred);
+/**
+ * Enumerate extended attributes in a buffer.
+ *
+ * This is a helper for implementing the CreateEx and SetEa operations in file systems
+ * that support extended attributes.
+ *
+ * @param FileSystem
+ * The file system object.
+ * @param EnumerateEa
+ * Pointer to function that receives a single extended attribute. The function
+ * should return STATUS_SUCCESS or an error code if unsuccessful.
+ * @param Context
+ * User context to supply to EnumEa.
+ * @param Ea
+ * Extended attributes buffer.
+ * @param EaLength
+ * Extended attributes buffer length.
+ * @return
+ * STATUS_SUCCESS or error code from EnumerateEa.
+ */
+FSP_API NTSTATUS FspFileSystemEnumerateEa(FSP_FILE_SYSTEM *FileSystem,
+ NTSTATUS (*EnumerateEa)(
+ FSP_FILE_SYSTEM *FileSystem, PVOID Context,
+ PFILE_FULL_EA_INFORMATION SingleEa),
+ PVOID Context,
+ PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength);
+/**
+ * Add extended attribute to a buffer.
+ *
+ * This is a helper for implementing the GetEa operation.
+ *
+ * @param SingleEa
+ * The extended attribute to add. A value of NULL acts as an EOF marker for a GetEa
+ * operation.
+ * @param Ea
+ * Pointer to a buffer that will receive the extended attribute. This should contain
+ * the same value passed to the GetEa Ea parameter.
+ * @param EaLength
+ * Length of buffer. This should contain the same value passed to the GetEa
+ * EaLength parameter.
+ * @param PBytesTransferred [out]
+ * Pointer to a memory location that will receive the actual number of bytes stored. This should
+ * contain the same value passed to the GetEa PBytesTransferred parameter.
+ * @return
+ * TRUE if the extended attribute was added, FALSE if there was not enough space to add it.
+ * @see
+ * GetEa
+ */
+FSP_API BOOLEAN FspFileSystemAddEa(PFILE_FULL_EA_INFORMATION SingleEa,
+ PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, PULONG PBytesTransferred);
/*
* Directory buffering
diff --git a/src/dll/fs.c b/src/dll/fs.c
index 9a0810b0..43b20d74 100644
--- a/src/dll/fs.c
+++ b/src/dll/fs.c
@@ -161,6 +161,8 @@ FSP_API NTSTATUS FspFileSystemCreate(PWSTR DevicePath,
FileSystem->Operations[FspFsctlTransactWriteKind] = FspFileSystemOpWrite;
FileSystem->Operations[FspFsctlTransactQueryInformationKind] = FspFileSystemOpQueryInformation;
FileSystem->Operations[FspFsctlTransactSetInformationKind] = FspFileSystemOpSetInformation;
+ FileSystem->Operations[FspFsctlTransactQueryEaKind] = FspFileSystemOpQueryEa;
+ FileSystem->Operations[FspFsctlTransactSetEaKind] = FspFileSystemOpSetEa;
FileSystem->Operations[FspFsctlTransactFlushBuffersKind] = FspFileSystemOpFlushBuffers;
FileSystem->Operations[FspFsctlTransactQueryVolumeInformationKind] = FspFileSystemOpQueryVolumeInformation;
FileSystem->Operations[FspFsctlTransactSetVolumeInformationKind] = FspFileSystemOpSetVolumeInformation;
diff --git a/src/dll/fsop.c b/src/dll/fsop.c
index 1a302fc6..dd50a308 100644
--- a/src/dll/fsop.c
+++ b/src/dll/fsop.c
@@ -436,10 +436,19 @@ static NTSTATUS FspFileSystemOpCreate_FileCreate(FSP_FILE_SYSTEM *FileSystem,
memset(&OpenFileInfo, 0, sizeof OpenFileInfo);
OpenFileInfo.NormalizedName = (PVOID)Response->Buffer;
OpenFileInfo.NormalizedNameSize = FSP_FSCTL_TRANSACT_RSP_BUFFER_SIZEMAX;
- Result = FileSystem->Interface->Create(FileSystem,
- (PWSTR)Request->Buffer, Request->Req.Create.CreateOptions, GrantedAccess,
- Request->Req.Create.FileAttributes, OpenDescriptor, Request->Req.Create.AllocationSize,
- AddrOfFileContext(FullContext), &OpenFileInfo.FileInfo);
+ if (0 != FileSystem->Interface->CreateEx)
+ Result = FileSystem->Interface->CreateEx(FileSystem,
+ (PWSTR)Request->Buffer, Request->Req.Create.CreateOptions, GrantedAccess,
+ Request->Req.Create.FileAttributes, OpenDescriptor, Request->Req.Create.AllocationSize,
+ 0 != Request->Req.Create.Ea.Size ?
+ (PVOID)(Request->Buffer + Request->Req.Create.Ea.Offset) : 0,
+ Request->Req.Create.Ea.Size,
+ AddrOfFileContext(FullContext), &OpenFileInfo.FileInfo);
+ else
+ Result = FileSystem->Interface->Create(FileSystem,
+ (PWSTR)Request->Buffer, Request->Req.Create.CreateOptions, GrantedAccess,
+ Request->Req.Create.FileAttributes, OpenDescriptor, Request->Req.Create.AllocationSize,
+ AddrOfFileContext(FullContext), &OpenFileInfo.FileInfo);
if (!NT_SUCCESS(Result))
{
FspDeleteSecurityDescriptor(OpenDescriptor, FspCreateSecurityDescriptor);
@@ -574,10 +583,19 @@ static NTSTATUS FspFileSystemOpCreate_FileOpenIf(FSP_FILE_SYSTEM *FileSystem,
memset(&OpenFileInfo, 0, sizeof OpenFileInfo);
OpenFileInfo.NormalizedName = (PVOID)Response->Buffer;
OpenFileInfo.NormalizedNameSize = FSP_FSCTL_TRANSACT_RSP_BUFFER_SIZEMAX;
- Result = FileSystem->Interface->Create(FileSystem,
- (PWSTR)Request->Buffer, Request->Req.Create.CreateOptions, GrantedAccess,
- Request->Req.Create.FileAttributes, OpenDescriptor, Request->Req.Create.AllocationSize,
- AddrOfFileContext(FullContext), &OpenFileInfo.FileInfo);
+ if (0 != FileSystem->Interface->CreateEx)
+ Result = FileSystem->Interface->CreateEx(FileSystem,
+ (PWSTR)Request->Buffer, Request->Req.Create.CreateOptions, GrantedAccess,
+ Request->Req.Create.FileAttributes, OpenDescriptor, Request->Req.Create.AllocationSize,
+ 0 != Request->Req.Create.Ea.Size ?
+ (PVOID)(Request->Buffer + Request->Req.Create.Ea.Offset) : 0,
+ Request->Req.Create.Ea.Size,
+ AddrOfFileContext(FullContext), &OpenFileInfo.FileInfo);
+ else
+ Result = FileSystem->Interface->Create(FileSystem,
+ (PWSTR)Request->Buffer, Request->Req.Create.CreateOptions, GrantedAccess,
+ Request->Req.Create.FileAttributes, OpenDescriptor, Request->Req.Create.AllocationSize,
+ AddrOfFileContext(FullContext), &OpenFileInfo.FileInfo);
if (!NT_SUCCESS(Result))
{
FspDeleteSecurityDescriptor(OpenDescriptor, FspCreateSecurityDescriptor);
@@ -699,10 +717,19 @@ static NTSTATUS FspFileSystemOpCreate_FileOverwriteIf(FSP_FILE_SYSTEM *FileSyste
memset(&OpenFileInfo, 0, sizeof OpenFileInfo);
OpenFileInfo.NormalizedName = (PVOID)Response->Buffer;
OpenFileInfo.NormalizedNameSize = FSP_FSCTL_TRANSACT_RSP_BUFFER_SIZEMAX;
- Result = FileSystem->Interface->Create(FileSystem,
- (PWSTR)Request->Buffer, Request->Req.Create.CreateOptions, GrantedAccess,
- Request->Req.Create.FileAttributes, ObjectDescriptor, Request->Req.Create.AllocationSize,
- AddrOfFileContext(FullContext), &OpenFileInfo.FileInfo);
+ if (0 != FileSystem->Interface->CreateEx)
+ Result = FileSystem->Interface->CreateEx(FileSystem,
+ (PWSTR)Request->Buffer, Request->Req.Create.CreateOptions, GrantedAccess,
+ Request->Req.Create.FileAttributes, ObjectDescriptor, Request->Req.Create.AllocationSize,
+ 0 != Request->Req.Create.Ea.Size ?
+ (PVOID)(Request->Buffer + Request->Req.Create.Ea.Offset) : 0,
+ Request->Req.Create.Ea.Size,
+ AddrOfFileContext(FullContext), &OpenFileInfo.FileInfo);
+ else
+ Result = FileSystem->Interface->Create(FileSystem,
+ (PWSTR)Request->Buffer, Request->Req.Create.CreateOptions, GrantedAccess,
+ Request->Req.Create.FileAttributes, ObjectDescriptor, Request->Req.Create.AllocationSize,
+ AddrOfFileContext(FullContext), &OpenFileInfo.FileInfo);
FspDeleteSecurityDescriptor(ObjectDescriptor, FspCreateSecurityDescriptor);
if (!NT_SUCCESS(Result))
return Result;
@@ -865,9 +892,9 @@ FSP_API NTSTATUS FspFileSystemOpCreate(FSP_FILE_SYSTEM *FileSystem,
{
NTSTATUS Result;
- if (0 == FileSystem->Interface->Create ||
+ if ((0 == FileSystem->Interface->Create && 0 == FileSystem->Interface->CreateEx) ||
0 == FileSystem->Interface->Open ||
- 0 == FileSystem->Interface->Overwrite)
+ (0 == FileSystem->Interface->Overwrite && 0 == FileSystem->Interface->OverwriteEx))
return STATUS_INVALID_DEVICE_REQUEST;
if (Request->Req.Create.OpenTargetDirectory)
@@ -910,16 +937,27 @@ FSP_API NTSTATUS FspFileSystemOpOverwrite(FSP_FILE_SYSTEM *FileSystem,
NTSTATUS Result;
FSP_FSCTL_FILE_INFO FileInfo;
- if (0 == FileSystem->Interface->Overwrite)
+ if (0 == FileSystem->Interface->Overwrite && 0 == FileSystem->Interface->OverwriteEx)
return STATUS_INVALID_DEVICE_REQUEST;
memset(&FileInfo, 0, sizeof FileInfo);
- Result = FileSystem->Interface->Overwrite(FileSystem,
- (PVOID)ValOfFileContext(Request->Req.Overwrite),
- Request->Req.Overwrite.FileAttributes,
- Request->Req.Overwrite.Supersede,
- Request->Req.Overwrite.AllocationSize,
- &FileInfo);
+ if (0 != FileSystem->Interface->OverwriteEx)
+ Result = FileSystem->Interface->OverwriteEx(FileSystem,
+ (PVOID)ValOfFileContext(Request->Req.Overwrite),
+ Request->Req.Overwrite.FileAttributes,
+ Request->Req.Overwrite.Supersede,
+ Request->Req.Overwrite.AllocationSize,
+ 0 != Request->Req.Overwrite.Ea.Size ?
+ (PVOID)(Request->Buffer + Request->Req.Overwrite.Ea.Offset) : 0,
+ Request->Req.Overwrite.Ea.Size,
+ &FileInfo);
+ else
+ Result = FileSystem->Interface->Overwrite(FileSystem,
+ (PVOID)ValOfFileContext(Request->Req.Overwrite),
+ Request->Req.Overwrite.FileAttributes,
+ Request->Req.Overwrite.Supersede,
+ Request->Req.Overwrite.AllocationSize,
+ &FileInfo);
if (!NT_SUCCESS(Result))
{
if (0 != FileSystem->Interface->Close)
@@ -1145,6 +1183,39 @@ FSP_API NTSTATUS FspFileSystemOpSetInformation(FSP_FILE_SYSTEM *FileSystem,
return STATUS_SUCCESS;
}
+FSP_API NTSTATUS FspFileSystemOpQueryEa(FSP_FILE_SYSTEM *FileSystem,
+ FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response)
+{
+ NTSTATUS Result;
+ ULONG BytesTransferred;
+
+ if (0 == FileSystem->Interface->GetEa)
+ return STATUS_INVALID_DEVICE_REQUEST;
+
+ BytesTransferred = 0;
+ Result = FileSystem->Interface->GetEa(FileSystem,
+ (PVOID)ValOfFileContext(Request->Req.QueryEa),
+ (PVOID)Response->Buffer, FSP_FSCTL_TRANSACT_RSP_BUFFER_SIZEMAX, &BytesTransferred);
+ if (!NT_SUCCESS(Result))
+ return STATUS_BUFFER_OVERFLOW != Result ? Result : STATUS_EA_LIST_INCONSISTENT;
+
+ Response->Size = (UINT16)(sizeof *Response + BytesTransferred);
+ Response->Rsp.QueryEa.Ea.Offset = 0;
+ Response->Rsp.QueryEa.Ea.Size = (UINT16)BytesTransferred;
+ return STATUS_SUCCESS;
+}
+
+FSP_API NTSTATUS FspFileSystemOpSetEa(FSP_FILE_SYSTEM *FileSystem,
+ FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response)
+{
+ if (0 == FileSystem->Interface->SetEa)
+ return STATUS_INVALID_DEVICE_REQUEST;
+
+ return FileSystem->Interface->SetEa(FileSystem,
+ (PVOID)ValOfFileContext(Request->Req.SetEa),
+ (PVOID)Request->Buffer, Request->Req.SetEa.Ea.Size);
+}
+
FSP_API NTSTATUS FspFileSystemOpQueryVolumeInformation(FSP_FILE_SYSTEM *FileSystem,
FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response)
{
@@ -1726,3 +1797,51 @@ FSP_API BOOLEAN FspFileSystemAddStreamInfo(FSP_FSCTL_STREAM_INFO *StreamInfo,
{
return FspFileSystemAddXxxInfo(StreamInfo, Buffer, Length, PBytesTransferred);
}
+
+FSP_API NTSTATUS FspFileSystemEnumerateEa(FSP_FILE_SYSTEM *FileSystem,
+ NTSTATUS (*EnumerateEa)(
+ FSP_FILE_SYSTEM *FileSystem, PVOID Context,
+ PFILE_FULL_EA_INFORMATION SingleEa),
+ PVOID Context,
+ PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength)
+{
+ NTSTATUS Result = STATUS_SUCCESS;
+ for (PFILE_FULL_EA_INFORMATION EaEnd = (PVOID)((PUINT8)Ea + EaLength);
+ EaEnd > Ea; Ea = FSP_NEXT_EA(Ea, EaEnd))
+ {
+ Result = EnumerateEa(FileSystem, Context, Ea);
+ if (!NT_SUCCESS(Result))
+ break;
+ }
+ return Result;
+}
+
+FSP_API BOOLEAN FspFileSystemAddEa(PFILE_FULL_EA_INFORMATION SingleEa,
+ PFILE_FULL_EA_INFORMATION Ea, ULONG Length, PULONG PBytesTransferred)
+{
+ if (0 != SingleEa)
+ {
+ PUINT8 EaPtr = (PUINT8)Ea + FSP_FSCTL_ALIGN_UP(*PBytesTransferred, sizeof(ULONG));
+ PUINT8 EaEnd = (PUINT8)Ea + Length;
+ ULONG EaLen = FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) +
+ SingleEa->EaNameLength + 1 + SingleEa->EaValueLength;
+
+ if (EaEnd < EaPtr + EaLen)
+ return FALSE;
+
+ memcpy(EaPtr, SingleEa, EaLen);
+ ((PFILE_FULL_EA_INFORMATION)EaPtr)->NextEntryOffset = FSP_FSCTL_ALIGN_UP(EaLen, sizeof(ULONG));
+ *PBytesTransferred = (ULONG)(EaPtr + EaLen - (PUINT8)Ea);
+ }
+ else if ((ULONG)FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) <= *PBytesTransferred)
+ {
+ PUINT8 EaEnd = (PUINT8)Ea + *PBytesTransferred;
+
+ while (EaEnd > (PUINT8)Ea + Ea->NextEntryOffset)
+ Ea = (PVOID)((PUINT8)Ea + Ea->NextEntryOffset);
+
+ Ea->NextEntryOffset = 0;
+ }
+
+ return TRUE;
+}
diff --git a/src/dll/library.h b/src/dll/library.h
index 17a67f7c..9970d31d 100644
--- a/src/dll/library.h
+++ b/src/dll/library.h
@@ -92,4 +92,7 @@ static inline BOOLEAN FspPathIsDrive(PWSTR FileName)
L':' == FileName[1] && L'\0' == FileName[2];
}
+#define FSP_NEXT_EA(Ea, EaEnd) \
+ (0 != (Ea)->NextEntryOffset ? (PVOID)((PUINT8)(Ea) + (Ea)->NextEntryOffset) : (EaEnd))
+
#endif
diff --git a/src/dotnet/FileSystemBase.cs b/src/dotnet/FileSystemBase.cs
index 87403ac9..1991d188 100644
--- a/src/dotnet/FileSystemBase.cs
+++ b/src/dotnet/FileSystemBase.cs
@@ -1078,6 +1078,108 @@ namespace Fsp
else
return STATUS_SUCCESS;
}
+ public virtual Int32 CreateEx(
+ String FileName,
+ UInt32 CreateOptions,
+ UInt32 GrantedAccess,
+ UInt32 FileAttributes,
+ Byte[] SecurityDescriptor,
+ UInt64 AllocationSize,
+ IntPtr Ea,
+ UInt32 EaLength,
+ out Object FileNode,
+ out Object FileDesc,
+ out FileInfo FileInfo,
+ out String NormalizedName)
+ {
+ return Create(
+ FileName,
+ CreateOptions,
+ GrantedAccess,
+ FileAttributes,
+ SecurityDescriptor,
+ AllocationSize,
+ out FileNode,
+ out FileDesc,
+ out FileInfo,
+ out NormalizedName);
+ }
+ public virtual Int32 OverwriteEx(
+ Object FileNode,
+ Object FileDesc,
+ UInt32 FileAttributes,
+ Boolean ReplaceFileAttributes,
+ UInt64 AllocationSize,
+ IntPtr Ea,
+ UInt32 EaLength,
+ out FileInfo FileInfo)
+ {
+ return Overwrite(
+ FileNode,
+ FileDesc,
+ FileAttributes,
+ ReplaceFileAttributes,
+ AllocationSize,
+ out FileInfo);
+ }
+ public virtual Int32 GetEa(
+ Object FileNode,
+ Object FileDesc,
+ IntPtr Ea,
+ UInt32 EaLength,
+ out UInt32 BytesTransferred)
+ {
+ Object Context = null;
+ String EaName;
+ Byte[] EaValue;
+ Boolean NeedEa;
+ FullEaInformation EaInfo = new FullEaInformation();
+ BytesTransferred = default(UInt32);
+ while (GetEaEntry(FileNode, FileDesc, ref Context, out EaName, out EaValue, out NeedEa))
+ {
+ EaInfo.Set(EaName, EaValue, NeedEa);
+ if (!Api.FspFileSystemAddEa(ref EaInfo, Ea, EaLength, out BytesTransferred))
+ return STATUS_SUCCESS;
+ }
+ Api.FspFileSystemEndEa(Ea, EaLength, out BytesTransferred);
+ return STATUS_SUCCESS;
+ }
+ public virtual Boolean GetEaEntry(
+ Object FileNode,
+ Object FileDesc,
+ ref Object Context,
+ out String EaName,
+ out Byte[] EaValue,
+ out Boolean NeedEa)
+ {
+ EaName = default(String);
+ EaValue = default(Byte[]);
+ NeedEa = default(Boolean);
+ return false;
+ }
+ public virtual Int32 SetEa(
+ Object FileNode,
+ Object FileDesc,
+ IntPtr Ea,
+ UInt32 EaLength)
+ {
+ return Api.FspFileSystemEnumerateEa(
+ FileNode,
+ FileDesc,
+ this.SetEaEntry,
+ Ea,
+ EaLength);
+ }
+ public virtual Int32 SetEaEntry(
+ Object FileNode,
+ Object FileDesc,
+ ref Object Context,
+ String EaName,
+ Byte[] EaValue,
+ Boolean NeedEa)
+ {
+ return STATUS_INVALID_DEVICE_REQUEST;
+ }
/* helpers */
///
diff --git a/src/dotnet/FileSystemHost.cs b/src/dotnet/FileSystemHost.cs
index 3f9140ac..a30fce20 100644
--- a/src/dotnet/FileSystemHost.cs
+++ b/src/dotnet/FileSystemHost.cs
@@ -183,6 +183,14 @@ namespace Fsp
get { return 0 != (_VolumeParams.Flags & VolumeParams.NamedStreams); }
set { _VolumeParams.Flags |= (value ? VolumeParams.NamedStreams : 0); }
}
+ ///
+ /// Gets or sets a value that determines whether the file system supports extended attributes.
+ ///
+ public Boolean ExtendedAttributes
+ {
+ get { return 0 != (_VolumeParams.Flags & VolumeParams.ExtendedAttributes); }
+ set { _VolumeParams.Flags |= (value ? VolumeParams.ExtendedAttributes : 0); }
+ }
public Boolean PostCleanupWhenModifiedOnly
{
get { return 0 != (_VolumeParams.Flags & VolumeParams.PostCleanupWhenModifiedOnly); }
@@ -464,6 +472,8 @@ namespace Fsp
UInt32 FileAttributes,
IntPtr SecurityDescriptor,
UInt64 AllocationSize,
+ IntPtr Ea,
+ UInt32 EaLength,
ref FullContext FullContext,
ref OpenFileInfo OpenFileInfo)
{
@@ -473,13 +483,15 @@ namespace Fsp
Object FileNode, FileDesc;
String NormalizedName;
Int32 Result;
- Result = FileSystem.Create(
+ Result = FileSystem.CreateEx(
FileName,
CreateOptions,
GrantedAccess,
FileAttributes,
Api.MakeSecurityDescriptor(SecurityDescriptor),
AllocationSize,
+ Ea,
+ EaLength,
out FileNode,
out FileDesc,
out OpenFileInfo.FileInfo,
@@ -538,6 +550,8 @@ namespace Fsp
UInt32 FileAttributes,
Boolean ReplaceFileAttributes,
UInt64 AllocationSize,
+ IntPtr Ea,
+ UInt32 EaLength,
out FileInfo FileInfo)
{
FileSystemBase FileSystem = (FileSystemBase)Api.GetUserContext(FileSystemPtr);
@@ -545,12 +559,14 @@ namespace Fsp
{
Object FileNode, FileDesc;
Api.GetFullContext(ref FullContext, out FileNode, out FileDesc);
- return FileSystem.Overwrite(
+ return FileSystem.OverwriteEx(
FileNode,
FileDesc,
FileAttributes,
ReplaceFileAttributes,
AllocationSize,
+ Ea,
+ EaLength,
out FileInfo);
}
catch (Exception ex)
@@ -1077,15 +1093,62 @@ namespace Fsp
return ExceptionHandler(FileSystem, ex);
}
}
+ private static Int32 GetEa(
+ IntPtr FileSystemPtr,
+ ref FullContext FullContext,
+ IntPtr Ea,
+ UInt32 EaLength,
+ out UInt32 PBytesTransferred)
+ {
+ FileSystemBase FileSystem = (FileSystemBase)Api.GetUserContext(FileSystemPtr);
+ try
+ {
+ Object FileNode, FileDesc;
+ Api.GetFullContext(ref FullContext, out FileNode, out FileDesc);
+ return FileSystem.GetEa(
+ FileNode,
+ FileDesc,
+ Ea,
+ EaLength,
+ out PBytesTransferred);
+ }
+ catch (Exception ex)
+ {
+ PBytesTransferred = default(UInt32);
+ return ExceptionHandler(FileSystem, ex);
+ }
+ }
+ private static Int32 SetEa(
+ IntPtr FileSystemPtr,
+ ref FullContext FullContext,
+ IntPtr Ea,
+ UInt32 EaLength)
+ {
+ FileSystemBase FileSystem = (FileSystemBase)Api.GetUserContext(FileSystemPtr);
+ try
+ {
+ Object FileNode, FileDesc;
+ Api.GetFullContext(ref FullContext, out FileNode, out FileDesc);
+ return FileSystem.SetEa(
+ FileNode,
+ FileDesc,
+ Ea,
+ EaLength);
+ }
+ catch (Exception ex)
+ {
+ return ExceptionHandler(FileSystem, ex);
+ }
+ }
static FileSystemHost()
{
_FileSystemInterface.GetVolumeInfo = GetVolumeInfo;
_FileSystemInterface.SetVolumeLabel = SetVolumeLabel;
_FileSystemInterface.GetSecurityByName = GetSecurityByName;
- _FileSystemInterface.Create = Create;
+ _FileSystemInterface.CreateEx = Create;
_FileSystemInterface.Open = Open;
- _FileSystemInterface.Overwrite = Overwrite;
+ _FileSystemInterface.OverwriteEx = Overwrite;
_FileSystemInterface.Cleanup = Cleanup;
_FileSystemInterface.Close = Close;
_FileSystemInterface.Read = Read;
@@ -1106,6 +1169,8 @@ namespace Fsp
_FileSystemInterface.GetDirInfoByName = GetDirInfoByName;
_FileSystemInterface.Control = Control;
_FileSystemInterface.SetDelete = SetDelete;
+ _FileSystemInterface.GetEa = GetEa;
+ _FileSystemInterface.SetEa = SetEa;
_FileSystemInterfacePtr = Marshal.AllocHGlobal(FileSystemInterface.Size);
Marshal.StructureToPtr(_FileSystemInterface, _FileSystemInterfacePtr, false);
diff --git a/src/dotnet/Interop.cs b/src/dotnet/Interop.cs
index 3945a034..7bc38599 100644
--- a/src/dotnet/Interop.cs
+++ b/src/dotnet/Interop.cs
@@ -52,6 +52,7 @@ namespace Fsp.Interop
internal const UInt32 UmFileContextIsUserContext2 = 0x00010000;
internal const UInt32 UmFileContextIsFullContext = 0x00020000;
internal const UInt32 AllowOpenInKernelMode = 0x01000000;
+ internal const UInt32 CasePreservedExtendedAttributes = 0x02000000;
internal const int PrefixSize = 192;
internal const int FileSystemNameSize = 16;
@@ -270,6 +271,40 @@ namespace Fsp.Interop
}
}
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct FullEaInformation
+ {
+ internal const int EaNameSize = 16384 - 8;
+
+ internal UInt32 NextEntryOffset;
+ internal Byte Flags;
+ internal Byte EaNameLength;
+ internal UInt16 EaValueLength;
+ internal unsafe fixed Byte EaName[EaNameSize];
+
+ internal unsafe void Set(String Name, Byte[] Value, Boolean NeedEa)
+ {
+ int NameLength = 254 < Name.Length ? 254 : Name.Length;
+ int ValueLength = EaNameSize - Name.Length - 1 < Value.Length ?
+ EaNameSize - Name.Length - 1 : Value.Length;
+
+ NextEntryOffset = 0;
+ Flags = NeedEa ? (Byte)0x80/*FILE_NEED_EA*/ : (Byte)0;
+ EaNameLength = (Byte)NameLength;
+ EaValueLength = (UInt16)ValueLength;
+
+ fixed (Byte *P = EaName)
+ {
+ int I = 0, J = 0;
+ for (; NameLength > I; I++)
+ P[I] = (Byte)Name[I];
+ P[I++] = 0;
+ for (; ValueLength > J; J++)
+ P[I + J] = Value[J];
+ }
+ }
+ }
+
[StructLayout(LayoutKind.Sequential)]
internal struct FullContext
{
@@ -478,6 +513,42 @@ namespace Fsp.Interop
ref FullContext FullContext,
[MarshalAs(UnmanagedType.LPWStr)] String FileName,
[MarshalAs(UnmanagedType.U1)] Boolean DeleteFile);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate Int32 CreateEx(
+ IntPtr FileSystem,
+ [MarshalAs(UnmanagedType.LPWStr)] String FileName,
+ UInt32 CreateOptions,
+ UInt32 GrantedAccess,
+ UInt32 FileAttributes,
+ IntPtr SecurityDescriptor,
+ UInt64 AllocationSize,
+ IntPtr Ea,
+ UInt32 EaLength,
+ ref FullContext FullContext,
+ ref OpenFileInfo OpenFileInfo);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate Int32 OverwriteEx(
+ IntPtr FileSystem,
+ ref FullContext FullContext,
+ UInt32 FileAttributes,
+ [MarshalAs(UnmanagedType.U1)] Boolean ReplaceFileAttributes,
+ UInt64 AllocationSize,
+ IntPtr Ea,
+ UInt32 EaLength,
+ out FileInfo FileInfo);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate Int32 GetEa(
+ IntPtr FileSystem,
+ ref FullContext FullContext,
+ IntPtr Ea,
+ UInt32 EaLength,
+ out UInt32 PBytesTransferred);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ internal delegate Int32 SetEa(
+ IntPtr FileSystem,
+ ref FullContext FullContext,
+ IntPtr Ea,
+ UInt32 EaLength);
}
internal static int Size = IntPtr.Size * 64;
@@ -509,7 +580,11 @@ namespace Fsp.Interop
internal Proto.GetDirInfoByName GetDirInfoByName;
internal Proto.Control Control;
internal Proto.SetDelete SetDelete;
- /* NTSTATUS (*Reserved[37])(); */
+ internal Proto.CreateEx CreateEx;
+ internal Proto.OverwriteEx OverwriteEx;
+ internal Proto.GetEa GetEa;
+ internal Proto.SetEa SetEa;
+ /* NTSTATUS (*Reserved[33])(); */
}
[SuppressUnmanagedCodeSecurity]
@@ -604,6 +679,13 @@ namespace Fsp.Interop
out UInt32 PBytesTransferred);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.U1)]
+ internal delegate Boolean FspFileSystemAddEa(
+ IntPtr SingleEa,
+ IntPtr Ea,
+ UInt32 EaLength,
+ out UInt32 PBytesTransferred);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ [return: MarshalAs(UnmanagedType.U1)]
internal delegate Boolean FspFileSystemAcquireDirectoryBuffer(
ref IntPtr PDirBuffer,
[MarshalAs(UnmanagedType.U1)] Boolean Reset,
@@ -737,6 +819,7 @@ namespace Fsp.Interop
internal static Proto.FspFileSystemResolveReparsePoints FspFileSystemResolveReparsePoints;
internal static Proto.FspFileSystemCanReplaceReparsePoint _FspFileSystemCanReplaceReparsePoint;
internal static Proto.FspFileSystemAddStreamInfo _FspFileSystemAddStreamInfo;
+ internal static Proto.FspFileSystemAddEa _FspFileSystemAddEa;
internal static Proto.FspFileSystemAcquireDirectoryBuffer FspFileSystemAcquireDirectoryBuffer;
internal static Proto.FspFileSystemFillDirectoryBuffer FspFileSystemFillDirectoryBuffer;
internal static Proto.FspFileSystemReleaseDirectoryBuffer FspFileSystemReleaseDirectoryBuffer;
@@ -806,6 +889,62 @@ namespace Fsp.Interop
return _FspFileSystemAddStreamInfo(IntPtr.Zero, Buffer, Length, out PBytesTransferred);
}
+ internal delegate Int32 EnumerateEa(
+ Object FileNode,
+ Object FileDesc,
+ ref Object Context,
+ String EaName,
+ Byte[] EaValue,
+ Boolean NeedEa);
+ internal static unsafe Int32 FspFileSystemEnumerateEa(
+ Object FileNode,
+ Object FileDesc,
+ EnumerateEa EnumerateEa,
+ IntPtr Ea,
+ UInt32 EaLength)
+ {
+ Object Context = null;
+ FullEaInformation *P = (FullEaInformation *)Ea;
+ FullEaInformation *EndP = (FullEaInformation *)(Ea.ToInt64() + EaLength);
+ Int32 Result;
+ Result = 0/*STATUS_SUCCESS*/;
+ for (; EndP > P;
+ P = 0 != P->NextEntryOffset ?
+ (FullEaInformation *)(((IntPtr)P).ToInt64() + P->NextEntryOffset) :
+ EndP)
+ {
+ String EaName = Marshal.PtrToStringAnsi((IntPtr)P->EaName, P->EaNameLength);
+ Byte[] EaValue = null;
+ if (0 != P->EaValueLength)
+ {
+ EaValue = new Byte[P->EaValueLength];
+ Marshal.Copy((IntPtr)(((IntPtr)P->EaName).ToInt64() + P->EaNameLength + 1),
+ EaValue, 0, P->EaValueLength);
+ }
+ Boolean NeedEa = 0 != (0x80/*FILE_NEED_EA*/ & P->Flags);
+ Result = EnumerateEa(FileNode, FileDesc, ref Context, EaName, EaValue, NeedEa);
+ if (0 > Result)
+ break;
+ }
+ return Result;
+ }
+ internal static unsafe Boolean FspFileSystemAddEa(
+ ref FullEaInformation EaInfo,
+ IntPtr Buffer,
+ UInt32 Length,
+ out UInt32 PBytesTransferred)
+ {
+ fixed (FullEaInformation *P = &EaInfo)
+ return _FspFileSystemAddEa((IntPtr)P, Buffer, Length, out PBytesTransferred);
+ }
+ internal static unsafe Boolean FspFileSystemEndEa(
+ IntPtr Buffer,
+ UInt32 Length,
+ out UInt32 PBytesTransferred)
+ {
+ return _FspFileSystemAddEa(IntPtr.Zero, Buffer, Length, out PBytesTransferred);
+ }
+
internal unsafe static Object GetUserContext(
IntPtr NativePtr)
{
@@ -1074,6 +1213,7 @@ namespace Fsp.Interop
FspFileSystemResolveReparsePoints = GetEntryPoint(Module);
_FspFileSystemCanReplaceReparsePoint = GetEntryPoint(Module);
_FspFileSystemAddStreamInfo = GetEntryPoint(Module);
+ _FspFileSystemAddEa = GetEntryPoint(Module);
FspFileSystemAcquireDirectoryBuffer = GetEntryPoint(Module);
FspFileSystemFillDirectoryBuffer = GetEntryPoint(Module);
FspFileSystemReleaseDirectoryBuffer = GetEntryPoint(Module);
diff --git a/src/sys/create.c b/src/sys/create.c
index dabfe06e..1ce68201 100644
--- a/src/sys/create.c
+++ b/src/sys/create.c
@@ -278,7 +278,7 @@ static NTSTATUS FspFsvolCreateNoLock(
ACCESS_MASK GrantedAccess = AccessState->PreviouslyGrantedAccess;
USHORT ShareAccess = IrpSp->Parameters.Create.ShareAccess;
PFILE_FULL_EA_INFORMATION EaBuffer = Irp->AssociatedIrp.SystemBuffer;
- //ULONG EaLength = IrpSp->Parameters.Create.EaLength;
+ ULONG EaLength = IrpSp->Parameters.Create.EaLength;
ULONG Flags = IrpSp->Flags;
KPROCESSOR_MODE RequestorMode =
FlagOn(Flags, SL_FORCE_ACCESS_CHECK) ? UserMode : Irp->RequestorMode;
@@ -302,9 +302,22 @@ static NTSTATUS FspFsvolCreateNoLock(
if (FlagOn(CreateOptions, FILE_OPEN_BY_FILE_ID))
return STATUS_NOT_IMPLEMENTED;
- /* no EA support currently */
+ /* was an EA buffer specified? */
if (0 != EaBuffer)
- return STATUS_EAS_NOT_SUPPORTED;
+ {
+ /* does the file system support EA? */
+ if (!FsvolDeviceExtension->VolumeParams.ExtendedAttributes)
+ return STATUS_EAS_NOT_SUPPORTED;
+
+ /* do we need EA knowledge? */
+ if (FlagOn(CreateOptions, FILE_NO_EA_KNOWLEDGE))
+ return STATUS_ACCESS_DENIED;
+
+ /* is the EA buffer valid? */
+ Result = FspEaBufferAndNamesValid(EaBuffer, EaLength, (PULONG)&Irp->IoStatus.Information);
+ if (!NT_SUCCESS(Result))
+ return Result;
+ }
/* cannot open a paging file */
if (FlagOn(Flags, SL_OPEN_PAGING_FILE))
@@ -541,6 +554,10 @@ static NTSTATUS FspFsvolCreateNoLock(
SecurityDescriptorSize = 0;
FileAttributes = 0;
+ /* cannot set EA on named stream */
+ EaBuffer = 0;
+ EaLength = 0;
+
/* remember the main file node */
ASSERT(0 == FileNode->MainFileNode);
FileNode->MainFileNode = FileDesc->MainFileObject->FsContext;
@@ -558,7 +575,9 @@ static NTSTATUS FspFsvolCreateNoLock(
}
/* create the user-mode file system request */
- Result = FspIopCreateRequestEx(Irp, &FileNode->FileName, SecurityDescriptorSize,
+ Result = FspIopCreateRequestEx(Irp, &FileNode->FileName,
+ 0 != EaBuffer ?
+ FSP_FSCTL_DEFAULT_ALIGN_UP(SecurityDescriptorSize) + EaLength : SecurityDescriptorSize,
FspFsvolCreateRequestFini, &Request);
if (!NT_SUCCESS(Result))
{
@@ -584,19 +603,22 @@ static NTSTATUS FspFsvolCreateNoLock(
FspIopRequestContext(Request, RequestFileDesc) = FileDesc;
/* populate the Create request */
+#define NEXTOFS(B) ((B).Offset + FSP_FSCTL_DEFAULT_ALIGN_UP((B).Size))
Request->Kind = FspFsctlTransactCreateKind;
Request->Req.Create.CreateOptions = CreateOptions;
Request->Req.Create.FileAttributes = FileAttributes;
- Request->Req.Create.SecurityDescriptor.Offset = 0 == SecurityDescriptorSize ? 0 :
- FSP_FSCTL_DEFAULT_ALIGN_UP(Request->FileName.Size);
+ Request->Req.Create.SecurityDescriptor.Offset = 0 != SecurityDescriptorSize ?
+ NEXTOFS(Request->FileName) : 0;
Request->Req.Create.SecurityDescriptor.Size = (UINT16)SecurityDescriptorSize;
Request->Req.Create.AllocationSize = AllocationSize;
Request->Req.Create.AccessToken = 0;
Request->Req.Create.DesiredAccess = DesiredAccess;
Request->Req.Create.GrantedAccess = GrantedAccess;
Request->Req.Create.ShareAccess = ShareAccess;
- Request->Req.Create.Ea.Offset = 0;
- Request->Req.Create.Ea.Size = 0;
+ Request->Req.Create.Ea.Offset = 0 != EaBuffer ?
+ (0 != Request->Req.Create.SecurityDescriptor.Offset ?
+ NEXTOFS(Request->Req.Create.SecurityDescriptor) : NEXTOFS(Request->FileName)) : 0;
+ Request->Req.Create.Ea.Size = 0 != EaBuffer ? (UINT16)EaLength : 0;
Request->Req.Create.UserMode = UserMode == RequestorMode;
Request->Req.Create.HasTraversePrivilege = HasTraversePrivilege;
Request->Req.Create.HasBackupPrivilege = HasBackupPrivilege;
@@ -605,6 +627,7 @@ static NTSTATUS FspFsvolCreateNoLock(
Request->Req.Create.CaseSensitive = CaseSensitive;
Request->Req.Create.HasTrailingBackslash = HasTrailingBackslash;
Request->Req.Create.NamedStream = MainFileName.Length;
+#undef APPEND
Request->Req.Create.AcceptsSecurityDescriptor = 0 == Request->Req.Create.NamedStream &&
!!FsvolDeviceExtension->VolumeParams.AllowOpenInKernelMode;
@@ -618,6 +641,11 @@ static NTSTATUS FspFsvolCreateNoLock(
RtlCopyMemory(Request->Buffer + Request->Req.Create.SecurityDescriptor.Offset,
SecurityDescriptor, SecurityDescriptorSize);
+ /* copy the EA buffer (if any) into the request */
+ if (0 != EaBuffer)
+ RtlCopyMemory(Request->Buffer + Request->Req.Create.Ea.Offset,
+ EaBuffer, EaLength);
+
/* fix FileNode->FileName if we are doing SL_OPEN_TARGET_DIRECTORY */
if (Request->Req.Create.OpenTargetDirectory)
{
@@ -1081,6 +1109,7 @@ NTSTATUS FspFsvolCreateComplete(
}
PVOID RequestDeviceObjectValue = FspIopRequestContext(Request, RequestDeviceObject);
+ FSP_FSCTL_TRANSACT_BUF Ea = Request->Req.Create.Ea;
/* disassociate the FileDesc momentarily from the Request */
FspIopRequestContext(Request, RequestDeviceObject) = 0;
@@ -1101,6 +1130,7 @@ NTSTATUS FspFsvolCreateComplete(
Request->Req.Overwrite.FileAttributes = FileAttributes;
Request->Req.Overwrite.AllocationSize = AllocationSize;
Request->Req.Overwrite.Supersede = FILE_SUPERSEDED == Response->IoStatus.Information;
+ Request->Req.Overwrite.Ea = Ea;
/*
* Post it as BestEffort.
@@ -1141,8 +1171,16 @@ NTSTATUS FspFsvolCreateComplete(
if (0 == FileNode->MainFileNode)
FspFileNodeOverwriteStreams(FileNode);
FspFileNodeSetFileInfo(FileNode, FileObject, &Response->Rsp.Overwrite.FileInfo, TRUE);
+ if (0 == FileNode->MainFileNode && FsvolDeviceExtension->VolumeParams.ExtendedAttributes)
+ {
+ /* invalidate any existing EA and increment the EA change count */
+ FspFileNodeSetEa(FileNode, 0, 0);
+ FileNode->EaChangeCount++;
+ }
FspFileNodeNotifyChange(FileNode,
- FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE,
+ FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE |
+ (0 == FileNode->MainFileNode && FsvolDeviceExtension->VolumeParams.ExtendedAttributes ?
+ FILE_NOTIFY_CHANGE_EA : 0),
FILE_ACTION_MODIFIED,
FALSE);
diff --git a/src/sys/device.c b/src/sys/device.c
index d6dda01c..edf74336 100644
--- a/src/sys/device.c
+++ b/src/sys/device.c
@@ -317,7 +317,7 @@ static NTSTATUS FspFsvolDeviceInit(PDEVICE_OBJECT DeviceObject)
NTSTATUS Result;
FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = FspFsvolDeviceExtension(DeviceObject);
LARGE_INTEGER IrpTimeout;
- LARGE_INTEGER SecurityTimeout, DirInfoTimeout, StreamInfoTimeout;
+ LARGE_INTEGER SecurityTimeout, DirInfoTimeout, StreamInfoTimeout, EaTimeout;
/*
* Volume device initialization is a mess, because of the different ways of
@@ -379,6 +379,16 @@ static NTSTATUS FspFsvolDeviceInit(PDEVICE_OBJECT DeviceObject)
return Result;
FsvolDeviceExtension->InitDoneStrm = 1;
+ /* create our EA meta cache */
+ EaTimeout.QuadPart = FspTimeoutFromMillis(FsvolDeviceExtension->VolumeParams.EaTimeout);
+ /* convert millis to nanos */
+ Result = FspMetaCacheCreate(
+ FspFsvolDeviceEaCacheCapacity, FspFsvolDeviceEaCacheItemSizeMax, &EaTimeout,
+ &FsvolDeviceExtension->EaCache);
+ if (!NT_SUCCESS(Result))
+ return Result;
+ FsvolDeviceExtension->InitDoneEa = 1;
+
/* initialize the FSRTL Notify mechanism */
Result = FspNotifyInitializeSync(&FsvolDeviceExtension->NotifySync);
if (!NT_SUCCESS(Result))
@@ -449,6 +459,10 @@ static VOID FspFsvolDeviceFini(PDEVICE_OBJECT DeviceObject)
FspNotifyUninitializeSync(&FsvolDeviceExtension->NotifySync);
}
+ /* delete the EA meta cache */
+ if (FsvolDeviceExtension->InitDoneEa)
+ FspMetaCacheDelete(FsvolDeviceExtension->EaCache);
+
/* delete the stream info meta cache */
if (FsvolDeviceExtension->InitDoneStrm)
FspMetaCacheDelete(FsvolDeviceExtension->StreamInfoCache);
diff --git a/src/sys/driver.h b/src/sys/driver.h
index fd00055f..391e1be9 100644
--- a/src/sys/driver.h
+++ b/src/sys/driver.h
@@ -452,12 +452,17 @@ enum
BOOLEAN FspFileNameIsValid(PUNICODE_STRING Path, ULONG MaxComponentLength,
PUNICODE_STRING StreamPart, PULONG StreamType);
BOOLEAN FspFileNameIsValidPattern(PUNICODE_STRING Pattern, ULONG MaxComponentLength);
+BOOLEAN FspEaNameIsValid(PSTRING Name);
VOID FspFileNameSuffix(PUNICODE_STRING Path, PUNICODE_STRING Remain, PUNICODE_STRING Suffix);
#if 0
NTSTATUS FspFileNameUpcase(
PUNICODE_STRING DestinationName,
PUNICODE_STRING SourceName,
PCWCH UpcaseTable);
+VOID FspEaNameUpcase(
+ PSTRING DestinationName,
+ PSTRING SourceName,
+ PCWCH UpcaseTable);
LONG FspFileNameCompare(
PUNICODE_STRING Name1,
PUNICODE_STRING Name2,
@@ -470,6 +475,7 @@ BOOLEAN FspFileNameIsPrefix(
PCWCH UpcaseTable);
#else
#define FspFileNameUpcase(D,S,U) (ASSERT(0 == (U)), RtlUpcaseUnicodeString(D,S,FALSE))
+#define FspEaNameUpcase(D,S,U) (ASSERT(0 == (U)), RtlUpperString(D,S))
#define FspFileNameCompare(N1,N2,I,U) (ASSERT(0 == (U)), RtlCompareUnicodeString(N1,N2,I))
#define FspFileNameIsPrefix(N1,N2,I,U) (ASSERT(0 == (U)), RtlPrefixUnicodeString(N1,N2,I))
#endif
@@ -509,6 +515,10 @@ NTSTATUS FspCcFlushCache(PSECTION_OBJECT_POINTERS SectionObjectPointer,
NTSTATUS FspQuerySecurityDescriptorInfo(SECURITY_INFORMATION SecurityInformation,
PSECURITY_DESCRIPTOR SecurityDescriptor, PULONG PLength,
PSECURITY_DESCRIPTOR ObjectsSecurityDescriptor);
+NTSTATUS FspEaBufferAndNamesValid(
+ PFILE_FULL_EA_INFORMATION Buffer,
+ ULONG Length,
+ PULONG PErrorOffset);
NTSTATUS FspNotifyInitializeSync(PNOTIFY_SYNC *NotifySync);
NTSTATUS FspNotifyFullChangeDirectory(
PNOTIFY_SYNC NotifySync,
@@ -567,6 +577,8 @@ NTSTATUS FspOplockFsctrl(
FspNotifyFullChangeDirectory(NS, NL, FC, 0, 0, FALSE, 0, 0, 0, 0)
#define FspNotifyReportChange(NS, NL, FN, FO, NP, F, A)\
FspNotifyFullReportChange(NS, NL, (PSTRING)(FN), FO, 0, (PSTRING)(NP), F, A, 0)
+#define FSP_NEXT_EA(Ea, EaEnd) \
+ (0 != (Ea)->NextEntryOffset ? (PVOID)((PUINT8)(Ea) + (Ea)->NextEntryOffset) : (EaEnd))
/* utility: synchronous work queue */
typedef struct
@@ -997,6 +1009,8 @@ enum
FspFsvolDeviceDirInfoCacheItemSizeMax = FSP_FSCTL_ALIGN_UP(16384, PAGE_SIZE),
FspFsvolDeviceStreamInfoCacheCapacity = 100,
FspFsvolDeviceStreamInfoCacheItemSizeMax = FSP_FSCTL_ALIGN_UP(16384, PAGE_SIZE),
+ FspFsvolDeviceEaCacheCapacity = 100,
+ FspFsvolDeviceEaCacheItemSizeMax = FSP_FSCTL_ALIGN_UP(16384, PAGE_SIZE),
};
typedef struct
{
@@ -1029,7 +1043,7 @@ typedef struct
typedef struct
{
FSP_DEVICE_EXTENSION Base;
- UINT32 InitDoneFsvrt:1, InitDoneIoq:1, InitDoneSec:1, InitDoneDir:1, InitDoneStrm:1,
+ UINT32 InitDoneFsvrt:1, InitDoneIoq:1, InitDoneSec:1, InitDoneDir:1, InitDoneStrm:1, InitDoneEa:1,
InitDoneCtxTab:1, InitDoneTimer:1, InitDoneInfo:1, InitDoneNotify:1, InitDoneStat:1;
PDEVICE_OBJECT FsctlDeviceObject;
PDEVICE_OBJECT FsvrtDeviceObject;
@@ -1043,6 +1057,7 @@ typedef struct
FSP_META_CACHE *SecurityCache;
FSP_META_CACHE *DirInfoCache;
FSP_META_CACHE *StreamInfoCache;
+ FSP_META_CACHE *EaCache;
KSPIN_LOCK ExpirationLock;
WORK_QUEUE_ITEM ExpirationWorkItem;
BOOLEAN ExpirationInProgress;
@@ -1271,6 +1286,9 @@ typedef struct FSP_FILE_NODE
ULONG SecurityChangeNumber;
ULONG DirInfoChangeNumber;
ULONG StreamInfoChangeNumber;
+ UINT64 Ea;
+ ULONG EaChangeNumber;
+ ULONG EaChangeCount;
BOOLEAN TruncateOnClose;
FILE_LOCK FileLock;
#if (NTDDI_VERSION < NTDDI_WIN8)
@@ -1309,6 +1327,8 @@ typedef struct
UNICODE_STRING DirectoryMarker;
UINT64 DirInfo;
ULONG DirInfoCacheHint;
+ ULONG EaIndex;
+ ULONG EaChangeCount;
/* stream support */
HANDLE MainFileHandle;
PFILE_OBJECT MainFileObject;
@@ -1439,6 +1459,17 @@ ULONG FspFileNodeStreamInfoChangeNumber(FSP_FILE_NODE *FileNode)
return FileNode->StreamInfoChangeNumber;
}
VOID FspFileNodeInvalidateStreamInfo(FSP_FILE_NODE *FileNode);
+BOOLEAN FspFileNodeReferenceEa(FSP_FILE_NODE *FileNode, PCVOID *PBuffer, PULONG PSize);
+VOID FspFileNodeSetEa(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size);
+BOOLEAN FspFileNodeTrySetEa(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size,
+ ULONG EaChangeNumber);
+static inline
+ULONG FspFileNodeEaChangeNumber(FSP_FILE_NODE *FileNode)
+{
+ if (0 != FileNode->MainFileNode)
+ FileNode = FileNode->MainFileNode;
+ return FileNode->EaChangeNumber;
+}
VOID FspFileNodeNotifyChange(FSP_FILE_NODE *FileNode, ULONG Filter, ULONG Action,
BOOLEAN InvalidateCaches);
NTSTATUS FspFileNodeProcessLockIrp(FSP_FILE_NODE *FileNode, PIRP Irp);
@@ -1471,6 +1502,7 @@ NTSTATUS FspMainFileClose(
#define FspFileNodeDereferenceSecurity(P) FspMetaCacheDereferenceItemBuffer(P)
#define FspFileNodeDereferenceDirInfo(P) FspMetaCacheDereferenceItemBuffer(P)
#define FspFileNodeDereferenceStreamInfo(P) FspMetaCacheDereferenceItemBuffer(P)
+#define FspFileNodeDereferenceEa(P) FspMetaCacheDereferenceItemBuffer(P)
#define FspFileNodeUnlockAll(N,F,P) FsRtlFastUnlockAll(&(N)->FileLock, F, P, N)
#if (NTDDI_VERSION < NTDDI_WIN8)
#define FspFileNodeAddrOfOplock(N) (&(N)->Oplock)
diff --git a/src/sys/ea.c b/src/sys/ea.c
index a128dff7..0b5462b4 100644
--- a/src/sys/ea.c
+++ b/src/sys/ea.c
@@ -21,30 +21,385 @@
#include
+static VOID FspFsvolQueryEaGetCopy(
+ BOOLEAN CasePreservedExtendedAttributes,
+ BOOLEAN ReturnSingleEntry,
+ PFILE_GET_EA_INFORMATION GetBufBgn, ULONG GetSize,
+ PFILE_FULL_EA_INFORMATION SrcBufBgn, ULONG SrcSize,
+ PFILE_FULL_EA_INFORMATION DstBufBgn, ULONG DstSize,
+ PIO_STATUS_BLOCK IoStatus);
+static VOID FspFsvolQueryEaIndexCopy(
+ BOOLEAN CasePreservedExtendedAttributes,
+ BOOLEAN ReturnSingleEntry,
+ BOOLEAN IndexSpecified, PULONG PEaIndex,
+ PFILE_FULL_EA_INFORMATION SrcBufBgn, ULONG SrcSize,
+ PFILE_FULL_EA_INFORMATION DstBufBgn, ULONG DstSize,
+ PIO_STATUS_BLOCK IoStatus);
+static VOID FspFsvolQueryEaCopy(
+ BOOLEAN CasePreservedExtendedAttributes,
+ PIO_STACK_LOCATION IrpSp,
+ PFILE_FULL_EA_INFORMATION SrcBufBgn, ULONG SrcSize,
+ PFILE_FULL_EA_INFORMATION DstBufBgn, ULONG DstSize,
+ PIO_STATUS_BLOCK IoStatus);
static NTSTATUS FspFsvolQueryEa(
PDEVICE_OBJECT DeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp);
FSP_IOCMPL_DISPATCH FspFsvolQueryEaComplete;
+static FSP_IOP_REQUEST_FINI FspFsvolQueryEaRequestFini;
static NTSTATUS FspFsvolSetEa(
PDEVICE_OBJECT DeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp);
FSP_IOCMPL_DISPATCH FspFsvolSetEaComplete;
+static FSP_IOP_REQUEST_FINI FspFsvolSetEaRequestFini;
FSP_DRIVER_DISPATCH FspQueryEa;
FSP_DRIVER_DISPATCH FspSetEa;
#ifdef ALLOC_PRAGMA
+#pragma alloc_text(PAGE, FspFsvolQueryEaGetCopy)
+#pragma alloc_text(PAGE, FspFsvolQueryEaIndexCopy)
#pragma alloc_text(PAGE, FspFsvolQueryEa)
#pragma alloc_text(PAGE, FspFsvolQueryEaComplete)
+#pragma alloc_text(PAGE, FspFsvolQueryEaRequestFini)
#pragma alloc_text(PAGE, FspFsvolSetEa)
#pragma alloc_text(PAGE, FspFsvolSetEaComplete)
+#pragma alloc_text(PAGE, FspFsvolSetEaRequestFini)
#pragma alloc_text(PAGE, FspQueryEa)
#pragma alloc_text(PAGE, FspSetEa)
#endif
-static NTSTATUS FspFsvolQueryEa(
- PDEVICE_OBJECT DeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp)
+enum
+{
+ /* QueryEa */
+ RequestFileNode = 0,
+ RequestEaChangeNumber = 1,
+
+ /* SetEa */
+ //RequestFileNode = 0,
+};
+
+static VOID FspFsvolQueryEaGetCopy(
+ BOOLEAN CasePreservedExtendedAttributes,
+ BOOLEAN ReturnSingleEntry,
+ PFILE_GET_EA_INFORMATION GetBufBgn, ULONG GetSize,
+ PFILE_FULL_EA_INFORMATION SrcBufBgn, ULONG SrcSize,
+ PFILE_FULL_EA_INFORMATION DstBufBgn, ULONG DstSize,
+ PIO_STATUS_BLOCK IoStatus)
{
PAGED_CODE();
- return STATUS_INVALID_DEVICE_REQUEST;
+ PFILE_GET_EA_INFORMATION GetBuf, GetBufEnd = (PVOID)((PUINT8)GetBufBgn + GetSize);
+ PFILE_GET_EA_INFORMATION Get;
+ PFILE_FULL_EA_INFORMATION SrcBuf, SrcBufEnd = (PVOID)((PUINT8)SrcBufBgn + SrcSize);
+ PFILE_FULL_EA_INFORMATION DstBuf, DstBufEnd = (PVOID)((PUINT8)DstBufBgn + DstSize);
+ PFILE_FULL_EA_INFORMATION PrevDstBuf;
+ PFILE_FULL_EA_INFORMATION Src;
+ STRING GetName, Name;
+ ULONG CopyLength;
+
+ IoStatus->Information = 0;
+
+ DstBuf = DstBufBgn, PrevDstBuf = 0;
+ for (GetBuf = GetBufBgn; GetBufEnd > GetBuf; GetBuf = FSP_NEXT_EA(GetBuf, GetBufEnd))
+ {
+ GetName.Length = GetName.MaximumLength = GetBuf->EaNameLength;
+ GetName.Buffer = GetBuf->EaName;
+
+ /* ignore duplicate names */
+ for (Get = GetBufBgn; GetBuf > Get; Get = (PVOID)((PUINT8)Get + Get->NextEntryOffset))
+ {
+ Name.Length = Name.MaximumLength = Get->EaNameLength;
+ Name.Buffer = Get->EaName;
+
+ if (RtlEqualString(&GetName, &Name, TRUE/* always case-insensitive */))
+ break;
+ }
+ if (GetBuf > Get)
+ continue;
+
+ if (!FspEaNameIsValid(&GetName))
+ {
+ IoStatus->Status = STATUS_INVALID_EA_NAME;
+ IoStatus->Information = (ULONG)((PUINT8)GetBuf - (PUINT8)GetBufBgn);
+ return;
+ }
+
+ Src = 0;
+ for (SrcBuf = SrcBufBgn; SrcBufEnd > SrcBuf; SrcBuf = FSP_NEXT_EA(SrcBuf, SrcBufEnd))
+ {
+ Name.Length = Name.MaximumLength = SrcBuf->EaNameLength;
+ Name.Buffer = SrcBuf->EaName;
+
+ if (RtlEqualString(&GetName, &Name, TRUE/* always case-insensitive */))
+ {
+ Src = SrcBuf;
+ break;
+ }
+ }
+
+ if (0 != Src)
+ CopyLength = FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) +
+ Src->EaNameLength + 1 + Src->EaValueLength;
+ else
+ CopyLength = FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) +
+ GetBuf->EaNameLength + 1;
+
+ if ((PUINT8)DstBuf + CopyLength > (PUINT8)DstBufEnd)
+ {
+ IoStatus->Status = STATUS_BUFFER_OVERFLOW;
+ IoStatus->Information = 0;
+ return;
+ }
+
+ if (0 != Src)
+ RtlMoveMemory(DstBuf, Src, CopyLength);
+ else
+ {
+ DstBuf->Flags = 0;
+ DstBuf->EaNameLength = GetBuf->EaNameLength;
+ DstBuf->EaValueLength = 0;
+ RtlCopyMemory(DstBuf->EaName, GetBuf->EaName, GetBuf->EaNameLength + 1);
+ }
+ DstBuf->NextEntryOffset = 0;
+ if (!CasePreservedExtendedAttributes)
+ {
+ Name.Length = Name.MaximumLength = DstBuf->EaNameLength;
+ Name.Buffer = DstBuf->EaName;
+ FspEaNameUpcase(&Name, &Name, 0);
+ }
+ if (0 != PrevDstBuf)
+ PrevDstBuf->NextEntryOffset = (ULONG)((PUINT8)DstBuf - (PUINT8)PrevDstBuf);
+ PrevDstBuf = DstBuf;
+ IoStatus->Information = (ULONG)((PUINT8)DstBuf - (PUINT8)DstBufBgn + CopyLength);
+ DstBuf = (PVOID)((PUINT8)DstBuf + FSP_FSCTL_ALIGN_UP(CopyLength, sizeof(ULONG)));
+
+ if (ReturnSingleEntry)
+ break;
+ }
+
+ IoStatus->Status = STATUS_SUCCESS;
+}
+
+static VOID FspFsvolQueryEaIndexCopy(
+ BOOLEAN CasePreservedExtendedAttributes,
+ BOOLEAN ReturnSingleEntry,
+ BOOLEAN IndexSpecified, PULONG PEaIndex,
+ PFILE_FULL_EA_INFORMATION SrcBufBgn, ULONG SrcSize,
+ PFILE_FULL_EA_INFORMATION DstBufBgn, ULONG DstSize,
+ PIO_STATUS_BLOCK IoStatus)
+{
+ PAGED_CODE();
+
+ ULONG EaIndex = 1;
+ PFILE_FULL_EA_INFORMATION SrcBuf, SrcBufEnd = (PVOID)((PUINT8)SrcBufBgn + SrcSize);
+ PFILE_FULL_EA_INFORMATION DstBuf, DstBufEnd = (PVOID)((PUINT8)DstBufBgn + DstSize);
+ PFILE_FULL_EA_INFORMATION PrevDstBuf;
+ STRING Name;
+ ULONG CopyLength;
+
+ if (IndexSpecified && 0 == *PEaIndex)
+ {
+ IoStatus->Status = STATUS_NONEXISTENT_EA_ENTRY;
+ IoStatus->Information = 0;
+ return;
+ }
+
+ for (SrcBuf = SrcBufBgn; EaIndex < *PEaIndex && SrcBufEnd > SrcBuf;
+ SrcBuf = FSP_NEXT_EA(SrcBuf, SrcBufEnd))
+ EaIndex++;
+
+ IoStatus->Information = 0;
+
+ DstBuf = DstBufBgn, PrevDstBuf = 0;
+ for (; SrcBufEnd > SrcBuf; SrcBuf = FSP_NEXT_EA(SrcBuf, SrcBufEnd))
+ {
+ CopyLength = FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) +
+ ((PFILE_FULL_EA_INFORMATION)SrcBuf)->EaNameLength + 1 +
+ ((PFILE_FULL_EA_INFORMATION)SrcBuf)->EaValueLength;
+
+ if ((PUINT8)DstBuf + CopyLength > (PUINT8)DstBufEnd)
+ {
+ if (0 != PrevDstBuf)
+ break;
+ IoStatus->Status = STATUS_BUFFER_TOO_SMALL;
+ IoStatus->Information = 0;
+ return;
+ }
+
+ RtlMoveMemory(DstBuf, SrcBuf, CopyLength);
+ DstBuf->NextEntryOffset = 0;
+ if (!CasePreservedExtendedAttributes)
+ {
+ Name.Length = Name.MaximumLength = DstBuf->EaNameLength;
+ Name.Buffer = DstBuf->EaName;
+ FspEaNameUpcase(&Name, &Name, 0);
+ }
+ if (0 != PrevDstBuf)
+ PrevDstBuf->NextEntryOffset = (ULONG)((PUINT8)DstBuf - (PUINT8)PrevDstBuf);
+ PrevDstBuf = DstBuf;
+ IoStatus->Information = (ULONG)((PUINT8)DstBuf - (PUINT8)DstBufBgn + CopyLength);
+ DstBuf = (PVOID)((PUINT8)DstBuf + FSP_FSCTL_ALIGN_UP(CopyLength, sizeof(ULONG)));
+
+ EaIndex++;
+
+ if (ReturnSingleEntry)
+ break;
+ }
+
+ if (IndexSpecified)
+ {
+ if (0 != PrevDstBuf)
+ {
+ *PEaIndex = EaIndex;
+ IoStatus->Status = SrcBufEnd > SrcBuf && !ReturnSingleEntry ?
+ STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS;
+ }
+ else
+ {
+ IoStatus->Status = *PEaIndex == EaIndex ?
+ STATUS_NO_MORE_EAS : STATUS_NONEXISTENT_EA_ENTRY;
+ IoStatus->Information = 0;
+ }
+ }
+ else
+ {
+ if (0 != PrevDstBuf)
+ {
+ *PEaIndex = EaIndex;
+ IoStatus->Status = SrcBufEnd > SrcBuf && !ReturnSingleEntry ?
+ STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS;
+ }
+ else
+ {
+ IoStatus->Status = SrcBufBgn == SrcBuf ?
+ STATUS_NO_EAS_ON_FILE : STATUS_NO_MORE_EAS;
+ IoStatus->Information = 0;
+ }
+ }
+}
+
+static VOID FspFsvolQueryEaCopy(
+ BOOLEAN CasePreservedExtendedAttributes,
+ PIO_STACK_LOCATION IrpSp,
+ PFILE_FULL_EA_INFORMATION SrcBufBgn, ULONG SrcSize,
+ PFILE_FULL_EA_INFORMATION DstBufBgn, ULONG DstSize,
+ PIO_STATUS_BLOCK IoStatus)
+{
+ PAGED_CODE();
+
+ PFILE_OBJECT FileObject = IrpSp->FileObject;
+ FSP_FILE_NODE *FileNode = FileObject->FsContext;
+ FSP_FILE_DESC *FileDesc = FileObject->FsContext2;
+ BOOLEAN RestartScan = BooleanFlagOn(IrpSp->Flags, SL_RESTART_SCAN);
+ BOOLEAN IndexSpecified = BooleanFlagOn(IrpSp->Flags, SL_INDEX_SPECIFIED);
+ BOOLEAN ReturnSingleEntry = BooleanFlagOn(IrpSp->Flags, SL_RETURN_SINGLE_ENTRY);
+ PFILE_GET_EA_INFORMATION EaList = IrpSp->Parameters.QueryEa.EaList;
+ ULONG EaListLength = IrpSp->Parameters.QueryEa.EaListLength;
+ ULONG EaIndex;
+
+ ASSERT(FileNode == FileDesc->FileNode);
+
+ if (0 != EaList)
+ {
+ FspFsvolQueryEaGetCopy(
+ CasePreservedExtendedAttributes,
+ ReturnSingleEntry,
+ EaList, EaListLength,
+ SrcBufBgn, SrcSize,
+ DstBufBgn, DstSize,
+ IoStatus);
+ }
+ else
+ {
+ if (!IndexSpecified &&
+ !RestartScan &&
+ 0 != FileDesc->EaIndex &&
+ FileNode->EaChangeCount != FileDesc->EaChangeCount)
+ {
+ IoStatus->Status = STATUS_EA_CORRUPT_ERROR;
+ IoStatus->Information = 0;
+ return;
+ }
+ if (IndexSpecified)
+ EaIndex = IrpSp->Parameters.QueryEa.EaIndex;
+ else if (RestartScan)
+ EaIndex = 0;
+ else
+ EaIndex = FileDesc->EaIndex;
+ FspFsvolQueryEaIndexCopy(
+ CasePreservedExtendedAttributes,
+ ReturnSingleEntry,
+ IndexSpecified, &EaIndex,
+ SrcBufBgn, SrcSize,
+ DstBufBgn, DstSize,
+ IoStatus);
+ if (NT_SUCCESS(IoStatus->Status) || STATUS_BUFFER_OVERFLOW == IoStatus->Status)
+ {
+ FileDesc->EaIndex = EaIndex;
+ FileDesc->EaChangeCount = FileNode->EaChangeCount;
+ }
+ }
+}
+
+static NTSTATUS FspFsvolQueryEa(
+ PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp)
+{
+ PAGED_CODE();
+
+ /* is this a valid FileObject? */
+ if (!FspFileNodeIsValid(IrpSp->FileObject->FsContext))
+ return STATUS_INVALID_DEVICE_REQUEST;
+
+ NTSTATUS Result;
+ FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = FspFsvolDeviceExtension(FsvolDeviceObject);
+ PFILE_OBJECT FileObject = IrpSp->FileObject;
+ FSP_FILE_NODE *FileNode = FileObject->FsContext;
+ FSP_FILE_DESC *FileDesc = FileObject->FsContext2;
+ PVOID Buffer = Irp->UserBuffer;
+ ULONG Length = IrpSp->Parameters.QueryEa.Length;
+ PVOID EaBuffer;
+ ULONG EaBufferSize;
+ FSP_FSCTL_TRANSACT_REQ *Request;
+
+ ASSERT(FileNode == FileDesc->FileNode);
+
+ FspFileNodeAcquireExclusive(FileNode, Main);
+ if (FspFileNodeReferenceEa(FileNode, &EaBuffer, &EaBufferSize))
+ {
+ FspFsvolQueryEaCopy(
+ !!FsvolDeviceExtension->VolumeParams.CasePreservedExtendedAttributes,
+ IrpSp,
+ EaBuffer, EaBufferSize,
+ Buffer, Length,
+ &Irp->IoStatus);
+ FspFileNodeDereferenceEa(EaBuffer);
+ FspFileNodeRelease(FileNode, Main);
+
+ return Irp->IoStatus.Status;
+ }
+
+ FspFileNodeConvertExclusiveToShared(FileNode, Main);
+ FspFileNodeAcquireShared(FileNode, Pgio);
+
+ Result = FspBufferUserBuffer(Irp, Length, IoWriteAccess);
+ if (!NT_SUCCESS(Result))
+ {
+ FspFileNodeRelease(FileNode, Full);
+ return Result;
+ }
+
+ Result = FspIopCreateRequestEx(Irp, 0, 0, FspFsvolQueryEaRequestFini, &Request);
+ if (!NT_SUCCESS(Result))
+ {
+ FspFileNodeRelease(FileNode, Full);
+ return Result;
+ }
+
+ Request->Kind = FspFsctlTransactQueryEaKind;
+ Request->Req.QueryEa.UserContext = FileNode->UserContext;
+ Request->Req.QueryEa.UserContext2 = FileDesc->UserContext2;
+
+ FspFileNodeSetOwner(FileNode, Full, Request);
+ FspIopRequestContext(Request, RequestFileNode) = FileNode;
+
+ return FSP_STATUS_IOQ_POST;
}
NTSTATUS FspFsvolQueryEaComplete(
@@ -52,15 +407,147 @@ NTSTATUS FspFsvolQueryEaComplete(
{
FSP_ENTER_IOC(PAGED_CODE());
- FSP_LEAVE_IOC("%s", "");
+ if (!NT_SUCCESS(Response->IoStatus.Status))
+ {
+ Irp->IoStatus.Information = 0;
+ Result = Response->IoStatus.Status;
+ FSP_RETURN();
+ }
+
+ PDEVICE_OBJECT FsvolDeviceObject = IrpSp->DeviceObject;
+ FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = FspFsvolDeviceExtension(FsvolDeviceObject);
+ PFILE_OBJECT FileObject = IrpSp->FileObject;
+ FSP_FILE_NODE *FileNode = FileObject->FsContext;
+ PVOID Buffer = Irp->AssociatedIrp.SystemBuffer;
+ ULONG Length = IrpSp->Parameters.QueryEa.Length;
+ PVOID EaBuffer = 0;
+ ULONG EaBufferSize = 0;
+ FSP_FSCTL_TRANSACT_REQ *Request = FspIrpRequest(Irp);
+ BOOLEAN Success;
+
+ if (0 != FspIopRequestContext(Request, RequestFileNode))
+ {
+ /* check that the EA buffer we got back is valid */
+ if (Response->Buffer + Response->Rsp.QueryEa.Ea.Size >
+ (PUINT8)Response + Response->Size)
+ {
+ Irp->IoStatus.Information = 0;
+ Result = STATUS_EA_LIST_INCONSISTENT;
+ FSP_RETURN();
+ }
+ Irp->IoStatus.Information = 0;
+ Result = IoCheckEaBufferValidity((PVOID)Response->Buffer, Response->Rsp.QueryEa.Ea.Size,
+ (PULONG)&Irp->IoStatus.Information);
+ if (!NT_SUCCESS(Result))
+ FSP_RETURN();
+
+ FspIopRequestContext(Request, RequestEaChangeNumber) = (PVOID)
+ FspFileNodeEaChangeNumber(FileNode);
+ FspIopRequestContext(Request, RequestFileNode) = 0;
+
+ FspFileNodeReleaseOwner(FileNode, Full, Request);
+ }
+
+ Success = DEBUGTEST(90) && FspFileNodeTryAcquireExclusive(FileNode, Main);
+ if (!Success)
+ {
+ FspIopRetryCompleteIrp(Irp, Response, &Result);
+ FSP_RETURN();
+ }
+
+ Success = !FspFileNodeTrySetEa(FileNode,
+ Response->Buffer, Response->Rsp.QueryEa.Ea.Size,
+ (ULONG)(UINT_PTR)FspIopRequestContext(Request, RequestEaChangeNumber));
+ Success = Success && FspFileNodeReferenceEa(FileNode, &EaBuffer, &EaBufferSize);
+ if (Success)
+ {
+ FspFsvolQueryEaCopy(
+ !!FsvolDeviceExtension->VolumeParams.CasePreservedExtendedAttributes,
+ IrpSp,
+ EaBuffer, EaBufferSize,
+ Buffer, Length,
+ &Irp->IoStatus);
+ FspFileNodeDereferenceEa(EaBuffer);
+ }
+ else
+ {
+ EaBuffer = (PVOID)Response->Buffer;
+ EaBufferSize = Response->Rsp.QueryEa.Ea.Size;
+ FspFsvolQueryEaCopy(
+ !!FsvolDeviceExtension->VolumeParams.CasePreservedExtendedAttributes,
+ IrpSp,
+ EaBuffer, EaBufferSize,
+ Buffer, Length,
+ &Irp->IoStatus);
+ }
+
+ FspFileNodeRelease(FileNode, Main);
+
+ Result = Irp->IoStatus.Status;
+
+ FSP_LEAVE_IOC("FileObject=%p",
+ IrpSp->FileObject);
}
-static NTSTATUS FspFsvolSetEa(
- PDEVICE_OBJECT DeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp)
+static VOID FspFsvolQueryEaRequestFini(FSP_FSCTL_TRANSACT_REQ *Request, PVOID Context[4])
{
PAGED_CODE();
- return STATUS_INVALID_DEVICE_REQUEST;
+ FSP_FILE_NODE *FileNode = Context[RequestFileNode];
+
+ if (0 != FileNode)
+ FspFileNodeReleaseOwner(FileNode, Full, Request);
+}
+
+static NTSTATUS FspFsvolSetEa(
+ PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp)
+{
+ PAGED_CODE();
+
+ /* is this a valid FileObject? */
+ if (!FspFileNodeIsValid(IrpSp->FileObject->FsContext))
+ return STATUS_INVALID_DEVICE_REQUEST;
+
+ NTSTATUS Result;
+ PFILE_OBJECT FileObject = IrpSp->FileObject;
+ FSP_FILE_NODE *FileNode = FileObject->FsContext;
+ FSP_FILE_DESC *FileDesc = FileObject->FsContext2;
+ PVOID Buffer;
+ ULONG Length = IrpSp->Parameters.SetEa.Length;
+ FSP_FSCTL_TRANSACT_REQ *Request;
+
+ ASSERT(FileNode == FileDesc->FileNode);
+
+ Result = FspBufferUserBuffer(Irp, Length, IoReadAccess);
+ if (!NT_SUCCESS(Result))
+ return Result;
+ Buffer = Irp->AssociatedIrp.SystemBuffer;
+
+ Result = FspEaBufferAndNamesValid(Buffer, Length, (PULONG)&Irp->IoStatus.Information);
+ if (!NT_SUCCESS(Result))
+ return Result;
+
+ FspFileNodeAcquireExclusive(FileNode, Full);
+
+ Result = FspIopCreateRequestEx(Irp, 0, Length, FspFsvolSetEaRequestFini,
+ &Request);
+ if (!NT_SUCCESS(Result))
+ {
+ FspFileNodeRelease(FileNode, Full);
+ return Result;
+ }
+
+ Request->Kind = FspFsctlTransactSetEaKind;
+ Request->Req.SetEa.UserContext = FileNode->UserContext;
+ Request->Req.SetEa.UserContext2 = FileDesc->UserContext2;
+ Request->Req.SetEa.Ea.Offset = 0;
+ Request->Req.SetEa.Ea.Size = (UINT16)Length;
+ RtlCopyMemory(Request->Buffer, Buffer, Length);
+
+ FspFileNodeSetOwner(FileNode, Full, Request);
+ FspIopRequestContext(Request, RequestFileNode) = FileNode;
+
+ return FSP_STATUS_IOQ_POST;
}
NTSTATUS FspFsvolSetEaComplete(
@@ -68,7 +555,64 @@ NTSTATUS FspFsvolSetEaComplete(
{
FSP_ENTER_IOC(PAGED_CODE());
- FSP_LEAVE_IOC("%s", "");
+ if (!NT_SUCCESS(Response->IoStatus.Status))
+ {
+ Irp->IoStatus.Information = 0;
+ Result = Response->IoStatus.Status;
+ FSP_RETURN();
+ }
+
+ PFILE_OBJECT FileObject = IrpSp->FileObject;
+ FSP_FILE_NODE *FileNode = FileObject->FsContext;
+ FSP_FSCTL_TRANSACT_REQ *Request = FspIrpRequest(Irp);
+ BOOLEAN Valid;
+
+ Valid = FALSE;
+ if (0 < Response->Rsp.SetEa.Ea.Size &&
+ Response->Buffer + Response->Rsp.SetEa.Ea.Size <=
+ (PUINT8)Response + Response->Size)
+ {
+ Irp->IoStatus.Information = 0;
+ Result = IoCheckEaBufferValidity((PVOID)Response->Buffer, Response->Rsp.QueryEa.Ea.Size,
+ (PULONG)&Irp->IoStatus.Information);
+ Valid = NT_SUCCESS(Result);
+ }
+
+ /* if the EA buffer that we got back is valid */
+ if (Valid)
+ {
+ /* update the cached EA */
+ FspFileNodeSetEa(FileNode,
+ Response->Buffer, Response->Rsp.SetEa.Ea.Size);
+ }
+ else
+ {
+ /* invalidate the cached EA */
+ FspFileNodeSetEa(FileNode, 0, 0);
+ }
+
+ FileNode->EaChangeCount++;
+
+ FspFileNodeNotifyChange(FileNode, FILE_NOTIFY_CHANGE_EA, FILE_ACTION_MODIFIED, FALSE);
+
+ FspIopRequestContext(Request, RequestFileNode) = 0;
+ FspFileNodeReleaseOwner(FileNode, Full, Request);
+
+ Irp->IoStatus.Information = 0;
+ Result = STATUS_SUCCESS;
+
+ FSP_LEAVE_IOC("FileObject=%p",
+ IrpSp->FileObject);
+}
+
+static VOID FspFsvolSetEaRequestFini(FSP_FSCTL_TRANSACT_REQ *Request, PVOID Context[4])
+{
+ PAGED_CODE();
+
+ FSP_FILE_NODE *FileNode = Context[RequestFileNode];
+
+ if (0 != FileNode)
+ FspFileNodeReleaseOwner(FileNode, Full, Request);
}
NTSTATUS FspQueryEa(
@@ -84,7 +628,8 @@ NTSTATUS FspQueryEa(
FSP_RETURN(Result = STATUS_INVALID_DEVICE_REQUEST);
}
- FSP_LEAVE_MJ("%s", "");
+ FSP_LEAVE_MJ("FileObject=%p",
+ IrpSp->FileObject);
}
NTSTATUS FspSetEa(
@@ -100,5 +645,6 @@ NTSTATUS FspSetEa(
FSP_RETURN(Result = STATUS_INVALID_DEVICE_REQUEST);
}
- FSP_LEAVE_MJ("%s", "");
+ FSP_LEAVE_MJ("FileObject=%p",
+ IrpSp->FileObject);
}
diff --git a/src/sys/file.c b/src/sys/file.c
index 228a20cb..dc4c7ab7 100644
--- a/src/sys/file.c
+++ b/src/sys/file.c
@@ -89,6 +89,10 @@ VOID FspFileNodeSetStreamInfo(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size
BOOLEAN FspFileNodeTrySetStreamInfo(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size,
ULONG StreamInfoChangeNumber);
VOID FspFileNodeInvalidateStreamInfo(FSP_FILE_NODE *FileNode);
+BOOLEAN FspFileNodeReferenceEa(FSP_FILE_NODE *FileNode, PCVOID *PBuffer, PULONG PSize);
+VOID FspFileNodeSetEa(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size);
+BOOLEAN FspFileNodeTrySetEa(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size,
+ ULONG EaChangeNumber);
VOID FspFileNodeNotifyChange(FSP_FILE_NODE *FileNode, ULONG Filter, ULONG Action,
BOOLEAN InvalidateCaches);
NTSTATUS FspFileNodeProcessLockIrp(FSP_FILE_NODE *FileNode, PIRP Irp);
@@ -158,6 +162,9 @@ VOID FspFileNodeOplockComplete(PVOID Context, PIRP Irp);
// !#pragma alloc_text(PAGE, FspFileNodeSetStreamInfo)
// !#pragma alloc_text(PAGE, FspFileNodeTrySetStreamInfo)
// !#pragma alloc_text(PAGE, FspFileNodeInvalidateStreamInfo)
+#pragma alloc_text(PAGE, FspFileNodeReferenceEa)
+#pragma alloc_text(PAGE, FspFileNodeSetEa)
+#pragma alloc_text(PAGE, FspFileNodeTrySetEa)
#pragma alloc_text(PAGE, FspFileNodeNotifyChange)
#pragma alloc_text(PAGE, FspFileNodeProcessLockIrp)
#pragma alloc_text(PAGE, FspFileNodeCompleteLockIrp)
@@ -361,6 +368,7 @@ VOID FspFileNodeDelete(FSP_FILE_NODE *FileNode)
FsRtlTeardownPerStreamContexts(&FileNode->Header);
+ FspMetaCacheInvalidateItem(FsvolDeviceExtension->EaCache, FileNode->Ea);
FspMetaCacheInvalidateItem(FsvolDeviceExtension->StreamInfoCache, FileNode->NonPaged->StreamInfo);
FspMetaCacheInvalidateItem(FsvolDeviceExtension->DirInfoCache, FileNode->NonPaged->DirInfo);
FspMetaCacheInvalidateItem(FsvolDeviceExtension->SecurityCache, FileNode->Security);
@@ -2115,6 +2123,48 @@ VOID FspFileNodeInvalidateStreamInfo(FSP_FILE_NODE *FileNode)
FspMetaCacheInvalidateItem(FsvolDeviceExtension->StreamInfoCache, StreamInfo);
}
+BOOLEAN FspFileNodeReferenceEa(FSP_FILE_NODE *FileNode, PCVOID *PBuffer, PULONG PSize)
+{
+ PAGED_CODE();
+
+ if (0 != FileNode->MainFileNode)
+ FileNode = FileNode->MainFileNode;
+
+ FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension =
+ FspFsvolDeviceExtension(FileNode->FsvolDeviceObject);
+
+ return FspMetaCacheReferenceItemBuffer(FsvolDeviceExtension->EaCache,
+ FileNode->Ea, PBuffer, PSize);
+}
+
+VOID FspFileNodeSetEa(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size)
+{
+ PAGED_CODE();
+
+ if (0 != FileNode->MainFileNode)
+ FileNode = FileNode->MainFileNode;
+
+ FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension =
+ FspFsvolDeviceExtension(FileNode->FsvolDeviceObject);
+
+ FspMetaCacheInvalidateItem(FsvolDeviceExtension->EaCache, FileNode->Ea);
+ FileNode->Ea = 0 != Buffer ?
+ FspMetaCacheAddItem(FsvolDeviceExtension->EaCache, Buffer, Size) : 0;
+ FileNode->EaChangeNumber++;
+}
+
+BOOLEAN FspFileNodeTrySetEa(FSP_FILE_NODE *FileNode, PCVOID Buffer, ULONG Size,
+ ULONG EaChangeNumber)
+{
+ PAGED_CODE();
+
+ if (FspFileNodeEaChangeNumber(FileNode) != EaChangeNumber)
+ return FALSE;
+
+ FspFileNodeSetEa(FileNode, Buffer, Size);
+ return TRUE;
+}
+
VOID FspFileNodeNotifyChange(FSP_FILE_NODE *FileNode, ULONG Filter, ULONG Action,
BOOLEAN InvalidateCaches)
{
diff --git a/src/sys/name.c b/src/sys/name.c
index 37493219..ebf8838d 100644
--- a/src/sys/name.c
+++ b/src/sys/name.c
@@ -24,6 +24,7 @@
BOOLEAN FspFileNameIsValid(PUNICODE_STRING Path, ULONG MaxComponentLength,
PUNICODE_STRING StreamPart, PULONG StreamType);
BOOLEAN FspFileNameIsValidPattern(PUNICODE_STRING Pattern, ULONG MaxComponentLength);
+BOOLEAN FspEaNameIsValid(PSTRING Name);
VOID FspFileNameSuffix(PUNICODE_STRING Path, PUNICODE_STRING Remain, PUNICODE_STRING Suffix);
NTSTATUS FspFileNameInExpression(
PUNICODE_STRING Expression,
@@ -35,6 +36,7 @@ NTSTATUS FspFileNameInExpression(
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, FspFileNameIsValid)
#pragma alloc_text(PAGE, FspFileNameIsValidPattern)
+#pragma alloc_text(PAGE, FspEaNameIsValid)
#pragma alloc_text(PAGE, FspFileNameSuffix)
#pragma alloc_text(PAGE, FspFileNameInExpression)
#endif
@@ -191,6 +193,37 @@ BOOLEAN FspFileNameIsValidPattern(PUNICODE_STRING Path, ULONG MaxComponentLength
return TRUE;
}
+BOOLEAN FspEaNameIsValid(PSTRING Name)
+{
+ PAGED_CODE();
+
+ /* see FastFat's FatIsEaNameValid */
+
+ if (0 == Name->Length || Name->Length > 254)
+ return FALSE;
+
+ PSTR NameEnd, NamePtr;
+ CHAR Char;
+
+ NamePtr = Name->Buffer;
+ NameEnd = NamePtr + Name->Length;
+
+ while (NameEnd > NamePtr)
+ {
+ Char = *NamePtr;
+
+ if (FsRtlIsLeadDbcsCharacter(Char))
+ NamePtr++;
+ else
+ if (!FsRtlIsAnsiCharacterLegalFat(Char, FALSE))
+ return FALSE;
+
+ NamePtr++;
+ }
+
+ return TRUE;
+}
+
VOID FspFileNameSuffix(PUNICODE_STRING Path, PUNICODE_STRING Remain, PUNICODE_STRING Suffix)
{
PAGED_CODE();
diff --git a/src/sys/util.c b/src/sys/util.c
index 7252aeb0..aabcf5c9 100644
--- a/src/sys/util.c
+++ b/src/sys/util.c
@@ -49,6 +49,10 @@ NTSTATUS FspCcFlushCache(PSECTION_OBJECT_POINTERS SectionObjectPointer,
NTSTATUS FspQuerySecurityDescriptorInfo(SECURITY_INFORMATION SecurityInformation,
PSECURITY_DESCRIPTOR SecurityDescriptor, PULONG PLength,
PSECURITY_DESCRIPTOR ObjectsSecurityDescriptor);
+NTSTATUS FspEaBufferAndNamesValid(
+ PFILE_FULL_EA_INFORMATION Buffer,
+ ULONG Length,
+ PULONG PErrorOffset);
NTSTATUS FspNotifyInitializeSync(PNOTIFY_SYNC *NotifySync);
NTSTATUS FspNotifyFullChangeDirectory(
PNOTIFY_SYNC NotifySync,
@@ -129,6 +133,7 @@ NTSTATUS FspIrpHookNext(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID Context);
#pragma alloc_text(PAGE, FspCcMdlWriteComplete)
#pragma alloc_text(PAGE, FspCcFlushCache)
#pragma alloc_text(PAGE, FspQuerySecurityDescriptorInfo)
+#pragma alloc_text(PAGE, FspEaBufferAndNamesValid)
#pragma alloc_text(PAGE, FspNotifyInitializeSync)
#pragma alloc_text(PAGE, FspNotifyFullChangeDirectory)
#pragma alloc_text(PAGE, FspNotifyFullReportChange)
@@ -578,6 +583,39 @@ NTSTATUS FspQuerySecurityDescriptorInfo(SECURITY_INFORMATION SecurityInformation
return STATUS_BUFFER_TOO_SMALL == Result ? STATUS_BUFFER_OVERFLOW : Result;
}
+NTSTATUS FspEaBufferAndNamesValid(
+ PFILE_FULL_EA_INFORMATION Buffer,
+ ULONG Length,
+ PULONG PErrorOffset)
+{
+ PAGED_CODE();
+
+ NTSTATUS Result;
+
+ *PErrorOffset = 0;
+
+ Result = IoCheckEaBufferValidity(Buffer, Length, PErrorOffset);
+ if (!NT_SUCCESS(Result))
+ return Result;
+
+ for (PFILE_FULL_EA_INFORMATION Ea = Buffer, EaEnd = (PVOID)((PUINT8)Ea + Length);
+ EaEnd > Ea; Ea = FSP_NEXT_EA(Ea, EaEnd))
+ {
+ STRING Name;
+
+ Name.Length = Name.MaximumLength = Ea->EaNameLength;
+ Name.Buffer = Ea->EaName;
+
+ if (!FspEaNameIsValid(&Name))
+ {
+ *PErrorOffset = (ULONG)((PUINT8)Ea - (PUINT8)Buffer);
+ return STATUS_INVALID_EA_NAME;
+ }
+ }
+
+ return STATUS_SUCCESS;
+}
+
NTSTATUS FspNotifyInitializeSync(PNOTIFY_SYNC *NotifySync)
{
PAGED_CODE();
diff --git a/src/sys/volinfo.c b/src/sys/volinfo.c
index 23bee90a..5ae2ea1c 100644
--- a/src/sys/volinfo.c
+++ b/src/sys/volinfo.c
@@ -96,7 +96,7 @@ static NTSTATUS FspFsvolQueryFsAttributeInformation(
(FsvolDeviceExtension->VolumeParams.ReparsePoints ? FILE_SUPPORTS_REPARSE_POINTS : 0) |
(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.ExtendedAttributes ? FILE_SUPPORTS_EXTENDED_ATTRIBUTES : 0) |
(FsvolDeviceExtension->VolumeParams.ReadOnlyVolume ? FILE_READ_ONLY_VOLUME : 0);
Info->MaximumComponentNameLength = FsvolDeviceExtension->VolumeParams.MaxComponentLength;
diff --git a/src/sys/volume.c b/src/sys/volume.c
index c5b31ec8..3ea77490 100644
--- a/src/sys/volume.c
+++ b/src/sys/volume.c
@@ -151,6 +151,7 @@ static NTSTATUS FspVolumeCreateNoLock(
VolumeParams.DirInfoTimeout = VolumeParams.FileInfoTimeout;
VolumeParams.SecurityTimeout = VolumeParams.FileInfoTimeout;
VolumeParams.StreamInfoTimeout = VolumeParams.FileInfoTimeout;
+ VolumeParams.EaTimeout = VolumeParams.FileInfoTimeout;
}
else
{
@@ -162,11 +163,14 @@ static NTSTATUS FspVolumeCreateNoLock(
VolumeParams.SecurityTimeout = VolumeParams.FileInfoTimeout;
if (!VolumeParams.StreamInfoTimeoutValid)
VolumeParams.StreamInfoTimeout = VolumeParams.FileInfoTimeout;
+ if (!VolumeParams.EaTimeoutValid)
+ VolumeParams.EaTimeout = VolumeParams.FileInfoTimeout;
}
VolumeParams.VolumeInfoTimeoutValid = 1;
VolumeParams.DirInfoTimeoutValid = 1;
VolumeParams.SecurityTimeoutValid = 1;
VolumeParams.StreamInfoTimeoutValid = 1;
+ VolumeParams.EaTimeoutValid = 1;
if (FILE_DEVICE_NETWORK_FILE_SYSTEM == FsctlDeviceObject->DeviceType)
{
VolumeParams.Prefix[sizeof VolumeParams.Prefix / sizeof(WCHAR) - 1] = L'\0';
diff --git a/tools/cloc.bat b/tools/cloc.bat
new file mode 100755
index 00000000..6b8ccce3
--- /dev/null
+++ b/tools/cloc.bat
@@ -0,0 +1,7 @@
+@echo off
+
+setlocal
+
+cd %~dp0..
+
+cloc --force-lang=c,i --fullpath "--not-match-d=ext/test|build/VStudio/.vs|build/VStudio/build" .
diff --git a/tst/memfs-dotnet/Program.cs b/tst/memfs-dotnet/Program.cs
index 70bce965..b3c2270e 100644
--- a/tst/memfs-dotnet/Program.cs
+++ b/tst/memfs-dotnet/Program.cs
@@ -55,6 +55,12 @@ namespace memfs
}
}
+ struct EaValueData
+ {
+ public Byte[] EaValue;
+ public Boolean NeedEa;
+ }
+
class FileNode
{
public FileNode(String FileName)
@@ -80,6 +86,13 @@ namespace memfs
return FileInfo;
}
}
+ public SortedDictionary GetEaMap(Boolean Force)
+ {
+ FileNode FileNode = null == MainFileNode ? this : MainFileNode;
+ if (null == EaMap && Force)
+ EaMap = new SortedDictionary(StringComparer.OrdinalIgnoreCase);
+ return EaMap;
+ }
private static UInt64 IndexNumber = 1;
public String FileName;
@@ -87,6 +100,7 @@ namespace memfs
public Byte[] FileSecurity;
public Byte[] FileData;
public Byte[] ReparseData;
+ private SortedDictionary EaMap;
public FileNode MainFileNode;
public int OpenCount;
}
@@ -259,6 +273,7 @@ namespace memfs
Host.NamedStreams = true;
Host.PostCleanupWhenModifiedOnly = true;
Host.PassQueryDirectoryFileName = true;
+ Host.ExtendedAttributes = true;
return STATUS_SUCCESS;
}
@@ -309,13 +324,15 @@ namespace memfs
return STATUS_SUCCESS;
}
- public override Int32 Create(
+ public override Int32 CreateEx(
String FileName,
UInt32 CreateOptions,
UInt32 GrantedAccess,
UInt32 FileAttributes,
Byte[] SecurityDescriptor,
UInt64 AllocationSize,
+ IntPtr Ea,
+ UInt32 EaLength,
out Object FileNode0,
out Object FileDesc,
out FileInfo FileInfo,
@@ -352,6 +369,12 @@ namespace memfs
FileNode.FileInfo.FileAttributes = 0 != (FileAttributes & (UInt32)System.IO.FileAttributes.Directory) ?
FileAttributes : FileAttributes | (UInt32)System.IO.FileAttributes.Archive;
FileNode.FileSecurity = SecurityDescriptor;
+ if (IntPtr.Zero != Ea)
+ {
+ Result = SetEa(FileNode, null, Ea, EaLength);
+ if (0 > Result)
+ return Result;
+ }
if (0 != AllocationSize)
{
Result = SetFileSizeInternal(FileNode, AllocationSize, true);
@@ -393,6 +416,18 @@ namespace memfs
return Result;
}
+ if (0 != (CreateOptions & FILE_NO_EA_KNOWLEDGE) &&
+ null == FileNode.MainFileNode)
+ {
+ SortedDictionary EaMap = FileNode.GetEaMap(false);
+ if (null != EaMap)
+ {
+ foreach (KeyValuePair Pair in EaMap)
+ if (Pair.Value.NeedEa)
+ return STATUS_ACCESS_DENIED;
+ }
+ }
+
Interlocked.Increment(ref FileNode.OpenCount);
FileNode0 = FileNode;
FileInfo = FileNode.GetFileInfo();
@@ -401,12 +436,14 @@ namespace memfs
return STATUS_SUCCESS;
}
- public override Int32 Overwrite(
+ public override Int32 OverwriteEx(
Object FileNode0,
Object FileDesc,
UInt32 FileAttributes,
Boolean ReplaceFileAttributes,
UInt64 AllocationSize,
+ IntPtr Ea,
+ UInt32 EaLength,
out FileInfo FileInfo)
{
FileInfo = default(FileInfo);
@@ -424,6 +461,16 @@ namespace memfs
FileNodeMap.Remove(StreamNode);
}
+ SortedDictionary EaMap = FileNode.GetEaMap(false);
+ if (null != EaMap)
+ EaMap.Clear();
+ if (IntPtr.Zero != Ea)
+ {
+ Result = SetEa(FileNode, null, Ea, EaLength);
+ if (0 > Result)
+ return Result;
+ }
+
Result = SetFileSizeInternal(FileNode, AllocationSize, true);
if (0 > Result)
return Result;
@@ -1019,6 +1066,67 @@ namespace memfs
StreamAllocationSize = default(UInt64);
return false;
}
+ public override Boolean GetEaEntry(
+ Object FileNode0,
+ Object FileDesc,
+ ref Object Context,
+ out String EaName,
+ out Byte[] EaValue,
+ out Boolean NeedEa)
+ {
+ FileNode FileNode = (FileNode)FileNode0;
+ IEnumerator> Enumerator =
+ (IEnumerator>)Context;
+
+ if (null == Enumerator)
+ {
+ SortedDictionary EaMap = FileNode.GetEaMap(false);
+ if (null == EaMap)
+ {
+ EaName = default(String);
+ EaValue = default(Byte[]);
+ NeedEa = default(Boolean);
+ return false;
+ }
+
+ Context = Enumerator = EaMap.GetEnumerator();
+ }
+
+ while (Enumerator.MoveNext())
+ {
+ KeyValuePair Pair = Enumerator.Current;
+ EaName = Pair.Key;
+ EaValue = Pair.Value.EaValue;
+ NeedEa = Pair.Value.NeedEa;
+ return true;
+ }
+
+ EaName = default(String);
+ EaValue = default(Byte[]);
+ NeedEa = default(Boolean);
+ return false;
+ }
+ public override Int32 SetEaEntry(
+ Object FileNode0,
+ Object FileDesc,
+ ref Object Context,
+ String EaName,
+ Byte[] EaValue,
+ Boolean NeedEa)
+ {
+ FileNode FileNode = (FileNode)FileNode0;
+ SortedDictionary EaMap = FileNode.GetEaMap(true);
+ if (null != EaValue)
+ {
+ EaValueData Data;
+ Data.EaValue = EaValue;
+ Data.NeedEa = NeedEa;
+ EaMap[EaName] = Data;
+ }
+ else
+ EaMap.Remove(EaName);
+ return STATUS_SUCCESS;
+ }
private FileNodeMap FileNodeMap;
private UInt32 MaxFileNodes;
diff --git a/tst/memfs/memfs.cpp b/tst/memfs/memfs.cpp
index f361b048..8dc4ba1e 100644
--- a/tst/memfs/memfs.cpp
+++ b/tst/memfs/memfs.cpp
@@ -64,6 +64,11 @@ FSP_FSCTL_STATIC_ASSERT(MEMFS_MAX_PATH > MAX_PATH,
*/
#define MEMFS_CONTROL
+/*
+ * Define the MEMFS_EA macro to include extended attributes support.
+ */
+#define MEMFS_EA
+
/*
* Define the DEBUG_BUFFER_CHECK macro on Windows 8 or above. This includes
* a check for the Write buffer to ensure that it is read-only.
@@ -237,6 +242,36 @@ BOOLEAN MemfsFileNameHasPrefix(PWSTR a, PWSTR b, BOOLEAN CaseInsensitive)
#endif
}
+#if defined(MEMFS_EA)
+static inline
+int MemfsEaNameCompare(PSTR a, PSTR b)
+{
+ /* EA names are always case-insensitive in MEMFS (to be inline with NTFS) */
+
+ int res;
+
+ res = CompareStringA(LOCALE_INVARIANT, NORM_IGNORECASE, a, -1, b, -1);
+ if (0 != res)
+ res -= 2;
+ else
+ res = _stricmp(a, b);
+
+ return res;
+}
+
+struct MEMFS_FILE_NODE_EA_LESS
+{
+ MEMFS_FILE_NODE_EA_LESS()
+ {
+ }
+ bool operator()(PSTR a, PSTR b) const
+ {
+ return 0 > MemfsEaNameCompare(a, b);
+ }
+};
+typedef std::map MEMFS_FILE_NODE_EA_MAP;
+#endif
+
typedef struct _MEMFS_FILE_NODE
{
WCHAR FileName[MEMFS_MAX_PATH];
@@ -247,6 +282,9 @@ typedef struct _MEMFS_FILE_NODE
#if defined(MEMFS_REPARSE_POINTS)
SIZE_T ReparseDataSize;
PVOID ReparseData;
+#endif
+#if defined(MEMFS_EA)
+ MEMFS_FILE_NODE_EA_MAP *EaMap;
#endif
volatile LONG RefCount;
#if defined(MEMFS_NAMED_STREAMS)
@@ -308,9 +346,27 @@ NTSTATUS MemfsFileNodeCreate(PWSTR FileName, MEMFS_FILE_NODE **PFileNode)
return STATUS_SUCCESS;
}
+#if defined(MEMFS_EA)
+static inline
+VOID MemfsFileNodeDeleteEaMap(MEMFS_FILE_NODE *FileNode)
+{
+ if (0 != FileNode->EaMap)
+ {
+ for (MEMFS_FILE_NODE_EA_MAP::iterator p = FileNode->EaMap->begin(), q = FileNode->EaMap->end();
+ p != q; ++p)
+ free(p->second);
+ delete FileNode->EaMap;
+ FileNode->EaMap = 0;
+ }
+}
+#endif
+
static inline
VOID MemfsFileNodeDelete(MEMFS_FILE_NODE *FileNode)
{
+#if defined(MEMFS_EA)
+ MemfsFileNodeDeleteEaMap(FileNode);
+#endif
#if defined(MEMFS_REPARSE_POINTS)
free(FileNode->ReparseData);
#endif
@@ -351,6 +407,121 @@ VOID MemfsFileNodeGetFileInfo(MEMFS_FILE_NODE *FileNode, FSP_FSCTL_FILE_INFO *Fi
#endif
}
+#if defined(MEMFS_EA)
+static inline
+NTSTATUS MemfsFileNodeGetEaMap(MEMFS_FILE_NODE *FileNode, MEMFS_FILE_NODE_EA_MAP **PEaMap)
+{
+#if defined(MEMFS_NAMED_STREAMS)
+ if (0 != FileNode->MainFileNode)
+ FileNode = FileNode->MainFileNode;
+#endif
+
+ *PEaMap = FileNode->EaMap;
+ if (0 != *PEaMap)
+ return STATUS_SUCCESS;
+
+ try
+ {
+ *PEaMap = FileNode->EaMap = new MEMFS_FILE_NODE_EA_MAP(MEMFS_FILE_NODE_EA_LESS());
+ return STATUS_SUCCESS;
+ }
+ catch (...)
+ {
+ *PEaMap = 0;
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+}
+
+static inline
+NTSTATUS MemfsFileNodeSetEa(
+ FSP_FILE_SYSTEM *FileSystem, PVOID Context,
+ PFILE_FULL_EA_INFORMATION Ea)
+{
+ MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)Context;
+ MEMFS_FILE_NODE_EA_MAP *EaMap;
+ FILE_FULL_EA_INFORMATION *FileNodeEa = 0;
+ MEMFS_FILE_NODE_EA_MAP::iterator p;
+ NTSTATUS Result;
+
+ Result = MemfsFileNodeGetEaMap(FileNode, &EaMap);
+ if (!NT_SUCCESS(Result))
+ return Result;
+
+ if (0 != Ea->EaValueLength)
+ {
+ FileNodeEa = (FILE_FULL_EA_INFORMATION *)malloc(
+ FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + Ea->EaNameLength + 1 + Ea->EaValueLength);
+ if (0 == FileNodeEa)
+ return STATUS_INSUFFICIENT_RESOURCES;
+ memcpy(FileNodeEa, Ea,
+ FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + Ea->EaNameLength + 1 + Ea->EaValueLength);
+ FileNodeEa->NextEntryOffset = 0;
+ }
+
+ p = EaMap->find(Ea->EaName);
+ if (p != EaMap->end())
+ {
+ free(p->second);
+ EaMap->erase(p);
+ }
+
+ if (0 != Ea->EaValueLength)
+ {
+ try
+ {
+ EaMap->insert(MEMFS_FILE_NODE_EA_MAP::value_type(FileNodeEa->EaName, FileNodeEa));
+ return STATUS_SUCCESS;
+ }
+ catch (...)
+ {
+ free(FileNodeEa);
+ return STATUS_INSUFFICIENT_RESOURCES;
+ }
+ }
+
+ return STATUS_SUCCESS;
+}
+
+static inline
+BOOLEAN MemfsFileNodeNeedEa(MEMFS_FILE_NODE *FileNode)
+{
+#if defined(MEMFS_NAMED_STREAMS)
+ if (0 != FileNode->MainFileNode)
+ FileNode = FileNode->MainFileNode;
+#endif
+
+ if (0 != FileNode->EaMap)
+ {
+ for (MEMFS_FILE_NODE_EA_MAP::iterator p = FileNode->EaMap->begin(), q = FileNode->EaMap->end();
+ p != q; ++p)
+ if (0 != (p->second->Flags & FILE_NEED_EA))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static inline
+BOOLEAN MemfsFileNodeEnumerateEa(MEMFS_FILE_NODE *FileNode,
+ BOOLEAN (*EnumFn)(PFILE_FULL_EA_INFORMATION Ea, PVOID), PVOID Context)
+{
+#if defined(MEMFS_NAMED_STREAMS)
+ if (0 != FileNode->MainFileNode)
+ FileNode = FileNode->MainFileNode;
+#endif
+
+ if (0 != FileNode->EaMap)
+ {
+ for (MEMFS_FILE_NODE_EA_MAP::iterator p = FileNode->EaMap->begin(), q = FileNode->EaMap->end();
+ p != q; ++p)
+ if (!EnumFn(p->second, Context))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+#endif
+
static inline
VOID MemfsFileNodeMapDump(MEMFS_FILE_NODE_MAP *FileNodeMap)
{
@@ -869,6 +1040,9 @@ static NTSTATUS GetSecurityByName(FSP_FILE_SYSTEM *FileSystem,
static NTSTATUS Create(FSP_FILE_SYSTEM *FileSystem,
PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess,
UINT32 FileAttributes, PSECURITY_DESCRIPTOR SecurityDescriptor, UINT64 AllocationSize,
+#if defined(MEMFS_EA)
+ PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength,
+#endif
PVOID *PFileNode, FSP_FSCTL_FILE_INFO *FileInfo)
{
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
@@ -948,6 +1122,18 @@ static NTSTATUS Create(FSP_FILE_SYSTEM *FileSystem,
memcpy(FileNode->FileSecurity, SecurityDescriptor, FileNode->FileSecuritySize);
}
+#if defined(MEMFS_EA)
+ if (0 != Ea)
+ {
+ Result = FspFileSystemEnumerateEa(FileSystem, MemfsFileNodeSetEa, FileNode, Ea, EaLength);
+ if (!NT_SUCCESS(Result))
+ {
+ MemfsFileNodeDelete(FileNode);
+ return Result;
+ }
+ }
+#endif
+
FileNode->FileInfo.AllocationSize = AllocationSize;
if (0 != FileNode->FileInfo.AllocationSize)
{
@@ -1005,6 +1191,22 @@ static NTSTATUS Open(FSP_FILE_SYSTEM *FileSystem,
return Result;
}
+#if defined(MEMFS_EA)
+ /* if the OP specified no EA's check the need EA count, but only if accessing main stream */
+ if (0 != (CreateOptions & FILE_NO_EA_KNOWLEDGE)
+#if defined(MEMFS_NAMED_STREAMS)
+ && (0 == FileNode->MainFileNode)
+#endif
+ )
+ {
+ if (MemfsFileNodeNeedEa(FileNode))
+ {
+ Result = STATUS_ACCESS_DENIED;
+ return Result;
+ }
+ }
+#endif
+
MemfsFileNodeReference(FileNode);
*PFileNode = FileNode;
MemfsFileNodeGetFileInfo(FileNode, FileInfo);
@@ -1025,6 +1227,9 @@ static NTSTATUS Open(FSP_FILE_SYSTEM *FileSystem,
static NTSTATUS Overwrite(FSP_FILE_SYSTEM *FileSystem,
PVOID FileNode0, UINT32 FileAttributes, BOOLEAN ReplaceFileAttributes, UINT64 AllocationSize,
+#if defined(MEMFS_EA)
+ PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength,
+#endif
FSP_FSCTL_FILE_INFO *FileInfo)
{
MEMFS *Memfs = (MEMFS *)FileSystem->UserContext;
@@ -1047,6 +1252,16 @@ static NTSTATUS Overwrite(FSP_FILE_SYSTEM *FileSystem,
MemfsFileNodeMapEnumerateFree(&Context);
#endif
+#if defined(MEMFS_EA)
+ MemfsFileNodeDeleteEaMap(FileNode);
+ if (0 != Ea)
+ {
+ Result = FspFileSystemEnumerateEa(FileSystem, MemfsFileNodeSetEa, FileNode, Ea, EaLength);
+ if (!NT_SUCCESS(Result))
+ return Result;
+ }
+#endif
+
Result = SetFileSizeInternal(FileSystem, FileNode, AllocationSize, TRUE);
if (!NT_SUCCESS(Result))
return Result;
@@ -1881,7 +2096,7 @@ static NTSTATUS GetStreamInfo(FSP_FILE_SYSTEM *FileSystem,
#if defined(MEMFS_CONTROL)
static NTSTATUS Control(FSP_FILE_SYSTEM *FileSystem,
- PVOID FileContext, UINT32 ControlCode,
+ PVOID FileNode, UINT32 ControlCode,
PVOID InputBuffer, ULONG InputBufferLength,
PVOID OutputBuffer, ULONG OutputBufferLength, PULONG PBytesTransferred)
{
@@ -1911,14 +2126,63 @@ static NTSTATUS Control(FSP_FILE_SYSTEM *FileSystem,
}
#endif
+#if defined(MEMFS_EA)
+typedef struct _MEMFS_GET_EA_CONTEXT
+{
+ PFILE_FULL_EA_INFORMATION Ea;
+ ULONG EaLength;
+ PULONG PBytesTransferred;
+} MEMFS_GET_EA_CONTEXT;
+
+static BOOLEAN GetEaEnumFn(PFILE_FULL_EA_INFORMATION Ea, PVOID Context0)
+{
+ MEMFS_GET_EA_CONTEXT *Context = (MEMFS_GET_EA_CONTEXT *)Context0;
+
+ return FspFileSystemAddEa(Ea,
+ Context->Ea, Context->EaLength, Context->PBytesTransferred);
+}
+
+static NTSTATUS GetEa(FSP_FILE_SYSTEM *FileSystem,
+ PVOID FileNode0,
+ PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, PULONG PBytesTransferred)
+{
+ MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0;
+ MEMFS_GET_EA_CONTEXT Context;
+
+ Context.Ea = Ea;
+ Context.EaLength = EaLength;
+ Context.PBytesTransferred = PBytesTransferred;
+
+ if (MemfsFileNodeEnumerateEa(FileNode, GetEaEnumFn, &Context))
+ FspFileSystemAddEa(0, Ea, EaLength, PBytesTransferred);
+
+ return STATUS_SUCCESS;
+}
+
+static NTSTATUS SetEa(FSP_FILE_SYSTEM *FileSystem,
+ PVOID FileNode,
+ PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength)
+{
+ return FspFileSystemEnumerateEa(FileSystem, MemfsFileNodeSetEa, FileNode, Ea, EaLength);
+}
+#endif
+
static FSP_FILE_SYSTEM_INTERFACE MemfsInterface =
{
GetVolumeInfo,
SetVolumeLabel,
GetSecurityByName,
+#if defined(MEMFS_EA)
+ 0,
+#else
Create,
+#endif
Open,
+#if defined(MEMFS_EA)
+ 0,
+#else
Overwrite,
+#endif
Cleanup,
Close,
Read,
@@ -1957,6 +2221,18 @@ static FSP_FILE_SYSTEM_INTERFACE MemfsInterface =
Control,
#else
0,
+#endif
+ 0,
+#if defined(MEMFS_EA)
+ Create,
+ Overwrite,
+ GetEa,
+ SetEa
+#else
+ 0,
+ 0,
+ 0,
+ 0,
#endif
};
@@ -2051,6 +2327,9 @@ NTSTATUS MemfsCreateFunnel(
VolumeParams.FlushAndPurgeOnCleanup = FlushAndPurgeOnCleanup;
#if defined(MEMFS_CONTROL)
VolumeParams.DeviceControl = 1;
+#endif
+#if defined(MEMFS_EA)
+ VolumeParams.ExtendedAttributes = 1;
#endif
VolumeParams.AllowOpenInKernelMode = 1;
if (0 != VolumePrefix)
diff --git a/tst/winfsp-tests/ea-test.c b/tst/winfsp-tests/ea-test.c
new file mode 100644
index 00000000..df29dd44
--- /dev/null
+++ b/tst/winfsp-tests/ea-test.c
@@ -0,0 +1,988 @@
+/**
+ * @file ea-test.c
+ *
+ * @copyright 2015-2019 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
+#include
+#include
+#include "memfs.h"
+
+#include "winfsp-tests.h"
+
+typedef struct _FILE_GET_EA_INFORMATION
+{
+ ULONG NextEntryOffset;
+ UCHAR EaNameLength;
+ CHAR EaName[1];
+} FILE_GET_EA_INFORMATION, *PFILE_GET_EA_INFORMATION;
+
+NTSYSAPI NTSTATUS NTAPI NtQueryEaFile(
+ IN HANDLE FileHandle,
+ OUT PIO_STATUS_BLOCK IoStatusBlock,
+ OUT PVOID Buffer,
+ IN ULONG Length,
+ IN BOOLEAN ReturnSingleEntry,
+ IN PVOID EaList OPTIONAL,
+ IN ULONG EaListLength,
+ IN PULONG EaIndex OPTIONAL,
+ IN BOOLEAN RestartScan);
+NTSYSAPI NTSTATUS NTAPI NtSetEaFile(
+ IN HANDLE FileHandle,
+ OUT PIO_STATUS_BLOCK IoStatusBlock,
+ IN PVOID EaBuffer,
+ IN ULONG EaBufferSize);
+
+BOOLEAN AddGetEa(PFILE_GET_EA_INFORMATION SingleEa,
+ PFILE_GET_EA_INFORMATION Ea, ULONG Length, PULONG PBytesTransferred)
+{
+ if (0 != SingleEa)
+ {
+ PUINT8 EaPtr = (PUINT8)Ea + FSP_FSCTL_ALIGN_UP(*PBytesTransferred, sizeof(ULONG));
+ PUINT8 EaEnd = (PUINT8)Ea + Length;
+ ULONG EaLen = FIELD_OFFSET(FILE_GET_EA_INFORMATION, EaName) +
+ SingleEa->EaNameLength + 1;
+
+ if (EaEnd < EaPtr + EaLen)
+ return FALSE;
+
+ memcpy(EaPtr, SingleEa, EaLen);
+ ((PFILE_GET_EA_INFORMATION)EaPtr)->NextEntryOffset = FSP_FSCTL_ALIGN_UP(EaLen, sizeof(ULONG));
+ *PBytesTransferred = (ULONG)(EaPtr + EaLen - (PUINT8)Ea);
+ }
+ else if ((ULONG)FIELD_OFFSET(FILE_GET_EA_INFORMATION, EaName) <= *PBytesTransferred)
+ {
+ PUINT8 EaEnd = (PUINT8)Ea + *PBytesTransferred;
+
+ while (EaEnd > (PUINT8)Ea + Ea->NextEntryOffset)
+ Ea = (PVOID)((PUINT8)Ea + Ea->NextEntryOffset);
+
+ Ea->NextEntryOffset = 0;
+ }
+
+ return TRUE;
+}
+
+static void ea_init_ea(
+ PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, PULONG PBytesTransferred)
+{
+ union
+ {
+ FILE_FULL_EA_INFORMATION V;
+ UINT8 B[128];
+ } SingleEa;
+
+ memset(&SingleEa, 0, sizeof SingleEa);
+ SingleEa.V.EaNameLength = (UCHAR)strlen("Aname1");
+ SingleEa.V.EaValueLength = (USHORT)strlen("first");
+ lstrcpyA(SingleEa.V.EaName, "Aname1");
+ memcpy(SingleEa.V.EaName + SingleEa.V.EaNameLength + 1, "first", SingleEa.V.EaValueLength);
+ FspFileSystemAddEa(&SingleEa.V, Ea, EaLength, PBytesTransferred);
+
+ memset(&SingleEa, 0, sizeof SingleEa);
+ SingleEa.V.Flags = FILE_NEED_EA;
+ SingleEa.V.EaNameLength = (UCHAR)strlen("bnameTwo");
+ SingleEa.V.EaValueLength = (USHORT)strlen("second");
+ lstrcpyA(SingleEa.V.EaName, "bnameTwo");
+ memcpy(SingleEa.V.EaName + SingleEa.V.EaNameLength + 1, "second", SingleEa.V.EaValueLength);
+ FspFileSystemAddEa(&SingleEa.V, Ea, EaLength, PBytesTransferred);
+
+ memset(&SingleEa, 0, sizeof SingleEa);
+ SingleEa.V.EaNameLength = (UCHAR)strlen("Cn3");
+ SingleEa.V.EaValueLength = (USHORT)strlen("third");
+ lstrcpyA(SingleEa.V.EaName, "Cn3");
+ memcpy(SingleEa.V.EaName + SingleEa.V.EaNameLength + 1, "third", SingleEa.V.EaValueLength);
+ FspFileSystemAddEa(&SingleEa.V, Ea, EaLength, PBytesTransferred);
+
+ FspFileSystemAddEa(0, Ea, EaLength, PBytesTransferred);
+}
+
+static void ea_init_bad_ea(
+ PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, PULONG PBytesTransferred)
+{
+ union
+ {
+ FILE_FULL_EA_INFORMATION V;
+ UINT8 B[128];
+ } SingleEa;
+
+ memset(&SingleEa, 0, sizeof SingleEa);
+ SingleEa.V.EaNameLength = (UCHAR)strlen("Aname1");
+ SingleEa.V.EaValueLength = (USHORT)strlen("first");
+ lstrcpyA(SingleEa.V.EaName, "Aname1");
+ memcpy(SingleEa.V.EaName + SingleEa.V.EaNameLength + 1, "first", SingleEa.V.EaValueLength);
+ FspFileSystemAddEa(&SingleEa.V, Ea, EaLength, PBytesTransferred);
+
+ memset(&SingleEa, 0, sizeof SingleEa);
+ SingleEa.V.Flags = FILE_NEED_EA;
+ SingleEa.V.EaNameLength = (UCHAR)strlen("bnameTwo*");
+ SingleEa.V.EaValueLength = (USHORT)strlen("second");
+ lstrcpyA(SingleEa.V.EaName, "bnameTwo*");
+ memcpy(SingleEa.V.EaName + SingleEa.V.EaNameLength + 1, "second", SingleEa.V.EaValueLength);
+ FspFileSystemAddEa(&SingleEa.V, Ea, EaLength, PBytesTransferred);
+
+ memset(&SingleEa, 0, sizeof SingleEa);
+ SingleEa.V.EaNameLength = (UCHAR)strlen("Cn3");
+ SingleEa.V.EaValueLength = (USHORT)strlen("third");
+ lstrcpyA(SingleEa.V.EaName, "Cn3");
+ memcpy(SingleEa.V.EaName + SingleEa.V.EaNameLength + 1, "third", SingleEa.V.EaValueLength);
+ FspFileSystemAddEa(&SingleEa.V, Ea, EaLength, PBytesTransferred);
+
+ FspFileSystemAddEa(0, Ea, EaLength, PBytesTransferred);
+}
+
+struct ea_check_ea_context
+{
+ ULONG Count;
+ ULONG EaCount[4];
+};
+
+static NTSTATUS ea_check_ea_enumerate(
+ FSP_FILE_SYSTEM *FileSystem, PVOID Context0,
+ PFILE_FULL_EA_INFORMATION SingleEa)
+{
+ struct ea_check_ea_context *Context = Context0;
+
+ if (0 == strcmp(SingleEa->EaName, "ANAME1"))
+ {
+ ASSERT(0 == SingleEa->Flags);
+ ASSERT(SingleEa->EaNameLength == (UCHAR)strlen("ANAME1"));
+ ASSERT(SingleEa->EaValueLength == (UCHAR)strlen("first"));
+ ASSERT(0 == memcmp(SingleEa->EaName + SingleEa->EaNameLength + 1, "first", SingleEa->EaValueLength));
+ Context->EaCount[0]++;
+ }
+
+ if (0 == strcmp(SingleEa->EaName, "BNAMETWO"))
+ {
+ 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));
+ Context->EaCount[1]++;
+ }
+
+ if (0 == strcmp(SingleEa->EaName, "CN3"))
+ {
+ ASSERT(0 == SingleEa->Flags);
+ ASSERT(SingleEa->EaNameLength == (UCHAR)strlen("CN3"));
+ ASSERT(SingleEa->EaValueLength == (UCHAR)strlen("third"));
+ ASSERT(0 == memcmp(SingleEa->EaName + SingleEa->EaNameLength + 1, "third", SingleEa->EaValueLength));
+ Context->EaCount[2]++;
+ }
+
+ if (0 == strcmp(SingleEa->EaName, "NONEXISTENT"))
+ {
+ ASSERT(0 == SingleEa->Flags);
+ ASSERT(SingleEa->EaNameLength == (UCHAR)strlen("NONEXISTENT"));
+ ASSERT(SingleEa->EaValueLength == 0);
+ Context->EaCount[3]++;
+ }
+
+ Context->Count++;
+
+ return STATUS_SUCCESS;
+}
+
+static void ea_check_ea(HANDLE Handle)
+{
+ NTSTATUS Result;
+ IO_STATUS_BLOCK Iosb;
+ union
+ {
+ FILE_FULL_EA_INFORMATION V;
+ UINT8 B[512];
+ } Ea;
+ union
+ {
+ FILE_GET_EA_INFORMATION V;
+ UINT8 B[512];
+ } GetEa;
+ union
+ {
+ FILE_GET_EA_INFORMATION V;
+ UINT8 B[128];
+ } SingleGetEa;
+ ULONG EaLength = 0;
+ ULONG EaIndex;
+ struct ea_check_ea_context Context;
+
+ EaLength = 0;
+ memset(&SingleGetEa, 0, sizeof SingleGetEa);
+ SingleGetEa.V.EaNameLength = (UCHAR)strlen("bnameTwo");
+ lstrcpyA(SingleGetEa.V.EaName, "bnameTwo");
+ AddGetEa(&SingleGetEa.V, &GetEa.V, sizeof GetEa, &EaLength);
+ AddGetEa(0, &GetEa.V, sizeof GetEa, &EaLength);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb, &Ea, sizeof Ea, FALSE, &GetEa.V, EaLength, 0, FALSE);
+ ASSERT(STATUS_SUCCESS == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(1 == Context.Count);
+ ASSERT(0 == Context.EaCount[0]);
+ ASSERT(1 == Context.EaCount[1]);
+ ASSERT(0 == Context.EaCount[2]);
+ ASSERT(0 == Context.EaCount[3]);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, (ULONG)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + strlen("bnameTwo") + 1),
+ FALSE, &GetEa.V, EaLength, 0, FALSE);
+ ASSERT(STATUS_BUFFER_OVERFLOW == Result);
+ ASSERT(0 == Iosb.Information);
+
+ EaLength = 0;
+ memset(&SingleGetEa, 0, sizeof SingleGetEa);
+ SingleGetEa.V.EaNameLength = (UCHAR)strlen("nonexistent");
+ lstrcpyA(SingleGetEa.V.EaName, "nonexistent");
+ AddGetEa(&SingleGetEa.V, &GetEa.V, sizeof GetEa, &EaLength);
+ AddGetEa(0, &GetEa.V, sizeof GetEa, &EaLength);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb, &Ea, sizeof Ea, FALSE, &GetEa.V, EaLength, 0, FALSE);
+ ASSERT(STATUS_SUCCESS == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(1 == Context.Count);
+ ASSERT(0 == Context.EaCount[0]);
+ ASSERT(0 == Context.EaCount[1]);
+ ASSERT(0 == Context.EaCount[2]);
+ ASSERT(1 == Context.EaCount[3]);
+
+ EaLength = 0;
+ memset(&SingleGetEa, 0, sizeof SingleGetEa);
+ SingleGetEa.V.EaNameLength = (UCHAR)strlen("bnameTwo");
+ lstrcpyA(SingleGetEa.V.EaName, "bnameTwo");
+ AddGetEa(&SingleGetEa.V, &GetEa.V, sizeof GetEa, &EaLength);
+ memset(&SingleGetEa, 0, sizeof SingleGetEa);
+ SingleGetEa.V.EaNameLength = (UCHAR)strlen("nonexistent");
+ lstrcpyA(SingleGetEa.V.EaName, "nonexistent");
+ AddGetEa(&SingleGetEa.V, &GetEa.V, sizeof GetEa, &EaLength);
+ AddGetEa(0, &GetEa.V, sizeof GetEa, &EaLength);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb, &Ea, sizeof Ea, FALSE, &GetEa.V, EaLength, 0, FALSE);
+ ASSERT(STATUS_SUCCESS == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(2 == Context.Count);
+ ASSERT(0 == Context.EaCount[0]);
+ ASSERT(1 == Context.EaCount[1]);
+ ASSERT(0 == Context.EaCount[2]);
+ ASSERT(1 == Context.EaCount[3]);
+
+ EaLength = 0;
+ memset(&SingleGetEa, 0, sizeof SingleGetEa);
+ SingleGetEa.V.EaNameLength = (UCHAR)strlen("Aname1");
+ lstrcpyA(SingleGetEa.V.EaName, "Aname1");
+ AddGetEa(&SingleGetEa.V, &GetEa.V, sizeof GetEa, &EaLength);
+ memset(&SingleGetEa, 0, sizeof SingleGetEa);
+ SingleGetEa.V.EaNameLength = (UCHAR)strlen("bnameTwo");
+ lstrcpyA(SingleGetEa.V.EaName, "bnameTwo");
+ AddGetEa(&SingleGetEa.V, &GetEa.V, sizeof GetEa, &EaLength);
+ AddGetEa(0, &GetEa.V, sizeof GetEa, &EaLength);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb, &Ea, sizeof Ea, FALSE, &GetEa.V, EaLength, 0, FALSE);
+ ASSERT(STATUS_SUCCESS == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(2 == Context.Count);
+ ASSERT(1 == Context.EaCount[0]);
+ ASSERT(1 == Context.EaCount[1]);
+ ASSERT(0 == Context.EaCount[2]);
+ ASSERT(0 == Context.EaCount[3]);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, (ULONG)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + strlen("Aname1") + 1 + strlen("first")),
+ FALSE, &GetEa.V, EaLength, 0, FALSE);
+ ASSERT(STATUS_BUFFER_OVERFLOW == Result);
+ ASSERT(0 == Iosb.Information);
+
+ EaLength = 0;
+ memset(&SingleGetEa, 0, sizeof SingleGetEa);
+ SingleGetEa.V.EaNameLength = (UCHAR)strlen("bnameTwo");
+ lstrcpyA(SingleGetEa.V.EaName, "bnameTwo");
+ AddGetEa(&SingleGetEa.V, &GetEa.V, sizeof GetEa, &EaLength);
+ memset(&SingleGetEa, 0, sizeof SingleGetEa);
+ SingleGetEa.V.EaNameLength = (UCHAR)strlen("bnameTwo");
+ lstrcpyA(SingleGetEa.V.EaName, "bnameTwo");
+ AddGetEa(&SingleGetEa.V, &GetEa.V, sizeof GetEa, &EaLength);
+ AddGetEa(0, &GetEa.V, sizeof GetEa, &EaLength);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb, &Ea, sizeof Ea, FALSE, &GetEa.V, EaLength, 0, FALSE);
+ ASSERT(STATUS_SUCCESS == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(1 == Context.Count);
+ ASSERT(0 == Context.EaCount[0]);
+ ASSERT(1 == Context.EaCount[1]);
+ ASSERT(0 == Context.EaCount[2]);
+ ASSERT(0 == Context.EaCount[3]);
+
+ EaLength = 0;
+ memset(&SingleGetEa, 0, sizeof SingleGetEa);
+ SingleGetEa.V.EaNameLength = (UCHAR)strlen("bnameTwo");
+ lstrcpyA(SingleGetEa.V.EaName, "bnameTwo");
+ AddGetEa(&SingleGetEa.V, &GetEa.V, sizeof GetEa, &EaLength);
+ memset(&SingleGetEa, 0, sizeof SingleGetEa);
+ SingleGetEa.V.EaNameLength = (UCHAR)strlen("bnameTwo*");
+ lstrcpyA(SingleGetEa.V.EaName, "bnameTwo*");
+ AddGetEa(&SingleGetEa.V, &GetEa.V, sizeof GetEa, &EaLength);
+ AddGetEa(0, &GetEa.V, sizeof GetEa, &EaLength);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb, &Ea, sizeof Ea, FALSE, &GetEa.V, EaLength, 0, FALSE);
+ ASSERT(STATUS_INVALID_EA_NAME == Result);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb, &Ea, sizeof Ea, FALSE, 0, 0, 0, FALSE);
+ ASSERT(STATUS_SUCCESS == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(3 == Context.Count);
+ ASSERT(1 == Context.EaCount[0]);
+ ASSERT(1 == Context.EaCount[1]);
+ ASSERT(1 == Context.EaCount[2]);
+ ASSERT(0 == Context.EaCount[3]);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb, &Ea, sizeof Ea, FALSE, 0, 0, 0, FALSE);
+ ASSERT(STATUS_NO_MORE_EAS == Result);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb, &Ea, sizeof Ea, TRUE, 0, 0, 0, TRUE);
+ ASSERT(STATUS_SUCCESS == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(1 == Context.Count);
+ ASSERT(1 == Context.EaCount[0]);
+ ASSERT(0 == Context.EaCount[1]);
+ ASSERT(0 == Context.EaCount[2]);
+ ASSERT(0 == Context.EaCount[3]);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb, &Ea, sizeof Ea, TRUE, 0, 0, 0, FALSE);
+ ASSERT(STATUS_SUCCESS == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(1 == Context.Count);
+ ASSERT(0 == Context.EaCount[0]);
+ ASSERT(1 == Context.EaCount[1]);
+ ASSERT(0 == Context.EaCount[2]);
+ ASSERT(0 == Context.EaCount[3]);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, (ULONG)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + strlen("Aname1") + 1),
+ FALSE, 0, 0, 0, TRUE);
+ ASSERT(STATUS_BUFFER_TOO_SMALL == Result);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, (ULONG)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + strlen("Aname1") + 1 + strlen("first")),
+ FALSE, 0, 0, 0, TRUE);
+ ASSERT(STATUS_BUFFER_OVERFLOW == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(1 == Context.Count);
+ ASSERT(1 == Context.EaCount[0]);
+ ASSERT(0 == Context.EaCount[1]);
+ ASSERT(0 == Context.EaCount[2]);
+ ASSERT(0 == Context.EaCount[3]);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, (ULONG)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + strlen("Aname1") + 1 + strlen("first")),
+ FALSE, 0, 0, 0, TRUE);
+ ASSERT(STATUS_BUFFER_OVERFLOW == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(1 == Context.Count);
+ ASSERT(1 == Context.EaCount[0]);
+ ASSERT(0 == Context.EaCount[1]);
+ ASSERT(0 == Context.EaCount[2]);
+ ASSERT(0 == Context.EaCount[3]);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, (ULONG)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + strlen("bnameTwo") + 1),
+ FALSE, 0, 0, 0, FALSE);
+ ASSERT(STATUS_BUFFER_TOO_SMALL == Result);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, (ULONG)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + strlen("bnameTwo") + 1 + strlen("bnameTwo")),
+ FALSE, 0, 0, 0, FALSE);
+ ASSERT(STATUS_BUFFER_OVERFLOW == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(1 == Context.Count);
+ ASSERT(0 == Context.EaCount[0]);
+ ASSERT(1 == Context.EaCount[1]);
+ ASSERT(0 == Context.EaCount[2]);
+ ASSERT(0 == Context.EaCount[3]);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, (ULONG)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + strlen("Cn3") + 1),
+ FALSE, 0, 0, 0, FALSE);
+ ASSERT(STATUS_BUFFER_TOO_SMALL == Result);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, (ULONG)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + strlen("Cn3") + 1 + strlen("third")),
+ FALSE, 0, 0, 0, FALSE);
+ ASSERT(STATUS_SUCCESS == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(1 == Context.Count);
+ ASSERT(0 == Context.EaCount[0]);
+ ASSERT(0 == Context.EaCount[1]);
+ ASSERT(1 == Context.EaCount[2]);
+ ASSERT(0 == Context.EaCount[3]);
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb, &Ea, sizeof Ea, FALSE, 0, 0, 0, FALSE);
+ ASSERT(STATUS_NO_MORE_EAS == Result);
+
+ memset(&Context, 0, sizeof Context);
+ EaIndex = 0;
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, sizeof Ea,
+ FALSE, 0, 0, &EaIndex, FALSE);
+ ASSERT(STATUS_NONEXISTENT_EA_ENTRY == Result);
+
+ memset(&Context, 0, sizeof Context);
+ EaIndex = 1;
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, (ULONG)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + strlen("Aname1") + 1),
+ FALSE, 0, 0, &EaIndex, FALSE);
+ ASSERT(STATUS_BUFFER_TOO_SMALL == Result);
+
+ memset(&Context, 0, sizeof Context);
+ EaIndex = 1;
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, (ULONG)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + strlen("Aname1") + 1 + strlen("first")),
+ FALSE, 0, 0, &EaIndex, FALSE);
+ ASSERT(STATUS_BUFFER_OVERFLOW == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(1 == Context.Count);
+ ASSERT(1 == Context.EaCount[0]);
+ ASSERT(0 == Context.EaCount[1]);
+ ASSERT(0 == Context.EaCount[2]);
+ ASSERT(0 == Context.EaCount[3]);
+
+ memset(&Context, 0, sizeof Context);
+ EaIndex = 2;
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, (ULONG)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + strlen("bnameTwo") + 1),
+ FALSE, 0, 0, &EaIndex, FALSE);
+ ASSERT(STATUS_BUFFER_TOO_SMALL == Result);
+
+ memset(&Context, 0, sizeof Context);
+ EaIndex = 2;
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, (ULONG)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + strlen("bnameTwo") + 1 + strlen("bnameTwo")),
+ FALSE, 0, 0, &EaIndex, FALSE);
+ ASSERT(STATUS_BUFFER_OVERFLOW == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(1 == Context.Count);
+ ASSERT(0 == Context.EaCount[0]);
+ ASSERT(1 == Context.EaCount[1]);
+ ASSERT(0 == Context.EaCount[2]);
+ ASSERT(0 == Context.EaCount[3]);
+
+ memset(&Context, 0, sizeof Context);
+ EaIndex = 3;
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, (ULONG)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + strlen("Cn3") + 1),
+ FALSE, 0, 0, &EaIndex, FALSE);
+ ASSERT(STATUS_BUFFER_TOO_SMALL == Result);
+
+ memset(&Context, 0, sizeof Context);
+ EaIndex = 3;
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, (ULONG)(FIELD_OFFSET(FILE_FULL_EA_INFORMATION, EaName) + strlen("Cn3") + 1 + strlen("third")),
+ FALSE, 0, 0, &EaIndex, FALSE);
+ ASSERT(STATUS_SUCCESS == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(1 == Context.Count);
+ ASSERT(0 == Context.EaCount[0]);
+ ASSERT(0 == Context.EaCount[1]);
+ ASSERT(1 == Context.EaCount[2]);
+ ASSERT(0 == Context.EaCount[3]);
+
+ memset(&Context, 0, sizeof Context);
+ EaIndex = 4;
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, sizeof Ea,
+ FALSE, 0, 0, &EaIndex, FALSE);
+ ASSERT(STATUS_NO_MORE_EAS == Result);
+
+ memset(&Context, 0, sizeof Context);
+ EaIndex = 5;
+ Result = NtQueryEaFile(Handle, &Iosb,
+ &Ea, sizeof Ea,
+ FALSE, 0, 0, &EaIndex, FALSE);
+ ASSERT(STATUS_NONEXISTENT_EA_ENTRY == Result);
+}
+
+static void ea_init_ea2(
+ PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, PULONG PBytesTransferred)
+{
+ union
+ {
+ FILE_FULL_EA_INFORMATION V;
+ UINT8 B[128];
+ } SingleEa;
+
+ memset(&SingleEa, 0, sizeof SingleEa);
+ SingleEa.V.EaNameLength = (UCHAR)strlen("Aname1");
+ SingleEa.V.EaValueLength = (USHORT)strlen("ValueForAname1");
+ lstrcpyA(SingleEa.V.EaName, "Aname1");
+ memcpy(SingleEa.V.EaName + SingleEa.V.EaNameLength + 1, "ValueForAname1", SingleEa.V.EaValueLength);
+ FspFileSystemAddEa(&SingleEa.V, Ea, EaLength, PBytesTransferred);
+
+ memset(&SingleEa, 0, sizeof SingleEa);
+ SingleEa.V.EaNameLength = (UCHAR)strlen("bnameTwo");
+ SingleEa.V.EaValueLength = (USHORT)strlen("ValueForBNameTwo");
+ lstrcpyA(SingleEa.V.EaName, "bnameTwo");
+ memcpy(SingleEa.V.EaName + SingleEa.V.EaNameLength + 1, "ValueForBNameTwo", SingleEa.V.EaValueLength);
+ FspFileSystemAddEa(&SingleEa.V, Ea, EaLength, PBytesTransferred);
+
+ memset(&SingleEa, 0, sizeof SingleEa);
+ SingleEa.V.EaNameLength = (UCHAR)strlen("Cn3");
+ SingleEa.V.EaValueLength = 0;
+ lstrcpyA(SingleEa.V.EaName, "Cn3");
+ FspFileSystemAddEa(&SingleEa.V, Ea, EaLength, PBytesTransferred);
+
+ memset(&SingleEa, 0, sizeof SingleEa);
+ SingleEa.V.EaNameLength = (UCHAR)strlen("dn4");
+ SingleEa.V.EaValueLength = (USHORT)strlen("ValueForDn4");
+ lstrcpyA(SingleEa.V.EaName, "dn4");
+ memcpy(SingleEa.V.EaName + SingleEa.V.EaNameLength + 1, "ValueForDn4", SingleEa.V.EaValueLength);
+ FspFileSystemAddEa(&SingleEa.V, Ea, EaLength, PBytesTransferred);
+
+ FspFileSystemAddEa(0, Ea, EaLength, PBytesTransferred);
+}
+
+static NTSTATUS ea_check_ea2_enumerate(
+ FSP_FILE_SYSTEM *FileSystem, PVOID Context0,
+ PFILE_FULL_EA_INFORMATION SingleEa)
+{
+ struct ea_check_ea_context *Context = Context0;
+
+ if (0 == strcmp(SingleEa->EaName, "ANAME1"))
+ {
+ ASSERT(0 == SingleEa->Flags);
+ ASSERT(SingleEa->EaNameLength == (UCHAR)strlen("ANAME1"));
+ ASSERT(SingleEa->EaValueLength == (UCHAR)strlen("ValueForAname1"));
+ ASSERT(0 == memcmp(SingleEa->EaName + SingleEa->EaNameLength + 1, "ValueForAname1", SingleEa->EaValueLength));
+ Context->EaCount[0]++;
+ }
+
+ if (0 == strcmp(SingleEa->EaName, "BNAMETWO"))
+ {
+ ASSERT(0 == SingleEa->Flags);
+ ASSERT(SingleEa->EaNameLength == (UCHAR)strlen("BNAMETWO"));
+ ASSERT(SingleEa->EaValueLength == (UCHAR)strlen("ValueForBNameTwo"));
+ ASSERT(0 == memcmp(SingleEa->EaName + SingleEa->EaNameLength + 1, "ValueForBNameTwo", SingleEa->EaValueLength));
+ Context->EaCount[1]++;
+ }
+
+ if (0 == strcmp(SingleEa->EaName, "CN3"))
+ {
+ ASSERT(0 == SingleEa->Flags);
+ ASSERT(SingleEa->EaNameLength == (UCHAR)strlen("CN3"));
+ ASSERT(SingleEa->EaValueLength == 0);
+ Context->EaCount[2]++;
+ }
+
+ if (0 == strcmp(SingleEa->EaName, "DN4"))
+ {
+ ASSERT(0 == SingleEa->Flags);
+ ASSERT(SingleEa->EaNameLength == (UCHAR)strlen("DN4"));
+ ASSERT(SingleEa->EaValueLength == (UCHAR)strlen("ValueForDn4"));
+ ASSERT(0 == memcmp(SingleEa->EaName + SingleEa->EaNameLength + 1, "ValueForDn4", SingleEa->EaValueLength));
+ Context->EaCount[3]++;
+ }
+
+ Context->Count++;
+
+ return STATUS_SUCCESS;
+}
+
+static void ea_check_ea2(HANDLE Handle)
+{
+ NTSTATUS Result;
+ IO_STATUS_BLOCK Iosb;
+ union
+ {
+ FILE_FULL_EA_INFORMATION V;
+ UINT8 B[512];
+ } Ea;
+ struct ea_check_ea_context Context;
+
+ memset(&Context, 0, sizeof Context);
+ Result = NtQueryEaFile(Handle, &Iosb, &Ea, sizeof Ea, FALSE, 0, 0, 0, TRUE);
+ ASSERT(STATUS_SUCCESS == Result);
+ Result = FspFileSystemEnumerateEa(0, ea_check_ea2_enumerate, &Context, &Ea.V, (ULONG)Iosb.Information);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(3 == Context.Count);
+ ASSERT(1 == Context.EaCount[0]);
+ ASSERT(1 == Context.EaCount[1]);
+ ASSERT(0 == Context.EaCount[2]);
+ ASSERT(1 == Context.EaCount[3]);
+}
+
+static void ea_create_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout)
+{
+ void *memfs = memfs_start_ex(Flags, FileInfoTimeout);
+
+ HANDLE DirHandle, FileHandle;
+ NTSTATUS Result;
+ BOOLEAN Success;
+ WCHAR FilePath[MAX_PATH];
+ WCHAR UnicodePathBuf[MAX_PATH] = L"file2";
+ UNICODE_STRING UnicodePath;
+ OBJECT_ATTRIBUTES Obja;
+ IO_STATUS_BLOCK Iosb;
+ LARGE_INTEGER LargeZero = { 0 };
+ union
+ {
+ FILE_FULL_EA_INFORMATION V;
+ UINT8 B[512];
+ } Ea;
+ ULONG EaLength;
+
+ StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\dir1",
+ Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs));
+
+ Success = CreateDirectoryW(FilePath, 0);
+ ASSERT(Success);
+
+ DirHandle = CreateFileW(FilePath,
+ GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_DELETE_ON_CLOSE, 0);
+ ASSERT(INVALID_HANDLE_VALUE != DirHandle);
+
+ UnicodePath.Length = (USHORT)wcslen(UnicodePathBuf) * sizeof(WCHAR);
+ UnicodePath.MaximumLength = sizeof UnicodePathBuf;
+ UnicodePath.Buffer = UnicodePathBuf;
+ InitializeObjectAttributes(&Obja, &UnicodePath, 0, DirHandle, 0);
+
+ EaLength = 0;
+ ea_init_bad_ea(&Ea.V, sizeof Ea, &EaLength);
+ Result = NtCreateFile(&FileHandle,
+ FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE, &Obja, &Iosb,
+ &LargeZero, FILE_ATTRIBUTE_NORMAL, 0,
+ FILE_CREATE, 0,
+ &Ea, EaLength);
+ ASSERT(STATUS_INVALID_EA_NAME == Result);
+
+ EaLength = 0;
+ ea_init_ea(&Ea.V, sizeof Ea, &EaLength);
+
+ Result = NtCreateFile(&FileHandle,
+ FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE, &Obja, &Iosb,
+ &LargeZero, FILE_ATTRIBUTE_NORMAL, 0,
+ FILE_CREATE, FILE_NO_EA_KNOWLEDGE,
+ &Ea, EaLength);
+ ASSERT(STATUS_ACCESS_DENIED == Result);
+
+ Result = NtCreateFile(&FileHandle,
+ FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE, &Obja, &Iosb,
+ &LargeZero, FILE_ATTRIBUTE_NORMAL, 0,
+ FILE_CREATE, 0,
+ &Ea, EaLength);
+ 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);
+
+ Result = NtCreateFile(&FileHandle,
+ FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE, &Obja, &Iosb,
+ &LargeZero, FILE_ATTRIBUTE_NORMAL, 0,
+ FILE_OPEN, FILE_DELETE_ON_CLOSE,
+ 0, 0);
+ ASSERT(STATUS_SUCCESS == Result);
+ ea_check_ea(FileHandle);
+ CloseHandle(FileHandle);
+
+ CloseHandle(DirHandle);
+
+ DirHandle = CreateFileW(FilePath,
+ GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_DELETE_ON_CLOSE, 0);
+ ASSERT(INVALID_HANDLE_VALUE == DirHandle);
+ ASSERT(ERROR_FILE_NOT_FOUND == GetLastError());
+
+ memfs_stop(memfs);
+}
+
+static void ea_create_test(void)
+{
+ if (NtfsTests)
+ {
+ WCHAR DirBuf[MAX_PATH];
+ GetTestDirectory(DirBuf);
+ ea_create_dotest(-1, DirBuf, 0);
+ }
+ if (WinFspDiskTests)
+ {
+ ea_create_dotest(MemfsDisk, 0, 0);
+ ea_create_dotest(MemfsDisk, 0, 1000);
+ }
+ if (WinFspNetTests)
+ {
+ ea_create_dotest(MemfsNet, L"\\\\memfs\\share", 0);
+ ea_create_dotest(MemfsNet, L"\\\\memfs\\share", 1000);
+ }
+}
+
+static void ea_overwrite_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout)
+{
+ void *memfs = memfs_start_ex(Flags, FileInfoTimeout);
+
+ HANDLE DirHandle, FileHandle;
+ NTSTATUS Result;
+ BOOLEAN Success;
+ WCHAR FilePath[MAX_PATH];
+ WCHAR UnicodePathBuf[MAX_PATH] = L"file2";
+ UNICODE_STRING UnicodePath;
+ OBJECT_ATTRIBUTES Obja;
+ IO_STATUS_BLOCK Iosb;
+ LARGE_INTEGER LargeZero = { 0 };
+ union
+ {
+ FILE_FULL_EA_INFORMATION V;
+ UINT8 B[512];
+ } Ea;
+ ULONG EaLength;
+
+ StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\dir1",
+ Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs));
+
+ Success = CreateDirectoryW(FilePath, 0);
+ ASSERT(Success);
+
+ DirHandle = CreateFileW(FilePath,
+ GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_DELETE_ON_CLOSE, 0);
+ ASSERT(INVALID_HANDLE_VALUE != DirHandle);
+
+ UnicodePath.Length = (USHORT)wcslen(UnicodePathBuf) * sizeof(WCHAR);
+ UnicodePath.MaximumLength = sizeof UnicodePathBuf;
+ UnicodePath.Buffer = UnicodePathBuf;
+ InitializeObjectAttributes(&Obja, &UnicodePath, 0, DirHandle, 0);
+
+ Result = NtCreateFile(&FileHandle,
+ FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE, &Obja, &Iosb,
+ &LargeZero, FILE_ATTRIBUTE_NORMAL, 0,
+ FILE_CREATE, 0,
+ 0, 0);
+ ASSERT(STATUS_SUCCESS == Result);
+ CloseHandle(FileHandle);
+
+ EaLength = 0;
+ ea_init_ea(&Ea.V, sizeof Ea, &EaLength);
+ Result = NtCreateFile(&FileHandle,
+ FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE, &Obja, &Iosb,
+ &LargeZero, FILE_ATTRIBUTE_NORMAL, 0,
+ FILE_OVERWRITE, 0,
+ &Ea, EaLength);
+ ASSERT(STATUS_SUCCESS == Result);
+ ea_check_ea(FileHandle);
+ CloseHandle(FileHandle);
+
+ EaLength = 0;
+ ea_init_ea2(&Ea.V, sizeof Ea, &EaLength);
+ Result = NtCreateFile(&FileHandle,
+ FILE_GENERIC_READ | FILE_GENERIC_WRITE | DELETE, &Obja, &Iosb,
+ &LargeZero, FILE_ATTRIBUTE_NORMAL, 0,
+ FILE_OVERWRITE, FILE_DELETE_ON_CLOSE,
+ &Ea, EaLength);
+ ASSERT(STATUS_SUCCESS == Result);
+ ea_check_ea2(FileHandle);
+ CloseHandle(FileHandle);
+
+ CloseHandle(DirHandle);
+
+ DirHandle = CreateFileW(FilePath,
+ GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_DELETE_ON_CLOSE, 0);
+ ASSERT(INVALID_HANDLE_VALUE == DirHandle);
+ ASSERT(ERROR_FILE_NOT_FOUND == GetLastError());
+
+ memfs_stop(memfs);
+}
+
+static void ea_overwrite_test(void)
+{
+ if (NtfsTests)
+ {
+ WCHAR DirBuf[MAX_PATH];
+ GetTestDirectory(DirBuf);
+ ea_overwrite_dotest(-1, DirBuf, 0);
+ }
+ if (WinFspDiskTests)
+ {
+ ea_overwrite_dotest(MemfsDisk, 0, 0);
+ ea_overwrite_dotest(MemfsDisk, 0, 1000);
+ }
+ if (WinFspNetTests)
+ {
+ ea_overwrite_dotest(MemfsNet, L"\\\\memfs\\share", 0);
+ ea_overwrite_dotest(MemfsNet, L"\\\\memfs\\share", 1000);
+ }
+}
+
+static void ea_getset_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout)
+{
+ void *memfs = memfs_start_ex(Flags, FileInfoTimeout);
+
+ HANDLE DirHandle, FileHandle;
+ NTSTATUS Result;
+ BOOLEAN Success;
+ IO_STATUS_BLOCK Iosb;
+ WCHAR FilePath[MAX_PATH];
+ union
+ {
+ FILE_FULL_EA_INFORMATION V;
+ UINT8 B[512];
+ } Ea;
+ ULONG EaLength;
+
+ StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\dir1",
+ Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs));
+
+ Success = CreateDirectoryW(FilePath, 0);
+ ASSERT(Success);
+
+ DirHandle = CreateFileW(FilePath,
+ GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_DELETE_ON_CLOSE, 0);
+ ASSERT(INVALID_HANDLE_VALUE != DirHandle);
+
+ EaLength = 0;
+ ea_init_bad_ea(&Ea.V, sizeof Ea, &EaLength);
+ Result = NtSetEaFile(DirHandle, &Iosb, &Ea, EaLength);
+ ASSERT(STATUS_INVALID_EA_NAME == Result);
+
+ EaLength = 0;
+ ea_init_ea(&Ea.V, sizeof Ea, &EaLength);
+ Result = NtSetEaFile(DirHandle, &Iosb, &Ea, EaLength);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(0 == Iosb.Information);
+ ea_check_ea(DirHandle);
+
+ EaLength = 0;
+ ea_init_ea2(&Ea.V, sizeof Ea, &EaLength);
+ Result = NtSetEaFile(DirHandle, &Iosb, &Ea, EaLength);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(0 == Iosb.Information);
+ Result = NtQueryEaFile(DirHandle, &Iosb, &Ea, sizeof Ea, FALSE, 0, 0, 0, FALSE);
+ ASSERT(STATUS_EA_CORRUPT_ERROR == Result);
+ ea_check_ea2(DirHandle);
+
+ CloseHandle(DirHandle);
+
+ DirHandle = CreateFileW(FilePath,
+ GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_DELETE_ON_CLOSE, 0);
+ ASSERT(INVALID_HANDLE_VALUE == DirHandle);
+ ASSERT(ERROR_FILE_NOT_FOUND == GetLastError());
+
+ StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\file0",
+ Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs));
+
+ FileHandle = CreateFileW(FilePath,
+ GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, CREATE_NEW,
+ FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, 0);
+ ASSERT(INVALID_HANDLE_VALUE != FileHandle);
+
+ EaLength = 0;
+ ea_init_bad_ea(&Ea.V, sizeof Ea, &EaLength);
+ Result = NtSetEaFile(FileHandle, &Iosb, &Ea, EaLength);
+ ASSERT(STATUS_INVALID_EA_NAME == Result);
+
+ EaLength = 0;
+ ea_init_ea(&Ea.V, sizeof Ea, &EaLength);
+ Result = NtSetEaFile(FileHandle, &Iosb, &Ea, EaLength);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(0 == Iosb.Information);
+ ea_check_ea(FileHandle);
+
+ EaLength = 0;
+ ea_init_ea2(&Ea.V, sizeof Ea, &EaLength);
+ Result = NtSetEaFile(FileHandle, &Iosb, &Ea, EaLength);
+ ASSERT(STATUS_SUCCESS == Result);
+ ASSERT(0 == Iosb.Information);
+ Result = NtQueryEaFile(FileHandle, &Iosb, &Ea, sizeof Ea, FALSE, 0, 0, 0, FALSE);
+ ASSERT(STATUS_EA_CORRUPT_ERROR == Result);
+ ea_check_ea2(FileHandle);
+
+ CloseHandle(FileHandle);
+
+ FileHandle = CreateFileW(FilePath,
+ GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING,
+ FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_DELETE_ON_CLOSE, 0);
+ ASSERT(INVALID_HANDLE_VALUE == FileHandle);
+ ASSERT(ERROR_FILE_NOT_FOUND == GetLastError());
+
+ memfs_stop(memfs);
+}
+
+static void ea_getset_test(void)
+{
+ if (NtfsTests)
+ {
+ WCHAR DirBuf[MAX_PATH];
+ GetTestDirectory(DirBuf);
+ ea_getset_dotest(-1, DirBuf, 0);
+ }
+ if (WinFspDiskTests)
+ {
+ ea_getset_dotest(MemfsDisk, 0, 0);
+ ea_getset_dotest(MemfsDisk, 0, 1000);
+ }
+ if (WinFspNetTests)
+ {
+ ea_getset_dotest(MemfsNet, L"\\\\memfs\\share", 0);
+ ea_getset_dotest(MemfsNet, L"\\\\memfs\\share", 1000);
+ }
+}
+
+void ea_tests(void)
+{
+ TEST_OPT(ea_create_test);
+ TEST_OPT(ea_overwrite_test);
+ TEST_OPT(ea_getset_test);
+}
diff --git a/tst/winfsp-tests/winfsp-tests.c b/tst/winfsp-tests/winfsp-tests.c
index 8e934338..e8be476c 100644
--- a/tst/winfsp-tests/winfsp-tests.c
+++ b/tst/winfsp-tests/winfsp-tests.c
@@ -206,6 +206,7 @@ int main(int argc, char *argv[])
TESTSUITE(exec_tests);
TESTSUITE(devctl_tests);
TESTSUITE(reparse_tests);
+ TESTSUITE(ea_tests);
TESTSUITE(stream_tests);
TESTSUITE(oplock_tests);