mirror of
https://github.com/winfsp/winfsp.git
synced 2025-04-22 08:23:05 -05:00
- FspFileSystemAcquireDirectoryBufferEx takes hint for initial capacity. - Buffer allocation strategy has been improved to minimize reallocation. - Quick sort of directory entries now implements median of three partitioning. This improves performance of sorting already sorted data.
1351 lines
40 KiB
C
1351 lines
40 KiB
C
/**
|
|
* @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 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,
|
|
};
|
|
|
|
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.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);
|
|
}
|