From 19823d84de16a144d6ea8e5b013e059f645737f3 Mon Sep 17 00:00:00 2001 From: Bill Zissimopoulos Date: Tue, 19 Oct 2021 15:23:15 +0100 Subject: [PATCH] sys,dll,inc: implement new Delete design and POSIX semantics --- appveyor.yml | 4 +- inc/winfsp/fsctl.h | 7 +- inc/winfsp/winfsp.h | 78 +++++++++++++++++- src/dll/fsop.c | 59 ++++++++++--- src/sys/cleanup.c | 17 ++-- src/sys/driver.h | 2 + src/sys/file.c | 64 ++++++++++++++- src/sys/fileinfo.c | 141 ++++++++++++++++++++++++++------ src/sys/volinfo.c | 3 +- tools/run-tests.bat | 12 +++ tst/memfs/memfs.cpp | 98 +++++++++++++++++----- tst/memfs/memfs.h | 3 +- tst/winfsp-tests/memfs-test.c | 3 +- tst/winfsp-tests/winfsp-tests.c | 6 ++ tst/winfsp-tests/winfsp-tests.h | 1 + 15 files changed, 424 insertions(+), 74 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 9fd8613c..f3353fa6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -42,8 +42,8 @@ install: build_script: - appveyor AddMessage "Reboot complete" -Category Information # build cygfuse -- C:\cygwin64\setup-x86_64.exe -qnNd -P cygport -- C:\cygwin64\bin\bash --login -c "make -C '%CD%\opt\cygfuse' dist" +#- C:\cygwin64\setup-x86_64.exe -qnNd -P cygport +#- C:\cygwin64\bin\bash --login -c "make -C '%CD%\opt\cygfuse' dist" #- C:\cygwin\setup-x86.exe -qnNd -P cygport #- C:\cygwin\bin\bash --login -c "make -C '%CD%\opt\cygfuse' dist" # build winfsp diff --git a/inc/winfsp/fsctl.h b/inc/winfsp/fsctl.h index 1ad8d124..86871fed 100644 --- a/inc/winfsp/fsctl.h +++ b/inc/winfsp/fsctl.h @@ -204,7 +204,8 @@ enum UINT32 WslFeatures:1; /* support features required for WSLinux */\ UINT32 DirectoryMarkerAsNextOffset:1; /* directory marker is next offset instead of last name */\ UINT32 RejectIrpPriorToTransact0:1; /* reject IRP's prior to FspFsctlTransact with 0 buffers */\ - UINT32 KmReservedFlags:3;\ + UINT32 SupportsPosixUnlinkRename:1; /* file system supports POSIX-style unlink and rename */\ + UINT32 KmReservedFlags:2;\ WCHAR Prefix[FSP_FSCTL_VOLUME_PREFIX_SIZE / sizeof(WCHAR)]; /* UNC prefix (\Server\Share) */\ WCHAR FileSystemName[FSP_FSCTL_VOLUME_FSNAME_SIZE / sizeof(WCHAR)]; #define FSP_FSCTL_VOLUME_PARAMS_V1_FIELD_DEFN\ @@ -413,6 +414,10 @@ typedef struct UINT32 Delete:1; } Disposition; struct + { + UINT32 Flags; + } DispositionEx; + struct { UINT64 FileSize; } EndOfFile; diff --git a/inc/winfsp/winfsp.h b/inc/winfsp/winfsp.h index fb44f895..2440bcd6 100644 --- a/inc/winfsp/winfsp.h +++ b/inc/winfsp/winfsp.h @@ -47,6 +47,19 @@ extern "C" { #endif +/* + * The FILE_DISPOSITION_* definitions appear to be missing from the user mode headers. + */ +#if !defined(FILE_DISPOSITION_DELETE) +#define FILE_DISPOSITION_DO_NOT_DELETE 0x00000000 +#define FILE_DISPOSITION_DELETE 0x00000001 +#define FILE_DISPOSITION_POSIX_SEMANTICS 0x00000002 +/* remaining flags are not needed for user mode file systems but included for completeness */ +#define FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK 0x00000004 +#define FILE_DISPOSITION_ON_CLOSE 0x00000008 +#define FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE 0x00000010 +#endif + /* * The REPARSE_DATA_BUFFER definitions appear to be missing from the user mode headers. */ @@ -348,6 +361,9 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE /** * Cleanup a file. * + * (NOTE: use of this function with the FspCleanupDelete 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 @@ -402,6 +418,7 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE * Close * CanDelete * SetDelete + * Delete */ VOID (*Cleanup)(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PWSTR FileName, ULONG Flags); @@ -575,6 +592,8 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE /** * Determine whether a file or directory can be deleted. * + * (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. @@ -599,6 +618,7 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE * @see * Cleanup * SetDelete + * Delete */ NTSTATUS (*CanDelete)(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PWSTR FileName); @@ -880,6 +900,8 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE /** * Set the file delete flag. * + * (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. @@ -908,6 +930,7 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE * @see * Cleanup * CanDelete + * Delete */ NTSTATUS (*SetDelete)(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PWSTR FileName, BOOLEAN DeleteFile); @@ -1040,12 +1063,65 @@ typedef struct _FSP_FILE_SYSTEM_INTERFACE PVOID FileContext, PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, FSP_FSCTL_FILE_INFO *FileInfo); + /** + * Set the file delete flag or delete a file. + * + * This function replaces CanDelete, SetDelete and uses of Cleanup with the FspCleanupDelete 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: + * + * + * This function gets called in all file deletion scenarios: + * + * + * NOTE: Delete takes precedence over CanDelete, SetDelete and Cleanup with the FspCleanupDelete flag. + * This means that if Delete is defined, CanDelete and SetDelete will never be called and + * Cleanup will never be called with the FspCleanupDelete flag. + * + * @param FileSystem + * The file system on which this request is posted. + * @param FileContext + * The file context of the file or directory to set the delete flag for. + * @param FileName + * The name of the file or directory to set the delete flag for. + * @param Flags + * File disposition flags + * @return + * STATUS_SUCCESS or error code. + * @see + * Cleanup + * CanDelete + * SetDelete + */ + NTSTATUS (*Delete)(FSP_FILE_SYSTEM *FileSystem, + PVOID FileContext, PWSTR FileName, ULONG Flags); /* * This ensures that this interface will always contain 64 function pointers. * Please update when changing the interface as it is important for future compatibility. */ - NTSTATUS (*Reserved[33])(); + NTSTATUS (*Reserved[32])(); } FSP_FILE_SYSTEM_INTERFACE; FSP_FSCTL_STATIC_ASSERT(sizeof(FSP_FILE_SYSTEM_INTERFACE) == 64 * sizeof(NTSTATUS (*)()), "FSP_FILE_SYSTEM_INTERFACE must have 64 entries."); diff --git a/src/dll/fsop.c b/src/dll/fsop.c index 536aa16d..aed2c385 100644 --- a/src/dll/fsop.c +++ b/src/dll/fsop.c @@ -56,7 +56,10 @@ FSP_API NTSTATUS FspFileSystemOpEnter(FSP_FILE_SYSTEM *FileSystem, (FspFsctlTransactCleanupKind == Request->Kind && Request->Req.Cleanup.Delete) || (FspFsctlTransactSetInformationKind == Request->Kind && - 10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass) || + (10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass || + 65/*FileRenameInformationEx*/ == Request->Req.SetInformation.FileInformationClass || + (64/*FileDispositionInformationEx*/ == Request->Req.SetInformation.FileInformationClass && + 3/*DELETE|POSIX_SEMANTICS*/ == (3 & Request->Req.SetInformation.Info.DispositionEx.Flags)))) || FspFsctlTransactSetVolumeInformationKind == Request->Kind || (FspFsctlTransactFlushBuffersKind == Request->Kind && 0 == Request->Req.FlushBuffers.UserContext && @@ -67,7 +70,9 @@ FSP_API NTSTATUS FspFileSystemOpEnter(FSP_FILE_SYSTEM *FileSystem, else if (FspFsctlTransactCreateKind == Request->Kind || (FspFsctlTransactSetInformationKind == Request->Kind && - 13/*FileDispositionInformation*/ == Request->Req.SetInformation.FileInformationClass) || + (13/*FileDispositionInformation*/ == Request->Req.SetInformation.FileInformationClass || + (64/*FileDispositionInformationEx*/ == Request->Req.SetInformation.FileInformationClass && + 3/*DELETE|POSIX_SEMANTICS*/ != (3 & Request->Req.SetInformation.Info.DispositionEx.Flags)))) || FspFsctlTransactQueryDirectoryKind == Request->Kind || FspFsctlTransactQueryVolumeInformationKind == Request->Kind) { @@ -95,7 +100,10 @@ FSP_API NTSTATUS FspFileSystemOpLeave(FSP_FILE_SYSTEM *FileSystem, (FspFsctlTransactCleanupKind == Request->Kind && Request->Req.Cleanup.Delete) || (FspFsctlTransactSetInformationKind == Request->Kind && - 10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass) || + (10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass || + 65/*FileRenameInformationEx*/ == Request->Req.SetInformation.FileInformationClass || + (64/*FileDispositionInformationEx*/ == Request->Req.SetInformation.FileInformationClass && + 3/*DELETE|POSIX_SEMANTICS*/ == (3 & Request->Req.SetInformation.Info.DispositionEx.Flags)))) || FspFsctlTransactSetVolumeInformationKind == Request->Kind || (FspFsctlTransactFlushBuffersKind == Request->Kind && 0 == Request->Req.FlushBuffers.UserContext && @@ -106,7 +114,9 @@ FSP_API NTSTATUS FspFileSystemOpLeave(FSP_FILE_SYSTEM *FileSystem, else if (FspFsctlTransactCreateKind == Request->Kind || (FspFsctlTransactSetInformationKind == Request->Kind && - 13/*FileDispositionInformation*/ == Request->Req.SetInformation.FileInformationClass) || + (13/*FileDispositionInformation*/ == Request->Req.SetInformation.FileInformationClass || + (64/*FileDispositionInformationEx*/ == Request->Req.SetInformation.FileInformationClass && + 3/*DELETE|POSIX_SEMANTICS*/ != (3 & Request->Req.SetInformation.Info.DispositionEx.Flags)))) || FspFsctlTransactQueryDirectoryKind == Request->Kind || FspFsctlTransactQueryVolumeInformationKind == Request->Kind) { @@ -978,16 +988,28 @@ FSP_API NTSTATUS FspFileSystemOpOverwrite(FSP_FILE_SYSTEM *FileSystem, FSP_API NTSTATUS FspFileSystemOpCleanup(FSP_FILE_SYSTEM *FileSystem, FSP_FSCTL_TRANSACT_REQ *Request, FSP_FSCTL_TRANSACT_RSP *Response) { + ULONG CleanupFlags = + (0 != Request->Req.Cleanup.Delete ? FspCleanupDelete : 0) | + (0 != Request->Req.Cleanup.SetAllocationSize ? FspCleanupSetAllocationSize : 0) | + (0 != Request->Req.Cleanup.SetArchiveBit ? FspCleanupSetArchiveBit : 0) | + (0 != Request->Req.Cleanup.SetLastAccessTime ? FspCleanupSetLastAccessTime : 0) | + (0 != Request->Req.Cleanup.SetLastWriteTime ? FspCleanupSetLastWriteTime : 0) | + (0 != Request->Req.Cleanup.SetChangeTime ? FspCleanupSetChangeTime : 0); + + if (Request->Req.Cleanup.Delete && 0 != FileSystem->Interface->Delete) + { + FileSystem->Interface->Delete(FileSystem, + (PVOID)ValOfFileContext(Request->Req.Cleanup), + 0 != Request->FileName.Size ? (PWSTR)Request->Buffer : 0, + (ULONG)-1); + CleanupFlags &= ~FspCleanupDelete; + } + if (0 != FileSystem->Interface->Cleanup) FileSystem->Interface->Cleanup(FileSystem, (PVOID)ValOfFileContext(Request->Req.Cleanup), 0 != Request->FileName.Size ? (PWSTR)Request->Buffer : 0, - (0 != Request->Req.Cleanup.Delete ? FspCleanupDelete : 0) | - (0 != Request->Req.Cleanup.SetAllocationSize ? FspCleanupSetAllocationSize : 0) | - (0 != Request->Req.Cleanup.SetArchiveBit ? FspCleanupSetArchiveBit : 0) | - (0 != Request->Req.Cleanup.SetLastAccessTime ? FspCleanupSetLastAccessTime : 0) | - (0 != Request->Req.Cleanup.SetLastWriteTime ? FspCleanupSetLastWriteTime : 0) | - (0 != Request->Req.Cleanup.SetChangeTime ? FspCleanupSetChangeTime : 0)); + CleanupFlags); return STATUS_SUCCESS; } @@ -1134,7 +1156,9 @@ FSP_API NTSTATUS FspFileSystemOpSetInformation(FSP_FILE_SYSTEM *FileSystem, &FileInfo); break; case 13/*FileDispositionInformation*/: - if (0 != FileSystem->Interface->GetFileInfo) + case 64/*FileDispositionInformationEx*/: + if (0 == (0x10/*IGNORE_READONLY_ATTRIBUTE*/ & Request->Req.SetInformation.Info.DispositionEx.Flags) && + 0 != FileSystem->Interface->GetFileInfo) { Result = FileSystem->Interface->GetFileInfo(FileSystem, (PVOID)ValOfFileContext(Request->Req.SetInformation), &FileInfo); @@ -1144,7 +1168,17 @@ FSP_API NTSTATUS FspFileSystemOpSetInformation(FSP_FILE_SYSTEM *FileSystem, break; } } - if (0 != FileSystem->Interface->SetDelete) + if (0 != FileSystem->Interface->Delete) + { + Result = FileSystem->Interface->Delete(FileSystem, + (PVOID)ValOfFileContext(Request->Req.SetInformation), + (PWSTR)Request->Buffer, + Request->Req.SetInformation.Info.DispositionEx.Flags & 3/*DELETE|POSIX_SEMANTICS*/); + } + else if (0 != (2/*POSIX_SEMANTICS*/ & Request->Req.SetInformation.Info.DispositionEx.Flags)) + /* only FSP_FILE_SYSTEM_INTERFACE::Delete can do POSIX semantics; return error if unimplemented */ + Result = STATUS_INVALID_PARAMETER; + else if (0 != FileSystem->Interface->SetDelete) { Result = FileSystem->Interface->SetDelete(FileSystem, (PVOID)ValOfFileContext(Request->Req.SetInformation), @@ -1162,6 +1196,7 @@ FSP_API NTSTATUS FspFileSystemOpSetInformation(FSP_FILE_SYSTEM *FileSystem, } break; case 10/*FileRenameInformation*/: + case 65/*FileRenameInformationEx*/: if (0 != FileSystem->Interface->Rename) { if (0 != Request->Req.SetInformation.Info.Rename.AccessToken) diff --git a/src/sys/cleanup.c b/src/sys/cleanup.c index ea8ddc19..46c249dc 100644 --- a/src/sys/cleanup.c +++ b/src/sys/cleanup.c @@ -82,21 +82,21 @@ static NTSTATUS FspFsvolCleanup( FSP_FILE_DESC *FileDesc = FileObject->FsContext2; FSP_FSCTL_TRANSACT_REQ *Request; ULONG CleanupFlags; - BOOLEAN DeletePending, SetAllocationSize, FileModified; + BOOLEAN Delete, SetAllocationSize, FileModified; ASSERT(FileNode == FileDesc->FileNode); FspFileNodeAcquireExclusive(FileNode, Main); FspFileNodeCleanup(FileNode, FileObject, &CleanupFlags); - DeletePending = CleanupFlags & 1; + Delete = (CleanupFlags & 1) && !FileNode->PosixDelete; SetAllocationSize = !!(CleanupFlags & 2); FileModified = BooleanFlagOn(FileObject->Flags, FO_FILE_MODIFIED); /* if this is a directory inform the FSRTL Notify mechanism */ if (FileNode->IsDirectory) { - if (DeletePending) + if (Delete) FspNotifyDeletePending( FsvolDeviceExtension->NotifySync, &FsvolDeviceExtension->NotifyList, FileNode); @@ -108,12 +108,12 @@ static NTSTATUS FspFsvolCleanup( FspFileNodeUnlockAll(FileNode, FileObject, IoGetRequestorProcess(Irp)); /* create the user-mode file system request; MustSucceed because IRP_MJ_CLEANUP cannot fail */ - FspIopCreateRequestMustSucceedEx(Irp, DeletePending ? &FileNode->FileName : 0, 0, + FspIopCreateRequestMustSucceedEx(Irp, Delete ? &FileNode->FileName : 0, 0, FspFsvolCleanupRequestFini, &Request); Request->Kind = FspFsctlTransactCleanupKind; Request->Req.Cleanup.UserContext = FileNode->UserContext; Request->Req.Cleanup.UserContext2 = FileDesc->UserContext2; - Request->Req.Cleanup.Delete = DeletePending; + Request->Req.Cleanup.Delete = Delete; Request->Req.Cleanup.SetAllocationSize = SetAllocationSize; Request->Req.Cleanup.SetArchiveBit = (FileModified || FileDesc->DidSetSecurity) && !FileDesc->DidSetFileAttributes; @@ -170,7 +170,12 @@ NTSTATUS FspFsvolCleanupComplete( ASSERT(FileNode == FileDesc->FileNode); /* send the appropriate notification; also invalidate dirinfo/etc. caches */ - if (Request->Req.Cleanup.Delete) + if (FileNode->PosixDelete) + { + NotifyFilter = 0; + NotifyAction = 0; + } + else if (Request->Req.Cleanup.Delete) { NotifyFilter = FileNode->IsDirectory ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME; diff --git a/src/sys/driver.h b/src/sys/driver.h index 8bc9992c..81d1cbaa 100644 --- a/src/sys/driver.h +++ b/src/sys/driver.h @@ -1462,6 +1462,7 @@ typedef struct FSP_FILE_NODE ULONG EaChangeNumber; ULONG EaChangeCount; BOOLEAN TruncateOnClose; + BOOLEAN PosixDelete; FILE_LOCK FileLock; #if (NTDDI_VERSION < NTDDI_WIN8) OPLOCK Oplock; @@ -1561,6 +1562,7 @@ NTSTATUS FspFileNodeOpen(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, VOID FspFileNodeCleanup(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, PULONG PCleanupFlags); VOID FspFileNodeCleanupFlush(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject); VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject); +VOID FspFileNodePosixDelete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject); VOID FspFileNodeClose(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, /* non-0 to remove share access */ BOOLEAN HandleCleanup); /* TRUE to decrement handle count */ diff --git a/src/sys/file.c b/src/sys/file.c index 649f3772..82cc5662 100644 --- a/src/sys/file.c +++ b/src/sys/file.c @@ -43,6 +43,7 @@ NTSTATUS FspFileNodeOpen(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, VOID FspFileNodeCleanup(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, PULONG PCleanupFlags); VOID FspFileNodeCleanupFlush(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject); VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject); +VOID FspFileNodePosixDelete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject); VOID FspFileNodeClose(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, /* non-0 to remove share access */ BOOLEAN HandleCleanup); /* TRUE to decrement handle count */ @@ -141,6 +142,7 @@ VOID FspFileNodeOplockComplete(PVOID Context, PIRP Irp); #pragma alloc_text(PAGE, FspFileNodeCleanup) #pragma alloc_text(PAGE, FspFileNodeCleanupFlush) #pragma alloc_text(PAGE, FspFileNodeCleanupComplete) +#pragma alloc_text(PAGE, FspFileNodePosixDelete) #pragma alloc_text(PAGE, FspFileNodeClose) #pragma alloc_text(PAGE, FspFileNodeFlushAndPurgeCache) #pragma alloc_text(PAGE, FspFileNodeOverwriteStreams) @@ -909,7 +911,7 @@ VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject DeletePending = 0 != FileNode->DeletePending; MemoryBarrier(); - if (DeletePending) + if (DeletePending && !FileNode->PosixDelete) { FspFsvolDeviceDeleteContextByName(FsvolDeviceObject, &FileNode->FileName, &DeletedFromContextTable); @@ -1013,6 +1015,66 @@ VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject FspFileNodeDereference(FileNode); } +VOID FspFileNodePosixDelete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject) +{ + /* + * Perform a POSIX delete of a FileNode. This removes the FileNode from the Context table. + * + * The FileNode must be acquired exclusive (Main or Full) when calling this function. + */ + + PAGED_CODE(); + + PDEVICE_OBJECT FsvolDeviceObject = FileNode->FsvolDeviceObject; + FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = FspFsvolDeviceExtension(FsvolDeviceObject); + BOOLEAN DeletedFromContextTable = FALSE; + + FspFsvolDeviceLockContextTable(FsvolDeviceObject); + + FspFsvolDeviceDeleteContextByName(FsvolDeviceObject, &FileNode->FileName, + &DeletedFromContextTable); + ASSERT(DeletedFromContextTable); + + FileNode->OpenCount = 0; + + if (FsvolDeviceExtension->VolumeParams.NamedStreams && + 0 == FileNode->MainFileNode) + { + BOOLEAN StreamDeletedFromContextTable; + USHORT FileNameLength = FileNode->FileName.Length; + + GATHER_DESCENDANTS(&FileNode->FileName, FALSE, + if (DescendantFileNode->FileName.Length > FileNameLength && + L'\\' == DescendantFileNode->FileName.Buffer[FileNameLength / sizeof(WCHAR)]) + break; + ASSERT(FileNode != DescendantFileNode); + ASSERT(0 != DescendantFileNode->OpenCount); + ); + + for ( + DescendantFileNodeIndex = 0; + DescendantFileNodeCount > DescendantFileNodeIndex; + DescendantFileNodeIndex++) + { + DescendantFileNode = DescendantFileNodes[DescendantFileNodeIndex]; + + FspFsvolDeviceDeleteContextByName(FsvolDeviceObject, &DescendantFileNode->FileName, + &StreamDeletedFromContextTable); + if (StreamDeletedFromContextTable) + { + DescendantFileNode->OpenCount = 0; + FspFileNodeDereference(DescendantFileNode); + } + } + + SCATTER_DESCENDANTS(FALSE); + } + + FspFsvolDeviceUnlockContextTable(FsvolDeviceObject); + + FspFileNodeDereference(FileNode); +} + VOID FspFileNodeClose(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, /* non-0 to remove share access */ BOOLEAN HandleCleanup) /* TRUE to decrement handle count */ diff --git a/src/sys/fileinfo.c b/src/sys/fileinfo.c index d4d8c670..50e1e250 100644 --- a/src/sys/fileinfo.c +++ b/src/sys/fileinfo.c @@ -1441,7 +1441,8 @@ static NTSTATUS FspFsvolSetDispositionInformation( NTSTATUS Result; PFILE_OBJECT FileObject = IrpSp->FileObject; - PFILE_DISPOSITION_INFORMATION Info = (PFILE_DISPOSITION_INFORMATION)Irp->AssociatedIrp.SystemBuffer; + FILE_INFORMATION_CLASS FileInformationClass = IrpSp->Parameters.SetFile.FileInformationClass; + UINT32 DispositionFlags; ULONG Length = IrpSp->Parameters.SetFile.Length; FSP_FILE_NODE *FileNode = FileObject->FsContext; FSP_FILE_DESC *FileDesc = FileObject->FsContext2; @@ -1449,9 +1450,34 @@ static NTSTATUS FspFsvolSetDispositionInformation( BOOLEAN Success; ASSERT(FileNode == FileDesc->FileNode); + ASSERT( + FileDispositionInformation == FileInformationClass || + FileDispositionInformationEx == FileInformationClass); + + if (FileDispositionInformation == FileInformationClass) + { + if (sizeof(FILE_DISPOSITION_INFORMATION) > Length) + return STATUS_INVALID_PARAMETER; + DispositionFlags = !!((PFILE_DISPOSITION_INFORMATION)Irp->AssociatedIrp.SystemBuffer)->DeleteFile; + DispositionFlags |= FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK; + // old-school delete always did image section check; see below + } + else + { + if (!FspFsvolDeviceExtension(FsvolDeviceObject)->VolumeParams.SupportsPosixUnlinkRename) + return STATUS_INVALID_PARAMETER; + if (sizeof(FILE_DISPOSITION_INFORMATION_EX) > Length) + return STATUS_INVALID_PARAMETER; + DispositionFlags = ((PFILE_DISPOSITION_INFORMATION_EX)Irp->AssociatedIrp.SystemBuffer)->Flags; + + /* !!!: REVISIT: + * For now we cannot handle the FILE_DISPOSITION_ON_CLOSE flag, + * as we need to understand the semantics better. + */ + if (FlagOn(DispositionFlags, FILE_DISPOSITION_ON_CLOSE)) + return STATUS_INVALID_PARAMETER; + } - if (sizeof(FILE_DISPOSITION_INFORMATION) > Length) - return STATUS_INVALID_PARAMETER; if (FileNode->IsRootDirectory) /* cannot delete root directory */ return STATUS_CANNOT_DELETE; @@ -1459,7 +1485,7 @@ static NTSTATUS FspFsvolSetDispositionInformation( retry: FspFileNodeAcquireExclusive(FileNode, Full); - if (Info->DeleteFile) + if (FlagOn(DispositionFlags, FILE_DISPOSITION_DELETE)) { /* * Perform oplock check. @@ -1487,15 +1513,40 @@ retry: if (!NT_SUCCESS(Result)) goto unlock_exit; - /* make sure no process is mapping the file as an image */ - Success = MmFlushImageSection(FileObject->SectionObjectPointer, MmFlushForDelete); - if (!Success) + if (FlagOn(DispositionFlags, FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK)) { - Result = STATUS_CANNOT_DELETE; - goto unlock_exit; + /* make sure no process is mapping the file as an image */ + Success = MmFlushImageSection(FileObject->SectionObjectPointer, MmFlushForDelete); + if (!Success) + { + Result = STATUS_CANNOT_DELETE; + goto unlock_exit; + } + } + + if (FlagOn(DispositionFlags, FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE)) + { + /* if FileDesc does not have FILE_WRITE_ATTRIBUTE access, remove IGNORE_READONLY_ATTRIBUTE */ + if (!FlagOn(FileDesc->GrantedAccess, FILE_WRITE_ATTRIBUTES)) + DispositionFlags &= ~FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE; } } + if (FileNode->PosixDelete) + { + Result = STATUS_SUCCESS; + goto unlock_exit; + } + + if (FlagOn(DispositionFlags, FILE_DISPOSITION_DELETE)) + DispositionFlags &= + FILE_DISPOSITION_DO_NOT_DELETE | + FILE_DISPOSITION_DELETE | + FILE_DISPOSITION_POSIX_SEMANTICS | + FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE; + else + DispositionFlags = FILE_DISPOSITION_DO_NOT_DELETE; + Result = FspIopCreateRequestEx(Irp, &FileNode->FileName, 0, FspFsvolSetInformationRequestFini, &Request); if (!NT_SUCCESS(Result)) @@ -1504,8 +1555,8 @@ retry: Request->Kind = FspFsctlTransactSetInformationKind; Request->Req.SetInformation.UserContext = FileNode->UserContext; Request->Req.SetInformation.UserContext2 = FileDesc->UserContext2; - Request->Req.SetInformation.FileInformationClass = FileDispositionInformation; - Request->Req.SetInformation.Info.Disposition.Delete = Info->DeleteFile; + Request->Req.SetInformation.FileInformationClass = FileInformationClass; + Request->Req.SetInformation.Info.DispositionEx.Flags = DispositionFlags; FspFileNodeSetOwner(FileNode, Full, Request); FspIopRequestContext(Request, RequestFileNode) = FileNode; @@ -1525,23 +1576,51 @@ static NTSTATUS FspFsvolSetDispositionInformationSuccess( PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT FileObject = IrpSp->FileObject; - PFILE_DISPOSITION_INFORMATION Info = (PFILE_DISPOSITION_INFORMATION)Irp->AssociatedIrp.SystemBuffer; FSP_FILE_NODE *FileNode = FileObject->FsContext; FSP_FSCTL_TRANSACT_REQ *Request = FspIrpRequest(Irp); + UINT32 DispositionFlags = Request->Req.SetInformation.Info.DispositionEx.Flags; + BOOLEAN DeleteFile = BooleanFlagOn(DispositionFlags, FILE_DISPOSITION_DELETE); - FileNode->DeletePending = Info->DeleteFile; - FileObject->DeletePending = Info->DeleteFile; + FileNode->DeletePending = DeleteFile; + FileObject->DeletePending = DeleteFile; - /* fastfat does this, although it seems unnecessary */ -#if 1 - if (FileNode->IsDirectory && Info->DeleteFile) + if (FlagOn(DispositionFlags, FILE_DISPOSITION_POSIX_SEMANTICS)) { - FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = - FspFsvolDeviceExtension(IrpSp->DeviceObject); - FspNotifyDeletePending( - FsvolDeviceExtension->NotifySync, &FsvolDeviceExtension->NotifyList, FileNode); + ASSERT(DeleteFile); + + FileNode->PosixDelete = TRUE; + + if (FileNode->IsDirectory) + { + FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = + FspFsvolDeviceExtension(IrpSp->DeviceObject); + FspNotifyDeletePending( + FsvolDeviceExtension->NotifySync, &FsvolDeviceExtension->NotifyList, FileNode); + } + + /* send the appropriate notification; also invalidate dirinfo/etc. caches */ + ULONG NotifyFilter, NotifyAction; + NotifyFilter = FileNode->IsDirectory ? + FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME; + NotifyAction = FILE_ACTION_REMOVED; + FspFileNodeNotifyChange(FileNode, NotifyFilter, NotifyAction, TRUE); + + /* perform POSIX delete: remove file node from the context table */ + FspFileNodePosixDelete(FileNode, FileObject); + } + else + { + /* fastfat does this, although it seems unnecessary */ + #if 1 + if (FileNode->IsDirectory && DeleteFile) + { + FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = + FspFsvolDeviceExtension(IrpSp->DeviceObject); + FspNotifyDeletePending( + FsvolDeviceExtension->NotifySync, &FsvolDeviceExtension->NotifyList, FileNode); + } + #endif } -#endif FspIopRequestContext(Request, RequestFileNode) = 0; FspFileNodeReleaseOwner(FileNode, Full, Request); @@ -1796,10 +1875,15 @@ static NTSTATUS FspFsvolSetInformation( FILE_INFORMATION_CLASS FileInformationClass = IrpSp->Parameters.SetFile.FileInformationClass; /* special case FileDispositionInformation/FileRenameInformation */ - if (FileDispositionInformation == FileInformationClass) + switch (FileInformationClass) + { + case FileDispositionInformation: + case FileDispositionInformationEx: return FspFsvolSetDispositionInformation(FsvolDeviceObject, Irp, IrpSp); - if (FileRenameInformation == FileInformationClass) + case FileRenameInformation: + //case FileRenameInformationEx: return FspFsvolSetRenameInformation(FsvolDeviceObject, Irp, IrpSp); + } NTSTATUS Result; PFILE_OBJECT FileObject = IrpSp->FileObject; @@ -1997,10 +2081,15 @@ NTSTATUS FspFsvolSetInformationComplete( FILE_INFORMATION_CLASS FileInformationClass = IrpSp->Parameters.SetFile.FileInformationClass; /* special case FileDispositionInformation/FileRenameInformation */ - if (FileDispositionInformation == FileInformationClass) + switch (FileInformationClass) + { + case FileDispositionInformation: + case FileDispositionInformationEx: FSP_RETURN(Result = FspFsvolSetDispositionInformationSuccess(Irp, Response)); - if (FileRenameInformation == FileInformationClass) + case FileRenameInformation: + //case FileRenameInformationEx: FSP_RETURN(Result = FspFsvolSetRenameInformationSuccess(Irp, Response)); + } PFILE_OBJECT FileObject = IrpSp->FileObject; FSP_FILE_NODE *FileNode = FileObject->FsContext; diff --git a/src/sys/volinfo.c b/src/sys/volinfo.c index 3c236854..970a652d 100644 --- a/src/sys/volinfo.c +++ b/src/sys/volinfo.c @@ -101,7 +101,8 @@ static NTSTATUS FspFsvolQueryFsAttributeInformation( (FsvolDeviceExtension->VolumeParams.NamedStreams ? FILE_NAMED_STREAMS : 0) | //(FsvolDeviceExtension->VolumeParams.HardLinks ? FILE_SUPPORTS_HARD_LINKS : 0) | (FsvolDeviceExtension->VolumeParams.ExtendedAttributes ? FILE_SUPPORTS_EXTENDED_ATTRIBUTES : 0) | - (FsvolDeviceExtension->VolumeParams.ReadOnlyVolume ? FILE_READ_ONLY_VOLUME : 0); + (FsvolDeviceExtension->VolumeParams.ReadOnlyVolume ? FILE_READ_ONLY_VOLUME : 0) | + (FsvolDeviceExtension->VolumeParams.SupportsPosixUnlinkRename ? FILE_SUPPORTS_POSIX_UNLINK_RENAME : 0); Info->MaximumComponentNameLength = FsvolDeviceExtension->VolumeParams.MaxComponentLength; RtlInitUnicodeString(&FileSystemName, FsvolDeviceExtension->VolumeParams.FileSystemName); diff --git a/tools/run-tests.bat b/tools/run-tests.bat index 7a1ce3e5..2bdee1fd 100755 --- a/tools/run-tests.bat +++ b/tools/run-tests.bat @@ -32,6 +32,7 @@ set dfl_tests=^ winfsp-tests-x64 ^ winfsp-tests-x64-case-randomize ^ winfsp-tests-x64-flushpurge ^ + winfsp-tests-x64-legacy-unlink-rename ^ winfsp-tests-x64-mountpoint-drive ^ winfsp-tests-x64-mountpoint-dir ^ winfsp-tests-x64-mountpoint-dir-case-sensitive ^ @@ -52,6 +53,7 @@ set dfl_tests=^ winfsp-tests-x86 ^ winfsp-tests-x86-case-randomize ^ winfsp-tests-x86-flushpurge ^ + winfsp-tests-x86-legacy-unlink-rename ^ winfsp-tests-x86-mountpoint-drive ^ winfsp-tests-x86-mountpoint-dir ^ winfsp-tests-x86-mountpoint-dir-case-sensitive ^ @@ -191,6 +193,11 @@ winfsp-tests-x64 --flush-and-purge-on-cleanup * +ea* if !ERRORLEVEL! neq 0 goto fail exit /b 0 +:winfsp-tests-x64-legacy-unlink-rename +winfsp-tests-x64 --legacy-unlink-rename * +ea* +if !ERRORLEVEL! neq 0 goto fail +exit /b 0 + :winfsp-tests-x64-mountpoint-drive winfsp-tests-x64 --mountpoint=X: --resilient * +ea* if !ERRORLEVEL! neq 0 goto fail @@ -236,6 +243,11 @@ winfsp-tests-x86 --flush-and-purge-on-cleanup * +ea* if !ERRORLEVEL! neq 0 goto fail exit /b 0 +:winfsp-tests-x86-legacy-unlink-rename +winfsp-tests-x86 --legacy-unlink-rename * +ea* +if !ERRORLEVEL! neq 0 goto fail +exit /b 0 + :winfsp-tests-x86-mountpoint-drive winfsp-tests-x86 --mountpoint=X: --resilient * +ea* if !ERRORLEVEL! neq 0 goto fail diff --git a/tst/memfs/memfs.cpp b/tst/memfs/memfs.cpp index bdd9e8aa..07c45fd2 100644 --- a/tst/memfs/memfs.cpp +++ b/tst/memfs/memfs.cpp @@ -88,6 +88,13 @@ FSP_FSCTL_STATIC_ASSERT(MEMFS_MAX_PATH > MAX_PATH, #define MEMFS_REJECT_EARLY_IRP #endif +/* + * Define the MEMFS_DELETE macro to include new Delete support + * (instead of Cleanup/FspCleanupDelete). This is required to + * properly support POSIX unlink/rename. + */ +#define MEMFS_DELETE + /* * 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. @@ -965,6 +972,8 @@ void SlowioReadDirectoryThread( * FSP_FILE_SYSTEM_INTERFACE */ +static NTSTATUS Delete(FSP_FILE_SYSTEM *FileSystem, + PVOID FileNode0, PWSTR FileName, ULONG Flags); #if defined(MEMFS_REPARSE_POINTS) static NTSTATUS GetReparsePointByName( FSP_FILE_SYSTEM *FileSystem, PVOID Context, @@ -1347,7 +1356,9 @@ static VOID Cleanup(FSP_FILE_SYSTEM *FileSystem, MEMFS_FILE_NODE *MainFileNode = FileNode; #endif +#if !defined(MEMFS_DELETE) assert(0 != Flags); /* FSP_FSCTL_VOLUME_PARAMS::PostCleanupWhenModifiedOnly ensures this */ +#endif if (Flags & FspCleanupSetArchiveBit) { @@ -1376,21 +1387,10 @@ static VOID Cleanup(FSP_FILE_SYSTEM *FileSystem, SetFileSizeInternal(FileSystem, FileNode, AllocationSize, TRUE); } - if ((Flags & FspCleanupDelete) && !MemfsFileNodeMapHasChild(Memfs->FileNodeMap, FileNode)) - { -#if defined(MEMFS_NAMED_STREAMS) - MEMFS_FILE_NODE_MAP_ENUM_CONTEXT Context = { FALSE }; - ULONG Index; - - MemfsFileNodeMapEnumerateNamedStreams(Memfs->FileNodeMap, FileNode, - MemfsFileNodeMapEnumerateFn, &Context); - for (Index = 0; Context.Count > Index; Index++) - MemfsFileNodeMapRemove(Memfs->FileNodeMap, Context.FileNodes[Index]); - MemfsFileNodeMapEnumerateFree(&Context); +#if !defined(MEMFS_DELETE) + if (Flags & FspCleanupDelete) + Delete(FileSystem, FileNode0, FileName, -1); #endif - - MemfsFileNodeMapRemove(Memfs->FileNodeMap, FileNode); - } } static VOID Close(FSP_FILE_SYSTEM *FileSystem, @@ -1651,17 +1651,13 @@ static NTSTATUS SetFileSize(FSP_FILE_SYSTEM *FileSystem, return STATUS_SUCCESS; } +#if !defined(MEMFS_DELETE) static NTSTATUS CanDelete(FSP_FILE_SYSTEM *FileSystem, PVOID FileNode0, PWSTR FileName) { - MEMFS *Memfs = (MEMFS *)FileSystem->UserContext; - MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0; - - if (MemfsFileNodeMapHasChild(Memfs->FileNodeMap, FileNode)) - return STATUS_DIRECTORY_NOT_EMPTY; - - return STATUS_SUCCESS; + return Delete(FileSystem, FileNode0, FileName, FILE_DISPOSITION_DELETE); } +#endif static NTSTATUS Rename(FSP_FILE_SYSTEM *FileSystem, PVOID FileNode0, @@ -2231,6 +2227,54 @@ static NTSTATUS SetEa(FSP_FILE_SYSTEM *FileSystem, } #endif +static NTSTATUS Delete(FSP_FILE_SYSTEM *FileSystem, + PVOID FileNode0, PWSTR FileName, ULONG Flags) +{ + MEMFS *Memfs = (MEMFS *)FileSystem->UserContext; + MEMFS_FILE_NODE *FileNode = (MEMFS_FILE_NODE *)FileNode0; + + switch (Flags) + { + case FILE_DISPOSITION_DO_NOT_DELETE: + // set file disposition flag: do not delete file at Cleanup + return STATUS_SUCCESS; + + case FILE_DISPOSITION_DELETE: + // set file disposition flag: delete file at Cleanup + if (MemfsFileNodeMapHasChild(Memfs->FileNodeMap, FileNode)) + return STATUS_DIRECTORY_NOT_EMPTY; + return STATUS_SUCCESS; + + case FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS: + // delete file now; open handles to file remain valid + /* fallthrough */ + + case -1: + // delete file now; called during Cleanup + if (MemfsFileNodeMapHasChild(Memfs->FileNodeMap, FileNode)) + return STATUS_DIRECTORY_NOT_EMPTY; + else + { +#if defined(MEMFS_NAMED_STREAMS) + MEMFS_FILE_NODE_MAP_ENUM_CONTEXT Context = { FALSE }; + ULONG Index; + + MemfsFileNodeMapEnumerateNamedStreams(Memfs->FileNodeMap, FileNode, + MemfsFileNodeMapEnumerateFn, &Context); + for (Index = 0; Context.Count > Index; Index++) + MemfsFileNodeMapRemove(Memfs->FileNodeMap, Context.FileNodes[Index]); + MemfsFileNodeMapEnumerateFree(&Context); +#endif + + MemfsFileNodeMapRemove(Memfs->FileNodeMap, FileNode); + return STATUS_SUCCESS; + } + + default: + return STATUS_INVALID_PARAMETER; + } +} + static FSP_FILE_SYSTEM_INTERFACE MemfsInterface = { GetVolumeInfo, @@ -2255,7 +2299,11 @@ static FSP_FILE_SYSTEM_INTERFACE MemfsInterface = GetFileInfo, SetBasicInfo, SetFileSize, +#if !defined(MEMFS_DELETE) CanDelete, +#else + 0, +#endif Rename, GetSecurity, SetSecurity, @@ -2293,11 +2341,15 @@ static FSP_FILE_SYSTEM_INTERFACE MemfsInterface = #if defined(MEMFS_EA) Overwrite, GetEa, - SetEa + SetEa, #else 0, 0, 0, +#endif +#if defined(MEMFS_DELETE) + Delete, +#else 0, #endif }; @@ -2323,6 +2375,7 @@ NTSTATUS MemfsCreateFunnel( FSP_FSCTL_VOLUME_PARAMS VolumeParams; BOOLEAN CaseInsensitive = !!(Flags & MemfsCaseInsensitive); BOOLEAN FlushAndPurgeOnCleanup = !!(Flags & MemfsFlushAndPurgeOnCleanup); + BOOLEAN SupportsPosixUnlinkRename = !(Flags & MemfsLegacyUnlinkRename); PWSTR DevicePath = MemfsNet == (Flags & MemfsDeviceMask) ? L"" FSP_FSCTL_NET_DEVICE_NAME : L"" FSP_FSCTL_DISK_DEVICE_NAME; UINT64 AllocationUnit; @@ -2404,6 +2457,7 @@ NTSTATUS MemfsCreateFunnel( #if defined(MEMFS_REJECT_EARLY_IRP) VolumeParams.RejectIrpPriorToTransact0 = 1; #endif + VolumeParams.SupportsPosixUnlinkRename = SupportsPosixUnlinkRename; if (0 != VolumePrefix) wcscpy_s(VolumeParams.Prefix, sizeof VolumeParams.Prefix / sizeof(WCHAR), VolumePrefix); wcscpy_s(VolumeParams.FileSystemName, sizeof VolumeParams.FileSystemName / sizeof(WCHAR), diff --git a/tst/memfs/memfs.h b/tst/memfs/memfs.h index bc4bd1d1..d3e66334 100644 --- a/tst/memfs/memfs.h +++ b/tst/memfs/memfs.h @@ -37,9 +37,10 @@ enum MemfsDeviceMask = 0x0000000f, MemfsCaseInsensitive = 0x80000000, MemfsFlushAndPurgeOnCleanup = 0x40000000, + MemfsLegacyUnlinkRename = 0x20000000, }; -#define MemfsCreate(Flags, FileInfoTimeout, MaxFileNodes, MaxFileSize, VolumePrefix, RootSddl, PMemfs)\ +#define MemfsCreate(Flags, FileInfoTimeout, MaxFileNodes, MaxFileSize, VolumePrefix, RootSddl, PMemfs)\ MemfsCreateFunnel(\ Flags,\ FileInfoTimeout,\ diff --git a/tst/winfsp-tests/memfs-test.c b/tst/winfsp-tests/memfs-test.c index 312e5b31..1fa64ea6 100644 --- a/tst/winfsp-tests/memfs-test.c +++ b/tst/winfsp-tests/memfs-test.c @@ -43,7 +43,8 @@ void *memfs_start_ex(ULONG Flags, ULONG FileInfoTimeout) Result = MemfsCreateFunnel( Flags | (OptCaseInsensitive ? MemfsCaseInsensitive : 0) | - (OptFlushAndPurgeOnCleanup ? MemfsFlushAndPurgeOnCleanup : 0), + (OptFlushAndPurgeOnCleanup ? MemfsFlushAndPurgeOnCleanup : 0) | + (OptLegacyUnlinkRename ? MemfsLegacyUnlinkRename : 0), FileInfoTimeout, 1024, 1024 * 1024, diff --git a/tst/winfsp-tests/winfsp-tests.c b/tst/winfsp-tests/winfsp-tests.c index d9828c84..9fe244f4 100644 --- a/tst/winfsp-tests/winfsp-tests.c +++ b/tst/winfsp-tests/winfsp-tests.c @@ -39,6 +39,7 @@ BOOLEAN OptCaseInsensitiveCmp = FALSE; BOOLEAN OptCaseInsensitive = FALSE; BOOLEAN OptCaseRandomize = FALSE; BOOLEAN OptFlushAndPurgeOnCleanup = FALSE; +BOOLEAN OptLegacyUnlinkRename = FALSE; BOOLEAN OptNotify = FALSE; WCHAR OptOplock = 0; WCHAR OptMountPointBuf[MAX_PATH], *OptMountPoint; @@ -296,6 +297,11 @@ int main(int argc, char *argv[]) OptFlushAndPurgeOnCleanup = TRUE; rmarg(argv, argc, argi); } + else if (0 == strcmp("--legacy-unlink-rename", a)) + { + OptLegacyUnlinkRename = TRUE; + rmarg(argv, argc, argi); + } else if (0 == strcmp("--notify", a)) { OptNotify = TRUE; diff --git a/tst/winfsp-tests/winfsp-tests.h b/tst/winfsp-tests/winfsp-tests.h index dbc52ec5..c3d41b89 100644 --- a/tst/winfsp-tests/winfsp-tests.h +++ b/tst/winfsp-tests/winfsp-tests.h @@ -159,6 +159,7 @@ extern BOOLEAN OptCaseInsensitiveCmp; extern BOOLEAN OptCaseInsensitive; extern BOOLEAN OptCaseRandomize; extern BOOLEAN OptFlushAndPurgeOnCleanup; +extern BOOLEAN OptLegacyUnlinkRename; extern BOOLEAN OptNotify; extern WCHAR OptOplock; extern WCHAR OptMountPointBuf[], *OptMountPoint;