mirror of
				https://github.com/winfsp/winfsp.git
				synced 2025-10-31 12:08:41 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			1359 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1359 lines
		
	
	
		
			40 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /**
 | |
|  * @file ptfs.c
 | |
|  *
 | |
|  * @copyright 2015-2025 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);
 | |
| }
 |