summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Storage/HBDMgmt-win.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/Storage/HBDMgmt-win.cpp')
-rw-r--r--src/VBox/Devices/Storage/HBDMgmt-win.cpp572
1 files changed, 572 insertions, 0 deletions
diff --git a/src/VBox/Devices/Storage/HBDMgmt-win.cpp b/src/VBox/Devices/Storage/HBDMgmt-win.cpp
new file mode 100644
index 00000000..4fa44217
--- /dev/null
+++ b/src/VBox/Devices/Storage/HBDMgmt-win.cpp
@@ -0,0 +1,572 @@
+/* $Id: HBDMgmt-win.cpp $ */
+/** @file
+ * VBox storage devices: Host block device management API.
+ */
+
+/*
+ * Copyright (C) 2015-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+#define LOG_GROUP LOG_GROUP_DRV_VD
+#include <VBox/cdefs.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/mem.h>
+#include <iprt/semaphore.h>
+#include <iprt/list.h>
+
+#include <iprt/nt/nt-and-windows.h>
+#include <iprt/win/windows.h>
+
+#include "HBDMgmt.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Claimed block device state.
+ */
+typedef struct HBDMGRDEV
+{
+ /** List node. */
+ RTLISTNODE ListNode;
+ /** The block device name. */
+ char *pszDevice;
+ /** Number of volumes for this block device. */
+ unsigned cVolumes;
+ /** Array of handle to the volumes for unmounting and taking it offline. */
+ HANDLE ahVolumes[1];
+} HBDMGRDEV;
+/** Pointer to a claimed block device. */
+typedef HBDMGRDEV *PHBDMGRDEV;
+
+/**
+ * Internal Host block device manager state.
+ */
+typedef struct HBDMGRINT
+{
+ /** List of claimed block devices. */
+ RTLISTANCHOR ListClaimed;
+ /** Fast mutex protecting the list. */
+ RTSEMFASTMUTEX hMtxList;
+} HBDMGRINT;
+/** Pointer to an interal block device manager state. */
+typedef HBDMGRINT *PHBDMGRINT;
+
+#define HBDMGR_NT_HARDDISK_START "\\Device\\Harddisk"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Unclaims the given block device and frees its state removing it from the list.
+ *
+ * @param pDev The block device to unclaim.
+ */
+static void hbdMgrDevUnclaim(PHBDMGRDEV pDev)
+{
+ LogFlowFunc(("pDev=%p{%s} cVolumes=%u\n", pDev, pDev->pszDevice, pDev->cVolumes));
+
+ for (unsigned i = 0; i < pDev->cVolumes; i++)
+ {
+ DWORD dwReturned = 0;
+
+ LogFlowFunc(("Taking volume %u online\n", i));
+ BOOL bRet = DeviceIoControl(pDev->ahVolumes[i], IOCTL_VOLUME_ONLINE, NULL, 0, NULL, 0, &dwReturned, NULL);
+ if (!bRet)
+ LogRel(("HBDMgmt: Failed to take claimed volume online during cleanup: %s{%Rrc}\n",
+ pDev->pszDevice, RTErrConvertFromWin32(GetLastError())));
+
+ CloseHandle(pDev->ahVolumes[i]);
+ }
+
+ RTListNodeRemove(&pDev->ListNode);
+ RTStrFree(pDev->pszDevice);
+ RTMemFree(pDev);
+}
+
+/**
+ * Returns the block device given by the filename if claimed or NULL.
+ *
+ * @returns Pointer to the claimed block device or NULL if not claimed.
+ * @param pThis The block device manager.
+ * @param pszFilename The name to look for.
+ */
+static PHBDMGRDEV hbdMgrDevFindByName(PHBDMGRINT pThis, const char *pszFilename)
+{
+ bool fFound = false;
+
+ PHBDMGRDEV pIt;
+ RTListForEach(&pThis->ListClaimed, pIt, HBDMGRDEV, ListNode)
+ {
+ if (!RTStrCmp(pszFilename, pIt->pszDevice))
+ {
+ fFound = true;
+ break;
+ }
+ }
+
+ return fFound ? pIt : NULL;
+}
+
+/**
+ * Queries the target in the NT namespace of the given symbolic link.
+ *
+ * @returns VBox status code.
+ * @param pwszLinkNt The symbolic link to query the target for.
+ * @param ppwszLinkTarget Where to store the link target in the NT namespace on success.
+ * Must be freed with RTUtf16Free().
+ */
+static int hbdMgrQueryNtLinkTarget(PRTUTF16 pwszLinkNt, PRTUTF16 *ppwszLinkTarget)
+{
+ int rc = VINF_SUCCESS;
+ HANDLE hFile = RTNT_INVALID_HANDLE_VALUE;
+ IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
+ UNICODE_STRING NtName;
+
+ NtName.Buffer = (PWSTR)pwszLinkNt;
+ NtName.Length = (USHORT)(RTUtf16Len(pwszLinkNt) * sizeof(RTUTF16));
+ NtName.MaximumLength = NtName.Length + sizeof(RTUTF16);
+
+ OBJECT_ATTRIBUTES ObjAttr;
+ InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
+
+ NTSTATUS rcNt = NtOpenSymbolicLinkObject(&hFile, SYMBOLIC_LINK_QUERY, &ObjAttr);
+ if (NT_SUCCESS(rcNt))
+ {
+ UNICODE_STRING UniStr;
+ RTUTF16 awszBuf[1024];
+ RT_ZERO(awszBuf);
+ UniStr.Buffer = awszBuf;
+ UniStr.MaximumLength = sizeof(awszBuf);
+ rcNt = NtQuerySymbolicLinkObject(hFile, &UniStr, NULL);
+ if (NT_SUCCESS(rcNt))
+ {
+ *ppwszLinkTarget = RTUtf16Dup((PRTUTF16)UniStr.Buffer);
+ if (!*ppwszLinkTarget)
+ rc = VERR_NO_STR_MEMORY;
+ }
+ else
+ rc = RTErrConvertFromNtStatus(rcNt);
+
+ CloseHandle(hFile);
+ }
+ else
+ rc = RTErrConvertFromNtStatus(rcNt);
+
+ return rc;
+}
+
+/**
+ * Queries the harddisk volume device in the NT namespace for the given Win32
+ * block device path.
+ *
+ * @returns VBox status code.
+ * @param pwszDriveWin32 The Win32 path to the block device (e.g. "\\.\PhysicalDrive0" for example)
+ * @param ppwszDriveNt Where to store the NT path to the volume on success.
+ * Must be freed with RTUtf16Free().
+ */
+static int hbdMgrQueryNtName(PRTUTF16 pwszDriveWin32, PRTUTF16 *ppwszDriveNt)
+{
+ int rc = VINF_SUCCESS;
+ RTUTF16 awszFileNt[1024];
+
+ /*
+ * Make sure the path is at least 5 characters long so we can safely access
+ * the part following \\.\ below.
+ */
+ AssertReturn(RTUtf16Len(pwszDriveWin32) >= 5, VERR_INVALID_STATE);
+
+ RT_ZERO(awszFileNt);
+ RTUtf16CatAscii(awszFileNt, RT_ELEMENTS(awszFileNt), "\\??\\");
+ RTUtf16Cat(awszFileNt, RT_ELEMENTS(awszFileNt), &pwszDriveWin32[4]);
+
+ /* Make sure there is no trailing \ at the end or we will fail. */
+ size_t cwcPath = RTUtf16Len(awszFileNt);
+ if (awszFileNt[cwcPath - 1] == L'\\')
+ awszFileNt[cwcPath - 1] = L'\0';
+
+ return hbdMgrQueryNtLinkTarget(awszFileNt, ppwszDriveNt);
+}
+
+static int hbdMgrQueryAllMountpointsForDisk(PRTUTF16 pwszDiskNt, PRTUTF16 **ppapwszVolumes,
+ unsigned *pcVolumes)
+{
+ /*
+ * Try to get the symlink target for every partition, we will take the easy approach
+ * and just try to open every partition starting with \Device\Harddisk<N>\Partition1
+ * (0 is a special reserved partition linking to the complete disk).
+ *
+ * For every partition we get the target \Device\HarddiskVolume<N> and query all mountpoints
+ * with that.
+ */
+ int rc = VINF_SUCCESS;
+ char *pszDiskNt = NULL;
+ unsigned cVolumes = 0;
+ unsigned cVolumesMax = 10;
+ PRTUTF16 *papwszVolumes = (PRTUTF16 *)RTMemAllocZ(cVolumesMax * sizeof(PRTUTF16));
+
+ if (!papwszVolumes)
+ return VERR_NO_MEMORY;
+
+ rc = RTUtf16ToUtf8(pwszDiskNt, &pszDiskNt);
+ if (RT_SUCCESS(rc))
+ {
+ /* Check that the path matches our expectation \Device\Harddisk<N>\DR<N>. */
+ if (!RTStrNCmp(pszDiskNt, HBDMGR_NT_HARDDISK_START, sizeof(HBDMGR_NT_HARDDISK_START) - 1))
+ {
+ uint32_t iDisk = 0;
+
+ rc = RTStrToUInt32Ex(pszDiskNt + sizeof(HBDMGR_NT_HARDDISK_START) - 1, NULL, 10, &iDisk);
+ if (RT_SUCCESS(rc) || rc == VWRN_TRAILING_CHARS)
+ {
+ uint32_t iPart = 1;
+
+ /* Try to query all mount points for all partitions, the simple way. */
+ do
+ {
+ char aszDisk[1024];
+ RT_ZERO(aszDisk);
+
+ size_t cchWritten = RTStrPrintf(&aszDisk[0], sizeof(aszDisk), "\\Device\\Harddisk%u\\Partition%u", iDisk, iPart);
+ if (cchWritten < sizeof(aszDisk))
+ {
+ PRTUTF16 pwszDisk = NULL;
+ rc = RTStrToUtf16(&aszDisk[0], &pwszDisk);
+ if (RT_SUCCESS(rc))
+ {
+ PRTUTF16 pwszTargetNt = NULL;
+
+ rc = hbdMgrQueryNtLinkTarget(pwszDisk, &pwszTargetNt);
+ if (RT_SUCCESS(rc))
+ {
+ if (cVolumes == cVolumesMax)
+ {
+ /* Increase array of volumes. */
+ PRTUTF16 *papwszVolumesNew = (PRTUTF16 *)RTMemAllocZ((cVolumesMax + 10) * sizeof(PRTUTF16));
+ if (papwszVolumesNew)
+ {
+ cVolumesMax += 10;
+ papwszVolumes = papwszVolumesNew;
+ }
+ else
+ {
+ RTUtf16Free(pwszTargetNt);
+ rc = VERR_NO_MEMORY;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ Assert(cVolumes < cVolumesMax);
+ papwszVolumes[cVolumes++] = pwszTargetNt;
+ iPart++;
+ }
+ }
+ else if (rc == VERR_FILE_NOT_FOUND)
+ {
+ /* The partition does not exist, so stop trying. */
+ rc = VINF_SUCCESS;
+ break;
+ }
+
+ RTUtf16Free(pwszDisk);
+ }
+ }
+ else
+ rc = VERR_BUFFER_OVERFLOW;
+
+ } while (RT_SUCCESS(rc));
+ }
+ }
+ else
+ rc = VERR_INVALID_STATE;
+
+ RTStrFree(pszDiskNt);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ *pcVolumes = cVolumes;
+ *ppapwszVolumes = papwszVolumes;
+ LogFlowFunc(("rc=%Rrc cVolumes=%u ppapwszVolumes=%p\n", rc, cVolumes, papwszVolumes));
+ }
+ else
+ {
+ for (unsigned i = 0; i < cVolumes; i++)
+ RTUtf16Free(papwszVolumes[i]);
+
+ RTMemFree(papwszVolumes);
+ }
+
+ return rc;
+}
+
+static NTSTATUS hbdMgrNtCreateFileWrapper(PRTUTF16 pwszVolume, HANDLE *phVolume)
+{
+ HANDLE hVolume = RTNT_INVALID_HANDLE_VALUE;
+ IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
+ UNICODE_STRING NtName;
+
+ NtName.Buffer = (PWSTR)pwszVolume;
+ NtName.Length = (USHORT)(RTUtf16Len(pwszVolume) * sizeof(RTUTF16));
+ NtName.MaximumLength = NtName.Length + sizeof(WCHAR);
+
+ OBJECT_ATTRIBUTES ObjAttr;
+ InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
+
+ NTSTATUS rcNt = NtCreateFile(&hVolume,
+ FILE_READ_DATA | FILE_WRITE_DATA | SYNCHRONIZE,
+ &ObjAttr,
+ &Ios,
+ NULL /* Allocation Size*/,
+ FILE_ATTRIBUTE_NORMAL,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ FILE_OPEN,
+ FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT,
+ NULL /*EaBuffer*/,
+ 0 /*EaLength*/);
+ if (NT_SUCCESS(rcNt))
+ rcNt = Ios.Status;
+
+ if (NT_SUCCESS(rcNt))
+ *phVolume = hVolume;
+
+ return rcNt;
+}
+
+DECLHIDDEN(int) HBDMgrCreate(PHBDMGR phHbdMgr)
+{
+ AssertPtrReturn(phHbdMgr, VERR_INVALID_POINTER);
+
+ PHBDMGRINT pThis = (PHBDMGRINT)RTMemAllocZ(sizeof(HBDMGRINT));
+ if (RT_UNLIKELY(!pThis))
+ return VERR_NO_MEMORY;
+
+ int rc = VINF_SUCCESS;
+ RTListInit(&pThis->ListClaimed);
+
+ rc = RTSemFastMutexCreate(&pThis->hMtxList);
+ if (RT_SUCCESS(rc))
+ {
+ *phHbdMgr = pThis;
+ return VINF_SUCCESS;
+ }
+
+ RTMemFree(pThis);
+ return rc;
+}
+
+DECLHIDDEN(void) HBDMgrDestroy(HBDMGR hHbdMgr)
+{
+ PHBDMGRINT pThis = hHbdMgr;
+ AssertPtrReturnVoid(pThis);
+
+ /* Go through all claimed block devices and release them. */
+ RTSemFastMutexRequest(pThis->hMtxList);
+ PHBDMGRDEV pIt, pItNext;
+ RTListForEachSafe(&pThis->ListClaimed, pIt, pItNext, HBDMGRDEV, ListNode)
+ {
+ hbdMgrDevUnclaim(pIt);
+ }
+ RTSemFastMutexRelease(pThis->hMtxList);
+
+ RTSemFastMutexDestroy(pThis->hMtxList);
+ RTMemFree(pThis);
+}
+
+DECLHIDDEN(bool) HBDMgrIsBlockDevice(const char *pszFilename)
+{
+ bool fIsBlockDevice = RTStrNICmp(pszFilename, "\\\\.\\PhysicalDrive", sizeof("\\\\.\\PhysicalDrive") - 1) == 0 ? true : false;
+ if (!fIsBlockDevice)
+ fIsBlockDevice = RTStrNICmp(pszFilename, "\\\\.\\Harddisk", sizeof("\\\\.\\Harddisk") - 1) == 0 ? true : false;
+
+ LogFlowFunc(("returns %s -> %RTbool\n", pszFilename, fIsBlockDevice));
+ return fIsBlockDevice;
+}
+
+DECLHIDDEN(int) HBDMgrClaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
+{
+ PHBDMGRINT pThis = hHbdMgr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+ AssertReturn(HBDMgrIsBlockDevice(pszFilename), VERR_INVALID_PARAMETER);
+
+ int rc = VINF_SUCCESS;
+ PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
+ if (!pDev)
+ {
+ PRTUTF16 pwszVolume = NULL;
+
+ rc = RTStrToUtf16(pszFilename, &pwszVolume);
+ if (RT_SUCCESS(rc))
+ {
+ PRTUTF16 pwszVolNt = NULL;
+
+ rc = hbdMgrQueryNtName(pwszVolume, &pwszVolNt);
+ if (RT_SUCCESS(rc))
+ {
+ PRTUTF16 *papwszVolumes = NULL;
+ unsigned cVolumes = 0;
+
+ /* Complete disks need to be handled differently. */
+ if (!RTStrNCmp(pszFilename, "\\\\.\\PhysicalDrive", sizeof("\\\\.\\PhysicalDrive") - 1))
+ {
+ rc = hbdMgrQueryAllMountpointsForDisk(pwszVolNt, &papwszVolumes, &cVolumes);
+ RTUtf16Free(pwszVolNt);
+ }
+ else
+ {
+ papwszVolumes = &pwszVolNt;
+ cVolumes = 1;
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+#ifdef LOG_ENABLED
+ for (unsigned i = 0; i < cVolumes; i++)
+ LogFlowFunc(("Volume %u: %ls\n", i, papwszVolumes[i]));
+#endif
+ pDev = (PHBDMGRDEV)RTMemAllocZ(RT_UOFFSETOF_DYN(HBDMGRDEV, ahVolumes[cVolumes]));
+ if (pDev)
+ {
+ pDev->cVolumes = 0;
+ pDev->pszDevice = RTStrDup(pszFilename);
+ if (pDev->pszDevice)
+ {
+ for (unsigned i = 0; i < cVolumes; i++)
+ {
+ HANDLE hVolume;
+
+ NTSTATUS rcNt = hbdMgrNtCreateFileWrapper(papwszVolumes[i], &hVolume);
+ if (NT_SUCCESS(rcNt))
+ {
+ DWORD dwReturned = 0;
+
+ Assert(hVolume != INVALID_HANDLE_VALUE);
+ BOOL bRet = DeviceIoControl(hVolume, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &dwReturned, NULL);
+ if (bRet)
+ {
+ bRet = DeviceIoControl(hVolume, IOCTL_VOLUME_OFFLINE, NULL, 0, NULL, 0, &dwReturned, NULL);
+ if (bRet)
+ pDev->ahVolumes[pDev->cVolumes++] = hVolume;
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+ }
+ else
+ rc = RTErrConvertFromWin32(GetLastError());
+
+ if (RT_FAILURE(rc))
+ CloseHandle(hVolume);
+ }
+ else
+ rc = RTErrConvertFromNtStatus(rcNt);
+ }
+ }
+ else
+ rc = VERR_NO_STR_MEMORY;
+
+ if (RT_SUCCESS(rc))
+ {
+ RTSemFastMutexRequest(pThis->hMtxList);
+ RTListAppend(&pThis->ListClaimed, &pDev->ListNode);
+ RTSemFastMutexRelease(pThis->hMtxList);
+ }
+ else
+ {
+ /* Close all open handles and take the volumes online again. */
+ for (unsigned i = 0; i < pDev->cVolumes; i++)
+ {
+ DWORD dwReturned = 0;
+ BOOL bRet = DeviceIoControl(pDev->ahVolumes[i], IOCTL_VOLUME_ONLINE, NULL, 0, NULL, 0, &dwReturned, NULL);
+ if (!bRet)
+ LogRel(("HBDMgmt: Failed to take claimed volume online during cleanup: %s{%Rrc}\n",
+ pDev->pszDevice, RTErrConvertFromWin32(GetLastError())));
+
+ CloseHandle(pDev->ahVolumes[i]);
+ }
+ if (pDev->pszDevice)
+ RTStrFree(pDev->pszDevice);
+ RTMemFree(pDev);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ for (unsigned i = 0; i < cVolumes; i++)
+ RTUtf16Free(papwszVolumes[i]);
+
+ RTMemFree(papwszVolumes);
+ }
+ }
+
+ RTUtf16Free(pwszVolume);
+ }
+ }
+ else
+ rc = VERR_ALREADY_EXISTS;
+
+ return rc;
+}
+
+DECLHIDDEN(int) HBDMgrUnclaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
+{
+ PHBDMGRINT pThis = hHbdMgr;
+ AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
+
+ RTSemFastMutexRequest(pThis->hMtxList);
+ int rc = VINF_SUCCESS;
+ PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
+ if (pDev)
+ hbdMgrDevUnclaim(pDev);
+ else
+ rc = VERR_NOT_FOUND;
+ RTSemFastMutexRelease(pThis->hMtxList);
+
+ return rc;
+}
+
+DECLHIDDEN(bool) HBDMgrIsBlockDeviceClaimed(HBDMGR hHbdMgr, const char *pszFilename)
+{
+ PHBDMGRINT pThis = hHbdMgr;
+ AssertPtrReturn(pThis, false);
+
+ RTSemFastMutexRequest(pThis->hMtxList);
+ PHBDMGRDEV pIt = hbdMgrDevFindByName(pThis, pszFilename);
+ RTSemFastMutexRelease(pThis->hMtxList);
+
+ return pIt ? true : false;
+}
+