/** * @file passthrough.c * * @copyright 2015-2017 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 file in * accordance with the commercial license agreement provided with the * software. */ #include #include #define PROGNAME "passthrough" #define ALLOCATION_UNIT 4096 #define FULLPATH_LENGTH (MAX_PATH + FSP_FSCTL_TRANSACT_PATH_SIZEMAX / sizeof(WCHAR)) #define info(format, ...) FspServiceLog(EVENTLOG_INFORMATION_TYPE, format, __VA_ARGS__) #define warn(format, ...) FspServiceLog(EVENTLOG_WARNING_TYPE, format, __VA_ARGS__) #define fail(format, ...) FspServiceLog(EVENTLOG_ERROR_TYPE, format, __VA_ARGS__) typedef struct { FSP_FILE_SYSTEM *FileSystem; PWSTR Path; } PTFS; #define ConcatPath(Ptfs, FN, FP) (0 == StringCbPrintfW(FP, sizeof FP, L"%s%s", Ptfs->Path, FN)) static NTSTATUS GetFileInfoInternal(HANDLE Handle, FSP_FSCTL_FILE_INFO *FileInfo) { BY_HANDLE_FILE_INFORMATION ByHandleFileInfo; if (!GetFileInformationByHandle(Handle, &ByHandleFileInfo)) return FspNtStatusFromWin32(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 = ((UINT64)ByHandleFileInfo.nFileIndexHigh << 32) | (UINT64)ByHandleFileInfo.nFileIndexLow; FileInfo->HardLinks = 0; return STATUS_SUCCESS; } static NTSTATUS GetVolumeInfo(FSP_FILE_SYSTEM *FileSystem, FSP_FSCTL_VOLUME_INFO *VolumeInfo) { PTFS *Ptfs = (PTFS *)FileSystem->UserContext; WCHAR Root[MAX_PATH]; ULARGE_INTEGER TotalSize, FreeSize; if (!GetVolumePathName(Ptfs->Path, Root, MAX_PATH)) return FspNtStatusFromWin32(GetLastError()); if (!GetDiskFreeSpaceEx(Root, 0, &TotalSize, &FreeSize)) return FspNtStatusFromWin32(GetLastError()); VolumeInfo->TotalSize = TotalSize.QuadPart; VolumeInfo->FreeSize = FreeSize.QuadPart; return STATUS_SUCCESS; } 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, PSECURITY_DESCRIPTOR SecurityDescriptor, SIZE_T *PSecurityDescriptorSize) { PTFS *Ptfs = (PTFS *)FileSystem->UserContext; WCHAR FullPath[FULLPATH_LENGTH]; HANDLE Handle; FILE_ATTRIBUTE_TAG_INFO AttributeTagInfo; DWORD SecurityDescriptorSizeNeeded; NTSTATUS Result; if (!ConcatPath(Ptfs, 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 = FspNtStatusFromWin32(GetLastError()); goto exit; } if (0 != PFileAttributes) { if (!GetFileInformationByHandleEx(Handle, FileAttributeTagInfo, &AttributeTagInfo, sizeof AttributeTagInfo)) { Result = FspNtStatusFromWin32(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)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } *PSecurityDescriptorSize = SecurityDescriptorSizeNeeded; } Result = STATUS_SUCCESS; exit: if (INVALID_HANDLE_VALUE != Handle) CloseHandle(Handle); return Result; } static NTSTATUS Create(FSP_FILE_SYSTEM *FileSystem, PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess, UINT32 FileAttributes, PSECURITY_DESCRIPTOR SecurityDescriptor, UINT64 AllocationSize, PVOID *PFileContext, FSP_FSCTL_FILE_INFO *FileInfo) { return STATUS_INVALID_DEVICE_REQUEST; } static NTSTATUS Open(FSP_FILE_SYSTEM *FileSystem, PWSTR FileName, UINT32 CreateOptions, UINT32 GrantedAccess, PVOID *PFileContext, FSP_FSCTL_FILE_INFO *FileInfo) { PTFS *Ptfs = (PTFS *)FileSystem->UserContext; WCHAR FullPath[FULLPATH_LENGTH]; HANDLE Handle; if (!ConcatPath(Ptfs, FileName, FullPath)) return STATUS_OBJECT_NAME_INVALID; Handle = CreateFileW(FullPath, GrantedAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); if (INVALID_HANDLE_VALUE == Handle) return FspNtStatusFromWin32(GetLastError()); return GetFileInfoInternal(Handle, FileInfo); } static NTSTATUS Overwrite(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, UINT32 FileAttributes, BOOLEAN ReplaceFileAttributes, UINT64 AllocationSize, FSP_FSCTL_FILE_INFO *FileInfo) { return STATUS_INVALID_DEVICE_REQUEST; } static VOID Cleanup(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PWSTR FileName, ULONG Flags) { } static VOID Close(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext) { HANDLE Handle = FileContext; CloseHandle(Handle); } static NTSTATUS Read(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PVOID Buffer, UINT64 Offset, ULONG Length, PULONG PBytesTransferred) { return STATUS_INVALID_DEVICE_REQUEST; } 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) { return STATUS_INVALID_DEVICE_REQUEST; } NTSTATUS Flush(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, FSP_FSCTL_FILE_INFO *FileInfo) { return STATUS_INVALID_DEVICE_REQUEST; } static NTSTATUS GetFileInfo(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, FSP_FSCTL_FILE_INFO *FileInfo) { return STATUS_INVALID_DEVICE_REQUEST; } static NTSTATUS SetBasicInfo(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, UINT32 FileAttributes, UINT64 CreationTime, UINT64 LastAccessTime, UINT64 LastWriteTime, UINT64 ChangeTime, FSP_FSCTL_FILE_INFO *FileInfo) { return STATUS_INVALID_DEVICE_REQUEST; } static NTSTATUS SetFileSize(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, UINT64 NewSize, BOOLEAN SetAllocationSize, FSP_FSCTL_FILE_INFO *FileInfo) { return STATUS_INVALID_DEVICE_REQUEST; } static NTSTATUS CanDelete(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PWSTR FileName) { return STATUS_INVALID_DEVICE_REQUEST; } static NTSTATUS Rename(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PWSTR FileName, PWSTR NewFileName, BOOLEAN ReplaceIfExists) { return STATUS_INVALID_DEVICE_REQUEST; } static NTSTATUS GetSecurity(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PSECURITY_DESCRIPTOR SecurityDescriptor, SIZE_T *PSecurityDescriptorSize) { return STATUS_INVALID_DEVICE_REQUEST; } static NTSTATUS SetSecurity(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, SECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR ModificationDescriptor) { return STATUS_INVALID_DEVICE_REQUEST; } static NTSTATUS ReadDirectory(FSP_FILE_SYSTEM *FileSystem, PVOID FileContext, PVOID Buffer, UINT64 Offset, ULONG Length, PWSTR Pattern, PULONG PBytesTransferred) { return STATUS_INVALID_DEVICE_REQUEST; } static FSP_FILE_SYSTEM_INTERFACE PtfsInterface = { GetVolumeInfo, SetVolumeLabel_, GetSecurityByName, Create, Open, Overwrite, Cleanup, Close, Read, Write, Flush, GetFileInfo, SetBasicInfo, SetFileSize, CanDelete, Rename, GetSecurity, SetSecurity, ReadDirectory, }; static VOID PtfsDelete(PTFS *Ptfs); static NTSTATUS PtfsCreate(PWSTR Path, PWSTR VolumePrefix, PWSTR MountPoint, UINT32 DebugFlags, PTFS **PPtfs) { WCHAR FullPath[MAX_PATH]; ULONG Length; HANDLE Handle; FILETIME CreationTime; DWORD LastError; FSP_FSCTL_VOLUME_PARAMS VolumeParams; PTFS *Ptfs = 0; NTSTATUS Result; *PPtfs = 0; if (!GetFullPathNameW(Path, MAX_PATH, FullPath, 0)) return FspNtStatusFromWin32(GetLastError()); Length = (ULONG)wcslen(FullPath); if (0 < Length && L'\\' == FullPath[Length - 1]) FullPath[--Length] = L'\0'; Handle = CreateFileW( FullPath, FILE_READ_ATTRIBUTES, 0, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); if (INVALID_HANDLE_VALUE == Handle) return FspNtStatusFromWin32(GetLastError()); LastError = GetFileTime(Handle, &CreationTime, 0, 0) ? GetLastError() : 0; CloseHandle(Handle); if (0 != LastError) return FspNtStatusFromWin32(LastError); /* from now on we must goto exit on failure */ Ptfs = malloc(sizeof *Ptfs); if (0 == Ptfs) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } memset(Ptfs, 0, sizeof *Ptfs); Length = (Length + 1) * sizeof(WCHAR); Ptfs->Path = malloc(Length); if (0 == Ptfs->Path) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } memcpy(Ptfs->Path, FullPath, Length); memset(&VolumeParams, 0, sizeof VolumeParams); VolumeParams.SectorSize = ALLOCATION_UNIT; VolumeParams.SectorsPerAllocationUnit = 1; VolumeParams.VolumeCreationTime = ((PLARGE_INTEGER)&CreationTime)->QuadPart; VolumeParams.VolumeSerialNumber = 0; VolumeParams.FileInfoTimeout = 1000; VolumeParams.CaseSensitiveSearch = 0; VolumeParams.CasePreservedNames = 1; VolumeParams.UnicodeOnDisk = 1; VolumeParams.PersistentAcls = 1; VolumeParams.PostCleanupWhenModifiedOnly = 1; VolumeParams.UmFileContextIsUserContext2 = 1; if (0 != VolumePrefix) wcscpy_s(VolumeParams.Prefix, sizeof VolumeParams.Prefix / sizeof(WCHAR), VolumePrefix); wcscpy_s(VolumeParams.FileSystemName, sizeof VolumeParams.FileSystemName / sizeof(WCHAR), L"" PROGNAME); Result = FspFileSystemCreate( VolumeParams.Prefix[0] ? L"" FSP_FSCTL_NET_DEVICE_NAME : L"" FSP_FSCTL_DISK_DEVICE_NAME, &VolumeParams, &PtfsInterface, &Ptfs->FileSystem); if (!NT_SUCCESS(Result)) goto exit; Ptfs->FileSystem->UserContext = Ptfs; Result = FspFileSystemSetMountPoint(Ptfs->FileSystem, MountPoint); if (!NT_SUCCESS(Result)) goto exit; FspFileSystemSetDebugLog(Ptfs->FileSystem, DebugFlags); Result = STATUS_SUCCESS; exit: if (NT_SUCCESS(Result)) *PPtfs = Ptfs; else if (0 != Ptfs) PtfsDelete(Ptfs); return Result; } static VOID PtfsDelete(PTFS *Ptfs) { if (0 != Ptfs->FileSystem) FspFileSystemDelete(Ptfs->FileSystem); if (0 != Ptfs->Path) free(Ptfs->Path); free(Ptfs); } 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; } static NTSTATUS SvcStart(FSP_SERVICE *Service, 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; PTFS *Ptfs = 0; 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 == MountPoint) goto usage; if (0 != DebugLogFile) { if (0 == wcscmp(L"-", DebugLogFile)) DebugLogHandle = GetStdHandle(STD_OUTPUT_HANDLE); else DebugLogHandle = CreateFileW( DebugLogFile, FILE_APPEND_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0); if (INVALID_HANDLE_VALUE == DebugLogHandle) { fail(L"cannot open debug log file"); goto usage; } FspDebugLogSetHandle(DebugLogHandle); } Result = PtfsCreate(PassThrough, VolumePrefix, MountPoint, DebugFlags, &Ptfs); if (!NT_SUCCESS(Result)) { fail(L"cannot create file system"); goto exit; } Result = FspFileSystemStartDispatcher(Ptfs->FileSystem, 0); if (!NT_SUCCESS(Result)) { fail(L"cannot start file system"); goto exit; } MountPoint = FspFileSystemMountPoint(Ptfs->FileSystem); 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); Service->UserContext = Ptfs; Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result) && 0 != Ptfs) PtfsDelete(Ptfs); return Result; 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 stdout]\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 } static NTSTATUS SvcStop(FSP_SERVICE *Service) { PTFS *Ptfs = Service->UserContext; FspFileSystemStopDispatcher(Ptfs->FileSystem); PtfsDelete(Ptfs); return STATUS_SUCCESS; } static NTSTATUS WinFspLoad(VOID) { #if defined(_WIN64) #define FSP_DLLNAME "winfsp-x64.dll" #else #define FSP_DLLNAME "winfsp-x86.dll" #endif #define FSP_DLLPATH "bin\\" FSP_DLLNAME LONG WINAPI __HrLoadAllImportsForDll(CONST CHAR *); WCHAR PathBuf[MAX_PATH - (sizeof L"" FSP_DLLPATH / sizeof(WCHAR) - 1)]; DWORD Size; LONG Result; HMODULE Module; Size = sizeof PathBuf; Result = RegGetValueW( HKEY_LOCAL_MACHINE, L"Software\\WinFsp", L"InstallDir", RRF_RT_REG_SZ | 0x00020000/*RRF_SUBKEY_WOW6432KEY*/, 0, PathBuf, &Size); if (ERROR_SUCCESS != Result) return STATUS_OBJECT_NAME_NOT_FOUND; RtlCopyMemory(PathBuf + (Size / sizeof(WCHAR) - 1), L"" FSP_DLLPATH, sizeof L"" FSP_DLLPATH); Module = LoadLibraryW(PathBuf); if (0 == Module) return STATUS_DLL_NOT_FOUND; Result = __HrLoadAllImportsForDll(FSP_DLLNAME); if (0 > Result) return STATUS_DELAY_LOAD_FAILED; return STATUS_SUCCESS; #undef FSP_DLLNAME #undef FSP_DLLPATH } int wmain(int argc, wchar_t **argv) { if (!NT_SUCCESS(WinFspLoad())) return ERROR_DELAY_LOAD_FAILED; return FspServiceRun(L"" PROGNAME, SvcStart, SvcStop, 0); }