mirror of
https://github.com/winfsp/winfsp.git
synced 2025-04-22 00:13:01 -05:00
718 lines
25 KiB
C
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;
|
|
}
|