dotnet: implement new Delete design and POSIX semantics

This commit is contained in:
Bill Zissimopoulos 2021-10-20 12:23:14 +01:00
parent 81248f3899
commit 76bfa395a8
No known key found for this signature in database
GPG Key ID: 3D4F95D52C7B3EA3
6 changed files with 187 additions and 54 deletions

View File

@ -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)

View File

@ -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);

View File

@ -289,6 +289,9 @@ namespace Fsp
/// </summary>
/// <remarks>
/// <para>
/// (NOTE: use of this function with the CleanupDelete flag is not recommended;
/// use Delete instead.)
/// </para><para>
/// 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
/// </param>
/// <seealso cref="CanDelete"/>
/// <seealso cref="SetDelete"/>
/// <seealso cref="Delete"/>
/// <seealso cref="Close"/>
public virtual void Cleanup(
Object FileNode,
@ -598,12 +602,14 @@ namespace Fsp
/// </summary>
/// <remarks>
/// <para>
/// (NOTE: use of this function is not recommended; use Delete instead.)
/// </para><para>
/// 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.
/// </para><para>
/// This function should <b>NEVER</b> delete the file or directory in question. Deletion should
/// happen during Cleanup with the FspCleanupDelete flag set.
/// happen during Cleanup with the CleanupDelete flag set.
/// </para><para>
/// This function gets called when Win32 API's such as DeleteFile or RemoveDirectory are used.
/// It does not get called when a file or directory is opened with FILE_DELETE_ON_CLOSE.
@ -624,6 +630,7 @@ namespace Fsp
/// <returns>STATUS_SUCCESS or error code.</returns>
/// <seealso cref="Cleanup"/>
/// <seealso cref="SetDelete"/>
/// <seealso cref="Delete"/>
public virtual Int32 CanDelete(
Object FileNode,
Object FileDesc,
@ -1036,12 +1043,14 @@ namespace Fsp
/// </summary>
/// <remarks>
/// <para>
/// (NOTE: use of this function is not recommended; use Delete instead.)
/// </para><para>
/// 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.
/// </para><para>
/// This function should <b>NEVER</b> delete the file or directory in question. Deletion should
/// happen during Cleanup with the FspCleanupDelete flag set.
/// happen during Cleanup with the CleanupDelete flag set.
/// </para><para>
/// This function gets called when Win32 API's such as DeleteFile or RemoveDirectory are used.
/// It does not get called when a file or directory is opened with FILE_DELETE_ON_CLOSE.
@ -1067,6 +1076,7 @@ namespace Fsp
/// <returns>STATUS_SUCCESS or error code.</returns>
/// <seealso cref="Cleanup"/>
/// <seealso cref="CanDelete"/>
/// <seealso cref="Delete"/>
public virtual Int32 SetDelete(
Object FileNode,
Object FileDesc,
@ -1188,6 +1198,85 @@ namespace Fsp
{
return STATUS_INVALID_DEVICE_REQUEST;
}
/// <summary>
/// Sets the file delete flag or deletes a file or directory.
/// </summary>
/// <remarks>
/// <para>
/// This function replaces CanDelete, SetDelete and uses of Cleanup with the CleanupDelete flag
/// and is recommended for use in all new code.
/// </para><para>
/// 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:
/// <list>
/// <item>FILE_DISPOSITION_DO_NOT_DELETE: Unmark the file for deletion.
/// Do <b>NOT</b> delete the file either now or at Cleanup time.</item>
/// <item>FILE_DISPOSITION_DELETE: Mark the file for deletion,
/// but do <b>NOT</b> 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.</item>
/// <item>FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS: Delete the file
/// <b>NOW</b> using POSIX semantics. Open user mode handles to the file remain valid.
/// This case will be received only when SupportsPosixUnlinkRename is set.</item>
/// <item>-1: Delete the file <b>NOW</b> using regular Windows semantics.
/// Called during Cleanup with no open user mode handles remaining.
/// If a file system implements Delete, Cleanup should <b>NOT</b> be used for deletion anymore.</item>
/// </list>
/// </para><para>
/// This function gets called in all file deletion scenarios:
/// <list>
/// <item>When the DeleteFile or RemoveDirectory API's are used.</item>
/// <item>When the SetInformationByHandle API with FileDispositionInfo or FileDispositionInfoEx is used.</item>
/// <item>When a file is opened using FILE_DELETE_ON_CLOSE.</item>
/// <item>Etc.</item>
/// </list>
/// </para><para>
/// 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.
/// </para>
/// </remarks>
/// <param name="FileNode">
/// The file node of the file or directory.
/// </param>
/// <param name="FileDesc">
/// The file descriptor of the file or directory.
/// </param>
/// <param name="FileName">
/// The name of the file or directory.
/// </param>
/// <param name="Flags">
/// File disposition flags.
/// </param>
/// <returns>STATUS_SUCCESS or error code.</returns>
/// <seealso cref="Cleanup"/>
/// <seealso cref="CanDelete"/>
/// <seealso cref="SetDelete"/>
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 */
/// <summary>

View File

@ -312,6 +312,11 @@ namespace Fsp
get { return 0 != (_VolumeParams.Flags & VolumeParams.RejectIrpPriorToTransact0); }
set { _VolumeParams.Flags |= (value ? VolumeParams.RejectIrpPriorToTransact0 : 0); }
}
public Boolean SupportsPosixUnlinkRename
{
get { return 0 != (_VolumeParams.Flags & VolumeParams.SupportsPosixUnlinkRename); }
set { _VolumeParams.Flags |= (value ? VolumeParams.SupportsPosixUnlinkRename : 0); }
}
/// <summary>
/// Gets or sets the prefix for a network file system.
/// </summary>
@ -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! */

View File

@ -55,6 +55,7 @@ namespace Fsp.Interop
internal const UInt32 CasePreservedExtendedAttributes = 0x02000000;
internal const UInt32 WslFeatures = 0x04000000;
internal const UInt32 RejectIrpPriorToTransact0 = 0x10000000;
internal const UInt32 SupportsPosixUnlinkRename = 0x20000000;
internal const int PrefixSize = 192;
internal const int FileSystemNameSize = 16;
@ -741,6 +742,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]

View File

@ -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<String> StreamFileNames = new List<String>(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<String> StreamFileNames = new List<String>(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;