diff --git a/src/dll/fsop.c b/src/dll/fsop.c index aed2c385..d8780dd9 100644 --- a/src/dll/fsop.c +++ b/src/dll/fsop.c @@ -998,11 +998,20 @@ FSP_API NTSTATUS FspFileSystemOpCleanup(FSP_FILE_SYSTEM *FileSystem, if (Request->Req.Cleanup.Delete && 0 != FileSystem->Interface->Delete) { - FileSystem->Interface->Delete(FileSystem, + NTSTATUS Result = FileSystem->Interface->Delete(FileSystem, (PVOID)ValOfFileContext(Request->Req.Cleanup), 0 != Request->FileName.Size ? (PWSTR)Request->Buffer : 0, (ULONG)-1); - CleanupFlags &= ~FspCleanupDelete; + /* + * If Delete returns STATUS_NOT_IMPLEMENTED it means that it is unable + * to handle file deletion during the Cleanup phase. In this case we + * will handle file deletion in Cleanup with the FspCleanupDelete flag. + * + * This is necessary for file systems like the .NET layer where Delete + * may be implemented but unable to handle all file deletion cases. + */ + if (STATUS_NOT_IMPLEMENTED != Result) + CleanupFlags &= ~FspCleanupDelete; } if (0 != FileSystem->Interface->Cleanup) diff --git a/src/dotnet/FileSystemBase+Const.cs b/src/dotnet/FileSystemBase+Const.cs index 3674e040..5211f4e9 100644 --- a/src/dotnet/FileSystemBase+Const.cs +++ b/src/dotnet/FileSystemBase+Const.cs @@ -57,6 +57,14 @@ namespace Fsp public const UInt32 CleanupSetLastWriteTime = 0x40; public const UInt32 CleanupSetChangeTime = 0x80; + /* Disposition */ + public const UInt32 FILE_DISPOSITION_DO_NOT_DELETE = 0x00000000; + public const UInt32 FILE_DISPOSITION_DELETE = 0x00000001; + public const UInt32 FILE_DISPOSITION_POSIX_SEMANTICS = 0x00000002; + public const UInt32 FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK = 0x00000004; + public const UInt32 FILE_DISPOSITION_ON_CLOSE = 0x00000008; + public const UInt32 FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE = 0x00000010; + /* NTSTATUS */ public const Int32 STATUS_SUCCESS = unchecked((Int32)0x00000000); public const Int32 STATUS_WAIT_1 = unchecked((Int32)0x00000001); diff --git a/src/dotnet/FileSystemBase.cs b/src/dotnet/FileSystemBase.cs index f9107bee..140934ed 100644 --- a/src/dotnet/FileSystemBase.cs +++ b/src/dotnet/FileSystemBase.cs @@ -289,6 +289,9 @@ namespace Fsp /// /// /// + /// (NOTE: use of this function with the CleanupDelete flag is not recommended; + /// use Delete instead.) + /// /// When CreateFile is used to open or create a file the kernel creates a kernel mode file /// object (type FILE_OBJECT) and a handle for it, which it returns to user-mode. The handle may /// be duplicated (using DuplicateHandle), but all duplicate handles always refer to the same @@ -342,6 +345,7 @@ namespace Fsp /// /// /// + /// /// public virtual void Cleanup( Object FileNode, @@ -598,12 +602,14 @@ namespace Fsp /// /// /// + /// (NOTE: use of this function is not recommended; use Delete instead.) + /// /// This function tests whether a file or directory can be safely deleted. This function does /// not need to perform access checks, but may performs tasks such as check for empty /// directories, etc. /// /// This function should NEVER delete the file or directory in question. Deletion should - /// happen during Cleanup with the FspCleanupDelete flag set. + /// happen during Cleanup with the CleanupDelete flag set. /// /// This function gets called when Win32 API's such as DeleteFile or RemoveDirectory are used. /// It does not get called when a file or directory is opened with FILE_DELETE_ON_CLOSE. @@ -624,6 +630,7 @@ namespace Fsp /// STATUS_SUCCESS or error code. /// /// + /// public virtual Int32 CanDelete( Object FileNode, Object FileDesc, @@ -1036,12 +1043,14 @@ namespace Fsp /// /// /// + /// (NOTE: use of this function is not recommended; use Delete instead.) + /// /// This function sets a flag to indicates whether the FSD file should delete a file /// when it is closed. This function does not need to perform access checks, but may /// performs tasks such as check for empty directories, etc. /// /// This function should NEVER delete the file or directory in question. Deletion should - /// happen during Cleanup with the FspCleanupDelete flag set. + /// happen during Cleanup with the CleanupDelete flag set. /// /// This function gets called when Win32 API's such as DeleteFile or RemoveDirectory are used. /// It does not get called when a file or directory is opened with FILE_DELETE_ON_CLOSE. @@ -1067,6 +1076,7 @@ namespace Fsp /// STATUS_SUCCESS or error code. /// /// + /// public virtual Int32 SetDelete( Object FileNode, Object FileDesc, @@ -1188,6 +1198,85 @@ namespace Fsp { return STATUS_INVALID_DEVICE_REQUEST; } + /// + /// Sets the file delete flag or deletes a file or directory. + /// + /// + /// + /// This function replaces CanDelete, SetDelete and uses of Cleanup with the CleanupDelete flag + /// and is recommended for use in all new code. + /// + /// Due to the complexity of file deletion in the Windows file system this function is used + /// in many scenarios. Its usage is controlled by the Flags parameter: + /// + /// FILE_DISPOSITION_DO_NOT_DELETE: Unmark the file for deletion. + /// Do NOT delete the file either now or at Cleanup time. + /// FILE_DISPOSITION_DELETE: Mark the file for deletion, + /// but do NOT delete the file. The file will be deleted at Cleanup time + /// (via a call to Delete with Flags = -1). + /// This function does not need to perform access checks, but may + /// performs tasks such as check for empty directories, etc. + /// FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS: Delete the file + /// NOW using POSIX semantics. Open user mode handles to the file remain valid. + /// This case will be received only when SupportsPosixUnlinkRename is set. + /// -1: Delete the file NOW using regular Windows semantics. + /// Called during Cleanup with no open user mode handles remaining. + /// If a file system implements Delete, Cleanup should NOT be used for deletion anymore. + /// + /// + /// This function gets called in all file deletion scenarios: + /// + /// When the DeleteFile or RemoveDirectory API's are used. + /// When the SetInformationByHandle API with FileDispositionInfo or FileDispositionInfoEx is used. + /// When a file is opened using FILE_DELETE_ON_CLOSE. + /// Etc. + /// + /// + /// NOTE: Delete takes precedence over CanDelete, SetDelete and Cleanup with the CleanupDelete flag. + /// This means that if Delete is defined, CanDelete and SetDelete will never be called and + /// Cleanup will never be called with the CleanupDelete flag. + /// + /// + /// + /// The file node of the file or directory. + /// + /// + /// The file descriptor of the file or directory. + /// + /// + /// The name of the file or directory. + /// + /// + /// File disposition flags. + /// + /// STATUS_SUCCESS or error code. + /// + /// + /// + public virtual Int32 Delete( + Object FileNode, + Object FileDesc, + String FileName, + UInt32 Flags) + { + switch (Flags) + { + case FILE_DISPOSITION_DO_NOT_DELETE: + return SetDelete(FileNode, FileDesc, FileName, false); + + case FILE_DISPOSITION_DELETE: + return SetDelete(FileNode, FileDesc, FileName, true); + + case FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS: + return STATUS_INVALID_PARAMETER; + + case ~(UInt32)0: + return STATUS_NOT_IMPLEMENTED; + + default: + return STATUS_INVALID_PARAMETER; + } + } /* helpers */ /// diff --git a/src/dotnet/FileSystemHost.cs b/src/dotnet/FileSystemHost.cs index 4723a494..5866faa9 100644 --- a/src/dotnet/FileSystemHost.cs +++ b/src/dotnet/FileSystemHost.cs @@ -312,6 +312,11 @@ namespace Fsp get { return 0 != (_VolumeParams.Flags & VolumeParams.RejectIrpPriorToTransact0); } set { _VolumeParams.Flags |= (value ? VolumeParams.RejectIrpPriorToTransact0 : 0); } } + public Boolean SupportsPosixUnlinkRename + { + get { return 0 != (_VolumeParams.Flags & VolumeParams.SupportsPosixUnlinkRename); } + set { _VolumeParams.Flags |= (value ? VolumeParams.SupportsPosixUnlinkRename : 0); } + } /// /// Gets or sets the prefix for a network file system. /// @@ -1343,28 +1348,6 @@ namespace Fsp return ExceptionHandler(FileSystem, ex); } } - private static Int32 SetDelete( - IntPtr FileSystemPtr, - ref FullContext FullContext, - String FileName, - Boolean DeleteFile) - { - FileSystemBase FileSystem = (FileSystemBase)Api.GetUserContext(FileSystemPtr); - try - { - Object FileNode, FileDesc; - Api.GetFullContext(ref FullContext, out FileNode, out FileDesc); - return FileSystem.SetDelete( - FileNode, - FileDesc, - FileName, - DeleteFile); - } - catch (Exception ex) - { - return ExceptionHandler(FileSystem, ex); - } - } private static Int32 GetEa( IntPtr FileSystemPtr, ref FullContext FullContext, @@ -1415,6 +1398,28 @@ namespace Fsp return ExceptionHandler(FileSystem, ex); } } + private static Int32 Delete( + IntPtr FileSystemPtr, + ref FullContext FullContext, + String FileName, + UInt32 Flags) + { + FileSystemBase FileSystem = (FileSystemBase)Api.GetUserContext(FileSystemPtr); + try + { + Object FileNode, FileDesc; + Api.GetFullContext(ref FullContext, out FileNode, out FileDesc); + return FileSystem.Delete( + FileNode, + FileDesc, + FileName, + Flags); + } + catch (Exception ex) + { + return ExceptionHandler(FileSystem, ex); + } + } static FileSystemHost() { @@ -1443,9 +1448,9 @@ namespace Fsp _FileSystemInterface.GetStreamInfo = GetStreamInfo; _FileSystemInterface.GetDirInfoByName = GetDirInfoByName; _FileSystemInterface.Control = Control; - _FileSystemInterface.SetDelete = SetDelete; _FileSystemInterface.GetEa = GetEa; _FileSystemInterface.SetEa = SetEa; + _FileSystemInterface.Delete = Delete; _FileSystemInterfacePtr = Marshal.AllocHGlobal(FileSystemInterface.Size); /* Marshal.AllocHGlobal does not zero memory; we must do it ourselves! */ diff --git a/src/dotnet/Interop.cs b/src/dotnet/Interop.cs index 0073e4bb..afaecbf6 100644 --- a/src/dotnet/Interop.cs +++ b/src/dotnet/Interop.cs @@ -55,6 +55,7 @@ namespace Fsp.Interop internal const UInt32 CasePreservedExtendedAttributes = 0x02000000; internal const UInt32 WslFeatures = 0x04000000; internal const UInt32 RejectIrpPriorToTransact0 = 0x10000000; + internal const UInt32 SupportsPosixUnlinkRename = 0x20000000; internal const int PrefixSize = 192; internal const int FileSystemNameSize = 16; @@ -741,6 +742,12 @@ namespace Fsp.Interop IntPtr Ea, UInt32 EaLength, out FileInfo FileInfo); + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + internal delegate Int32 Delete( + IntPtr FileSystem, + ref FullContext FullContext, + [MarshalAs(UnmanagedType.LPWStr)] String FileName, + UInt32 Flags); } internal static int Size = IntPtr.Size * 64; @@ -776,7 +783,8 @@ namespace Fsp.Interop internal Proto.OverwriteEx OverwriteEx; internal Proto.GetEa GetEa; internal Proto.SetEa SetEa; - /* NTSTATUS (*Reserved[33])(); */ + internal Proto.Delete Delete; + /* NTSTATUS (*Reserved[32])(); */ } [SuppressUnmanagedCodeSecurity] diff --git a/tst/memfs-dotnet/Program.cs b/tst/memfs-dotnet/Program.cs index da6481b1..97492ba9 100644 --- a/tst/memfs-dotnet/Program.cs +++ b/tst/memfs-dotnet/Program.cs @@ -286,6 +286,7 @@ namespace memfs Host.ExtendedAttributes = true; Host.WslFeatures = true; Host.RejectIrpPriorToTransact0 = true; + Host.SupportsPosixUnlinkRename = true; return STATUS_SUCCESS; } @@ -563,19 +564,6 @@ namespace memfs AllocationUnit * AllocationUnit; SetFileSizeInternal(FileNode, AllocationSize, true); } - - if (0 != (Flags & CleanupDelete) && !FileNodeMap.HasChild(FileNode)) - { - List StreamFileNames = new List(FileNodeMap.GetStreamFileNames(FileNode)); - foreach (String StreamFileName in StreamFileNames) - { - FileNode StreamNode = FileNodeMap.Get(StreamFileName); - if (null == StreamNode) - continue; /* should not happen */ - FileNodeMap.Remove(StreamNode); - } - FileNodeMap.Remove(FileNode); - } } public override void Close( @@ -920,19 +908,6 @@ namespace memfs return STATUS_SUCCESS; } - public override Int32 CanDelete( - Object FileNode0, - Object FileDesc, - String FileName) - { - FileNode FileNode = (FileNode)FileNode0; - - if (FileNodeMap.HasChild(FileNode)) - return STATUS_DIRECTORY_NOT_EMPTY; - - return STATUS_SUCCESS; - } - public override Int32 Rename( Object FileNode0, Object FileDesc, @@ -1330,6 +1305,45 @@ namespace memfs FileNode.FileInfo.EaSize = FileNode.FileInfo.EaSize + EaSizePlus - EaSizeMinus; return STATUS_SUCCESS; } + public override Int32 Delete( + Object FileNode0, + Object FileDesc, + String FileName, + UInt32 Flags) + { + FileNode FileNode = (FileNode)FileNode0; + + switch (Flags) + { + case FILE_DISPOSITION_DO_NOT_DELETE: + return STATUS_SUCCESS; + + case FILE_DISPOSITION_DELETE: + if (FileNodeMap.HasChild(FileNode)) + return STATUS_DIRECTORY_NOT_EMPTY; + return STATUS_SUCCESS; + + case FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS: + case ~(UInt32)0: + if (FileNodeMap.HasChild(FileNode)) + return STATUS_DIRECTORY_NOT_EMPTY; + + List StreamFileNames = new List(FileNodeMap.GetStreamFileNames(FileNode)); + foreach (String StreamFileName in StreamFileNames) + { + FileNode StreamNode = FileNodeMap.Get(StreamFileName); + if (null == StreamNode) + continue; /* should not happen */ + FileNodeMap.Remove(StreamNode); + } + + FileNodeMap.Remove(FileNode); + return STATUS_SUCCESS; + + default: + return STATUS_INVALID_PARAMETER; + } + } private FileNodeMap FileNodeMap; private UInt32 MaxFileNodes;