diff --git a/src/dll/fsop.c b/src/dll/fsop.c index eb8f5764..8789d78f 100644 --- a/src/dll/fsop.c +++ b/src/dll/fsop.c @@ -1158,11 +1158,11 @@ FSP_API NTSTATUS FspFileSystemOpSetInformation(FSP_FILE_SYSTEM *FileSystem, Result = FileSystem->Interface->SetDelete(FileSystem, (PVOID)ValOfFileContext(Request->Req.SetInformation), (PWSTR)Request->Buffer, - Request->Req.SetInformation.Info.Disposition.Delete); + 0 != (1/*DELETE*/ & Request->Req.SetInformation.Info.DispositionEx.Flags)); } else if (0 != FileSystem->Interface->CanDelete) { - if (Request->Req.SetInformation.Info.Disposition.Delete) + if (0 != (1/*DELETE*/ & Request->Req.SetInformation.Info.DispositionEx.Flags)) Result = FileSystem->Interface->CanDelete(FileSystem, (PVOID)ValOfFileContext(Request->Req.SetInformation), (PWSTR)Request->Buffer); diff --git a/src/sys/cleanup.c b/src/sys/cleanup.c index 46c249dc..473ec011 100644 --- a/src/sys/cleanup.c +++ b/src/sys/cleanup.c @@ -89,7 +89,7 @@ static NTSTATUS FspFsvolCleanup( FspFileNodeAcquireExclusive(FileNode, Main); FspFileNodeCleanup(FileNode, FileObject, &CleanupFlags); - Delete = (CleanupFlags & 1) && !FileNode->PosixDelete; + Delete = CleanupFlags & 1; SetAllocationSize = !!(CleanupFlags & 2); FileModified = BooleanFlagOn(FileObject->Flags, FO_FILE_MODIFIED); @@ -170,17 +170,17 @@ NTSTATUS FspFsvolCleanupComplete( ASSERT(FileNode == FileDesc->FileNode); /* send the appropriate notification; also invalidate dirinfo/etc. caches */ - if (FileNode->PosixDelete) - { - NotifyFilter = 0; - NotifyAction = 0; - } - else if (Request->Req.Cleanup.Delete) + if (Request->Req.Cleanup.Delete) { NotifyFilter = FileNode->IsDirectory ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME; NotifyAction = FILE_ACTION_REMOVED; } + else if (FileNode->PosixDelete) + { + NotifyFilter = 0; + NotifyAction = 0; + } else { /* send notification for any metadata changes */ @@ -241,7 +241,7 @@ static VOID FspFsvolCleanupRequestFini(FSP_FSCTL_TRANSACT_REQ *Request, PVOID Co FspFileNodeReleaseOwner(FileNode, Pgio, Request); - FspFileNodeCleanupComplete(FileNode, FileObject); + FspFileNodeCleanupComplete(FileNode, FileObject, !!Request->Req.Cleanup.Delete); if (!FileNode->IsDirectory) FspFileNodeOplockCheck(FileNode, Irp); SetFlag(FileObject->Flags, FO_CLEANUP_COMPLETE); diff --git a/src/sys/driver.h b/src/sys/driver.h index d3a30d8d..51301a88 100644 --- a/src/sys/driver.h +++ b/src/sys/driver.h @@ -1492,7 +1492,7 @@ typedef struct UINT64 UserContext2; UINT32 GrantedAccess; UINT32 - CaseSensitive:1, HasTraversePrivilege:1, DeleteOnClose:1, + CaseSensitive:1, HasTraversePrivilege:1, DeleteOnClose:1, PosixDelete:1, DidSetMetadata:1, DidSetFileAttributes:1, DidSetReparsePoint:1, DidSetSecurity:1, DidSetCreationTime:1, DidSetLastAccessTime:1, DidSetLastWriteTime:1, DidSetChangeTime:1, @@ -1561,8 +1561,7 @@ NTSTATUS FspFileNodeOpen(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, FSP_FILE_NODE **POpenedFileNode, PULONG PSharingViolationReason); 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 FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, BOOLEAN Delete); 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 528eead8..078ac24f 100644 --- a/src/sys/file.c +++ b/src/sys/file.c @@ -42,8 +42,7 @@ NTSTATUS FspFileNodeOpen(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, FSP_FILE_NODE **POpenedFileNode, PULONG PSharingViolationReason); 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 FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, BOOLEAN Delete); VOID FspFileNodeClose(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, /* non-0 to remove share access */ BOOLEAN HandleCleanup); /* TRUE to decrement handle count */ @@ -143,7 +142,6 @@ 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) @@ -792,7 +790,7 @@ VOID FspFileNodeCleanup(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, PULONG PDEVICE_OBJECT FsvolDeviceObject = FileNode->FsvolDeviceObject; FSP_FILE_DESC *FileDesc = FileObject->FsContext2; - BOOLEAN DeletePending, SetAllocationSize, SingleHandle; + BOOLEAN DeletePending, Delete, SetAllocationSize, SingleHandle; FspFsvolDeviceLockContextTable(FsvolDeviceObject); @@ -807,7 +805,19 @@ VOID FspFileNodeCleanup(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, PULONG FspFsvolDeviceUnlockContextTable(FsvolDeviceObject); - *PCleanupFlags = SingleHandle ? DeletePending | (SetAllocationSize << 1) : 0; + Delete = FALSE; + if (!FileNode->PosixDelete) + { + if (FileDesc->PosixDelete) + { + FileNode->PosixDelete = TRUE; + Delete = TRUE; + } + else if (SingleHandle) + Delete = DeletePending; + } + + *PCleanupFlags = SingleHandle ? Delete | (SetAllocationSize << 1) : Delete; } VOID FspFileNodeCleanupFlush(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject) @@ -863,7 +873,7 @@ VOID FspFileNodeCleanupFlush(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject) } } -VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject) +VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject, BOOLEAN Delete) { /* * Complete the cleanup of a FileNode. Remove its share access and @@ -904,6 +914,52 @@ VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject IoRemoveShareAccess(FileObject, &FileNode->ShareAccess); + if (Delete) + { + FspFsvolDeviceDeleteContextByName(FsvolDeviceObject, &FileNode->FileName, + &DeletedFromContextTable); + ASSERT(DeletedFromContextTable); + + FileNode->OpenCount = 0; + + /* + * We now have to deal with the scenario where there are cleaned up, + * but unclosed streams for this file still in the context table. + */ + 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); + } + } + ASSERT(0 < FileNode->HandleCount); if (0 == --FileNode->HandleCount) { @@ -912,54 +968,9 @@ VOID FspFileNodeCleanupComplete(FSP_FILE_NODE *FileNode, PFILE_OBJECT FileObject DeletePending = 0 != FileNode->DeletePending; MemoryBarrier(); - if (DeletePending && !FileNode->PosixDelete) - { - FspFsvolDeviceDeleteContextByName(FsvolDeviceObject, &FileNode->FileName, - &DeletedFromContextTable); - ASSERT(DeletedFromContextTable); - - FileNode->OpenCount = 0; + if (DeletePending) FileNode->Header.FileSize.QuadPart = 0; - /* - * We now have to deal with the scenario where there are cleaned up, - * but unclosed streams for this file still in the context table. - */ - 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); - ASSERT(0 == DescendantFileNode->HandleCount); - ); - - for ( - DescendantFileNodeIndex = 0; - DescendantFileNodeCount > DescendantFileNodeIndex; - DescendantFileNodeIndex++) - { - DescendantFileNode = DescendantFileNodes[DescendantFileNodeIndex]; - - FspFsvolDeviceDeleteContextByName(FsvolDeviceObject, &DescendantFileNode->FileName, - &StreamDeletedFromContextTable); - if (StreamDeletedFromContextTable) - { - DescendantFileNode->OpenCount = 0; - FspFileNodeDereference(DescendantFileNode); - } - } - - SCATTER_DESCENDANTS(FALSE); - } - } - if (DeletePending || FileNode->TruncateOnClose) { UINT64 AllocationUnit = @@ -1016,66 +1027,6 @@ 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 34512f9d..ee617d93 100644 --- a/src/sys/fileinfo.c +++ b/src/sys/fileinfo.c @@ -1460,7 +1460,7 @@ static NTSTATUS FspFsvolSetDispositionInformation( 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 + // old-school delete does image section check } else { @@ -1470,12 +1470,9 @@ static NTSTATUS FspFsvolSetDispositionInformation( 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. - */ + /* WinFsp does not support the FILE_DISPOSITION_ON_CLOSE flag */ if (FlagOn(DispositionFlags, FILE_DISPOSITION_ON_CLOSE)) - return STATUS_INVALID_PARAMETER; + return STATUS_NOT_SUPPORTED; } if (FileNode->IsRootDirectory) @@ -1485,6 +1482,12 @@ static NTSTATUS FspFsvolSetDispositionInformation( retry: FspFileNodeAcquireExclusive(FileNode, Full); + if (FileNode->PosixDelete) + { + Result = STATUS_ACCESS_DENIED; + goto unlock_exit; + } + if (FlagOn(DispositionFlags, FILE_DISPOSITION_DELETE)) { /* @@ -1534,18 +1537,25 @@ retry: goto unlock_exit; } + /* + * The documentation states: + * A return value of STATUS_CANNOT_DELETE indicates that either the file is read-only, + * or there's an existing mapped view to the file. Specifying FILE_DISPOSITION_IGNORE_- + * READONLY_ATTRIBUTE avoids this return value due to the file being read-only, provided + * the caller has FILE_WRITE_ATTRIBUTES access to the file (the access that would be + * required to clear the read-only attribute). + * + * This appears to be incorrect with NTFS on Win10 and Win11. See: + * https://github.com/MicrosoftDocs/windows-driver-docs-ddi/issues/1216 + */ +#if 0 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; +#endif } if (FlagOn(DispositionFlags, FILE_DISPOSITION_DELETE)) @@ -1587,50 +1597,29 @@ static NTSTATUS FspFsvolSetDispositionInformationSuccess( PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); PFILE_OBJECT FileObject = IrpSp->FileObject; FSP_FILE_NODE *FileNode = FileObject->FsContext; + FSP_FILE_DESC *FileDesc = FileObject->FsContext2; FSP_FSCTL_TRANSACT_REQ *Request = FspIrpRequest(Irp); UINT32 DispositionFlags = Request->Req.SetInformation.Info.DispositionEx.Flags; - BOOLEAN DeleteFile = BooleanFlagOn(DispositionFlags, FILE_DISPOSITION_DELETE); + BOOLEAN Delete = BooleanFlagOn(DispositionFlags, FILE_DISPOSITION_DELETE); - FileNode->DeletePending = DeleteFile; - FileObject->DeletePending = DeleteFile; + FileNode->DeletePending = Delete; + FileObject->DeletePending = Delete; - if (FlagOn(DispositionFlags, FILE_DISPOSITION_POSIX_SEMANTICS)) + if (!Delete) + FileDesc->PosixDelete = FALSE; + else if (FlagOn(DispositionFlags, FILE_DISPOSITION_POSIX_SEMANTICS)) + FileDesc->PosixDelete = TRUE; + + /* fastfat does this, although it seems unnecessary */ +#if 1 + if (FileNode->IsDirectory && Delete) { - 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 + FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = + FspFsvolDeviceExtension(IrpSp->DeviceObject); + FspNotifyDeletePending( + FsvolDeviceExtension->NotifySync, &FsvolDeviceExtension->NotifyList, FileNode); } +#endif FspIopRequestContext(Request, RequestFileNode) = 0; FspFileNodeReleaseOwner(FileNode, Full, Request); diff --git a/tst/winfsp-tests/info-test.c b/tst/winfsp-tests/info-test.c index 349774b1..88011924 100644 --- a/tst/winfsp-tests/info-test.c +++ b/tst/winfsp-tests/info-test.c @@ -894,6 +894,242 @@ void delete_standby_test(void) } } +static void delete_ex_dotest(ULONG Flags, PWSTR VolPrefix, PWSTR Prefix, ULONG FileInfoTimeout) +{ + BOOLEAN Success; + DWORD FileSystemFlags; + + Success = GetVolumeInformationW(L"C:\\", + 0, 0, + 0, 0, &FileSystemFlags, + 0, 0); + if (!Success || 0 == (FileSystemFlags & 0x400/*FILE_SUPPORTS_POSIX_UNLINK_RENAME*/)) + /* skip this test if the system lacks FILE_SUPPORTS_POSIX_UNLINK_RENAME capability */ + return; + + void *memfs = memfs_start_ex(Flags, FileInfoTimeout); + + NTSYSCALLAPI NTSTATUS NTAPI + NtSetInformationFile( + HANDLE FileHandle, + PIO_STATUS_BLOCK IoStatusBlock, + PVOID FileInformation, + ULONG Length, + FILE_INFORMATION_CLASS FileInformationClass); + typedef struct + { + ULONG Flags; + } FILE_DISPOSITION_INFORMATION_EX, *PFILE_DISPOSITION_INFORMATION_EX; + + HANDLE Handle0, Handle1, Handle2, FindHandle; + WCHAR FilePath[MAX_PATH]; + WIN32_FIND_DATAW FindData; + FILE_DISPOSITION_INFORMATION_EX DispositionInfo; + IO_STATUS_BLOCK IoStatus; + + StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\", + VolPrefix ? L"" : L"\\\\?\\GLOBALROOT", VolPrefix ? VolPrefix : memfs_volumename(memfs)); + + Success = GetVolumeInformationW(FilePath, + 0, 0, + 0, 0, &FileSystemFlags, + 0, 0); + ASSERT(Success); + if (0 != (FileSystemFlags & 0x400/*FILE_SUPPORTS_POSIX_UNLINK_RENAME*/)) + { + StringCbPrintfW(FilePath, sizeof FilePath, L"%s%s\\file", + Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs)); + + /* POSIX Semantics / Ignore Readonly */ + + Handle0 = CreateFileW(FilePath, + GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, + CREATE_NEW, FILE_ATTRIBUTE_READONLY, 0); + ASSERT(INVALID_HANDLE_VALUE != Handle0); + + Handle1 = CreateFileW(FilePath, + DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, + OPEN_EXISTING, 0, 0); + ASSERT(INVALID_HANDLE_VALUE != Handle1); + + memset(&DispositionInfo, 0, sizeof DispositionInfo); + DispositionInfo.Flags = 3; /* DELETE | POSIX_SEMANTICS */ + IoStatus.Status = NtSetInformationFile( + Handle1, &IoStatus, + &DispositionInfo, sizeof DispositionInfo, + 64/*FileDispositionInformationEx*/); + ASSERT(STATUS_CANNOT_DELETE == IoStatus.Status); + + memset(&DispositionInfo, 0, sizeof DispositionInfo); + DispositionInfo.Flags = 0x13; /* DELETE | POSIX_SEMANTICS | IGNORE_READONLY_ATTRIBUTE */ + IoStatus.Status = NtSetInformationFile( + Handle1, &IoStatus, + &DispositionInfo, sizeof DispositionInfo, + 64/*FileDispositionInformationEx*/); + ASSERT(0 == IoStatus.Status); + + FindHandle = FindFirstFileW(FilePath, &FindData); + ASSERT(INVALID_HANDLE_VALUE != FindHandle); + ASSERT(0 == mywcscmp(FindData.cFileName, 4, L"file", 4)); + FindClose(FindHandle); + + Handle2 = CreateFileW(FilePath, + 0, 0, 0, + OPEN_EXISTING, 0, 0); + ASSERT(INVALID_HANDLE_VALUE == Handle2); + ASSERT(ERROR_ACCESS_DENIED == GetLastError()); + + CloseHandle(Handle1); + + FindHandle = FindFirstFileW(FilePath, &FindData); + ASSERT(INVALID_HANDLE_VALUE == FindHandle); + ASSERT(ERROR_FILE_NOT_FOUND == GetLastError()); + + Handle2 = CreateFileW(FilePath, + 0, 0, 0, + OPEN_EXISTING, 0, 0); + ASSERT(INVALID_HANDLE_VALUE == Handle2); + ASSERT(ERROR_FILE_NOT_FOUND == GetLastError()); + + memset(&DispositionInfo, 0, sizeof DispositionInfo); + DispositionInfo.Flags = 0; /* DO_NOT_DELETE */ + IoStatus.Status = NtSetInformationFile( + Handle0, &IoStatus, + &DispositionInfo, sizeof DispositionInfo, + 64/*FileDispositionInformationEx*/); + ASSERT(STATUS_ACCESS_DENIED == IoStatus.Status); + + memset(&DispositionInfo, 0, sizeof DispositionInfo); + DispositionInfo.Flags = 1; /* DELETE */ + IoStatus.Status = NtSetInformationFile( + Handle0, &IoStatus, + &DispositionInfo, sizeof DispositionInfo, + 64/*FileDispositionInformationEx*/); + ASSERT(STATUS_ACCESS_DENIED == IoStatus.Status); + + CloseHandle(Handle0); + + /* POSIX Semantics / Set/Reset */ + + Handle0 = CreateFileW(FilePath, + GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, + CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0); + ASSERT(INVALID_HANDLE_VALUE != Handle0); + + Handle1 = CreateFileW(FilePath, + DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, + OPEN_EXISTING, 0, 0); + ASSERT(INVALID_HANDLE_VALUE != Handle1); + + memset(&DispositionInfo, 0, sizeof DispositionInfo); + DispositionInfo.Flags = 3; /* DELETE | POSIX_SEMANTICS */ + IoStatus.Status = NtSetInformationFile( + Handle1, &IoStatus, + &DispositionInfo, sizeof DispositionInfo, + 64/*FileDispositionInformationEx*/); + ASSERT(STATUS_SUCCESS == IoStatus.Status); + + memset(&DispositionInfo, 0, sizeof DispositionInfo); + DispositionInfo.Flags = 0; /* DO_NOT_DELETE */ + IoStatus.Status = NtSetInformationFile( + Handle1, &IoStatus, + &DispositionInfo, sizeof DispositionInfo, + 64/*FileDispositionInformationEx*/); + ASSERT(STATUS_SUCCESS == IoStatus.Status); + + CloseHandle(Handle1); + + FindHandle = FindFirstFileW(FilePath, &FindData); + ASSERT(INVALID_HANDLE_VALUE != FindHandle); + ASSERT(0 == mywcscmp(FindData.cFileName, 4, L"file", 4)); + FindClose(FindHandle); + + Handle1 = CreateFileW(FilePath, + DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, + OPEN_EXISTING, 0, 0); + ASSERT(INVALID_HANDLE_VALUE != Handle1); + + memset(&DispositionInfo, 0, sizeof DispositionInfo); + DispositionInfo.Flags = 3; /* DELETE | POSIX_SEMANTICS */ + IoStatus.Status = NtSetInformationFile( + Handle1, &IoStatus, + &DispositionInfo, sizeof DispositionInfo, + 64/*FileDispositionInformationEx*/); + ASSERT(STATUS_SUCCESS == IoStatus.Status); + + CloseHandle(Handle1); + + CloseHandle(Handle0); + +#if 0 + /* On Close */ + + Handle0 = CreateFileW(FilePath, + GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, + CREATE_NEW, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, 0); + ASSERT(INVALID_HANDLE_VALUE != Handle0); + + memset(&DispositionInfo, 0, sizeof DispositionInfo); + DispositionInfo.Flags = 8; /* DO_NOT_DELETE | ON_CLOSE */ + IoStatus.Status = NtSetInformationFile( + Handle0, &IoStatus, + &DispositionInfo, sizeof DispositionInfo, + 64/*FileDispositionInformationEx*/); + ASSERT(0 == IoStatus.Status); + + CloseHandle(Handle0); + + Handle0 = CreateFileW(FilePath, + DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, + OPEN_EXISTING, 0, 0); + ASSERT(INVALID_HANDLE_VALUE != Handle0); + + memset(&DispositionInfo, 0, sizeof DispositionInfo); + DispositionInfo.Flags = 9; /* DELETE | ON_CLOSE */; + IoStatus.Status = NtSetInformationFile( + Handle0, &IoStatus, + &DispositionInfo, sizeof DispositionInfo, + 64/*FileDispositionInformationEx*/); + ASSERT(STATUS_NOT_SUPPORTED == IoStatus.Status); + + memset(&DispositionInfo, 0, sizeof DispositionInfo); + DispositionInfo.Flags = 3; /* DELETE | POSIX_SEMANTICS */; + IoStatus.Status = NtSetInformationFile( + Handle0, &IoStatus, + &DispositionInfo, sizeof DispositionInfo, + 64/*FileDispositionInformationEx*/); + ASSERT(0 == IoStatus.Status); + + CloseHandle(Handle0); +#endif + } + + memfs_stop(memfs); +} + +void delete_ex_test(void) +{ + if (OptLegacyUnlinkRename) + return; + + if (NtfsTests) + { + WCHAR DirBuf[MAX_PATH], DriveBuf[3]; + GetTestDirectoryAndDrive(DirBuf, DriveBuf); + delete_ex_dotest(-1, DriveBuf, DirBuf, 0); + } + if (WinFspDiskTests) + { + delete_ex_dotest(MemfsDisk, 0, 0, 0); + delete_ex_dotest(MemfsDisk, 0, 0, 1000); + } + if (WinFspNetTests) + { + delete_ex_dotest(MemfsNet, L"\\\\memfs\\share", L"\\\\memfs\\share", 0); + delete_ex_dotest(MemfsNet, L"\\\\memfs\\share", L"\\\\memfs\\share", 1000); + } +} + static void rename_dotest(ULONG Flags, PWSTR Prefix, ULONG FileInfoTimeout) { void *memfs = memfs_start_ex(Flags, FileInfoTimeout); @@ -2225,6 +2461,8 @@ void info_tests(void) if (!OptShareName) TEST(delete_mmap_test); TEST(delete_standby_test); + if (!OptLegacyUnlinkRename) + TEST(delete_ex_test); TEST(rename_test); TEST(rename_backslash_test); TEST(rename_open_test);