diff --git a/inc/winfsp/fsctl.h b/inc/winfsp/fsctl.h index 86871fed..6d2c5bb3 100644 --- a/inc/winfsp/fsctl.h +++ b/inc/winfsp/fsctl.h @@ -426,6 +426,12 @@ typedef struct FSP_FSCTL_TRANSACT_BUF NewFileName; UINT64 AccessToken; /* request access token (PID,HANDLE) */ } Rename; + struct + { + FSP_FSCTL_TRANSACT_BUF NewFileName; + UINT64 AccessToken; /* request access token (PID,HANDLE) */ + UINT32 Flags; + } RenameEx; } Info; } SetInformation; struct diff --git a/inc/winfsp/winfsp.h b/inc/winfsp/winfsp.h index 2440bcd6..304d1db8 100644 --- a/inc/winfsp/winfsp.h +++ b/inc/winfsp/winfsp.h @@ -1459,7 +1459,8 @@ UINT32 FspFileSystemOperationProcessId(VOID) case FspFsctlTransactCreateKind: return FSP_FSCTL_TRANSACT_REQ_TOKEN_PID(Request->Req.Create.AccessToken); case FspFsctlTransactSetInformationKind: - if (10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass) + if (10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass || + 65/*FileRenameInformationEx*/ == Request->Req.SetInformation.FileInformationClass) return FSP_FSCTL_TRANSACT_REQ_TOKEN_PID(Request->Req.SetInformation.Info.Rename.AccessToken); /* fall through! */ default: diff --git a/src/dll/fsop.c b/src/dll/fsop.c index d8780dd9..0c6eba37 100644 --- a/src/dll/fsop.c +++ b/src/dll/fsop.c @@ -383,7 +383,10 @@ NTSTATUS FspFileSystemRenameCheck(FSP_FILE_SYSTEM *FileSystem, Request->Req.SetInformation.Info.Rename.NewFileName.Size; CreateRequest->Kind = FspFsctlTransactCreateKind; CreateRequest->Req.Create.CreateOptions = - FILE_DELETE_ON_CLOSE | /* force read-only check! */ + (65/*FileRenameInformationEx*/ == Request->Req.SetInformation.FileInformationClass && + 0 != (0x40/*IGNORE_READONLY_ATTRIBUTE*/ & Request->Req.SetInformation.Info.RenameEx.Flags) ? + 0 : + FILE_DELETE_ON_CLOSE) | /* force read-only check! */ FILE_OPEN_REPARSE_POINT; /* allow rename over reparse point */ CreateRequest->Req.Create.AccessToken = Request->Req.SetInformation.Info.Rename.AccessToken; CreateRequest->Req.Create.UserMode = TRUE; diff --git a/src/dll/fuse/fuse_intf.c b/src/dll/fuse/fuse_intf.c index 69635f2d..2c04af0c 100644 --- a/src/dll/fuse/fuse_intf.c +++ b/src/dll/fuse/fuse_intf.c @@ -141,7 +141,8 @@ NTSTATUS fsp_fuse_op_enter(FSP_FILE_SYSTEM *FileSystem, AccessToken = Request->Req.Create.AccessToken; } else if (FspFsctlTransactSetInformationKind == Request->Kind && - 10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass) + (10/*FileRenameInformation*/ == Request->Req.SetInformation.FileInformationClass || + 65/*FileRenameInformationEx*/ == Request->Req.SetInformation.FileInformationClass)) { FileName = (PWSTR)(Request->Buffer + Request->Req.SetInformation.Info.Rename.NewFileName.Offset); AccessToken = Request->Req.SetInformation.Info.Rename.AccessToken; diff --git a/src/sys/driver.h b/src/sys/driver.h index 81d1cbaa..d3a30d8d 100644 --- a/src/sys/driver.h +++ b/src/sys/driver.h @@ -1577,7 +1577,8 @@ NTSTATUS FspFileNodeCheckBatchOplocksOnAllStreams( PUNICODE_STRING StreamFileName); NTSTATUS FspFileNodeRenameCheck(PDEVICE_OBJECT FsvolDeviceObject, PIRP OplockIrp, FSP_FILE_NODE *FileNode, ULONG AcquireFlags, - PUNICODE_STRING FileName, BOOLEAN CheckingOldName); + PUNICODE_STRING FileName, BOOLEAN CheckingOldName, + BOOLEAN PosixRename); VOID FspFileNodeRename(FSP_FILE_NODE *FileNode, PUNICODE_STRING NewFileName); VOID FspFileNodeGetFileInfo(FSP_FILE_NODE *FileNode, FSP_FSCTL_FILE_INFO *FileInfo); BOOLEAN FspFileNodeTryGetFileInfo(FSP_FILE_NODE *FileNode, FSP_FSCTL_FILE_INFO *FileInfo); diff --git a/src/sys/file.c b/src/sys/file.c index 82cc5662..528eead8 100644 --- a/src/sys/file.c +++ b/src/sys/file.c @@ -58,7 +58,8 @@ NTSTATUS FspFileNodeCheckBatchOplocksOnAllStreams( PUNICODE_STRING StreamFileName); NTSTATUS FspFileNodeRenameCheck(PDEVICE_OBJECT FsvolDeviceObject, PIRP OplockIrp, FSP_FILE_NODE *FileNode, ULONG AcquireFlags, - PUNICODE_STRING FileName, BOOLEAN CheckingOldName); + PUNICODE_STRING FileName, BOOLEAN CheckingOldName, + BOOLEAN PosixRename); VOID FspFileNodeRename(FSP_FILE_NODE *FileNode, PUNICODE_STRING NewFileName); VOID FspFileNodeGetFileInfo(FSP_FILE_NODE *FileNode, FSP_FSCTL_FILE_INFO *FileInfo); BOOLEAN FspFileNodeTryGetFileInfo(FSP_FILE_NODE *FileNode, FSP_FSCTL_FILE_INFO *FileInfo); @@ -1355,7 +1356,8 @@ NTSTATUS FspFileNodeCheckBatchOplocksOnAllStreams( NTSTATUS FspFileNodeRenameCheck(PDEVICE_OBJECT FsvolDeviceObject, PIRP OplockIrp, FSP_FILE_NODE *FileNode, ULONG AcquireFlags, - PUNICODE_STRING FileName, BOOLEAN CheckingOldName) + PUNICODE_STRING FileName, BOOLEAN CheckingOldName, + BOOLEAN PosixRename) { PAGED_CODE(); @@ -1389,7 +1391,7 @@ NTSTATUS FspFileNodeRenameCheck(PDEVICE_OBJECT FsvolDeviceObject, PIRP OplockIrp * "rename" resource exclusively, which disallows new Opens. */ - if (!CheckingOldName) + if (!PosixRename && !CheckingOldName) { /* replaced file cannot be a directory or mapped as an image */ for ( @@ -1568,11 +1570,32 @@ NTSTATUS FspFileNodeRenameCheck(PDEVICE_OBJECT FsvolDeviceObject, PIRP OplockIrp if (DescendantFileNode != FileNode && 0 < DescendantFileNode->HandleCount) { + /* + * If we are doing a POSIX rename, then it is ok if we have open handles, + * provided that we do not have sharing violations. + * + * Check our share access: + * + * - If all openers are allowing FILE_SHARE_DELETE. + * - And all named streams openers are allowing FILE_SHARE_DELETE. + * + * Then we are good to go. + * + * (WinFsp cannot rename streams and there is no need to check MainFileDenyDeleteCount). + * + * NOTE: These are derived rules. AFAIK there is no documentation on how NTFS + * does this in the case of POSIX rename. + */ + if (PosixRename && + DescendantFileNode->ShareAccess.OpenCount == DescendantFileNode->ShareAccess.SharedDelete && + 0 == DescendantFileNode->StreamDenyDeleteCount) + continue; + /* release the FileNode and rename lock in case of failure! */ FspFileNodeReleaseF(FileNode, AcquireFlags); FspFsvolDeviceFileRenameRelease(FsvolDeviceObject); - Result = STATUS_ACCESS_DENIED; + Result = PosixRename ? STATUS_SHARING_VIOLATION : STATUS_ACCESS_DENIED; break; } } @@ -1661,14 +1684,14 @@ VOID FspFileNodeRename(FSP_FILE_NODE *FileNode, PUNICODE_STRING NewFileName) if (!Inserted) { /* - * Handle files that have been Cleanup'ed but not Close'd. + * Handle files that have been replaced after a Rename. * For example, this can happen when the user has mapped and closed a file - * or immediately after breaking a Batch oplock. + * or immediately after breaking a Batch oplock or + * when doing a POSIX rename. */ ASSERT(FspFileNodeIsValid(InsertedFileNode)); ASSERT(DescendantFileNode != InsertedFileNode); - ASSERT(0 == InsertedFileNode->HandleCount); ASSERT(0 != InsertedFileNode->OpenCount); InsertedFileNode->OpenCount = 0; diff --git a/src/sys/fileinfo.c b/src/sys/fileinfo.c index 50e1e250..3072cd17 100644 --- a/src/sys/fileinfo.c +++ b/src/sys/fileinfo.c @@ -1639,7 +1639,9 @@ static NTSTATUS FspFsvolSetRenameInformation( FSP_FSVOL_DEVICE_EXTENSION *FsvolDeviceExtension = FspFsvolDeviceExtension(FsvolDeviceObject); PFILE_OBJECT FileObject = IrpSp->FileObject; PFILE_OBJECT TargetFileObject = IrpSp->Parameters.SetFile.FileObject; + FILE_INFORMATION_CLASS FileInformationClass = IrpSp->Parameters.SetFile.FileInformationClass; BOOLEAN ReplaceIfExists = IrpSp->Parameters.SetFile.ReplaceIfExists; + UINT32 RenameFlags = !!ReplaceIfExists; PFILE_RENAME_INFORMATION Info = (PFILE_RENAME_INFORMATION)Irp->AssociatedIrp.SystemBuffer; ULONG Length = IrpSp->Parameters.SetFile.Length; FSP_FILE_NODE *FileNode = FileObject->FsContext; @@ -1654,11 +1656,21 @@ static NTSTATUS FspFsvolSetRenameInformation( PSECURITY_SUBJECT_CONTEXT SecuritySubjectContext = 0; ASSERT(FileNode == FileDesc->FileNode); + ASSERT( + FileRenameInformation == FileInformationClass || + FileRenameInformationEx == FileInformationClass); if (sizeof(FILE_RENAME_INFORMATION) > Length) return STATUS_INVALID_PARAMETER; if (sizeof(WCHAR) > Info->FileNameLength) return STATUS_INVALID_PARAMETER; + if (FileRenameInformationEx == FileInformationClass) + { + if (!FspFsvolDeviceExtension(FsvolDeviceObject)->VolumeParams.SupportsPosixUnlinkRename) + return STATUS_INVALID_PARAMETER; + RenameFlags |= Info->Flags & + (FILE_RENAME_POSIX_SEMANTICS | FILE_RENAME_IGNORE_READONLY_ATTRIBUTE); + } if (FileNode->IsRootDirectory) /* cannot rename root directory */ return STATUS_INVALID_PARAMETER; @@ -1736,13 +1748,14 @@ retry: Request->Kind = FspFsctlTransactSetInformationKind; Request->Req.SetInformation.UserContext = FileNode->UserContext; Request->Req.SetInformation.UserContext2 = FileDesc->UserContext2; - Request->Req.SetInformation.FileInformationClass = FileRenameInformation; + Request->Req.SetInformation.FileInformationClass = FileInformationClass; Request->Req.SetInformation.Info.Rename.NewFileName.Offset = Request->FileName.Size; Request->Req.SetInformation.Info.Rename.NewFileName.Size = NewFileName.Length + sizeof(WCHAR); + Request->Req.SetInformation.Info.RenameEx.Flags = RenameFlags; } /* - * Special rules for renaming open files: + * Special rules for renaming open files without POSIX semantics: * - A file cannot be renamed if it has any open handles, * unless it is only open because of a batch opportunistic lock (oplock) * and the batch oplock can be broken immediately. @@ -1754,13 +1767,15 @@ retry: Result = FspFileNodeRenameCheck(FsvolDeviceObject, Irp, FileNode, FspFileNodeAcquireFull, - &FileNode->FileName, TRUE); + &FileNode->FileName, TRUE, + 0 != (FILE_RENAME_POSIX_SEMANTICS & RenameFlags)); /* FspFileNodeRenameCheck releases FileNode and rename lock on failure */ if (STATUS_OPLOCK_BREAK_IN_PROGRESS == Result) goto retry; if (!NT_SUCCESS(Result)) { - Result = STATUS_ACCESS_DENIED; + if (STATUS_SHARING_VIOLATION != Result) + Result = STATUS_ACCESS_DENIED; goto exit; } @@ -1768,13 +1783,15 @@ retry: { Result = FspFileNodeRenameCheck(FsvolDeviceObject, Irp, FileNode, FspFileNodeAcquireFull, - &NewFileName, FALSE); + &NewFileName, FALSE, + 0 != (FILE_RENAME_POSIX_SEMANTICS & RenameFlags)); /* FspFileNodeRenameCheck releases FileNode and rename lock on failure */ if (STATUS_OPLOCK_BREAK_IN_PROGRESS == Result) goto retry; if (!NT_SUCCESS(Result)) { - Result = STATUS_ACCESS_DENIED; + if (STATUS_SHARING_VIOLATION != Result) + Result = STATUS_ACCESS_DENIED; goto exit; } } @@ -1881,7 +1898,7 @@ static NTSTATUS FspFsvolSetInformation( case FileDispositionInformationEx: return FspFsvolSetDispositionInformation(FsvolDeviceObject, Irp, IrpSp); case FileRenameInformation: - //case FileRenameInformationEx: + case FileRenameInformationEx: return FspFsvolSetRenameInformation(FsvolDeviceObject, Irp, IrpSp); } @@ -2008,8 +2025,10 @@ NTSTATUS FspFsvolSetInformationPrepare( PAGED_CODE(); PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp); + FILE_INFORMATION_CLASS FileInformationClass = IrpSp->Parameters.SetFile.FileInformationClass; - if (FileRenameInformation != IrpSp->Parameters.SetFile.FileInformationClass || + if ((FileRenameInformation != FileInformationClass && + FileRenameInformationEx != FileInformationClass) || 0 == FspIopRequestContext(Request, RequestSubjectContextOrAccessToken)) return STATUS_SUCCESS; @@ -2087,7 +2106,7 @@ NTSTATUS FspFsvolSetInformationComplete( case FileDispositionInformationEx: FSP_RETURN(Result = FspFsvolSetDispositionInformationSuccess(Irp, Response)); case FileRenameInformation: - //case FileRenameInformationEx: + case FileRenameInformationEx: FSP_RETURN(Result = FspFsvolSetRenameInformationSuccess(Irp, Response)); } diff --git a/tst/winfsp-tests/info-test.c b/tst/winfsp-tests/info-test.c index 53efcf80..025c8f1f 100644 --- a/tst/winfsp-tests/info-test.c +++ b/tst/winfsp-tests/info-test.c @@ -1784,6 +1784,179 @@ void rename_pid_dotest(ULONG Flags, PWSTR Prefix) ASSERT(0 < rename_pid_Pass);// && 0 == rename_pid_Fail); } +static void rename_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 & 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; + HANDLE RootDirectory; + ULONG FileNameLength; + WCHAR FileName[1]; + } FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION; + + HANDLE Handle0, Handle1, Handle2; + WCHAR File0Path[MAX_PATH]; + WCHAR File2Path[MAX_PATH]; + union + { + FILE_RENAME_INFORMATION I; + UINT8 B[sizeof(FILE_RENAME_INFORMATION) + MAX_PATH * sizeof(WCHAR)]; + } RenameInfo; + IO_STATUS_BLOCK IoStatus; + + StringCbPrintfW(File0Path, sizeof File0Path, L"%s%s\\", + VolPrefix ? L"" : L"\\\\?\\GLOBALROOT", VolPrefix ? VolPrefix : memfs_volumename(memfs)); + + Success = GetVolumeInformationW(File0Path, + 0, 0, + 0, 0, &FileSystemFlags, + 0, 0); + ASSERT(Success); + if (0 != (FileSystemFlags & FILE_SUPPORTS_POSIX_UNLINK_RENAME)) + { + StringCbPrintfW(File0Path, sizeof File0Path, L"%s%s\\file0", + Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs)); + + StringCbPrintfW(File2Path, sizeof File2Path, L"%s%s\\file2", + Prefix ? L"" : L"\\\\?\\GLOBALROOT", Prefix ? Prefix : memfs_volumename(memfs)); + + Handle0 = CreateFileW(File0Path, + 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); + CloseHandle(Handle0); + + Handle0 = CreateFileW(File0Path, + DELETE, 0, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + ASSERT(INVALID_HANDLE_VALUE != Handle0); + + Handle1 = CreateFileW(File0Path, + FILE_READ_ATTRIBUTES, 0, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + ASSERT(INVALID_HANDLE_VALUE != Handle1); + + memset(&RenameInfo.I, 0, sizeof RenameInfo.I); + RenameInfo.I.Flags = 2/*FILE_RENAME_POSIX_SEMANTICS*/; + RenameInfo.I.FileNameLength = (ULONG)(wcslen(L"file2") * sizeof(WCHAR)); + memcpy(RenameInfo.I.FileName, L"file2", RenameInfo.I.FileNameLength); + IoStatus.Status = NtSetInformationFile( + Handle0, &IoStatus, + &RenameInfo.I, FIELD_OFFSET(FILE_RENAME_INFORMATION, FileName) + RenameInfo.I.FileNameLength, + 65/*FileRenameInformationEx*/); + ASSERT(0 == IoStatus.Status); + + Handle2 = CreateFileW(File2Path, + FILE_READ_ATTRIBUTES, 0, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + ASSERT(INVALID_HANDLE_VALUE != Handle2); + + CloseHandle(Handle2); + CloseHandle(Handle1); + CloseHandle(Handle0); + + Handle0 = CreateFileW(File0Path, + 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); + CloseHandle(Handle0); + + Handle1 = CreateFileW(File0Path, + GENERIC_READ | GENERIC_WRITE, 0, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + ASSERT(INVALID_HANDLE_VALUE != Handle1); + + Handle2 = CreateFileW(File2Path, + DELETE, 0, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + ASSERT(INVALID_HANDLE_VALUE != Handle0); + + memset(&RenameInfo.I, 0, sizeof RenameInfo.I); + RenameInfo.I.Flags = 3/*FILE_RENAME_REPLACE_IF_EXISTS|FILE_RENAME_POSIX_SEMANTICS*/; + RenameInfo.I.FileNameLength = (ULONG)(wcslen(L"file0") * sizeof(WCHAR)); + memcpy(RenameInfo.I.FileName, L"file0", RenameInfo.I.FileNameLength); + IoStatus.Status = NtSetInformationFile( + Handle2, &IoStatus, + &RenameInfo.I, FIELD_OFFSET(FILE_RENAME_INFORMATION, FileName) + RenameInfo.I.FileNameLength, + 65/*FileRenameInformationEx*/); + ASSERT(STATUS_SHARING_VIOLATION == IoStatus.Status); + + CloseHandle(Handle2); + CloseHandle(Handle1); + + Handle1 = CreateFileW(File0Path, + FILE_READ_ATTRIBUTES, 0, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + ASSERT(INVALID_HANDLE_VALUE != Handle1); + + Handle2 = CreateFileW(File2Path, + DELETE, 0, 0, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); + ASSERT(INVALID_HANDLE_VALUE != Handle0); + + memset(&RenameInfo.I, 0, sizeof RenameInfo.I); + RenameInfo.I.Flags = 3/*FILE_RENAME_REPLACE_IF_EXISTS|FILE_RENAME_POSIX_SEMANTICS*/; + RenameInfo.I.FileNameLength = (ULONG)(wcslen(L"file0") * sizeof(WCHAR)); + memcpy(RenameInfo.I.FileName, L"file0", RenameInfo.I.FileNameLength); + IoStatus.Status = NtSetInformationFile( + Handle2, &IoStatus, + &RenameInfo.I, FIELD_OFFSET(FILE_RENAME_INFORMATION, FileName) + RenameInfo.I.FileNameLength, + 65/*FileRenameInformationEx*/); + ASSERT(0 == IoStatus.Status); + + CloseHandle(Handle2); + CloseHandle(Handle1); + + Success = DeleteFileW(File0Path); + ASSERT(Success); + } + + memfs_stop(memfs); +} + +void rename_ex_test(void) +{ + if (OptLegacyUnlinkRename) + return; + + if (NtfsTests) + { + WCHAR DirBuf[MAX_PATH], DriveBuf[3]; + GetTestDirectoryAndDrive(DirBuf, DriveBuf); + rename_ex_dotest(-1, DriveBuf, DirBuf, 0); + } + if (WinFspDiskTests) + { + rename_ex_dotest(MemfsDisk, 0, 0, 0); + rename_ex_dotest(MemfsDisk, 0, 0, 1000); + } + if (WinFspNetTests) + { + rename_ex_dotest(MemfsNet, L"\\\\memfs\\share", L"\\\\memfs\\share", 0); + rename_ex_dotest(MemfsNet, L"\\\\memfs\\share", L"\\\\memfs\\share", 1000); + } +} + void rename_pid_test(void) { if (NtfsTests) @@ -2060,6 +2233,8 @@ void info_tests(void) if (!OptShareName) TEST(rename_mmap_test); TEST(rename_standby_test); + if (!OptLegacyUnlinkRename) + TEST(rename_ex_test); if (!NtfsTests) TEST(rename_pid_test); TEST(getvolinfo_test);