mirror of
				https://github.com/winfsp/winfsp.git
				synced 2025-10-30 19:48:38 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			1022 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1022 lines
		
	
	
		
			28 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /**
 | |
|  * @file passthrough-cpp.cpp
 | |
|  *
 | |
|  * @copyright 2015-2019 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 <winfsp/winfsp.hpp>
 | |
| #include <strsafe.h>
 | |
| 
 | |
| #define PROGNAME                        "passthrough-cpp"
 | |
| 
 | |
| #define ALLOCATION_UNIT                 4096
 | |
| #define FULLPATH_SIZE                   (MAX_PATH + FSP_FSCTL_TRANSACT_PATH_SIZEMAX / sizeof(WCHAR))
 | |
| 
 | |
| #define info(format, ...)               Service::Log(EVENTLOG_INFORMATION_TYPE, format, __VA_ARGS__)
 | |
| #define warn(format, ...)               Service::Log(EVENTLOG_WARNING_TYPE, format, __VA_ARGS__)
 | |
| #define fail(format, ...)               Service::Log(EVENTLOG_ERROR_TYPE, format, __VA_ARGS__)
 | |
| 
 | |
| #define ConcatPath(FN, FP)              (0 == StringCbPrintfW(FP, sizeof FP, L"%s%s", _Path, FN))
 | |
| #define HandleFromFileDesc(FD)          ((PtfsFileDesc *)(FD))->Handle
 | |
| 
 | |
| using namespace Fsp;
 | |
| 
 | |
| class Ptfs : public FileSystemBase
 | |
| {
 | |
| public:
 | |
|     Ptfs();
 | |
|     ~Ptfs();
 | |
|     NTSTATUS SetPath(PWSTR Path);
 | |
| 
 | |
| protected:
 | |
|     static NTSTATUS GetFileInfoInternal(HANDLE Handle, FileInfo *FileInfo);
 | |
|     NTSTATUS Init(PVOID Host);
 | |
|     NTSTATUS GetVolumeInfo(
 | |
|         VolumeInfo *VolumeInfo);
 | |
|     NTSTATUS GetSecurityByName(
 | |
|         PWSTR FileName,
 | |
|         PUINT32 PFileAttributes/* or ReparsePointIndex */,
 | |
|         PSECURITY_DESCRIPTOR SecurityDescriptor,
 | |
|         SIZE_T *PSecurityDescriptorSize);
 | |
|     NTSTATUS Create(
 | |
|         PWSTR FileName,
 | |
|         UINT32 CreateOptions,
 | |
|         UINT32 GrantedAccess,
 | |
|         UINT32 FileAttributes,
 | |
|         PSECURITY_DESCRIPTOR SecurityDescriptor,
 | |
|         UINT64 AllocationSize,
 | |
|         PVOID *PFileNode,
 | |
|         PVOID *PFileDesc,
 | |
|         OpenFileInfo *OpenFileInfo);
 | |
|     NTSTATUS Open(
 | |
|         PWSTR FileName,
 | |
|         UINT32 CreateOptions,
 | |
|         UINT32 GrantedAccess,
 | |
|         PVOID *PFileNode,
 | |
|         PVOID *PFileDesc,
 | |
|         OpenFileInfo *OpenFileInfo);
 | |
|     NTSTATUS Overwrite(
 | |
|         PVOID FileNode,
 | |
|         PVOID FileDesc,
 | |
|         UINT32 FileAttributes,
 | |
|         BOOLEAN ReplaceFileAttributes,
 | |
|         UINT64 AllocationSize,
 | |
|         FileInfo *FileInfo);
 | |
|     VOID Cleanup(
 | |
|         PVOID FileNode,
 | |
|         PVOID FileDesc,
 | |
|         PWSTR FileName,
 | |
|         ULONG Flags);
 | |
|     VOID Close(
 | |
|         PVOID FileNode,
 | |
|         PVOID FileDesc);
 | |
|     NTSTATUS Read(
 | |
|         PVOID FileNode,
 | |
|         PVOID FileDesc,
 | |
|         PVOID Buffer,
 | |
|         UINT64 Offset,
 | |
|         ULONG Length,
 | |
|         PULONG PBytesTransferred);
 | |
|     NTSTATUS Write(
 | |
|         PVOID FileNode,
 | |
|         PVOID FileDesc,
 | |
|         PVOID Buffer,
 | |
|         UINT64 Offset,
 | |
|         ULONG Length,
 | |
|         BOOLEAN WriteToEndOfFile,
 | |
|         BOOLEAN ConstrainedIo,
 | |
|         PULONG PBytesTransferred,
 | |
|         FileInfo *FileInfo);
 | |
|     NTSTATUS Flush(
 | |
|         PVOID FileNode,
 | |
|         PVOID FileDesc,
 | |
|         FileInfo *FileInfo);
 | |
|     NTSTATUS GetFileInfo(
 | |
|         PVOID FileNode,
 | |
|         PVOID FileDesc,
 | |
|         FileInfo *FileInfo);
 | |
|     NTSTATUS SetBasicInfo(
 | |
|         PVOID FileNode,
 | |
|         PVOID FileDesc,
 | |
|         UINT32 FileAttributes,
 | |
|         UINT64 CreationTime,
 | |
|         UINT64 LastAccessTime,
 | |
|         UINT64 LastWriteTime,
 | |
|         UINT64 ChangeTime,
 | |
|         FileInfo *FileInfo);
 | |
|     NTSTATUS SetFileSize(
 | |
|         PVOID FileNode,
 | |
|         PVOID FileDesc,
 | |
|         UINT64 NewSize,
 | |
|         BOOLEAN SetAllocationSize,
 | |
|         FileInfo *FileInfo);
 | |
|     NTSTATUS CanDelete(
 | |
|         PVOID FileNode,
 | |
|         PVOID FileDesc,
 | |
|         PWSTR FileName);
 | |
|     NTSTATUS Rename(
 | |
|         PVOID FileNode,
 | |
|         PVOID FileDesc,
 | |
|         PWSTR FileName,
 | |
|         PWSTR NewFileName,
 | |
|         BOOLEAN ReplaceIfExists);
 | |
|     NTSTATUS GetSecurity(
 | |
|         PVOID FileNode,
 | |
|         PVOID FileDesc,
 | |
|         PSECURITY_DESCRIPTOR SecurityDescriptor,
 | |
|         SIZE_T *PSecurityDescriptorSize);
 | |
|     NTSTATUS SetSecurity(
 | |
|         PVOID FileNode,
 | |
|         PVOID FileDesc,
 | |
|         SECURITY_INFORMATION SecurityInformation,
 | |
|         PSECURITY_DESCRIPTOR ModificationDescriptor);
 | |
|     NTSTATUS ReadDirectory(
 | |
|         PVOID FileNode,
 | |
|         PVOID FileDesc,
 | |
|         PWSTR Pattern,
 | |
|         PWSTR Marker,
 | |
|         PVOID Buffer,
 | |
|         ULONG Length,
 | |
|         PULONG PBytesTransferred);
 | |
|     NTSTATUS ReadDirectoryEntry(
 | |
|         PVOID FileNode,
 | |
|         PVOID FileDesc,
 | |
|         PWSTR Pattern,
 | |
|         PWSTR Marker,
 | |
|         PVOID *PContext,
 | |
|         DirInfo *DirInfo);
 | |
| 
 | |
| private:
 | |
|     PWSTR _Path;
 | |
|     UINT64 _CreationTime;
 | |
| };
 | |
| 
 | |
| struct PtfsFileDesc
 | |
| {
 | |
|     PtfsFileDesc() : Handle(INVALID_HANDLE_VALUE), DirBuffer()
 | |
|     {
 | |
|     }
 | |
|     ~PtfsFileDesc()
 | |
|     {
 | |
|         CloseHandle(Handle);
 | |
|         Ptfs::DeleteDirectoryBuffer(&DirBuffer);
 | |
|     }
 | |
|     HANDLE Handle;
 | |
|     PVOID DirBuffer;
 | |
| };
 | |
| 
 | |
| Ptfs::Ptfs() : FileSystemBase(), _Path()
 | |
| {
 | |
| }
 | |
| 
 | |
| Ptfs::~Ptfs()
 | |
| {
 | |
|     delete[] _Path;
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::SetPath(PWSTR Path)
 | |
| {
 | |
|     WCHAR FullPath[MAX_PATH];
 | |
|     ULONG Length;
 | |
|     HANDLE Handle;
 | |
|     FILETIME CreationTime;
 | |
|     DWORD LastError;
 | |
| 
 | |
|     Handle = CreateFileW(
 | |
|         Path, FILE_READ_ATTRIBUTES, 0, 0,
 | |
|         OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
 | |
|     if (INVALID_HANDLE_VALUE == Handle)
 | |
|         return NtStatusFromWin32(GetLastError());
 | |
| 
 | |
|     Length = GetFinalPathNameByHandleW(Handle, FullPath, FULLPATH_SIZE - 1, 0);
 | |
|     if (0 == Length)
 | |
|     {
 | |
|         LastError = GetLastError();
 | |
|         CloseHandle(Handle);
 | |
|         return NtStatusFromWin32(LastError);
 | |
|     }
 | |
|     if (L'\\' == FullPath[Length - 1])
 | |
|         FullPath[--Length] = L'\0';
 | |
| 
 | |
|     if (!GetFileTime(Handle, &CreationTime, 0, 0))
 | |
|     {
 | |
|         LastError = GetLastError();
 | |
|         CloseHandle(Handle);
 | |
|         return NtStatusFromWin32(LastError);
 | |
|     }
 | |
| 
 | |
|     CloseHandle(Handle);
 | |
| 
 | |
|     Length++;
 | |
|     _Path = new WCHAR[Length];
 | |
|     memcpy(_Path, FullPath, Length * sizeof(WCHAR));
 | |
| 
 | |
|     _CreationTime = ((PLARGE_INTEGER)&CreationTime)->QuadPart;
 | |
| 
 | |
|     return STATUS_SUCCESS;
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::GetFileInfoInternal(HANDLE Handle, FileInfo *FileInfo)
 | |
| {
 | |
|     BY_HANDLE_FILE_INFORMATION ByHandleFileInfo;
 | |
| 
 | |
|     if (!GetFileInformationByHandle(Handle, &ByHandleFileInfo))
 | |
|         return NtStatusFromWin32(GetLastError());
 | |
| 
 | |
|     FileInfo->FileAttributes = ByHandleFileInfo.dwFileAttributes;
 | |
|     FileInfo->ReparseTag = 0;
 | |
|     FileInfo->FileSize =
 | |
|         ((UINT64)ByHandleFileInfo.nFileSizeHigh << 32) | (UINT64)ByHandleFileInfo.nFileSizeLow;
 | |
|     FileInfo->AllocationSize = (FileInfo->FileSize + ALLOCATION_UNIT - 1)
 | |
|         / ALLOCATION_UNIT * ALLOCATION_UNIT;
 | |
|     FileInfo->CreationTime = ((PLARGE_INTEGER)&ByHandleFileInfo.ftCreationTime)->QuadPart;
 | |
|     FileInfo->LastAccessTime = ((PLARGE_INTEGER)&ByHandleFileInfo.ftLastAccessTime)->QuadPart;
 | |
|     FileInfo->LastWriteTime = ((PLARGE_INTEGER)&ByHandleFileInfo.ftLastWriteTime)->QuadPart;
 | |
|     FileInfo->ChangeTime = FileInfo->LastWriteTime;
 | |
|     FileInfo->IndexNumber = 0;
 | |
|     FileInfo->HardLinks = 0;
 | |
| 
 | |
|     return STATUS_SUCCESS;
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::Init(PVOID Host0)
 | |
| {
 | |
|     FileSystemHost *Host = (FileSystemHost *)Host0;
 | |
|     Host->SetSectorSize(ALLOCATION_UNIT);
 | |
|     Host->SetSectorsPerAllocationUnit(1);
 | |
|     Host->SetFileInfoTimeout(1000);
 | |
|     Host->SetCaseSensitiveSearch(FALSE);
 | |
|     Host->SetCasePreservedNames(TRUE);
 | |
|     Host->SetUnicodeOnDisk(TRUE);
 | |
|     Host->SetPersistentAcls(TRUE);
 | |
|     Host->SetPostCleanupWhenModifiedOnly(TRUE);
 | |
|     Host->SetPassQueryDirectoryPattern(TRUE);
 | |
|     Host->SetVolumeCreationTime(_CreationTime);
 | |
|     Host->SetVolumeSerialNumber(0);
 | |
|     Host->SetFlushAndPurgeOnCleanup(TRUE);
 | |
|     return STATUS_SUCCESS;
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::GetVolumeInfo(
 | |
|     VolumeInfo *VolumeInfo)
 | |
| {
 | |
|     WCHAR Root[MAX_PATH];
 | |
|     ULARGE_INTEGER TotalSize, FreeSize;
 | |
| 
 | |
|     if (!GetVolumePathName(_Path, Root, MAX_PATH))
 | |
|         return NtStatusFromWin32(GetLastError());
 | |
| 
 | |
|     if (!GetDiskFreeSpaceEx(Root, 0, &TotalSize, &FreeSize))
 | |
|         return NtStatusFromWin32(GetLastError());
 | |
| 
 | |
|     VolumeInfo->TotalSize = TotalSize.QuadPart;
 | |
|     VolumeInfo->FreeSize = FreeSize.QuadPart;
 | |
| 
 | |
|     return STATUS_SUCCESS;
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::GetSecurityByName(
 | |
|     PWSTR FileName,
 | |
|     PUINT32 PFileAttributes/* or ReparsePointIndex */,
 | |
|     PSECURITY_DESCRIPTOR SecurityDescriptor,
 | |
|     SIZE_T *PSecurityDescriptorSize)
 | |
| {
 | |
|     WCHAR FullPath[FULLPATH_SIZE];
 | |
|     HANDLE Handle;
 | |
|     FILE_ATTRIBUTE_TAG_INFO AttributeTagInfo;
 | |
|     DWORD SecurityDescriptorSizeNeeded;
 | |
|     NTSTATUS Result;
 | |
| 
 | |
|     if (!ConcatPath(FileName, FullPath))
 | |
|         return STATUS_OBJECT_NAME_INVALID;
 | |
| 
 | |
|     Handle = CreateFileW(FullPath,
 | |
|         FILE_READ_ATTRIBUTES | READ_CONTROL, 0, 0,
 | |
|         OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
 | |
|     if (INVALID_HANDLE_VALUE == Handle)
 | |
|     {
 | |
|         Result = NtStatusFromWin32(GetLastError());
 | |
|         goto exit;
 | |
|     }
 | |
| 
 | |
|     if (0 != PFileAttributes)
 | |
|     {
 | |
|         if (!GetFileInformationByHandleEx(Handle,
 | |
|             FileAttributeTagInfo, &AttributeTagInfo, sizeof AttributeTagInfo))
 | |
|         {
 | |
|             Result = NtStatusFromWin32(GetLastError());
 | |
|             goto exit;
 | |
|         }
 | |
| 
 | |
|         *PFileAttributes = AttributeTagInfo.FileAttributes;
 | |
|     }
 | |
| 
 | |
|     if (0 != PSecurityDescriptorSize)
 | |
|     {
 | |
|         if (!GetKernelObjectSecurity(Handle,
 | |
|             OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
 | |
|             SecurityDescriptor, (DWORD)*PSecurityDescriptorSize, &SecurityDescriptorSizeNeeded))
 | |
|         {
 | |
|             *PSecurityDescriptorSize = SecurityDescriptorSizeNeeded;
 | |
|             Result = NtStatusFromWin32(GetLastError());
 | |
|             goto exit;
 | |
|         }
 | |
| 
 | |
|         *PSecurityDescriptorSize = SecurityDescriptorSizeNeeded;
 | |
|     }
 | |
| 
 | |
|     Result = STATUS_SUCCESS;
 | |
| 
 | |
| exit:
 | |
|     if (INVALID_HANDLE_VALUE != Handle)
 | |
|         CloseHandle(Handle);
 | |
| 
 | |
|     return Result;
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::Create(
 | |
|     PWSTR FileName,
 | |
|     UINT32 CreateOptions,
 | |
|     UINT32 GrantedAccess,
 | |
|     UINT32 FileAttributes,
 | |
|     PSECURITY_DESCRIPTOR SecurityDescriptor,
 | |
|     UINT64 AllocationSize,
 | |
|     PVOID *PFileNode,
 | |
|     PVOID *PFileDesc,
 | |
|     OpenFileInfo *OpenFileInfo)
 | |
| {
 | |
|     WCHAR FullPath[FULLPATH_SIZE];
 | |
|     SECURITY_ATTRIBUTES SecurityAttributes;
 | |
|     ULONG CreateFlags;
 | |
|     PtfsFileDesc *FileDesc;
 | |
| 
 | |
|     if (!ConcatPath(FileName, FullPath))
 | |
|         return STATUS_OBJECT_NAME_INVALID;
 | |
| 
 | |
|     FileDesc = new PtfsFileDesc;
 | |
| 
 | |
|     SecurityAttributes.nLength = sizeof SecurityAttributes;
 | |
|     SecurityAttributes.lpSecurityDescriptor = SecurityDescriptor;
 | |
|     SecurityAttributes.bInheritHandle = FALSE;
 | |
| 
 | |
|     CreateFlags = FILE_FLAG_BACKUP_SEMANTICS;
 | |
|     if (CreateOptions & FILE_DELETE_ON_CLOSE)
 | |
|         CreateFlags |= FILE_FLAG_DELETE_ON_CLOSE;
 | |
| 
 | |
|     if (CreateOptions & FILE_DIRECTORY_FILE)
 | |
|     {
 | |
|         /*
 | |
|          * It is not widely known but CreateFileW can be used to create directories!
 | |
|          * It requires the specification of both FILE_FLAG_BACKUP_SEMANTICS and
 | |
|          * FILE_FLAG_POSIX_SEMANTICS. It also requires that FileAttributes has
 | |
|          * FILE_ATTRIBUTE_DIRECTORY set.
 | |
|          */
 | |
|         CreateFlags |= FILE_FLAG_POSIX_SEMANTICS;
 | |
|         FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
 | |
|     }
 | |
|     else
 | |
|         FileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
 | |
| 
 | |
|     if (0 == FileAttributes)
 | |
|         FileAttributes = FILE_ATTRIBUTE_NORMAL;
 | |
| 
 | |
|     FileDesc->Handle = CreateFileW(FullPath,
 | |
|         GrantedAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &SecurityAttributes,
 | |
|         CREATE_NEW, CreateFlags | FileAttributes, 0);
 | |
|     if (INVALID_HANDLE_VALUE == FileDesc->Handle)
 | |
|     {
 | |
|         delete FileDesc;
 | |
|         return NtStatusFromWin32(GetLastError());
 | |
|     }
 | |
| 
 | |
|     *PFileDesc = FileDesc;
 | |
| 
 | |
|     return GetFileInfoInternal(FileDesc->Handle, &OpenFileInfo->FileInfo);
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::Open(
 | |
|     PWSTR FileName,
 | |
|     UINT32 CreateOptions,
 | |
|     UINT32 GrantedAccess,
 | |
|     PVOID *PFileNode,
 | |
|     PVOID *PFileDesc,
 | |
|     OpenFileInfo *OpenFileInfo)
 | |
| {
 | |
|     WCHAR FullPath[FULLPATH_SIZE];
 | |
|     ULONG CreateFlags;
 | |
|     PtfsFileDesc *FileDesc;
 | |
| 
 | |
|     if (!ConcatPath(FileName, FullPath))
 | |
|         return STATUS_OBJECT_NAME_INVALID;
 | |
| 
 | |
|     FileDesc = new PtfsFileDesc;
 | |
| 
 | |
|     CreateFlags = FILE_FLAG_BACKUP_SEMANTICS;
 | |
|     if (CreateOptions & FILE_DELETE_ON_CLOSE)
 | |
|         CreateFlags |= FILE_FLAG_DELETE_ON_CLOSE;
 | |
| 
 | |
|     FileDesc->Handle = CreateFileW(FullPath,
 | |
|         GrantedAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0,
 | |
|         OPEN_EXISTING, CreateFlags, 0);
 | |
|     if (INVALID_HANDLE_VALUE == FileDesc->Handle)
 | |
|     {
 | |
|         delete FileDesc;
 | |
|         return NtStatusFromWin32(GetLastError());
 | |
|     }
 | |
| 
 | |
|     *PFileDesc = FileDesc;
 | |
| 
 | |
|     return GetFileInfoInternal(FileDesc->Handle, &OpenFileInfo->FileInfo);
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::Overwrite(
 | |
|     PVOID FileNode,
 | |
|     PVOID FileDesc,
 | |
|     UINT32 FileAttributes,
 | |
|     BOOLEAN ReplaceFileAttributes,
 | |
|     UINT64 AllocationSize,
 | |
|     FileInfo *FileInfo)
 | |
| {
 | |
|     HANDLE Handle = HandleFromFileDesc(FileDesc);
 | |
|     FILE_BASIC_INFO BasicInfo = { 0 };
 | |
|     FILE_ALLOCATION_INFO AllocationInfo = { 0 };
 | |
|     FILE_ATTRIBUTE_TAG_INFO AttributeTagInfo;
 | |
| 
 | |
|     if (ReplaceFileAttributes)
 | |
|     {
 | |
|         if (0 == FileAttributes)
 | |
|             FileAttributes = FILE_ATTRIBUTE_NORMAL;
 | |
| 
 | |
|         BasicInfo.FileAttributes = FileAttributes;
 | |
|         if (!SetFileInformationByHandle(Handle,
 | |
|             FileBasicInfo, &BasicInfo, sizeof BasicInfo))
 | |
|             return NtStatusFromWin32(GetLastError());
 | |
|     }
 | |
|     else if (0 != FileAttributes)
 | |
|     {
 | |
|         if (!GetFileInformationByHandleEx(Handle,
 | |
|             FileAttributeTagInfo, &AttributeTagInfo, sizeof AttributeTagInfo))
 | |
|             return NtStatusFromWin32(GetLastError());
 | |
| 
 | |
|         BasicInfo.FileAttributes = FileAttributes | AttributeTagInfo.FileAttributes;
 | |
|         if (BasicInfo.FileAttributes ^ FileAttributes)
 | |
|         {
 | |
|             if (!SetFileInformationByHandle(Handle,
 | |
|                 FileBasicInfo, &BasicInfo, sizeof BasicInfo))
 | |
|                 return NtStatusFromWin32(GetLastError());
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (!SetFileInformationByHandle(Handle,
 | |
|         FileAllocationInfo, &AllocationInfo, sizeof AllocationInfo))
 | |
|         return NtStatusFromWin32(GetLastError());
 | |
| 
 | |
|     return GetFileInfoInternal(Handle, FileInfo);
 | |
| }
 | |
| 
 | |
| VOID Ptfs::Cleanup(
 | |
|     PVOID FileNode,
 | |
|     PVOID FileDesc,
 | |
|     PWSTR FileName,
 | |
|     ULONG Flags)
 | |
| {
 | |
|     HANDLE Handle = HandleFromFileDesc(FileDesc);
 | |
| 
 | |
|     if (Flags & CleanupDelete)
 | |
|     {
 | |
|         CloseHandle(Handle);
 | |
| 
 | |
|         /* this will make all future uses of Handle to fail with STATUS_INVALID_HANDLE */
 | |
|         HandleFromFileDesc(FileDesc) = INVALID_HANDLE_VALUE;
 | |
|     }
 | |
| }
 | |
| 
 | |
| VOID Ptfs::Close(
 | |
|     PVOID FileNode,
 | |
|     PVOID FileDesc0)
 | |
| {
 | |
|     PtfsFileDesc *FileDesc = (PtfsFileDesc *)FileDesc0;
 | |
| 
 | |
|     delete FileDesc;
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::Read(
 | |
|     PVOID FileNode,
 | |
|     PVOID FileDesc,
 | |
|     PVOID Buffer,
 | |
|     UINT64 Offset,
 | |
|     ULONG Length,
 | |
|     PULONG PBytesTransferred)
 | |
| {
 | |
|     HANDLE Handle = HandleFromFileDesc(FileDesc);
 | |
|     OVERLAPPED Overlapped = { 0 };
 | |
| 
 | |
|     Overlapped.Offset = (DWORD)Offset;
 | |
|     Overlapped.OffsetHigh = (DWORD)(Offset >> 32);
 | |
| 
 | |
|     if (!ReadFile(Handle, Buffer, Length, PBytesTransferred, &Overlapped))
 | |
|         return NtStatusFromWin32(GetLastError());
 | |
| 
 | |
|     return STATUS_SUCCESS;
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::Write(
 | |
|     PVOID FileNode,
 | |
|     PVOID FileDesc,
 | |
|     PVOID Buffer,
 | |
|     UINT64 Offset,
 | |
|     ULONG Length,
 | |
|     BOOLEAN WriteToEndOfFile,
 | |
|     BOOLEAN ConstrainedIo,
 | |
|     PULONG PBytesTransferred,
 | |
|     FileInfo *FileInfo)
 | |
| {
 | |
|     HANDLE Handle = HandleFromFileDesc(FileDesc);
 | |
|     LARGE_INTEGER FileSize;
 | |
|     OVERLAPPED Overlapped = { 0 };
 | |
| 
 | |
|     if (ConstrainedIo)
 | |
|     {
 | |
|         if (!GetFileSizeEx(Handle, &FileSize))
 | |
|             return NtStatusFromWin32(GetLastError());
 | |
| 
 | |
|         if (Offset >= (UINT64)FileSize.QuadPart)
 | |
|             return STATUS_SUCCESS;
 | |
|         if (Offset + Length > (UINT64)FileSize.QuadPart)
 | |
|             Length = (ULONG)((UINT64)FileSize.QuadPart - Offset);
 | |
|     }
 | |
| 
 | |
|     Overlapped.Offset = (DWORD)Offset;
 | |
|     Overlapped.OffsetHigh = (DWORD)(Offset >> 32);
 | |
| 
 | |
|     if (!WriteFile(Handle, Buffer, Length, PBytesTransferred, &Overlapped))
 | |
|         return NtStatusFromWin32(GetLastError());
 | |
| 
 | |
|     return GetFileInfoInternal(Handle, FileInfo);
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::Flush(
 | |
|     PVOID FileNode,
 | |
|     PVOID FileDesc,
 | |
|     FileInfo *FileInfo)
 | |
| {
 | |
|     HANDLE Handle = HandleFromFileDesc(FileDesc);
 | |
| 
 | |
|     /* we do not flush the whole volume, so just return SUCCESS */
 | |
|     if (0 == Handle)
 | |
|         return STATUS_SUCCESS;
 | |
| 
 | |
|     if (!FlushFileBuffers(Handle))
 | |
|         return NtStatusFromWin32(GetLastError());
 | |
| 
 | |
|     return GetFileInfoInternal(Handle, FileInfo);
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::GetFileInfo(
 | |
|     PVOID FileNode,
 | |
|     PVOID FileDesc,
 | |
|     FileInfo *FileInfo)
 | |
| {
 | |
|     HANDLE Handle = HandleFromFileDesc(FileDesc);
 | |
| 
 | |
|     return GetFileInfoInternal(Handle, FileInfo);
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::SetBasicInfo(
 | |
|     PVOID FileNode,
 | |
|     PVOID FileDesc,
 | |
|     UINT32 FileAttributes,
 | |
|     UINT64 CreationTime,
 | |
|     UINT64 LastAccessTime,
 | |
|     UINT64 LastWriteTime,
 | |
|     UINT64 ChangeTime,
 | |
|     FileInfo *FileInfo)
 | |
| {
 | |
|     HANDLE Handle = HandleFromFileDesc(FileDesc);
 | |
|     FILE_BASIC_INFO BasicInfo = { 0 };
 | |
| 
 | |
|     if (INVALID_FILE_ATTRIBUTES == FileAttributes)
 | |
|         FileAttributes = 0;
 | |
|     else if (0 == FileAttributes)
 | |
|         FileAttributes = FILE_ATTRIBUTE_NORMAL;
 | |
| 
 | |
|     BasicInfo.FileAttributes = FileAttributes;
 | |
|     BasicInfo.CreationTime.QuadPart = CreationTime;
 | |
|     BasicInfo.LastAccessTime.QuadPart = LastAccessTime;
 | |
|     BasicInfo.LastWriteTime.QuadPart = LastWriteTime;
 | |
|     //BasicInfo.ChangeTime = ChangeTime;
 | |
| 
 | |
|     if (!SetFileInformationByHandle(Handle,
 | |
|         FileBasicInfo, &BasicInfo, sizeof BasicInfo))
 | |
|         return NtStatusFromWin32(GetLastError());
 | |
| 
 | |
|     return GetFileInfoInternal(Handle, FileInfo);
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::SetFileSize(
 | |
|     PVOID FileNode,
 | |
|     PVOID FileDesc,
 | |
|     UINT64 NewSize,
 | |
|     BOOLEAN SetAllocationSize,
 | |
|     FileInfo *FileInfo)
 | |
| {
 | |
|     HANDLE Handle = HandleFromFileDesc(FileDesc);
 | |
|     FILE_ALLOCATION_INFO AllocationInfo;
 | |
|     FILE_END_OF_FILE_INFO EndOfFileInfo;
 | |
| 
 | |
|     if (SetAllocationSize)
 | |
|     {
 | |
|         /*
 | |
|          * This file system does not maintain AllocationSize, although NTFS clearly can.
 | |
|          * However it must always be FileSize <= AllocationSize and NTFS will make sure
 | |
|          * to truncate the FileSize if it sees an AllocationSize < FileSize.
 | |
|          *
 | |
|          * If OTOH a very large AllocationSize is passed, the call below will increase
 | |
|          * the AllocationSize of the underlying file, although our file system does not
 | |
|          * expose this fact. This AllocationSize is only temporary as NTFS will reset
 | |
|          * the AllocationSize of the underlying file when it is closed.
 | |
|          */
 | |
| 
 | |
|         AllocationInfo.AllocationSize.QuadPart = NewSize;
 | |
| 
 | |
|         if (!SetFileInformationByHandle(Handle,
 | |
|             FileAllocationInfo, &AllocationInfo, sizeof AllocationInfo))
 | |
|             return NtStatusFromWin32(GetLastError());
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         EndOfFileInfo.EndOfFile.QuadPart = NewSize;
 | |
| 
 | |
|         if (!SetFileInformationByHandle(Handle,
 | |
|             FileEndOfFileInfo, &EndOfFileInfo, sizeof EndOfFileInfo))
 | |
|             return NtStatusFromWin32(GetLastError());
 | |
|     }
 | |
| 
 | |
|     return GetFileInfoInternal(Handle, FileInfo);
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::CanDelete(
 | |
|     PVOID FileNode,
 | |
|     PVOID FileDesc,
 | |
|     PWSTR FileName)
 | |
| {
 | |
|     HANDLE Handle = HandleFromFileDesc(FileDesc);
 | |
|     FILE_DISPOSITION_INFO DispositionInfo;
 | |
| 
 | |
|     DispositionInfo.DeleteFile = TRUE;
 | |
| 
 | |
|     if (!SetFileInformationByHandle(Handle,
 | |
|         FileDispositionInfo, &DispositionInfo, sizeof DispositionInfo))
 | |
|         return NtStatusFromWin32(GetLastError());
 | |
| 
 | |
|     return STATUS_SUCCESS;
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::Rename(
 | |
|     PVOID FileNode,
 | |
|     PVOID FileDesc,
 | |
|     PWSTR FileName,
 | |
|     PWSTR NewFileName,
 | |
|     BOOLEAN ReplaceIfExists)
 | |
| {
 | |
|     WCHAR FullPath[FULLPATH_SIZE], NewFullPath[FULLPATH_SIZE];
 | |
| 
 | |
|     if (!ConcatPath(FileName, FullPath))
 | |
|         return STATUS_OBJECT_NAME_INVALID;
 | |
| 
 | |
|     if (!ConcatPath(NewFileName, NewFullPath))
 | |
|         return STATUS_OBJECT_NAME_INVALID;
 | |
| 
 | |
|     if (!MoveFileExW(FullPath, NewFullPath, ReplaceIfExists ? MOVEFILE_REPLACE_EXISTING : 0))
 | |
|         return NtStatusFromWin32(GetLastError());
 | |
| 
 | |
|     return STATUS_SUCCESS;
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::GetSecurity(
 | |
|     PVOID FileNode,
 | |
|     PVOID FileDesc,
 | |
|     PSECURITY_DESCRIPTOR SecurityDescriptor,
 | |
|     SIZE_T *PSecurityDescriptorSize)
 | |
| {
 | |
|     HANDLE Handle = HandleFromFileDesc(FileDesc);
 | |
|     DWORD SecurityDescriptorSizeNeeded;
 | |
| 
 | |
|     if (!GetKernelObjectSecurity(Handle,
 | |
|         OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
 | |
|         SecurityDescriptor, (DWORD)*PSecurityDescriptorSize, &SecurityDescriptorSizeNeeded))
 | |
|     {
 | |
|         *PSecurityDescriptorSize = SecurityDescriptorSizeNeeded;
 | |
|         return NtStatusFromWin32(GetLastError());
 | |
|     }
 | |
| 
 | |
|     *PSecurityDescriptorSize = SecurityDescriptorSizeNeeded;
 | |
| 
 | |
|     return STATUS_SUCCESS;
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::SetSecurity(
 | |
|     PVOID FileNode,
 | |
|     PVOID FileDesc,
 | |
|     SECURITY_INFORMATION SecurityInformation,
 | |
|     PSECURITY_DESCRIPTOR ModificationDescriptor)
 | |
| {
 | |
|     HANDLE Handle = HandleFromFileDesc(FileDesc);
 | |
| 
 | |
|     if (!SetKernelObjectSecurity(Handle, SecurityInformation, ModificationDescriptor))
 | |
|         return NtStatusFromWin32(GetLastError());
 | |
| 
 | |
|     return STATUS_SUCCESS;
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::ReadDirectory(
 | |
|     PVOID FileNode,
 | |
|     PVOID FileDesc0,
 | |
|     PWSTR Pattern,
 | |
|     PWSTR Marker,
 | |
|     PVOID Buffer,
 | |
|     ULONG Length,
 | |
|     PULONG PBytesTransferred)
 | |
| {
 | |
|     PtfsFileDesc *FileDesc = (PtfsFileDesc *)FileDesc0;
 | |
|     return BufferedReadDirectory(&FileDesc->DirBuffer,
 | |
|         FileNode, FileDesc, Pattern, Marker, Buffer, Length, PBytesTransferred);
 | |
| }
 | |
| 
 | |
| NTSTATUS Ptfs::ReadDirectoryEntry(
 | |
|     PVOID FileNode,
 | |
|     PVOID FileDesc0,
 | |
|     PWSTR Pattern,
 | |
|     PWSTR Marker,
 | |
|     PVOID *PContext,
 | |
|     DirInfo *DirInfo)
 | |
| {
 | |
|     PtfsFileDesc *FileDesc = (PtfsFileDesc *)FileDesc0;
 | |
|     HANDLE Handle = FileDesc->Handle;
 | |
|     WCHAR FullPath[FULLPATH_SIZE];
 | |
|     ULONG Length, PatternLength;
 | |
|     HANDLE FindHandle;
 | |
|     WIN32_FIND_DATAW FindData;
 | |
| 
 | |
|     if (0 == *PContext)
 | |
|     {
 | |
|         if (0 == Pattern)
 | |
|             Pattern = L"*";
 | |
|         PatternLength = (ULONG)wcslen(Pattern);
 | |
| 
 | |
|         Length = GetFinalPathNameByHandleW(Handle, FullPath, FULLPATH_SIZE - 1, 0);
 | |
|         if (0 == Length)
 | |
|             return NtStatusFromWin32(GetLastError());
 | |
|         if (Length + 1 + PatternLength >= FULLPATH_SIZE)
 | |
|             return STATUS_OBJECT_NAME_INVALID;
 | |
| 
 | |
|         if (L'\\' != FullPath[Length - 1])
 | |
|             FullPath[Length++] = L'\\';
 | |
|         memcpy(FullPath + Length, Pattern, PatternLength * sizeof(WCHAR));
 | |
|         FullPath[Length + PatternLength] = L'\0';
 | |
| 
 | |
|         FindHandle = FindFirstFileW(FullPath, &FindData);
 | |
|         if (INVALID_HANDLE_VALUE == FindHandle)
 | |
|             return STATUS_NO_MORE_FILES;
 | |
| 
 | |
|         *PContext = FindHandle;
 | |
|     }
 | |
|     else
 | |
|     {
 | |
|         FindHandle = *PContext;
 | |
|         if (!FindNextFileW(FindHandle, &FindData))
 | |
|         {
 | |
|             FindClose(FindHandle);
 | |
|             return STATUS_NO_MORE_FILES;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     memset(DirInfo, 0, sizeof *DirInfo);
 | |
|     Length = (ULONG)wcslen(FindData.cFileName);
 | |
|     DirInfo->Size = (UINT16)(FIELD_OFFSET(Ptfs::DirInfo, FileNameBuf) + Length * sizeof(WCHAR));
 | |
|     DirInfo->FileInfo.FileAttributes = FindData.dwFileAttributes;
 | |
|     DirInfo->FileInfo.ReparseTag = 0;
 | |
|     DirInfo->FileInfo.FileSize =
 | |
|         ((UINT64)FindData.nFileSizeHigh << 32) | (UINT64)FindData.nFileSizeLow;
 | |
|     DirInfo->FileInfo.AllocationSize = (DirInfo->FileInfo.FileSize + ALLOCATION_UNIT - 1)
 | |
|         / ALLOCATION_UNIT * ALLOCATION_UNIT;
 | |
|     DirInfo->FileInfo.CreationTime = ((PLARGE_INTEGER)&FindData.ftCreationTime)->QuadPart;
 | |
|     DirInfo->FileInfo.LastAccessTime = ((PLARGE_INTEGER)&FindData.ftLastAccessTime)->QuadPart;
 | |
|     DirInfo->FileInfo.LastWriteTime = ((PLARGE_INTEGER)&FindData.ftLastWriteTime)->QuadPart;
 | |
|     DirInfo->FileInfo.ChangeTime = DirInfo->FileInfo.LastWriteTime;
 | |
|     DirInfo->FileInfo.IndexNumber = 0;
 | |
|     DirInfo->FileInfo.HardLinks = 0;
 | |
|     memcpy(DirInfo->FileNameBuf, FindData.cFileName, Length * sizeof(WCHAR));
 | |
| 
 | |
|     return STATUS_SUCCESS;
 | |
| }
 | |
| 
 | |
| class PtfsService : public Service
 | |
| {
 | |
| public:
 | |
|     PtfsService();
 | |
| 
 | |
| protected:
 | |
|     NTSTATUS OnStart(ULONG Argc, PWSTR *Argv);
 | |
|     NTSTATUS OnStop();
 | |
| 
 | |
| private:
 | |
|     Ptfs _Ptfs;
 | |
|     FileSystemHost _Host;
 | |
| };
 | |
| 
 | |
| static NTSTATUS EnableBackupRestorePrivileges(VOID)
 | |
| {
 | |
|     union
 | |
|     {
 | |
|         TOKEN_PRIVILEGES P;
 | |
|         UINT8 B[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)];
 | |
|     } Privileges;
 | |
|     HANDLE Token;
 | |
| 
 | |
|     Privileges.P.PrivilegeCount = 2;
 | |
|     Privileges.P.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
 | |
|     Privileges.P.Privileges[1].Attributes = SE_PRIVILEGE_ENABLED;
 | |
| 
 | |
|     if (!LookupPrivilegeValueW(0, SE_BACKUP_NAME, &Privileges.P.Privileges[0].Luid) ||
 | |
|         !LookupPrivilegeValueW(0, SE_RESTORE_NAME, &Privileges.P.Privileges[1].Luid))
 | |
|         return FspNtStatusFromWin32(GetLastError());
 | |
| 
 | |
|     if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &Token))
 | |
|         return FspNtStatusFromWin32(GetLastError());
 | |
| 
 | |
|     if (!AdjustTokenPrivileges(Token, FALSE, &Privileges.P, 0, 0, 0))
 | |
|     {
 | |
|         CloseHandle(Token);
 | |
| 
 | |
|         return FspNtStatusFromWin32(GetLastError());
 | |
|     }
 | |
| 
 | |
|     CloseHandle(Token);
 | |
| 
 | |
|     return STATUS_SUCCESS;
 | |
| }
 | |
| 
 | |
| static ULONG wcstol_deflt(wchar_t *w, ULONG deflt)
 | |
| {
 | |
|     wchar_t *endp;
 | |
|     ULONG ul = wcstol(w, &endp, 0);
 | |
|     return L'\0' != w[0] && L'\0' == *endp ? ul : deflt;
 | |
| }
 | |
| 
 | |
| PtfsService::PtfsService() : Service(L"" PROGNAME), _Ptfs(), _Host(_Ptfs)
 | |
| {
 | |
| }
 | |
| 
 | |
| NTSTATUS PtfsService::OnStart(ULONG argc, PWSTR *argv)
 | |
| {
 | |
| #define argtos(v)                       if (arge > ++argp) v = *argp; else goto usage
 | |
| #define argtol(v)                       if (arge > ++argp) v = wcstol_deflt(*argp, v); else goto usage
 | |
| 
 | |
|     wchar_t **argp, **arge;
 | |
|     PWSTR DebugLogFile = 0;
 | |
|     ULONG DebugFlags = 0;
 | |
|     PWSTR VolumePrefix = 0;
 | |
|     PWSTR PassThrough = 0;
 | |
|     PWSTR MountPoint = 0;
 | |
|     HANDLE DebugLogHandle = INVALID_HANDLE_VALUE;
 | |
|     WCHAR PassThroughBuf[MAX_PATH];
 | |
|     NTSTATUS Result;
 | |
| 
 | |
|     for (argp = argv + 1, arge = argv + argc; arge > argp; argp++)
 | |
|     {
 | |
|         if (L'-' != argp[0][0])
 | |
|             break;
 | |
|         switch (argp[0][1])
 | |
|         {
 | |
|         case L'?':
 | |
|             goto usage;
 | |
|         case L'd':
 | |
|             argtol(DebugFlags);
 | |
|             break;
 | |
|         case L'D':
 | |
|             argtos(DebugLogFile);
 | |
|             break;
 | |
|         case L'm':
 | |
|             argtos(MountPoint);
 | |
|             break;
 | |
|         case L'p':
 | |
|             argtos(PassThrough);
 | |
|             break;
 | |
|         case L'u':
 | |
|             argtos(VolumePrefix);
 | |
|             break;
 | |
|         default:
 | |
|             goto usage;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (arge > argp)
 | |
|         goto usage;
 | |
| 
 | |
|     if (0 == PassThrough && 0 != VolumePrefix)
 | |
|     {
 | |
|         PWSTR P;
 | |
| 
 | |
|         P = wcschr(VolumePrefix, L'\\');
 | |
|         if (0 != P && L'\\' != P[1])
 | |
|         {
 | |
|             P = wcschr(P + 1, L'\\');
 | |
|             if (0 != P &&
 | |
|                 (
 | |
|                 (L'A' <= P[1] && P[1] <= L'Z') ||
 | |
|                 (L'a' <= P[1] && P[1] <= L'z')
 | |
|                 ) &&
 | |
|                 L'$' == P[2])
 | |
|             {
 | |
|                 StringCbPrintf(PassThroughBuf, sizeof PassThroughBuf, L"%c:%s", P[1], P + 3);
 | |
|                 PassThrough = PassThroughBuf;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (0 == PassThrough || 0 == MountPoint)
 | |
|         goto usage;
 | |
| 
 | |
|     EnableBackupRestorePrivileges();
 | |
| 
 | |
|     if (0 != DebugLogFile)
 | |
|     {
 | |
|         Result = FileSystemHost::SetDebugLogFile(DebugLogFile);
 | |
|         if (!NT_SUCCESS(Result))
 | |
|         {
 | |
|             fail(L"cannot open debug log file");
 | |
|             goto usage;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     Result = _Ptfs.SetPath(PassThrough);
 | |
|     if (!NT_SUCCESS(Result))
 | |
|     {
 | |
|         fail(L"cannot create file system");
 | |
|         return Result;
 | |
|     }
 | |
| 
 | |
|     _Host.SetPrefix(VolumePrefix);
 | |
|     Result = _Host.Mount(MountPoint, 0, FALSE, DebugFlags);
 | |
|     if (!NT_SUCCESS(Result))
 | |
|     {
 | |
|         fail(L"cannot mount file system");
 | |
|         return Result;
 | |
|     }
 | |
| 
 | |
|     MountPoint = _Host.MountPoint();
 | |
|     info(L"%s%s%s -p %s -m %s",
 | |
|         L"" PROGNAME,
 | |
|         0 != VolumePrefix && L'\0' != VolumePrefix[0] ? L" -u " : L"",
 | |
|             0 != VolumePrefix && L'\0' != VolumePrefix[0] ? VolumePrefix : L"",
 | |
|         PassThrough,
 | |
|         MountPoint);
 | |
| 
 | |
|     return STATUS_SUCCESS;
 | |
| 
 | |
| usage:
 | |
|     static wchar_t usage[] = L""
 | |
|         "usage: %s OPTIONS\n"
 | |
|         "\n"
 | |
|         "options:\n"
 | |
|         "    -d DebugFlags       [-1: enable all debug logs]\n"
 | |
|         "    -D DebugLogFile     [file path; use - for stderr]\n"
 | |
|         "    -u \\Server\\Share    [UNC prefix (single backslash)]\n"
 | |
|         "    -p Directory        [directory to expose as pass through file system]\n"
 | |
|         "    -m MountPoint       [X:|*|directory]\n";
 | |
| 
 | |
|     fail(usage, L"" PROGNAME);
 | |
| 
 | |
|     return STATUS_UNSUCCESSFUL;
 | |
| 
 | |
| #undef argtos
 | |
| #undef argtol
 | |
| }
 | |
| 
 | |
| NTSTATUS PtfsService::OnStop()
 | |
| {
 | |
|     _Host.Unmount();
 | |
|     return STATUS_SUCCESS;
 | |
| }
 | |
| 
 | |
| int wmain(int argc, wchar_t **argv)
 | |
| {
 | |
|     return PtfsService().Run();
 | |
| }
 |