/** * @file dll/mount.c * * @copyright 2015-2024 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 #include #include static INIT_ONCE FspMountInitOnce = INIT_ONCE_STATIC_INIT; static NTSTATUS (NTAPI *FspNtOpenSymbolicLinkObject)( PHANDLE LinkHandle, ACCESS_MASK DesiredAccess, POBJECT_ATTRIBUTES ObjectAttributes); static NTSTATUS (NTAPI *FspNtMakeTemporaryObject)( HANDLE Handle); static NTSTATUS (NTAPI *FspNtClose)( HANDLE Handle); static BOOLEAN FspMountDoNotUseLauncherValue; static BOOLEAN FspMountBroadcastDriveChangeValue; static BOOLEAN FspMountUseMountmgrFromFSDValue; static VOID FspMountInitializeFromRegistry(VOID) { DWORD Value; DWORD Size; LONG Result; Value = 0; Size = sizeof Value; Result = RegGetValueW(HKEY_LOCAL_MACHINE, L"" FSP_FSCTL_PRODUCT_FULL_REGKEY, L"MountDoNotUseLauncher", RRF_RT_REG_DWORD, 0, &Value, &Size); if (ERROR_SUCCESS == Result) FspMountDoNotUseLauncherValue = !!Value; Value = 0; Size = sizeof Value; Result = RegGetValueW(HKEY_LOCAL_MACHINE, L"" FSP_FSCTL_PRODUCT_FULL_REGKEY, L"MountBroadcastDriveChange", RRF_RT_REG_DWORD, 0, &Value, &Size); if (ERROR_SUCCESS == Result) FspMountBroadcastDriveChangeValue = !!Value; Value = 0; Size = sizeof Value; Result = RegGetValueW(HKEY_LOCAL_MACHINE, L"" FSP_FSCTL_PRODUCT_FULL_REGKEY, L"MountUseMountmgrFromFSD", RRF_RT_REG_DWORD, 0, &Value, &Size); if (ERROR_SUCCESS == Result) FspMountUseMountmgrFromFSDValue = !!Value; } static BOOL WINAPI FspMountInitialize( PINIT_ONCE InitOnce, PVOID Parameter, PVOID *Context) { HANDLE Handle; Handle = GetModuleHandleW(L"ntdll.dll"); if (0 != Handle) { FspNtOpenSymbolicLinkObject = (PVOID)GetProcAddress(Handle, "NtOpenSymbolicLinkObject"); FspNtMakeTemporaryObject = (PVOID)GetProcAddress(Handle, "NtMakeTemporaryObject"); FspNtClose = (PVOID)GetProcAddress(Handle, "NtClose"); if (0 == FspNtOpenSymbolicLinkObject || 0 == FspNtMakeTemporaryObject || 0 == FspNtClose) { FspNtOpenSymbolicLinkObject = 0; FspNtMakeTemporaryObject = 0; FspNtClose = 0; } } FspMountInitializeFromRegistry(); return TRUE; } static NTSTATUS FspMountSet_Directory(PWSTR VolumeName, PWSTR MountPoint, PSECURITY_DESCRIPTOR SecurityDescriptor, PHANDLE PMountHandle); static NTSTATUS FspMountRemove_Directory(HANDLE MountHandle); static NTSTATUS FspMountSet_MountmgrDrive(HANDLE VolumeHandle, PWSTR VolumeName, PWSTR MountPoint) { if (FspMountUseMountmgrFromFSDValue) /* use MountManager from FSD and exit */ return FspFsctlUseMountmgr(VolumeHandle, MountPoint + 4); GUID UniqueId; NTSTATUS Result; /* transform our volume into one that can be used by the MountManager */ Result = FspFsctlMakeMountdev(VolumeHandle, FALSE, &UniqueId); if (!NT_SUCCESS(Result)) goto exit; /* use the MountManager to create the drive */ UNICODE_STRING UVolumeName, UMountPoint; UVolumeName.Length = UVolumeName.MaximumLength = (USHORT)(lstrlenW(VolumeName) * sizeof(WCHAR)); UVolumeName.Buffer = VolumeName; UMountPoint.Length = UMountPoint.MaximumLength = (USHORT)((lstrlenW(MountPoint) - 4) * sizeof(WCHAR)); UMountPoint.Buffer = MountPoint + 4; Result = FspMountmgrCreateDrive(&UVolumeName, &UniqueId, &UMountPoint); if (!NT_SUCCESS(Result)) goto exit; Result = STATUS_SUCCESS; exit: return Result; } static NTSTATUS FspMountSet_MountmgrDirectory(HANDLE VolumeHandle, PWSTR VolumeName, PWSTR MountPoint, PSECURITY_DESCRIPTOR SecurityDescriptor, PHANDLE PMountHandle) { GUID UniqueId; HANDLE MountHandle = INVALID_HANDLE_VALUE; NTSTATUS Result; *PMountHandle = 0; /* create the directory mount point */ Result = FspMountSet_Directory(VolumeName, MountPoint + 4, SecurityDescriptor, &MountHandle); if (!NT_SUCCESS(Result)) goto exit; if (FspMountUseMountmgrFromFSDValue) { /* use MountManager from FSD and exit */ Result = FspFsctlUseMountmgr(VolumeHandle, MountPoint + 4); if (!NT_SUCCESS(Result)) goto exit; *PMountHandle = MountHandle; Result = STATUS_SUCCESS; goto exit; } /* transform our volume into one that can be used by the MountManager */ Result = FspFsctlMakeMountdev(VolumeHandle, FALSE, &UniqueId); if (!NT_SUCCESS(Result)) goto exit; /* notify the MountManager about the created directory mount point */ UNICODE_STRING UVolumeName, UMountPoint; UVolumeName.Length = UVolumeName.MaximumLength = (USHORT)(lstrlenW(VolumeName) * sizeof(WCHAR)); UVolumeName.Buffer = VolumeName; UMountPoint.Length = UMountPoint.MaximumLength = (USHORT)((lstrlenW(MountPoint) - 4) * sizeof(WCHAR)); UMountPoint.Buffer = MountPoint + 4; Result = FspMountmgrNotifyCreateDirectory(&UVolumeName, &UniqueId, &UMountPoint); if (!NT_SUCCESS(Result)) goto exit; *PMountHandle = MountHandle; Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result) && INVALID_HANDLE_VALUE != MountHandle) FspMountRemove_Directory(MountHandle); return Result; } static NTSTATUS FspMountRemove_MountmgrDrive(HANDLE VolumeHandle, PWSTR MountPoint) { if (FspMountUseMountmgrFromFSDValue) /* use MountManager from FSD and exit */ return FspFsctlUseMountmgr(VolumeHandle, 0); /* use the MountManager to delete the drive */ UNICODE_STRING UMountPoint; UMountPoint.Length = UMountPoint.MaximumLength = (USHORT)((lstrlenW(MountPoint) - 4) * sizeof(WCHAR)); UMountPoint.Buffer = MountPoint + 4; return FspMountmgrDeleteDrive(&UMountPoint); } static NTSTATUS FspMountRemove_MountmgrDirectory(HANDLE VolumeHandle, PWSTR VolumeName, PWSTR MountPoint, HANDLE MountHandle) { NTSTATUS Result; if (FspMountUseMountmgrFromFSDValue) /* use MountManager from FSD, but do not exit; additional processing is required below */ FspFsctlUseMountmgr(VolumeHandle, 0); else { /* notify the MountManager about the deleted directory mount point */ UNICODE_STRING UVolumeName, UMountPoint; UVolumeName.Length = UVolumeName.MaximumLength = (USHORT)(lstrlenW(VolumeName) * sizeof(WCHAR)); UVolumeName.Buffer = VolumeName; UMountPoint.Length = UMountPoint.MaximumLength = (USHORT)((lstrlenW(MountPoint) - 4) * sizeof(WCHAR)); UMountPoint.Buffer = MountPoint + 4; FspMountmgrNotifyDeleteDirectory(&UVolumeName, &UMountPoint); } /* delete the directory mount point */ Result = FspMountRemove_Directory(MountHandle); if (!NT_SUCCESS(Result)) goto exit; Result = STATUS_SUCCESS; exit: return Result; } static NTSTATUS FspLauncherDefineDosDevice( WCHAR Sign, PWSTR MountPoint, PWSTR VolumeName) { if (2 != lstrlenW(MountPoint) || FSP_FSCTL_VOLUME_NAME_SIZEMAX / sizeof(WCHAR) <= lstrlenW(VolumeName)) return STATUS_INVALID_PARAMETER; WCHAR Argv0[4]; PWSTR Argv[2]; NTSTATUS Result; ULONG ErrorCode; Argv0[0] = Sign; Argv0[1] = MountPoint[0]; Argv0[2] = MountPoint[1]; Argv0[3] = L'\0'; Argv[0] = Argv0; Argv[1] = VolumeName; Result = FspLaunchCallLauncherPipe(FspLaunchCmdDefineDosDevice, 2, Argv, 0, 0, 0, &ErrorCode); return !NT_SUCCESS(Result) ? Result : FspNtStatusFromWin32(ErrorCode); } struct FspMountBroadcastDriveChangeData { HMODULE Module; WPARAM WParam; WCHAR MountPoint[]; }; static DWORD WINAPI FspMountBroadcastDriveChangeThread(PVOID Data0) { struct FspMountBroadcastDriveChangeData *Data = Data0; HMODULE Module = Data->Module; WPARAM WParam = Data->WParam; PWSTR MountPoint = Data->MountPoint; BOOLEAN IsLocalSystem; DEV_BROADCAST_VOLUME DriveChange; DWORD Recipients; FspServiceContextCheck(0, &IsLocalSystem); memset(&DriveChange, 0, sizeof DriveChange); DriveChange.dbcv_size = sizeof DriveChange; DriveChange.dbcv_devicetype = DBT_DEVTYP_VOLUME; DriveChange.dbcv_flags = DBTF_NET; DriveChange.dbcv_unitmask = 1 << ((MountPoint[0] | 0x20) - 'a'); Recipients = BSM_APPLICATIONS | (IsLocalSystem ? BSM_ALLDESKTOPS : 0); BroadcastSystemMessageW( BSF_POSTMESSAGE, &Recipients, WM_DEVICECHANGE, WParam, (LPARAM)&DriveChange); MemFree(Data); FreeLibraryAndExitThread(Module, 0); return 0; } static NTSTATUS FspMountBroadcastDriveChange(PWSTR MountPoint, WPARAM WParam) { /* * DefineDosDeviceW (either directly or via the CSRSS) broadcasts a WM_DEVICECHANGE message * when a drive is added/removed. Unfortunately on some systems this broadcast fails. The * result is that Explorer does not receive the WM_DEVICECHANGE notification and does not * become aware of the drive change. This results in only minor UI issues for local drives, * but more seriously it makes network drives inaccessible from some Explorer windows. * * The problem is that BroadcastSystemMessage can hang indefinitely when supplied the flags * NOHANG | FORCEIFHUNG | NOTIMEOUTIFNOTHUNG. The NOTIMEOUTIFNOTHUNG flag instructs the BSM * API to not timeout an app that is not considered hung; however an app that is not hung may * still fail to respond to a broadcasted message indefinitely. This can result in the BSM * API never returning ("hanging"). * * To resolve this we simply call BroadcastSystemMessage with BSF_POSTMESSAGE. (It would work * with BSF_NOHANG | BSF_FORCEIFHUNG and without NOTIMEOUTIFNOTHUNG, but BSF_POSTMESSAGE is * faster). We do this in a separate thread to avoid blocking caller's thread. */ NTSTATUS Result; HMODULE Module; HANDLE Thread; struct FspMountBroadcastDriveChangeData *Data = 0; int Size; if (!GetModuleHandleExW( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (PVOID)FspMountBroadcastDriveChangeThread, &Module)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } Size = (lstrlenW(MountPoint) + 1) * sizeof(WCHAR); Data = MemAlloc(sizeof *Data + Size); if (0 == Data) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } Data->Module = Module; Data->WParam = WParam; memcpy(Data->MountPoint, MountPoint, Size); Thread = CreateThread(0, 0, FspMountBroadcastDriveChangeThread, Data, 0, 0); if (0 == Thread) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } CloseHandle(Thread); Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result)) { MemFree(Data); FreeLibrary(Module); } return Result; } static VOID FspMountNotifyShellDriveChange(VOID) { HRESULT HResult; LPITEMIDLIST Pidl; HResult = SHGetKnownFolderIDList(&FOLDERID_ComputerFolder, KF_FLAG_DEFAULT, 0, &Pidl); if (SUCCEEDED(HResult)) { SHChangeNotify(SHCNE_UPDATEDIR, SHCNF_IDLIST, Pidl, 0); CoTaskMemFree(Pidl); } } static NTSTATUS FspMountSet_Drive(PWSTR VolumeName, PWSTR MountPoint, PHANDLE PMountHandle) { NTSTATUS Result; BOOLEAN IsLocalSystem, IsServiceContext; *PMountHandle = 0; Result = FspServiceContextCheck(0, &IsLocalSystem); IsServiceContext = NT_SUCCESS(Result) && !IsLocalSystem; if (IsServiceContext && !FspMountDoNotUseLauncherValue) { /* * If the current process is in the service context but not LocalSystem, * ask the launcher to DefineDosDevice for us. This is because the launcher * runs in the LocalSystem context and can create global drives. * * In this case the launcher will also add DELETE access to the drive symlink * for us, so that we can make it temporary below. */ Result = FspLauncherDefineDosDevice(L'+', MountPoint, VolumeName); if (!NT_SUCCESS(Result)) return Result; } else { if (!DefineDosDeviceW(DDD_RAW_TARGET_PATH, MountPoint, VolumeName)) return FspNtStatusFromWin32(GetLastError()); /* * On some systems DefineDosDeviceW fails to properly broadcast the WM_DEVICECHANGE * notification. So use a workaround. See comments in FspMountBroadcastDriveChange. */ if (FspMountBroadcastDriveChangeValue) FspMountBroadcastDriveChange(MountPoint, DBT_DEVICEARRIVAL); } if (0 != FspNtOpenSymbolicLinkObject) { WCHAR SymlinkBuf[6]; UNICODE_STRING Symlink; OBJECT_ATTRIBUTES Obja; memcpy(SymlinkBuf, L"\\??\\X:", sizeof SymlinkBuf); SymlinkBuf[4] = MountPoint[0]; Symlink.Length = Symlink.MaximumLength = sizeof SymlinkBuf; Symlink.Buffer = SymlinkBuf; memset(&Obja, 0, sizeof Obja); Obja.Length = sizeof Obja; Obja.ObjectName = &Symlink; Obja.Attributes = OBJ_CASE_INSENSITIVE; Result = FspNtOpenSymbolicLinkObject(PMountHandle, DELETE, &Obja); if (NT_SUCCESS(Result)) { Result = FspNtMakeTemporaryObject(*PMountHandle); if (!NT_SUCCESS(Result)) { FspNtClose(*PMountHandle); *PMountHandle = 0; } } } /* HACK: * * Handles do not use the low 2 bits (unless they are console handles). * Abuse this fact to remember that we are running in the service context. */ *PMountHandle = (HANDLE)(UINT_PTR)((DWORD)(UINT_PTR)*PMountHandle | IsServiceContext); return STATUS_SUCCESS; } static NTSTATUS FspMountRemove_Drive(PWSTR VolumeName, PWSTR MountPoint, HANDLE MountHandle) { NTSTATUS Result; BOOLEAN IsServiceContext; IsServiceContext = 0 != ((DWORD)(UINT_PTR)MountHandle & 1); MountHandle = (HANDLE)(UINT_PTR)((DWORD)(UINT_PTR)MountHandle & ~1); if (IsServiceContext) /* * If the current process is in the service context but not LocalSystem, * ask the launcher to DefineDosDevice for us. This is because the launcher * runs in the LocalSystem context and can remove global drives. */ Result = FspLauncherDefineDosDevice(L'-', MountPoint, VolumeName); else { Result = DefineDosDeviceW(DDD_RAW_TARGET_PATH | DDD_REMOVE_DEFINITION | DDD_EXACT_MATCH_ON_REMOVE, MountPoint, VolumeName) ? STATUS_SUCCESS : FspNtStatusFromWin32(GetLastError()); /* * On some systems DefineDosDeviceW fails to properly broadcast the WM_DEVICECHANGE * notification. So use a workaround. See comments in FspMountBroadcastDriveChange. */ if (FspMountBroadcastDriveChangeValue) FspMountBroadcastDriveChange(MountPoint, DBT_DEVICEREMOVECOMPLETE); } if (0 != MountHandle) FspNtClose(MountHandle); return Result; } static NTSTATUS FspMountSet_Directory(PWSTR VolumeName, PWSTR MountPoint, PSECURITY_DESCRIPTOR SecurityDescriptor, PHANDLE PMountHandle) { NTSTATUS Result; SECURITY_ATTRIBUTES SecurityAttributes; HANDLE MountHandle = INVALID_HANDLE_VALUE; DWORD Backslashes, Bytes; USHORT VolumeNameLength, BackslashLength, ReparseDataLength; PREPARSE_DATA_BUFFER ReparseData = 0; PWSTR P, PathBuffer; *PMountHandle = 0; /* * Windows does not allow mount points (junctions) to point to network file systems. * * Count how many backslashes our VolumeName has. If it is 3 or more this is a network * file system. Preemptively return STATUS_NETWORK_ACCESS_DENIED. */ for (P = VolumeName, Backslashes = 0; *P; P++) if (L'\\' == *P) if (3 == ++Backslashes) { Result = STATUS_NETWORK_ACCESS_DENIED; goto exit; } memset(&SecurityAttributes, 0, sizeof SecurityAttributes); SecurityAttributes.nLength = sizeof SecurityAttributes; SecurityAttributes.lpSecurityDescriptor = SecurityDescriptor; MountHandle = FspCreateDirectoryFileW(MountPoint, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, &SecurityAttributes, FILE_ATTRIBUTE_DIRECTORY | FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS | FILE_FLAG_DELETE_ON_CLOSE); if (INVALID_HANDLE_VALUE == MountHandle) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } VolumeNameLength = (USHORT)lstrlenW(VolumeName); BackslashLength = 0 == VolumeNameLength || L'\\' != VolumeName[VolumeNameLength - 1]; VolumeNameLength *= sizeof(WCHAR); BackslashLength *= sizeof(WCHAR); ReparseDataLength = (USHORT)( FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) - FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer)) + 2 * (VolumeNameLength + BackslashLength + sizeof(WCHAR)); ReparseData = MemAlloc(REPARSE_DATA_BUFFER_HEADER_SIZE + ReparseDataLength); if (0 == ReparseData) { Result = STATUS_INSUFFICIENT_RESOURCES; goto exit; } ReparseData->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT; ReparseData->ReparseDataLength = ReparseDataLength; ReparseData->Reserved = 0; ReparseData->MountPointReparseBuffer.SubstituteNameOffset = 0; ReparseData->MountPointReparseBuffer.SubstituteNameLength = VolumeNameLength + BackslashLength; ReparseData->MountPointReparseBuffer.PrintNameOffset = ReparseData->MountPointReparseBuffer.SubstituteNameLength + sizeof(WCHAR); ReparseData->MountPointReparseBuffer.PrintNameLength = VolumeNameLength + BackslashLength; PathBuffer = ReparseData->MountPointReparseBuffer.PathBuffer; memcpy(PathBuffer, VolumeName, VolumeNameLength); if (BackslashLength) PathBuffer[VolumeNameLength / sizeof(WCHAR)] = L'\\'; PathBuffer[(VolumeNameLength + BackslashLength) / sizeof(WCHAR)] = L'\0'; PathBuffer = ReparseData->MountPointReparseBuffer.PathBuffer + (ReparseData->MountPointReparseBuffer.PrintNameOffset) / sizeof(WCHAR); memcpy(PathBuffer, VolumeName, VolumeNameLength); if (BackslashLength) PathBuffer[VolumeNameLength / sizeof(WCHAR)] = L'\\'; PathBuffer[(VolumeNameLength + BackslashLength) / sizeof(WCHAR)] = L'\0'; if (!DeviceIoControl(MountHandle, FSCTL_SET_REPARSE_POINT, ReparseData, REPARSE_DATA_BUFFER_HEADER_SIZE + ReparseData->ReparseDataLength, 0, 0, &Bytes, 0)) { Result = FspNtStatusFromWin32(GetLastError()); goto exit; } *PMountHandle = MountHandle; Result = STATUS_SUCCESS; exit: if (!NT_SUCCESS(Result) && INVALID_HANDLE_VALUE != MountHandle) CloseHandle(MountHandle); MemFree(ReparseData); return Result; } static NTSTATUS FspMountRemove_Directory(HANDLE MountHandle) { /* directory is marked DELETE_ON_CLOSE */ return CloseHandle(MountHandle) ? STATUS_SUCCESS : FspNtStatusFromWin32(GetLastError()); } NTSTATUS FspMountSet_Internal(FSP_MOUNT_DESC *Desc) { InitOnceExecuteOnce(&FspMountInitOnce, FspMountInitialize, 0, 0); Desc->MountHandle = 0; if (L'*' == Desc->MountPoint[0] && ':' == Desc->MountPoint[1] && L'\0' == Desc->MountPoint[2]) { NTSTATUS Result; DWORD Drives; WCHAR Drive; Drives = GetLogicalDrives(); if (0 == Drives) return FspNtStatusFromWin32(GetLastError()); for (Drive = 'Z'; 'D' <= Drive; Drive--) if (0 == (Drives & (1 << (Drive - 'A')))) { Desc->MountPoint[0] = Drive; Result = FspMountSet_Drive(Desc->VolumeName, Desc->MountPoint, &Desc->MountHandle); if (NT_SUCCESS(Result)) return Result; } Desc->MountPoint[0] = L'*'; return STATUS_NO_SUCH_DEVICE; } else if (FspPathIsMountmgrDrive(Desc->MountPoint)) return FspMountSet_MountmgrDrive(Desc->VolumeHandle, Desc->VolumeName, Desc->MountPoint); else if (FspPathIsMountmgrMountPoint(Desc->MountPoint)) return FspMountSet_MountmgrDirectory(Desc->VolumeHandle, Desc->VolumeName, Desc->MountPoint, Desc->Security, &Desc->MountHandle); else if (FspPathIsDrive(Desc->MountPoint)) return FspMountSet_Drive(Desc->VolumeName, Desc->MountPoint, &Desc->MountHandle); else return FspMountSet_Directory(Desc->VolumeName, Desc->MountPoint, Desc->Security, &Desc->MountHandle); } NTSTATUS FspMountRemove_Internal(FSP_MOUNT_DESC *Desc) { InitOnceExecuteOnce(&FspMountInitOnce, FspMountInitialize, 0, 0); if (FspPathIsMountmgrDrive(Desc->MountPoint)) return FspMountRemove_MountmgrDrive(Desc->VolumeHandle, Desc->MountPoint); else if (FspPathIsMountmgrMountPoint(Desc->MountPoint)) return FspMountRemove_MountmgrDirectory(Desc->VolumeHandle, Desc->VolumeName, Desc->MountPoint, Desc->MountHandle); else if (FspPathIsDrive(Desc->MountPoint)) return FspMountRemove_Drive(Desc->VolumeName, Desc->MountPoint, Desc->MountHandle); else return FspMountRemove_Directory(Desc->MountHandle); } FSP_API NTSTATUS FspMountSet(FSP_MOUNT_DESC *Desc) { NTSTATUS Result; BOOLEAN IsDrive; IsDrive = (L'*' == Desc->MountPoint[0] && ':' == Desc->MountPoint[1] && L'\0' == Desc->MountPoint[2]) || FspPathIsMountmgrDrive(Desc->MountPoint) || FspPathIsDrive(Desc->MountPoint); #if defined(FSP_CFG_REJECT_EARLY_IRP) /* * In the original WinFsp design the FSD could accept incoming file system requests * immediately after the in-kernel file system instance was created. Such requests would * be queued in the internal FSD queues and only delivered to the user mode file system * when its dispatcher was started and actively receiving them. * * At the same time the original WinFsp API design was that a user mode file system first * calls FspFileSystemSetMountPoint to create the file system mount point and then calls * FspFileSystemStartDispatcher to start the dispatcher. This design made sense at the time: * creating a mount point involved the creation of a symbolic link of one kind or another, * which could fail for a number of reasons (e.g. drive already exists), so there was no * point to start the dispatcher if the mounting did not succeed. Compatibility with FUSE * and the Unix mounting protocol was another consideration. * * Unfortunately this API design has proved problematic. The problem is that with the * proliferation of ways to mount a file system in WinFsp more and more system components and * third party filters may attempt to access the mount point upon its creation and before the * file system dispatcher is ready. This can result in various consequences. * * In order to properly fix this problem we should probably mandate that a user mode file * system should start its dispatcher first and then create its mountpoint. This way the user * mode file system would be ready to handle any requests from system components or third * party filters during mount point creation. Unfortunately we cannot easily do so because * of backwards compatibility. * * This problem first appeared as an incompatibility with Avast AntiVirus (GitHub issue #221). * In order to avoid backwards incompatible API changes the "Transact0" work around was * devised: when the FSD first creates an in-kernel file system it remains inoperative and any * requests posted to it will fail with STATUS_CANCELLED, until it receives a Transact0 * message (an FSP_FSCTL_TRANSACT message with 0 buffers). In order to enable this work around * a user mode file system would specify the RejectIrpPriorToTransact0 flag upon creation. * * Another instance of this problem appeared when support for directory mounting via the Mount * Manager was added: mounting would not complete unless the RejectIrpPriorToTransact0 flag * was set. At this point the RejectIrpPriorToTransact0 was hard coded to 1 in the FSD. * * However if we are creating a drive the Transact0 work around is unnecessary and perhaps * harmful. So in this case send a Transact0 message to the FSD to allow file system requests * to be queued rather than rejected with STATUS_CANCELLED. */ if (IsDrive) FspFsctlTransact(Desc->VolumeHandle, 0, 0, 0, 0, FALSE); #endif Result = FspMountSet_Internal(Desc); return Result; } FSP_API NTSTATUS FspMountRemove(FSP_MOUNT_DESC *Desc) { NTSTATUS Result; BOOLEAN IsDrive; IsDrive = FspPathIsMountmgrDrive(Desc->MountPoint) || FspPathIsDrive(Desc->MountPoint); Result = FspMountRemove_Internal(Desc); if (NT_SUCCESS(Result) && IsDrive) /* send an extra notification to remove the "ghost" drive in the shell's navigation pane */ FspMountNotifyShellDriveChange(); return Result; }