winfsp/src/dll/mount.c
2024-01-08 12:38:44 +00:00

718 lines
25 KiB
C

/**
* @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 <dll/library.h>
#include <dbt.h>
#include <shlobj.h>
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;
}