/** * @file ptfs.c * * @copyright 2015-2022 Bill Zissimopoulos */ /* * This file is part of WinFsp. * * You can redistribute it and/or modify it under the terms of the GNU * General Public License version 3 as published by the Free Software * Foundation. * * Licensees holding a valid commercial license may use this software * in accordance with the commercial license agreement provided in * conjunction with the software. The terms and conditions of any such * commercial license agreement shall govern, supersede, and render * ineffective any application of the GPLv3 license to this software, * notwithstanding of any reference thereto in the software or * associated repository. */ #include "ptfs.h" #define FileSystemContext ((PTFS *)(FileSystem)->UserContext) #define FileContextHandle (((FILE_CONTEXT *)(FileContext))->Handle) #define FileContextIsDirectory (((FILE_CONTEXT *)(FileContext))->IsDirectory) #define FileContextDirFileSize (((FILE_CONTEXT *)(FileContext))->DirFileSize) #define FileContextDirBuffer (&((FILE_CONTEXT *)(FileContext))->DirBuffer) typedef struct { HANDLE Handle; BOOLEAN IsDirectory; ULONG DirFileSize; PVOID DirBuffer; } FILE_CONTEXT; static NTSTATUS GetReparsePointByName( FSP_FILE_SYSTEM *FileSystem, PVOID Context, PWSTR FileName, BOOLEAN IsDirectory, PVOID Buffer, PSIZE_T PSize); static NTSTATUS SetDisposition( HANDLE Handle, BOOLEAN DeleteFile); static NTSTATUS GetVolumeInfo(FSP_FILE_SYSTEM *FileSystem, FSP_FSCTL_VOLUME_INFO *VolumeInfo) { PTFS *Ptfs = FileSystemContext; IO_STATUS_BLOCK Iosb; FILE_FS_SIZE_INFORMATION FsSizeInfo; NTSTATUS Result; Result = NtQueryVolumeInformationFile( Ptfs->RootHandle, &Iosb, &FsSizeInfo, sizeof FsSizeInfo, 3/*FileFsSizeInformation*/); if (!NT_SUCCESS(Result)) goto exit; VolumeInfo->TotalSize = FsSizeInfo.TotalAllocationUnits.QuadPart * Ptfs->AllocationUnit; VolumeInfo->FreeSize = FsSizeInfo.AvailableAllocationUnits.QuadPart * Ptfs->AllocationUnit; Result = STATUS_SUCCESS; exit: return Result; } static NTSTATUS SetVolumeLabel_(FSP_FILE_SYSTEM *FileSystem, PWSTR VolumeLabel, FSP_FSCTL_VOLUME_INFO *VolumeInfo) { return STATUS_INVALID_DEVICE_REQUEST; } static NTSTATUS GetSecurityByName(FSP_FILE_SYSTEM *FileSystem, PWSTR FileName, PUINT32 PFileAttributes/* or ReparsePointIndex */, PSECURITY_DESCRIPTOR SecurityDescriptor, SIZE_T *PSecurityDescriptorSize) { PTFS *Ptfs = FileSystemContext; HANDLE Handle = 0; IO_STATUS_BLOCK Iosb; FILE_ATTRIBUTE_TAG_INFORMATION FileAttrInfo; ULONG SecurityDescriptorSizeNeeded; NTSTATUS Result; if (0 != (FILE_SUPPORTS_REPARSE_POINTS & Ptfs->FsAttributes) && FspFileSystemFindReparsePoint(FileSystem, GetReparsePointByName, 0, FileName, PFileAttributes)) { Result = STATUS_REPARSE; goto exit; } Result = LfsOpenFile( &Handle, READ_CONTROL | (Ptfs->HasSecurityPrivilege ? ACCESS_SYSTEM_SECURITY : 0), Ptfs->RootHandle, FileName, FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT); if (!NT_SUCCESS(Result)) goto exit; if (0 != PFileAttributes) { Result = NtQueryInformationFile( Handle, &Iosb, &FileAttrInfo, sizeof FileAttrInfo, 35/*FileAttributeTagInformation*/); if (!NT_SUCCESS(Result)) goto exit; *PFileAttributes = FileAttrInfo.FileAttributes; /* cache FileAttributes for Open */ FspFileSystemGetOperationContext()->Response->Rsp.Create.Opened.FileInfo.FileAttributes = FileAttrInfo.FileAttributes; } if (0 != PSecurityDescriptorSize) { Result = NtQuerySecurityObject( Handle, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | (Ptfs->HasSecurityPrivilege ? SACL_SECURITY_INFORMATION : 0), SecurityDescriptor, (ULONG)*PSecurityDescriptorSize, &SecurityDescriptorSizeNeeded); if (!NT_SUCCESS(Result)) goto exit; *PSecurityDescriptorSize = SecurityDescriptorSizeNeeded; } Result = STATUS_SUCCESS; exit: if (0 != Handle) NtClose(Handle); return Result; } static NTSTATUS CreateEx(FSP_FILE_SYSTEM *FileSystem, PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess, UINT32 FileAttributes, PSECURITY_DESCRIPTOR SecurityDescriptor, UINT64 AllocationSize, PVOID ExtraBuffer, ULONG ExtraLength, BOOLEAN ExtraBufferIsReparsePoint, PVOID *PFileContext, FSP_FSCTL_FILE_INFO *FileInfo) { PTFS *Ptfs = FileSystemContext; FILE_CONTEXT *FileContext = 0; HANDLE Handle = 0; BOOLEAN IsDirectory = !!(CreateOptions & FILE_DIRECTORY_FILE); UINT32 MaximumAccess = IsDirectory ? GrantedAccess : MAXIMUM_ALLOWED; NTSTATUS Result; CreateOptions &= FILE_DIRECTORY_FILE | FILE_NON_DIRECTORY_FILE | FILE_NO_EA_KNOWLEDGE; /* WORKAROUND: * * WOW64 appears to have a bug in some versions of the OS (seen on Win10 1909 and * Server 2012 R2), where NtQueryDirectoryFile may produce garbage if called on a * directory that has been opened without FILE_SYNCHRONOUS_IO_NONALERT. (Garbage: * after a STATUS_PENDING has been waited, Iosb.Information reports bytes transferred * but the buffer does not get filled). * * So make sure to always open directories in a synchronous manner. */ if (IsDirectory) { MaximumAccess |= SYNCHRONIZE; //GrantedAccess |= SYNCHRONIZE; CreateOptions |= FILE_SYNCHRONOUS_IO_NONALERT; } Result = LfsCreateFile( &Handle, MaximumAccess | (Ptfs->HasSecurityPrivilege ? ACCESS_SYSTEM_SECURITY : 0), Ptfs->RootHandle, FileName, SecurityDescriptor, 0 != AllocationSize ? (PLARGE_INTEGER)&AllocationSize : 0, 0 != FileAttributes ? FileAttributes : FILE_ATTRIBUTE_NORMAL, FILE_CREATE, FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT | CreateOptions, ExtraBufferIsReparsePoint ? 0 : ExtraBuffer, ExtraBufferIsReparsePoint ? 0 : ExtraLength); if (!NT_SUCCESS(Result) && MAXIMUM_ALLOWED == MaximumAccess) switch (Result) { case STATUS_INVALID_PARAMETER: Result = LfsCreateFile( &Handle, GrantedAccess | (Ptfs->HasSecurityPrivilege ? ACCESS_SYSTEM_SECURITY : 0), Ptfs->RootHandle, FileName, SecurityDescriptor, 0 != AllocationSize ? (PLARGE_INTEGER)&AllocationSize : 0, 0 != FileAttributes ? FileAttributes : FILE_ATTRIBUTE_NORMAL, FILE_CREATE, FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT | CreateOptions, ExtraBufferIsReparsePoint ? 0 : ExtraBuffer, ExtraBufferIsReparsePoint ? 0 : ExtraLength); break; } if (!NT_SUCCESS(Result)) goto exit; if (ExtraBufferIsReparsePoint) { /* this can happen on a WSL mount */ Result = LfsFsControlFile( Handle, FSCTL_SET_REPARSE_POINT, ExtraBuffer, (ULONG)ExtraLength, 0, 0, &ExtraLength); if (!NT_SUCCESS(Result)) goto exit; } Result = LfsGetFileInfo(Handle, Ptfs->RootPrefixLength, FileInfo); if (!NT_SUCCESS(Result)) goto exit; FileContext = malloc(sizeof *FileContext); if (0 == FileContext) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } memset(FileContext, 0, sizeof *FileContext); FileContext->Handle = Handle; FileContext->IsDirectory = IsDirectory; FileContext->DirFileSize = (ULONG)FileInfo->FileSize; *PFileContext = FileContext; Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result)) { if (0 != Handle) NtClose(Handle); free(FileContext); } return Result; } static NTSTATUS Open(FSP_FILE_SYSTEM *FileSystem, PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess, PVOID *PFileContext, FSP_FSCTL_FILE_INFO *FileInfo) { PTFS *Ptfs = FileSystemContext; FILE_CONTEXT *FileContext = 0; BOOLEAN IsDirectory = !!(FILE_ATTRIBUTE_DIRECTORY & FspFileSystemGetOperationContext()->Response->Rsp.Create.Opened.FileInfo.FileAttributes); UINT32 MaximumAccess = IsDirectory ? GrantedAccess : MAXIMUM_ALLOWED; HANDLE Handle = 0; NTSTATUS Result; CreateOptions &= FILE_DIRECTORY_FILE | FILE_NON_DIRECTORY_FILE | FILE_NO_EA_KNOWLEDGE; /* WORKAROUND: * * WOW64 appears to have a bug in some versions of the OS (seen on Win10 1909 and * Server 2012 R2), where NtQueryDirectoryFile may produce garbage if called on a * directory that has been opened without FILE_SYNCHRONOUS_IO_NONALERT. (Garbage: * after a STATUS_PENDING has been waited, Iosb.Information reports bytes transferred * but the buffer does not get filled). * * So make sure to always open directories in a synchronous manner. */ if (IsDirectory) { MaximumAccess |= SYNCHRONIZE; //GrantedAccess |= SYNCHRONIZE; CreateOptions |= FILE_SYNCHRONOUS_IO_NONALERT; } Result = LfsOpenFile( &Handle, MaximumAccess | (Ptfs->HasSecurityPrivilege ? ACCESS_SYSTEM_SECURITY : 0), Ptfs->RootHandle, FileName, FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT | CreateOptions); if (!NT_SUCCESS(Result) && MAXIMUM_ALLOWED == MaximumAccess) switch (Result) { case STATUS_ACCESS_DENIED: case STATUS_MEDIA_WRITE_PROTECTED: case STATUS_SHARING_VIOLATION: case STATUS_INVALID_PARAMETER: Result = LfsOpenFile( &Handle, GrantedAccess | (Ptfs->HasSecurityPrivilege ? ACCESS_SYSTEM_SECURITY : 0), Ptfs->RootHandle, FileName, FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT | CreateOptions); break; } if (!NT_SUCCESS(Result)) goto exit; Result = LfsGetFileInfo(Handle, Ptfs->RootPrefixLength, FileInfo); if (!NT_SUCCESS(Result)) goto exit; FileContext = malloc(sizeof *FileContext); if (0 == FileContext) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } memset(FileContext, 0, sizeof *FileContext); FileContext->Handle = Handle; FileContext->IsDirectory = IsDirectory; FileContext->DirFileSize = (ULONG)FileInfo->FileSize; *PFileContext = FileContext; Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result)) { if (0 != Handle) NtClose(Handle); free(FileContext); } return Result; } static NTSTATUS OverwriteEx(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, UINT32 FileAttributes, BOOLEAN ReplaceFileAttributes, UINT64 AllocationSize, PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, FSP_FSCTL_FILE_INFO *FileInfo) { HANDLE Handle = FileContextHandle; HANDLE NewHandle; NTSTATUS Result; Result = LfsCreateFile( &NewHandle, ReplaceFileAttributes ? DELETE : FILE_WRITE_DATA, Handle, L"", 0, 0 != AllocationSize ? (PLARGE_INTEGER)&AllocationSize : 0, ReplaceFileAttributes ? (0 != FileAttributes ? FileAttributes : FILE_ATTRIBUTE_NORMAL) : FileAttributes, ReplaceFileAttributes ? FILE_SUPERSEDE : FILE_OVERWRITE, FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT, Ea, EaLength); if (!NT_SUCCESS(Result)) goto exit; NtClose(NewHandle); Result = LfsGetFileInfo(Handle, -1, FileInfo); exit: return Result; } static VOID Cleanup(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PWSTR FileName, ULONG Flags) { PTFS *Ptfs = FileSystemContext; HANDLE Handle = FileContextHandle; if (Flags & FspCleanupDelete) { SetDisposition(Handle, TRUE); NtClose(Handle); /* this will make all future uses of Handle to fail with STATUS_INVALID_HANDLE */ FileContextHandle = 0; } else if ((Flags & FspCleanupSetAllocationSize) && (Ptfs->FsAttributeMask & PtfsSetAllocationSizeOnCleanup)) { IO_STATUS_BLOCK Iosb; FILE_STANDARD_INFORMATION FileStdInfo; FILE_ALLOCATION_INFORMATION FileAllocInfo; NTSTATUS Result; Result = NtQueryInformationFile( Handle, &Iosb, &FileStdInfo, sizeof FileStdInfo, 5/*FileStandardInformation*/); if (NT_SUCCESS(Result)) { FileAllocInfo.AllocationSize.QuadPart = FileStdInfo.EndOfFile.QuadPart; Result = NtSetInformationFile( Handle, &Iosb, &FileAllocInfo, sizeof FileAllocInfo, 19/*FileAllocationInformation*/); } } } static VOID Close(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext) { HANDLE Handle = FileContextHandle; if (0 != Handle) NtClose(Handle); FspFileSystemDeleteDirectoryBuffer(FileContextDirBuffer); free(FileContext); } static NTSTATUS Read(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PVOID Buffer, UINT64 Offset, ULONG Length, PULONG PBytesTransferred) { HANDLE Handle = FileContextHandle; return LfsReadFile( Handle, Buffer, Offset, Length, PBytesTransferred); } static NTSTATUS Write(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PVOID Buffer, UINT64 Offset, ULONG Length, BOOLEAN WriteToEndOfFile, BOOLEAN ConstrainedIo, PULONG PBytesTransferred, FSP_FSCTL_FILE_INFO *FileInfo) { HANDLE Handle = FileContextHandle; IO_STATUS_BLOCK Iosb; FILE_STANDARD_INFORMATION FileStdInfo; NTSTATUS Result; if (ConstrainedIo) { Result = NtQueryInformationFile( Handle, &Iosb, &FileStdInfo, sizeof FileStdInfo, 5/*FileStandardInformation*/); if (!NT_SUCCESS(Result)) goto exit; if (Offset >= (UINT64)FileStdInfo.EndOfFile.QuadPart) return STATUS_SUCCESS; if (Offset + Length > (UINT64)FileStdInfo.EndOfFile.QuadPart) Length = (ULONG)((UINT64)FileStdInfo.EndOfFile.QuadPart - Offset); } Result = LfsWriteFile( Handle, Buffer, Offset, Length, PBytesTransferred); if (!NT_SUCCESS(Result)) goto exit; Result = LfsGetFileInfo(Handle, -1, FileInfo); exit: return Result; } static NTSTATUS Flush(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, FSP_FSCTL_FILE_INFO *FileInfo) { /* we do not flush the whole volume, so just return SUCCESS */ if (0 == FileContext) return STATUS_SUCCESS; HANDLE Handle = FileContextHandle; IO_STATUS_BLOCK Iosb; NTSTATUS Result; Result = NtFlushBuffersFile( Handle, &Iosb); if (!NT_SUCCESS(Result)) goto exit; Result = LfsGetFileInfo(Handle, -1, FileInfo); exit: return Result; } static NTSTATUS GetFileInfo(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, FSP_FSCTL_FILE_INFO *FileInfo) { HANDLE Handle = FileContextHandle; return LfsGetFileInfo(Handle, -1, FileInfo); } static NTSTATUS SetBasicInfo(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, UINT32 FileAttributes, UINT64 CreationTime, UINT64 LastAccessTime, UINT64 LastWriteTime, UINT64 ChangeTime, FSP_FSCTL_FILE_INFO *FileInfo) { HANDLE Handle = FileContextHandle; IO_STATUS_BLOCK Iosb; FILE_BASIC_INFORMATION FileBasicInfo; NTSTATUS Result; if (INVALID_FILE_ATTRIBUTES == FileAttributes) FileAttributes = 0; else if (0 == FileAttributes) FileAttributes = FILE_ATTRIBUTE_NORMAL; memset(&FileBasicInfo, 0, sizeof FileBasicInfo); FileBasicInfo.CreationTime.QuadPart = CreationTime; FileBasicInfo.LastAccessTime.QuadPart = LastAccessTime; FileBasicInfo.LastWriteTime.QuadPart = LastWriteTime; FileBasicInfo.ChangeTime.QuadPart = ChangeTime; FileBasicInfo.FileAttributes = FileAttributes; Result = NtSetInformationFile( Handle, &Iosb, &FileBasicInfo, sizeof FileBasicInfo, 4/*FileBasicInformation*/); if (!NT_SUCCESS(Result)) goto exit; Result = LfsGetFileInfo(Handle, -1, FileInfo); exit: return Result; } static NTSTATUS SetFileSize(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, UINT64 NewSize, BOOLEAN SetAllocationSize, FSP_FSCTL_FILE_INFO *FileInfo) { HANDLE Handle = FileContextHandle; IO_STATUS_BLOCK Iosb; FILE_ALLOCATION_INFORMATION FileAllocInfo; FILE_END_OF_FILE_INFORMATION FileEofInfo; NTSTATUS Result; if (SetAllocationSize) { FileAllocInfo.AllocationSize.QuadPart = NewSize; Result = NtSetInformationFile( Handle, &Iosb, &FileAllocInfo, sizeof FileAllocInfo, 19/*FileAllocationInformation*/); } else { FileEofInfo.EndOfFile.QuadPart = NewSize; Result = NtSetInformationFile( Handle, &Iosb, &FileEofInfo, sizeof FileEofInfo, 20/*FileEndOfFileInformation*/); } if (!NT_SUCCESS(Result)) goto exit; Result = LfsGetFileInfo(Handle, -1, FileInfo); exit: return Result; } static NTSTATUS SetDelete(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PWSTR FileName, BOOLEAN DeleteFile) { return SetDisposition(FileContextHandle, DeleteFile); } static NTSTATUS SetDisposition( HANDLE Handle, BOOLEAN DeleteFile) { IO_STATUS_BLOCK Iosb; FILE_DISPOSITION_INFORMATION_EX FileDispInfoEx; FILE_DISPOSITION_INFORMATION FileDispInfo; NTSTATUS Result; FileDispInfoEx.Flags = DeleteFile ? 0x17/*DELETE | POSIX_SEMANTICS | IGNORE_READONLY_ATTRIBUTE | FORCE_IMAGE_SECTION_CHECK*/ : 0; Result = NtSetInformationFile( Handle, &Iosb, &FileDispInfoEx, sizeof FileDispInfoEx, 64/*FileDispositionInformationEx*/); if (!NT_SUCCESS(Result)) { switch (Result) { case STATUS_ACCESS_DENIED: case STATUS_DIRECTORY_NOT_EMPTY: case STATUS_CANNOT_DELETE: case STATUS_FILE_DELETED: goto exit; } FileDispInfo.DeleteFile = DeleteFile; Result = NtSetInformationFile( Handle, &Iosb, &FileDispInfo, sizeof FileDispInfo, 13/*FileDispositionInformation*/); if (!NT_SUCCESS(Result)) goto exit; } Result = STATUS_SUCCESS; exit: return Result; } static NTSTATUS Rename(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PWSTR FileName, PWSTR NewFileName, BOOLEAN ReplaceIfExists) { PTFS *Ptfs = FileSystemContext; HANDLE Handle = FileContextHandle; BOOLEAN PosixReplaceIfExists; IO_STATUS_BLOCK Iosb; union { FILE_RENAME_INFORMATION V; UINT8 B[FIELD_OFFSET(FILE_RENAME_INFORMATION, FileName) + FSP_FSCTL_TRANSACT_PATH_SIZEMAX]; } FileRenInfo; NTSTATUS Result; FileRenInfo.V.RootDirectory = Ptfs->RootHandle; FileRenInfo.V.FileNameLength = (ULONG)(wcslen(NewFileName + 1) * sizeof(WCHAR)); if (FSP_FSCTL_TRANSACT_PATH_SIZEMAX < FileRenInfo.V.FileNameLength) { Result = STATUS_INVALID_PARAMETER; goto exit; } memcpy(FileRenInfo.V.FileName, NewFileName + 1, FileRenInfo.V.FileNameLength); /* POSIX rename is able to replace existing (empty) directories; verify this is what caller wants! */ PosixReplaceIfExists = ReplaceIfExists && (!FileContextIsDirectory || 0 != (2/**POSIX_SEMANTICS*/ & FspFileSystemGetOperationContext()->Request->Req.SetInformation.Info.RenameEx.Flags)); FileRenInfo.V.Flags = (PosixReplaceIfExists ? 1/*REPLACE_IF_EXISTS*/ : 0) | 0x42 /*POSIX_SEMANTICS | IGNORE_READONLY_ATTRIBUTE*/; Result = NtSetInformationFile( Handle, &Iosb, &FileRenInfo, FIELD_OFFSET(FILE_RENAME_INFORMATION, FileName) + FileRenInfo.V.FileNameLength, 65/*FileRenameInformationEx*/); if (!NT_SUCCESS(Result)) { switch (Result) { case STATUS_OBJECT_NAME_COLLISION: if (ReplaceIfExists && !PosixReplaceIfExists) Result = STATUS_ACCESS_DENIED; goto exit; case STATUS_ACCESS_DENIED: goto exit; } FileRenInfo.V.Flags = 0; FileRenInfo.V.ReplaceIfExists = ReplaceIfExists; Result = NtSetInformationFile( Handle, &Iosb, &FileRenInfo, FIELD_OFFSET(FILE_RENAME_INFORMATION, FileName) + FileRenInfo.V.FileNameLength, 10/*FileRenameInformation*/); if (!NT_SUCCESS(Result)) goto exit; } Result = STATUS_SUCCESS; exit: return Result; } static NTSTATUS GetSecurity(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PSECURITY_DESCRIPTOR SecurityDescriptor, SIZE_T *PSecurityDescriptorSize) { PTFS *Ptfs = FileSystemContext; HANDLE Handle = FileContextHandle; ULONG SecurityDescriptorSizeNeeded; NTSTATUS Result; Result = NtQuerySecurityObject( Handle, OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | (Ptfs->HasSecurityPrivilege ? SACL_SECURITY_INFORMATION : 0), SecurityDescriptor, (ULONG)*PSecurityDescriptorSize, &SecurityDescriptorSizeNeeded); if (!NT_SUCCESS(Result)) goto exit; *PSecurityDescriptorSize = SecurityDescriptorSizeNeeded; Result = STATUS_SUCCESS; exit: return Result; } static NTSTATUS SetSecurity(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, SECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR ModificationDescriptor) { HANDLE Handle = FileContextHandle; return NtSetSecurityObject(Handle, SecurityInformation, ModificationDescriptor); } static inline VOID CopyQueryInfoToDirInfo( FILE_ID_BOTH_DIR_INFORMATION *QueryInfo, FSP_FSCTL_DIR_INFO *DirInfo) { memset(DirInfo, 0, sizeof *DirInfo); DirInfo->Size = (UINT16)(FIELD_OFFSET(FSP_FSCTL_DIR_INFO, FileNameBuf) + QueryInfo->FileNameLength); DirInfo->FileInfo.FileAttributes = QueryInfo->FileAttributes; DirInfo->FileInfo.ReparseTag = 0 != (FILE_ATTRIBUTE_REPARSE_POINT & QueryInfo->FileAttributes) ? QueryInfo->EaSize : 0; DirInfo->FileInfo.AllocationSize = QueryInfo->AllocationSize.QuadPart; DirInfo->FileInfo.FileSize = QueryInfo->EndOfFile.QuadPart; DirInfo->FileInfo.CreationTime = QueryInfo->CreationTime.QuadPart; DirInfo->FileInfo.LastAccessTime = QueryInfo->LastAccessTime.QuadPart; DirInfo->FileInfo.LastWriteTime = QueryInfo->LastWriteTime.QuadPart; DirInfo->FileInfo.ChangeTime = QueryInfo->ChangeTime.QuadPart; DirInfo->FileInfo.IndexNumber = QueryInfo->FileId.QuadPart; DirInfo->FileInfo.HardLinks = 0; DirInfo->FileInfo.EaSize = 0 != (FILE_ATTRIBUTE_REPARSE_POINT & QueryInfo->FileAttributes) ? 0 : LfsGetEaSize(QueryInfo->EaSize); } static NTSTATUS BufferedReadDirectory(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PWSTR Pattern, PWSTR Marker, PVOID Buffer, ULONG Length, PULONG PBytesTransferred) { PTFS *Ptfs = FileSystemContext; HANDLE Handle = FileContextHandle; ULONG CapacityHint = FileContextDirFileSize; /* * An analysis of the relationship between the required buffer capacity and the * NTFS directory size (as reported by FileStandardInformation) showed the ratio * between the two to be between 0.5 and 1 with some outliers outside those bounds. * (For this analysis file names of average length of 4 or 24 were used and NTFS * had 8.3 file names disabled.) * * We use the NTFS directory size as our capacity hint (i.e. ratio of 1), which * means that we may overestimate the required buffer capacity in most cases. * This is ok since our goal is to improve performance. */ PVOID PDirBuffer = FileContextDirBuffer; BOOLEAN RestartScan; ULONG BytesTransferred; UINT8 QueryBuffer[16 * 1024]; FILE_ID_BOTH_DIR_INFORMATION *QueryInfo; ULONG QueryNext; union { FSP_FSCTL_DIR_INFO V; UINT8 B[FIELD_OFFSET(FSP_FSCTL_DIR_INFO, FileNameBuf) + 255 * sizeof(WCHAR)]; } DirInfo; NTSTATUS Result, DirBufferResult; DirBufferResult = STATUS_SUCCESS; if (FspFileSystemAcquireDirectoryBufferEx(PDirBuffer, 0 == Marker, CapacityHint, &DirBufferResult)) { for (RestartScan = TRUE;; RestartScan = FALSE) { Result = LfsQueryDirectoryFile( Handle, QueryBuffer, sizeof QueryBuffer, 37/*FileIdBothDirectoryInformation*/, FALSE, Pattern, RestartScan, &BytesTransferred); if (!NT_SUCCESS(Result)) break; for (QueryInfo = (FILE_ID_BOTH_DIR_INFORMATION *)QueryBuffer; ; QueryInfo = (FILE_ID_BOTH_DIR_INFORMATION *)((PUINT8)QueryInfo + QueryNext)) { if (QueryBuffer + BytesTransferred < (PUINT8)QueryInfo + FIELD_OFFSET(FILE_ID_BOTH_DIR_INFORMATION, FileName)) goto done; QueryNext = QueryInfo->NextEntryOffset; CopyQueryInfoToDirInfo(QueryInfo, &DirInfo.V); memcpy(DirInfo.V.FileNameBuf, QueryInfo->FileName, QueryInfo->FileNameLength); if (!FspFileSystemFillDirectoryBuffer(PDirBuffer, &DirInfo.V, &DirBufferResult)) goto done; if (0 == QueryNext) break; } } done: FspFileSystemReleaseDirectoryBuffer(PDirBuffer); } if (!NT_SUCCESS(DirBufferResult)) return DirBufferResult; FspFileSystemReadDirectoryBuffer(PDirBuffer, Marker, Buffer, Length, PBytesTransferred); return STATUS_SUCCESS; } static NTSTATUS ResolveReparsePoints(FSP_FILE_SYSTEM *FileSystem, PWSTR FileName, UINT32 ReparsePointIndex, BOOLEAN ResolveLastPathComponent, PIO_STATUS_BLOCK PIoStatus, PVOID Buffer, PSIZE_T PSize) { return FspFileSystemResolveReparsePoints(FileSystem, GetReparsePointByName, 0, FileName, ReparsePointIndex, ResolveLastPathComponent, PIoStatus, Buffer, PSize); } static NTSTATUS GetReparsePointByName( FSP_FILE_SYSTEM *FileSystem, PVOID Context, PWSTR FileName, BOOLEAN IsDirectory, PVOID Buffer, PSIZE_T PSize) { PTFS *Ptfs = FileSystemContext; HANDLE Handle = 0; union { REPARSE_DATA_BUFFER V; UINT8 B[FSP_FSCTL_TRANSACT_RSP_BUFFER_SIZEMAX]; } ReparseBuffer; SIZE_T ReparseBufferSize; ULONG BytesTransferred; NTSTATUS Result; if (0 == Buffer) { Buffer = &ReparseBuffer; PSize = &ReparseBufferSize; ReparseBufferSize = sizeof ReparseBuffer; } Result = LfsOpenFile( &Handle, 0, Ptfs->RootHandle, FileName, FILE_OPEN_FOR_BACKUP_INTENT | FILE_OPEN_REPARSE_POINT | (IsDirectory ? FILE_DIRECTORY_FILE : 0)); if (!NT_SUCCESS(Result)) goto exit; Result = LfsFsControlFile( Handle, FSCTL_GET_REPARSE_POINT, 0, 0, Buffer, (ULONG)*PSize, &BytesTransferred); if (STATUS_BUFFER_OVERFLOW == Result) { Result = STATUS_BUFFER_TOO_SMALL; goto exit; } else if (!NT_SUCCESS(Result)) goto exit; *PSize = BytesTransferred; Result = STATUS_SUCCESS; exit: if (0 != Handle) NtClose(Handle); return Result; } static NTSTATUS GetReparsePoint(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PWSTR FileName, PVOID Buffer, PSIZE_T PSize) { HANDLE Handle = FileContextHandle; ULONG BytesTransferred; NTSTATUS Result; Result = LfsFsControlFile( Handle, FSCTL_GET_REPARSE_POINT, 0, 0, Buffer, (ULONG)*PSize, &BytesTransferred); if (STATUS_BUFFER_OVERFLOW == Result) { Result = STATUS_BUFFER_TOO_SMALL; goto exit; } else if (!NT_SUCCESS(Result)) goto exit; *PSize = BytesTransferred; Result = STATUS_SUCCESS; exit: return Result; } static NTSTATUS SetReparsePoint(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PWSTR FileName, PVOID Buffer, SIZE_T Size) { HANDLE Handle = FileContextHandle; ULONG BytesTransferred; NTSTATUS Result; Result = LfsFsControlFile( Handle, FSCTL_SET_REPARSE_POINT, Buffer, (ULONG)Size, 0, 0, &BytesTransferred); if (!NT_SUCCESS(Result)) goto exit; Result = STATUS_SUCCESS; exit: return Result; } static NTSTATUS DeleteReparsePoint(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PWSTR FileName, PVOID Buffer, SIZE_T Size) { HANDLE Handle = FileContextHandle; ULONG BytesTransferred; NTSTATUS Result; Result = LfsFsControlFile( Handle, FSCTL_DELETE_REPARSE_POINT, Buffer, (ULONG)Size, 0, 0, &BytesTransferred); if (!NT_SUCCESS(Result)) goto exit; Result = STATUS_SUCCESS; exit: return Result; } static NTSTATUS GetStreamInfo(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PVOID Buffer, ULONG Length, PULONG PBytesTransferred) { HANDLE Handle = FileContextHandle; IO_STATUS_BLOCK Iosb; UINT8 QueryBuffer[16 * 1024]; FILE_STREAM_INFORMATION *QueryInfo; ULONG QueryNext; union { FSP_FSCTL_STREAM_INFO V; UINT8 B[FIELD_OFFSET(FSP_FSCTL_STREAM_INFO, StreamNameBuf) + 255 * sizeof(WCHAR)]; } StreamInfo; PWCHAR P, EndP, NameP; NTSTATUS Result; Result = NtQueryInformationFile( Handle, &Iosb, QueryBuffer, sizeof QueryBuffer, 22/*FileStreamInformation*/); if (!NT_SUCCESS(Result) && STATUS_BUFFER_OVERFLOW != Result) goto exit; for (QueryInfo = (FILE_STREAM_INFORMATION *)QueryBuffer; ; QueryInfo = (FILE_STREAM_INFORMATION *)((PUINT8)QueryInfo + QueryNext)) { if (QueryBuffer + Iosb.Information < (PUINT8)QueryInfo + FIELD_OFFSET(FILE_STREAM_INFORMATION, StreamName)) break; QueryNext = QueryInfo->NextEntryOffset; NameP = 0; for (P = QueryInfo->StreamName, EndP = P + QueryInfo->StreamNameLength / sizeof(WCHAR);; P++) if (L':' == *P) { if (0 == NameP) NameP = P + 1; else break; } if (0 == NameP) NameP = P; memset(&StreamInfo.V, 0, sizeof StreamInfo.V); StreamInfo.V.Size = (UINT16)(FIELD_OFFSET(FSP_FSCTL_STREAM_INFO, StreamNameBuf) + ((PUINT8)P - (PUINT8)NameP)); StreamInfo.V.StreamSize = QueryInfo->StreamSize.QuadPart; StreamInfo.V.StreamAllocationSize = QueryInfo->StreamAllocationSize.QuadPart; memcpy(StreamInfo.V.StreamNameBuf, NameP, ((PUINT8)P - (PUINT8)NameP)); if (!FspFileSystemAddStreamInfo(&StreamInfo.V, Buffer, Length, PBytesTransferred)) goto done; if (0 == QueryNext) break; } FspFileSystemAddStreamInfo(0, Buffer, Length, PBytesTransferred); done: Result = STATUS_SUCCESS; exit: return Result; } static NTSTATUS GetEa(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, PULONG PBytesTransferred) { HANDLE Handle = FileContextHandle; IO_STATUS_BLOCK Iosb; NTSTATUS Result; Result = NtQueryEaFile( Handle, &Iosb, Ea, EaLength, FALSE, 0, 0, 0, TRUE); if (!NT_SUCCESS(Result) && STATUS_BUFFER_OVERFLOW != Result) { /* on error report an empty EA list */ Iosb.Information = 0; } *PBytesTransferred = (ULONG)Iosb.Information; Result = STATUS_SUCCESS; return Result; } static NTSTATUS SetEa(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PFILE_FULL_EA_INFORMATION Ea, ULONG EaLength, FSP_FSCTL_FILE_INFO *FileInfo) { HANDLE Handle = FileContextHandle; IO_STATUS_BLOCK Iosb; NTSTATUS Result; Result = NtSetEaFile( Handle, &Iosb, Ea, EaLength); if (!NT_SUCCESS(Result)) goto exit; Result = LfsGetFileInfo(Handle, -1, FileInfo); exit: return Result; } static VOID DispatcherStopped(FSP_FILE_SYSTEM *FileSystem, BOOLEAN Normally) { FspFileSystemStopServiceIfNecessary(FileSystem, Normally); } static FSP_FILE_SYSTEM_INTERFACE PtfsInterface = { .GetVolumeInfo = GetVolumeInfo, .SetVolumeLabel = SetVolumeLabel_, .GetSecurityByName = GetSecurityByName, .CreateEx = CreateEx, .Open = Open, .OverwriteEx = OverwriteEx, .Cleanup = Cleanup, .Close = Close, .Read = Read, .Write = Write, .Flush = Flush, .GetFileInfo = GetFileInfo, .SetBasicInfo = SetBasicInfo, .SetFileSize = SetFileSize, .SetDelete = SetDelete, .Rename = Rename, .GetSecurity = GetSecurity, .SetSecurity = SetSecurity, .ReadDirectory = BufferedReadDirectory, .ResolveReparsePoints = ResolveReparsePoints, .GetReparsePoint = GetReparsePoint, .SetReparsePoint = SetReparsePoint, .DeleteReparsePoint = DeleteReparsePoint, .GetStreamInfo = GetStreamInfo, .GetEa = GetEa, .SetEa = SetEa, .DispatcherStopped = DispatcherStopped, }; NTSTATUS PtfsCreate( PWSTR RootPath, ULONG FileInfoTimeout, ULONG FsAttributeMask, PWSTR VolumePrefix, PWSTR MountPoint, UINT32 DebugFlags, PTFS **PPtfs) { PTFS *Ptfs = 0; FSP_FILE_SYSTEM *FileSystem = 0; BOOL HasSecurityPrivilege = FALSE; PRIVILEGE_SET PrivilegeSet; HANDLE ProcessToken; HANDLE RootHandle = INVALID_HANDLE_VALUE; IO_STATUS_BLOCK Iosb; union { FILE_FS_ATTRIBUTE_INFORMATION V; UINT8 B[FIELD_OFFSET(FILE_FS_ATTRIBUTE_INFORMATION, FileSystemName) + MAX_PATH * sizeof(WCHAR)]; } FsAttrInfo; FILE_FS_SIZE_INFORMATION FsSizeInfo; FILE_ALL_INFORMATION FileAllInfo; FSP_FSCTL_VOLUME_PARAMS VolumeParams; NTSTATUS Result; *PPtfs = 0; if (LookupPrivilegeValueW(0, SE_SECURITY_NAME, &PrivilegeSet.Privilege[0].Luid) && OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &ProcessToken)) { PrivilegeSet.PrivilegeCount = 1; PrivilegeSet.Control = PRIVILEGE_SET_ALL_NECESSARY; PrivilegeSet.Privilege[0].Attributes = 0; PrivilegeCheck(ProcessToken, &PrivilegeSet, &HasSecurityPrivilege); CloseHandle(ProcessToken); } RootHandle = CreateFileW( RootPath, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, 0); if (INVALID_HANDLE_VALUE == RootHandle) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } Result = NtQueryVolumeInformationFile( RootHandle, &Iosb, &FsAttrInfo, sizeof FsAttrInfo, 5/*FileFsAttributeInformation*/); if (!NT_SUCCESS(Result)) goto exit; Result = NtQueryVolumeInformationFile( RootHandle, &Iosb, &FsSizeInfo, sizeof FsSizeInfo, 3/*FileFsSizeInformation*/); if (!NT_SUCCESS(Result)) goto exit; Result = NtQueryInformationFile( RootHandle, &Iosb, &FileAllInfo, sizeof FileAllInfo, 18/*FileAllInformation*/); if (!NT_SUCCESS(Result) && STATUS_BUFFER_OVERFLOW != Result) goto exit; FsAttributeMask &= PtfsAttributesMask; FsAttrInfo.V.FileSystemAttributes &= FILE_CASE_PRESERVED_NAMES | FILE_UNICODE_ON_DISK | FILE_PERSISTENT_ACLS | ((FsAttributeMask & PtfsReparsePoints) ? FILE_SUPPORTS_REPARSE_POINTS : 0) | ((FsAttributeMask & PtfsNamedStreams) ? FILE_NAMED_STREAMS : 0) | ((FsAttributeMask & PtfsExtendedAttributes) ? FILE_SUPPORTS_EXTENDED_ATTRIBUTES : 0) | 0x00000400/*FILE_SUPPORTS_POSIX_UNLINK_RENAME*/ | FILE_READ_ONLY_VOLUME; memset(&VolumeParams, 0, sizeof VolumeParams); VolumeParams.SectorSize = (UINT16)FsSizeInfo.BytesPerSector; VolumeParams.SectorsPerAllocationUnit = (UINT16)FsSizeInfo.SectorsPerAllocationUnit; VolumeParams.MaxComponentLength = (UINT16)FsAttrInfo.V.MaximumComponentNameLength; VolumeParams.VolumeCreationTime = FileAllInfo.BasicInformation.CreationTime.QuadPart; VolumeParams.VolumeSerialNumber = 0; VolumeParams.FileInfoTimeout = FileInfoTimeout; VolumeParams.CaseSensitiveSearch = 0; VolumeParams.CasePreservedNames = !!(FsAttrInfo.V.FileSystemAttributes & FILE_CASE_PRESERVED_NAMES); VolumeParams.UnicodeOnDisk = !!(FsAttrInfo.V.FileSystemAttributes & FILE_UNICODE_ON_DISK); VolumeParams.PersistentAcls = !!(FsAttrInfo.V.FileSystemAttributes & FILE_PERSISTENT_ACLS); VolumeParams.ReparsePoints = !!(FsAttrInfo.V.FileSystemAttributes & FILE_SUPPORTS_REPARSE_POINTS); VolumeParams.NamedStreams = !!(FsAttrInfo.V.FileSystemAttributes & FILE_NAMED_STREAMS); VolumeParams.ExtendedAttributes = !!(FsAttrInfo.V.FileSystemAttributes & FILE_SUPPORTS_EXTENDED_ATTRIBUTES); VolumeParams.SupportsPosixUnlinkRename = !!(FsAttrInfo.V.FileSystemAttributes & 0x00000400 /*FILE_SUPPORTS_POSIX_UNLINK_RENAME*/); VolumeParams.ReadOnlyVolume = !!(FsAttrInfo.V.FileSystemAttributes & FILE_READ_ONLY_VOLUME); VolumeParams.PostCleanupWhenModifiedOnly = 1; VolumeParams.PostDispositionWhenNecessaryOnly = 1; VolumeParams.PassQueryDirectoryPattern = 1; VolumeParams.FlushAndPurgeOnCleanup = !!(FsAttributeMask & PtfsFlushAndPurgeOnCleanup); VolumeParams.WslFeatures = !!(FsAttributeMask & PtfsWslFeatures); VolumeParams.AllowOpenInKernelMode = 1; VolumeParams.RejectIrpPriorToTransact0 = 1; VolumeParams.UmFileContextIsUserContext2 = 1; if (0 != VolumePrefix) wcscpy_s(VolumeParams.Prefix, sizeof VolumeParams.Prefix / sizeof(WCHAR), VolumePrefix); memcpy(VolumeParams.FileSystemName, FsAttrInfo.V.FileSystemName, sizeof VolumeParams.FileSystemName <= FsAttrInfo.V.FileSystemNameLength ? sizeof VolumeParams.FileSystemName : FsAttrInfo.V.FileSystemNameLength); Result = FspFileSystemCreate( VolumeParams.Prefix[0] ? L"" FSP_FSCTL_NET_DEVICE_NAME : L"" FSP_FSCTL_DISK_DEVICE_NAME, &VolumeParams, &PtfsInterface, &FileSystem); if (!NT_SUCCESS(Result)) goto exit; FspFileSystemSetDebugLog(FileSystem, DebugFlags); Ptfs = malloc(sizeof *Ptfs); if (0 == Ptfs) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } memset(Ptfs, 0, sizeof *Ptfs); Ptfs->FileSystem = FileSystem; Ptfs->HasSecurityPrivilege = HasSecurityPrivilege; Ptfs->RootHandle = RootHandle; Ptfs->RootPrefixLength = FileAllInfo.NameInformation.FileNameLength; Ptfs->FsAttributeMask = FsAttributeMask; Ptfs->FsAttributes = FsAttrInfo.V.FileSystemAttributes; Ptfs->AllocationUnit = VolumeParams.SectorSize * VolumeParams.SectorsPerAllocationUnit; FileSystem->UserContext = Ptfs; Result = FspFileSystemSetMountPoint(FileSystem, MountPoint); if (!NT_SUCCESS(Result)) goto exit; *PPtfs = Ptfs; Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result)) { free(Ptfs); if (0 != FileSystem) FspFileSystemDelete(FileSystem); if (INVALID_HANDLE_VALUE != RootHandle) CloseHandle(RootHandle); } return Result; } VOID PtfsDelete(PTFS *Ptfs) { FspFileSystemDelete(Ptfs->FileSystem); CloseHandle(Ptfs->RootHandle); free(Ptfs); }