From c208e0ecbd77ec11b34b392110b540313143bfd3 Mon Sep 17 00:00:00 2001 From: Bill Zissimopoulos Date: Sat, 4 Dec 2021 12:36:12 +0000 Subject: [PATCH] sys: cache FileDesc->DispositionStatus DeleteFileW and RemoveDirectoryW in recent versions of Windows 10 have been changed to perform a FileDispositionInformationEx with POSIX semantics and if that fails to retry with FileDispositionInformation. Unfortunately this is done even for legitimate error codes such as STATUS_DIRECTORY_NOT_EMPTY. This means that user mode file systems have to do unnecessary CanDelete checks even when they support FileDispositionInformationEx. The extra check incurs extra context switches, and in some cases it may also be costly to compute (e.g. FUSE). We optimize this away by storing the status of the last CanDelete check in the FileDesc and then continue returning the same status code for all checks for the same FileDesc. --- src/sys/driver.h | 1 + src/sys/fileinfo.c | 74 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/sys/driver.h b/src/sys/driver.h index 51301a88..d164e3a4 100644 --- a/src/sys/driver.h +++ b/src/sys/driver.h @@ -1497,6 +1497,7 @@ typedef struct DidSetFileAttributes:1, DidSetReparsePoint:1, DidSetSecurity:1, DidSetCreationTime:1, DidSetLastAccessTime:1, DidSetLastWriteTime:1, DidSetChangeTime:1, DirectoryHasSuchFile:1; + NTSTATUS DispositionStatus; UNICODE_STRING DirectoryPattern; UNICODE_STRING DirectoryMarker; UINT64 DirInfo; diff --git a/src/sys/fileinfo.c b/src/sys/fileinfo.c index 25e0c53b..fc7a3675 100644 --- a/src/sys/fileinfo.c +++ b/src/sys/fileinfo.c @@ -83,6 +83,8 @@ static NTSTATUS FspFsvolSetDispositionInformation( PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp); static NTSTATUS FspFsvolSetDispositionInformationSuccess( PIRP Irp, const FSP_FSCTL_TRANSACT_RSP *Response); +static NTSTATUS FspFsvolSetDispositionInformationFailure( + PIRP Irp, const FSP_FSCTL_TRANSACT_RSP *Response); static NTSTATUS FspFsvolSetRenameInformation( PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp); static NTSTATUS FspFsvolSetRenameInformationSuccess( @@ -125,6 +127,7 @@ FAST_IO_QUERY_OPEN FspFastIoQueryOpen; #pragma alloc_text(PAGE, FspFsvolSetPositionInformation) #pragma alloc_text(PAGE, FspFsvolSetDispositionInformation) #pragma alloc_text(PAGE, FspFsvolSetDispositionInformationSuccess) +#pragma alloc_text(PAGE, FspFsvolSetDispositionInformationFailure) #pragma alloc_text(PAGE, FspFsvolSetRenameInformation) #pragma alloc_text(PAGE, FspFsvolSetRenameInformationSuccess) #pragma alloc_text(PAGE, FspFsvolSetInformation) @@ -1567,6 +1570,27 @@ retry: else DispositionFlags = FILE_DISPOSITION_DO_NOT_DELETE; + /* + * DeleteFileW and RemoveDirectoryW in recent versions of Windows 10 have been changed to + * perform a FileDispositionInformationEx with POSIX semantics and if that fails to retry + * with FileDispositionInformation. Unfortunately this is done even for legitimate error + * codes such as STATUS_DIRECTORY_NOT_EMPTY. + * + * This means that user mode file systems have to do unnecessary CanDelete checks even when + * they support FileDispositionInformationEx. The extra check incurs extra context switches, + * and in some cases it may also be costly to compute (e.g. FUSE). + * + * We optimize this away by storing the status of the last CanDelete check in the FileDesc + * and then continue returning the same status code for all checks for the same FileDesc. + */ + if (FILE_DISPOSITION_DELETE == (DispositionFlags & ~FILE_DISPOSITION_POSIX_SEMANTICS) && + STATUS_SUCCESS != FileDesc->DispositionStatus) + { + Result = FileDesc->DispositionStatus; + goto unlock_exit; + } + FileDesc->DispositionStatus = STATUS_SUCCESS; + Result = FspIopCreateRequestEx(Irp, &FileNode->FileName, 0, FspFsvolSetInformationRequestFini, &Request); if (!NT_SUCCESS(Result)) @@ -1629,6 +1653,44 @@ static NTSTATUS FspFsvolSetDispositionInformationSuccess( return STATUS_SUCCESS; } +static NTSTATUS FspFsvolSetDispositionInformationFailure( + PIRP Irp, const FSP_FSCTL_TRANSACT_RSP *Response) +{ + PAGED_CODE(); + + PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); + PFILE_OBJECT FileObject = IrpSp->FileObject; + FSP_FILE_DESC *FileDesc = FileObject->FsContext2; + FSP_FSCTL_TRANSACT_REQ *Request = FspIrpRequest(Irp); + UINT32 DispositionFlags = Request->Req.SetInformation.Info.DispositionEx.Flags; + + /* + * DeleteFileW and RemoveDirectoryW in recent versions of Windows 10 have been changed to + * perform a FileDispositionInformationEx with POSIX semantics and if that fails to retry + * with FileDispositionInformation. Unfortunately this is done even for legitimate error + * codes such as STATUS_DIRECTORY_NOT_EMPTY. + * + * This means that user mode file systems have to do unnecessary CanDelete checks even when + * they support FileDispositionInformationEx. The extra check incurs extra context switches, + * and in some cases it may also be costly to compute (e.g. FUSE). + * + * We optimize this away by storing the status of the last CanDelete check in the FileDesc + * and then continue returning the same status code for all checks for the same FileDesc. + */ + switch (Response->IoStatus.Status) + { + case STATUS_ACCESS_DENIED: + case STATUS_DIRECTORY_NOT_EMPTY: + case STATUS_CANNOT_DELETE: + if (FILE_DISPOSITION_DELETE == (DispositionFlags & ~FILE_DISPOSITION_POSIX_SEMANTICS)) + FileDesc->DispositionStatus = Response->IoStatus.Status; + break; + } + + Irp->IoStatus.Information = 0; + return Response->IoStatus.Status; +} + static NTSTATUS FspFsvolSetRenameInformation( PDEVICE_OBJECT FsvolDeviceObject, PIRP Irp, PIO_STACK_LOCATION IrpSp) { @@ -2095,15 +2157,23 @@ NTSTATUS FspFsvolSetInformationComplete( { FSP_ENTER_IOC(PAGED_CODE()); + FILE_INFORMATION_CLASS FileInformationClass = IrpSp->Parameters.SetFile.FileInformationClass; + if (!NT_SUCCESS(Response->IoStatus.Status)) { + /* special case FileDispositionInformation */ + switch (FileInformationClass) + { + case FileDispositionInformation: + case FileDispositionInformationEx: + FSP_RETURN(Result = FspFsvolSetDispositionInformationFailure(Irp, Response)); + } + Irp->IoStatus.Information = 0; Result = Response->IoStatus.Status; FSP_RETURN(); } - FILE_INFORMATION_CLASS FileInformationClass = IrpSp->Parameters.SetFile.FileInformationClass; - /* special case FileDispositionInformation/FileRenameInformation */ switch (FileInformationClass) {