VirtualBox

source: vbox/trunk/src/VBox/Devices/Storage/HBDMgmt-win.cpp

Last change on this file was 106061, checked in by vboxsync, 3 months ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.5 KB
Line 
1/* $Id: HBDMgmt-win.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * VBox storage devices: Host block device management API.
4 */
5
6/*
7 * Copyright (C) 2015-2024 Oracle and/or its affiliates.
8 *
9 * This file is part of VirtualBox base platform packages, as
10 * available from https://www.virtualbox.org.
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation, in version 3 of the
15 * License.
16 *
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, see <https://www.gnu.org/licenses>.
24 *
25 * SPDX-License-Identifier: GPL-3.0-only
26 */
27#define LOG_GROUP LOG_GROUP_DRV_VD
28#include <VBox/cdefs.h>
29#include <VBox/err.h>
30#include <VBox/log.h>
31#include <iprt/assert.h>
32#include <iprt/string.h>
33#include <iprt/mem.h>
34#include <iprt/semaphore.h>
35#include <iprt/list.h>
36
37#include <iprt/nt/nt-and-windows.h>
38#include <iprt/win/windows.h>
39
40#include "HBDMgmt.h"
41
42
43/*********************************************************************************************************************************
44* Defined Constants And Macros *
45*********************************************************************************************************************************/
46
47
48/*********************************************************************************************************************************
49* Structures and Typedefs *
50*********************************************************************************************************************************/
51
52/**
53 * Claimed block device state.
54 */
55typedef struct HBDMGRDEV
56{
57 /** List node. */
58 RTLISTNODE ListNode;
59 /** The block device name. */
60 char *pszDevice;
61 /** Number of volumes for this block device. */
62 unsigned cVolumes;
63 /** Array of handle to the volumes for unmounting and taking it offline. */
64 HANDLE ahVolumes[1];
65} HBDMGRDEV;
66/** Pointer to a claimed block device. */
67typedef HBDMGRDEV *PHBDMGRDEV;
68
69/**
70 * Internal Host block device manager state.
71 */
72typedef struct HBDMGRINT
73{
74 /** List of claimed block devices. */
75 RTLISTANCHOR ListClaimed;
76 /** Fast mutex protecting the list. */
77 RTSEMFASTMUTEX hMtxList;
78} HBDMGRINT;
79/** Pointer to an interal block device manager state. */
80typedef HBDMGRINT *PHBDMGRINT;
81
82#define HBDMGR_NT_HARDDISK_START "\\Device\\Harddisk"
83
84
85/*********************************************************************************************************************************
86* Global Variables *
87*********************************************************************************************************************************/
88
89
90/*********************************************************************************************************************************
91* Internal Functions *
92*********************************************************************************************************************************/
93
94/**
95 * Unclaims the given block device and frees its state removing it from the list.
96 *
97 * @param pDev The block device to unclaim.
98 */
99static void hbdMgrDevUnclaim(PHBDMGRDEV pDev)
100{
101 LogFlowFunc(("pDev=%p{%s} cVolumes=%u\n", pDev, pDev->pszDevice, pDev->cVolumes));
102
103 for (unsigned i = 0; i < pDev->cVolumes; i++)
104 {
105 DWORD dwReturned = 0;
106
107 LogFlowFunc(("Taking volume %u online\n", i));
108 BOOL bRet = DeviceIoControl(pDev->ahVolumes[i], IOCTL_VOLUME_ONLINE, NULL, 0, NULL, 0, &dwReturned, NULL);
109 if (!bRet)
110 LogRel(("HBDMgmt: Failed to take claimed volume online during cleanup: %s{%Rrc}\n",
111 pDev->pszDevice, RTErrConvertFromWin32(GetLastError())));
112
113 CloseHandle(pDev->ahVolumes[i]);
114 }
115
116 RTListNodeRemove(&pDev->ListNode);
117 RTStrFree(pDev->pszDevice);
118 RTMemFree(pDev);
119}
120
121/**
122 * Returns the block device given by the filename if claimed or NULL.
123 *
124 * @returns Pointer to the claimed block device or NULL if not claimed.
125 * @param pThis The block device manager.
126 * @param pszFilename The name to look for.
127 */
128static PHBDMGRDEV hbdMgrDevFindByName(PHBDMGRINT pThis, const char *pszFilename)
129{
130 bool fFound = false;
131
132 PHBDMGRDEV pIt;
133 RTListForEach(&pThis->ListClaimed, pIt, HBDMGRDEV, ListNode)
134 {
135 if (!RTStrCmp(pszFilename, pIt->pszDevice))
136 {
137 fFound = true;
138 break;
139 }
140 }
141
142 return fFound ? pIt : NULL;
143}
144
145/**
146 * Queries the target in the NT namespace of the given symbolic link.
147 *
148 * @returns VBox status code.
149 * @param pwszLinkNt The symbolic link to query the target for.
150 * @param ppwszLinkTarget Where to store the link target in the NT namespace on success.
151 * Must be freed with RTUtf16Free().
152 */
153static int hbdMgrQueryNtLinkTarget(PRTUTF16 pwszLinkNt, PRTUTF16 *ppwszLinkTarget)
154{
155 int rc = VINF_SUCCESS;
156 HANDLE hFile = RTNT_INVALID_HANDLE_VALUE;
157 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
158 UNICODE_STRING NtName;
159
160 NtName.Buffer = (PWSTR)pwszLinkNt;
161 NtName.Length = (USHORT)(RTUtf16Len(pwszLinkNt) * sizeof(RTUTF16));
162 NtName.MaximumLength = NtName.Length + sizeof(RTUTF16);
163
164 OBJECT_ATTRIBUTES ObjAttr;
165 InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
166
167 NTSTATUS rcNt = NtOpenSymbolicLinkObject(&hFile, SYMBOLIC_LINK_QUERY, &ObjAttr);
168 if (NT_SUCCESS(rcNt))
169 {
170 UNICODE_STRING UniStr;
171 RTUTF16 awszBuf[1024];
172 RT_ZERO(awszBuf);
173 UniStr.Buffer = awszBuf;
174 UniStr.MaximumLength = sizeof(awszBuf);
175 rcNt = NtQuerySymbolicLinkObject(hFile, &UniStr, NULL);
176 if (NT_SUCCESS(rcNt))
177 {
178 *ppwszLinkTarget = RTUtf16Dup((PRTUTF16)UniStr.Buffer);
179 if (!*ppwszLinkTarget)
180 rc = VERR_NO_STR_MEMORY;
181 }
182 else
183 rc = RTErrConvertFromNtStatus(rcNt);
184
185 CloseHandle(hFile);
186 }
187 else
188 rc = RTErrConvertFromNtStatus(rcNt);
189
190 return rc;
191}
192
193/**
194 * Queries the harddisk volume device in the NT namespace for the given Win32
195 * block device path.
196 *
197 * @returns VBox status code.
198 * @param pwszDriveWin32 The Win32 path to the block device (e.g. "\\.\PhysicalDrive0" for example)
199 * @param ppwszDriveNt Where to store the NT path to the volume on success.
200 * Must be freed with RTUtf16Free().
201 */
202static int hbdMgrQueryNtName(PRTUTF16 pwszDriveWin32, PRTUTF16 *ppwszDriveNt)
203{
204 int rc = VINF_SUCCESS;
205 RTUTF16 awszFileNt[1024];
206
207 /*
208 * Make sure the path is at least 5 characters long so we can safely access
209 * the part following \\.\ below.
210 */
211 AssertReturn(RTUtf16Len(pwszDriveWin32) >= 5, VERR_INVALID_STATE);
212
213 RT_ZERO(awszFileNt);
214 RTUtf16CatAscii(awszFileNt, RT_ELEMENTS(awszFileNt), "\\??\\");
215 RTUtf16Cat(awszFileNt, RT_ELEMENTS(awszFileNt), &pwszDriveWin32[4]);
216
217 /* Make sure there is no trailing \ at the end or we will fail. */
218 size_t cwcPath = RTUtf16Len(awszFileNt);
219 if (awszFileNt[cwcPath - 1] == L'\\')
220 awszFileNt[cwcPath - 1] = L'\0';
221
222 return hbdMgrQueryNtLinkTarget(awszFileNt, ppwszDriveNt);
223}
224
225static int hbdMgrQueryAllMountpointsForDisk(PRTUTF16 pwszDiskNt, PRTUTF16 **ppapwszVolumes,
226 unsigned *pcVolumes)
227{
228 /*
229 * Try to get the symlink target for every partition, we will take the easy approach
230 * and just try to open every partition starting with \Device\Harddisk<N>\Partition1
231 * (0 is a special reserved partition linking to the complete disk).
232 *
233 * For every partition we get the target \Device\HarddiskVolume<N> and query all mountpoints
234 * with that.
235 */
236 int rc = VINF_SUCCESS;
237 char *pszDiskNt = NULL;
238 unsigned cVolumes = 0;
239 unsigned cVolumesMax = 10;
240 PRTUTF16 *papwszVolumes = (PRTUTF16 *)RTMemAllocZ(cVolumesMax * sizeof(PRTUTF16));
241
242 if (!papwszVolumes)
243 return VERR_NO_MEMORY;
244
245 rc = RTUtf16ToUtf8(pwszDiskNt, &pszDiskNt);
246 if (RT_SUCCESS(rc))
247 {
248 /* Check that the path matches our expectation \Device\Harddisk<N>\DR<N>. */
249 if (!RTStrNCmp(pszDiskNt, HBDMGR_NT_HARDDISK_START, sizeof(HBDMGR_NT_HARDDISK_START) - 1))
250 {
251 uint32_t iDisk = 0;
252
253 rc = RTStrToUInt32Ex(pszDiskNt + sizeof(HBDMGR_NT_HARDDISK_START) - 1, NULL, 10, &iDisk);
254 if (RT_SUCCESS(rc) || rc == VWRN_TRAILING_CHARS)
255 {
256 uint32_t iPart = 1;
257
258 /* Try to query all mount points for all partitions, the simple way. */
259 do
260 {
261 char aszDisk[1024];
262 RT_ZERO(aszDisk);
263
264 size_t cchWritten = RTStrPrintf(&aszDisk[0], sizeof(aszDisk), "\\Device\\Harddisk%u\\Partition%u", iDisk, iPart);
265 if (cchWritten < sizeof(aszDisk))
266 {
267 PRTUTF16 pwszDisk = NULL;
268 rc = RTStrToUtf16(&aszDisk[0], &pwszDisk);
269 if (RT_SUCCESS(rc))
270 {
271 PRTUTF16 pwszTargetNt = NULL;
272
273 rc = hbdMgrQueryNtLinkTarget(pwszDisk, &pwszTargetNt);
274 if (RT_SUCCESS(rc))
275 {
276 if (cVolumes == cVolumesMax)
277 {
278 /* Increase array of volumes. */
279 PRTUTF16 *papwszVolumesNew = (PRTUTF16 *)RTMemAllocZ((cVolumesMax + 10) * sizeof(PRTUTF16));
280 if (papwszVolumesNew)
281 {
282 cVolumesMax += 10;
283 papwszVolumes = papwszVolumesNew;
284 }
285 else
286 {
287 RTUtf16Free(pwszTargetNt);
288 rc = VERR_NO_MEMORY;
289 }
290 }
291
292 if (RT_SUCCESS(rc))
293 {
294 Assert(cVolumes < cVolumesMax);
295 papwszVolumes[cVolumes++] = pwszTargetNt;
296 iPart++;
297 }
298 }
299 else if (rc == VERR_FILE_NOT_FOUND)
300 {
301 /* The partition does not exist, so stop trying. */
302 rc = VINF_SUCCESS;
303 break;
304 }
305
306 RTUtf16Free(pwszDisk);
307 }
308 }
309 else
310 rc = VERR_BUFFER_OVERFLOW;
311
312 } while (RT_SUCCESS(rc));
313 }
314 }
315 else
316 rc = VERR_INVALID_STATE;
317
318 RTStrFree(pszDiskNt);
319 }
320
321 if (RT_SUCCESS(rc))
322 {
323 *pcVolumes = cVolumes;
324 *ppapwszVolumes = papwszVolumes;
325 LogFlowFunc(("rc=%Rrc cVolumes=%u ppapwszVolumes=%p\n", rc, cVolumes, papwszVolumes));
326 }
327 else
328 {
329 for (unsigned i = 0; i < cVolumes; i++)
330 RTUtf16Free(papwszVolumes[i]);
331
332 RTMemFree(papwszVolumes);
333 }
334
335 return rc;
336}
337
338static NTSTATUS hbdMgrNtCreateFileWrapper(PRTUTF16 pwszVolume, HANDLE *phVolume)
339{
340 HANDLE hVolume = RTNT_INVALID_HANDLE_VALUE;
341 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
342 UNICODE_STRING NtName;
343
344 NtName.Buffer = (PWSTR)pwszVolume;
345 NtName.Length = (USHORT)(RTUtf16Len(pwszVolume) * sizeof(RTUTF16));
346 NtName.MaximumLength = NtName.Length + sizeof(WCHAR);
347
348 OBJECT_ATTRIBUTES ObjAttr;
349 InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
350
351 NTSTATUS rcNt = NtCreateFile(&hVolume,
352 FILE_READ_DATA | FILE_WRITE_DATA | SYNCHRONIZE,
353 &ObjAttr,
354 &Ios,
355 NULL /* Allocation Size*/,
356 FILE_ATTRIBUTE_NORMAL,
357 FILE_SHARE_READ | FILE_SHARE_WRITE,
358 FILE_OPEN,
359 FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT,
360 NULL /*EaBuffer*/,
361 0 /*EaLength*/);
362 if (NT_SUCCESS(rcNt))
363 rcNt = Ios.Status;
364
365 if (NT_SUCCESS(rcNt))
366 *phVolume = hVolume;
367
368 return rcNt;
369}
370
371DECLHIDDEN(int) HBDMgrCreate(PHBDMGR phHbdMgr)
372{
373 AssertPtrReturn(phHbdMgr, VERR_INVALID_POINTER);
374
375 PHBDMGRINT pThis = (PHBDMGRINT)RTMemAllocZ(sizeof(HBDMGRINT));
376 if (RT_UNLIKELY(!pThis))
377 return VERR_NO_MEMORY;
378
379 int rc = VINF_SUCCESS;
380 RTListInit(&pThis->ListClaimed);
381
382 rc = RTSemFastMutexCreate(&pThis->hMtxList);
383 if (RT_SUCCESS(rc))
384 {
385 *phHbdMgr = pThis;
386 return VINF_SUCCESS;
387 }
388
389 RTMemFree(pThis);
390 return rc;
391}
392
393DECLHIDDEN(void) HBDMgrDestroy(HBDMGR hHbdMgr)
394{
395 PHBDMGRINT pThis = hHbdMgr;
396 AssertPtrReturnVoid(pThis);
397
398 /* Go through all claimed block devices and release them. */
399 RTSemFastMutexRequest(pThis->hMtxList);
400 PHBDMGRDEV pIt, pItNext;
401 RTListForEachSafe(&pThis->ListClaimed, pIt, pItNext, HBDMGRDEV, ListNode)
402 {
403 hbdMgrDevUnclaim(pIt);
404 }
405 RTSemFastMutexRelease(pThis->hMtxList);
406
407 RTSemFastMutexDestroy(pThis->hMtxList);
408 RTMemFree(pThis);
409}
410
411DECLHIDDEN(bool) HBDMgrIsBlockDevice(const char *pszFilename)
412{
413 bool fIsBlockDevice = RTStrNICmp(pszFilename, "\\\\.\\PhysicalDrive", sizeof("\\\\.\\PhysicalDrive") - 1) == 0 ? true : false;
414 if (!fIsBlockDevice)
415 fIsBlockDevice = RTStrNICmp(pszFilename, "\\\\.\\Harddisk", sizeof("\\\\.\\Harddisk") - 1) == 0 ? true : false;
416
417 LogFlowFunc(("returns %s -> %RTbool\n", pszFilename, fIsBlockDevice));
418 return fIsBlockDevice;
419}
420
421DECLHIDDEN(int) HBDMgrClaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
422{
423 PHBDMGRINT pThis = hHbdMgr;
424 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
425 AssertReturn(HBDMgrIsBlockDevice(pszFilename), VERR_INVALID_PARAMETER);
426
427 int rc = VINF_SUCCESS;
428 PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
429 if (!pDev)
430 {
431 PRTUTF16 pwszVolume = NULL;
432
433 rc = RTStrToUtf16(pszFilename, &pwszVolume);
434 if (RT_SUCCESS(rc))
435 {
436 PRTUTF16 pwszVolNt = NULL;
437
438 rc = hbdMgrQueryNtName(pwszVolume, &pwszVolNt);
439 if (RT_SUCCESS(rc))
440 {
441 PRTUTF16 *papwszVolumes = NULL;
442 unsigned cVolumes = 0;
443
444 /* Complete disks need to be handled differently. */
445 if (!RTStrNCmp(pszFilename, "\\\\.\\PhysicalDrive", sizeof("\\\\.\\PhysicalDrive") - 1))
446 {
447 rc = hbdMgrQueryAllMountpointsForDisk(pwszVolNt, &papwszVolumes, &cVolumes);
448 RTUtf16Free(pwszVolNt);
449 }
450 else
451 {
452 papwszVolumes = &pwszVolNt;
453 cVolumes = 1;
454 }
455
456 if (RT_SUCCESS(rc))
457 {
458#ifdef LOG_ENABLED
459 for (unsigned i = 0; i < cVolumes; i++)
460 LogFlowFunc(("Volume %u: %ls\n", i, papwszVolumes[i]));
461#endif
462 pDev = (PHBDMGRDEV)RTMemAllocZ(RT_UOFFSETOF_DYN(HBDMGRDEV, ahVolumes[cVolumes]));
463 if (pDev)
464 {
465 pDev->cVolumes = 0;
466 pDev->pszDevice = RTStrDup(pszFilename);
467 if (pDev->pszDevice)
468 {
469 for (unsigned i = 0; i < cVolumes; i++)
470 {
471 HANDLE hVolume;
472
473 NTSTATUS rcNt = hbdMgrNtCreateFileWrapper(papwszVolumes[i], &hVolume);
474 if (NT_SUCCESS(rcNt))
475 {
476 DWORD dwReturned = 0;
477
478 Assert(hVolume != INVALID_HANDLE_VALUE);
479 BOOL bRet = DeviceIoControl(hVolume, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &dwReturned, NULL);
480 if (bRet)
481 {
482 bRet = DeviceIoControl(hVolume, IOCTL_VOLUME_OFFLINE, NULL, 0, NULL, 0, &dwReturned, NULL);
483 if (bRet)
484 pDev->ahVolumes[pDev->cVolumes++] = hVolume;
485 else
486 rc = RTErrConvertFromWin32(GetLastError());
487 }
488 else
489 rc = RTErrConvertFromWin32(GetLastError());
490
491 if (RT_FAILURE(rc))
492 CloseHandle(hVolume);
493 }
494 else
495 rc = RTErrConvertFromNtStatus(rcNt);
496 }
497 }
498 else
499 rc = VERR_NO_STR_MEMORY;
500
501 if (RT_SUCCESS(rc))
502 {
503 RTSemFastMutexRequest(pThis->hMtxList);
504 RTListAppend(&pThis->ListClaimed, &pDev->ListNode);
505 RTSemFastMutexRelease(pThis->hMtxList);
506 }
507 else
508 {
509 /* Close all open handles and take the volumes online again. */
510 for (unsigned i = 0; i < pDev->cVolumes; i++)
511 {
512 DWORD dwReturned = 0;
513 BOOL bRet = DeviceIoControl(pDev->ahVolumes[i], IOCTL_VOLUME_ONLINE, NULL, 0, NULL, 0, &dwReturned, NULL);
514 if (!bRet)
515 LogRel(("HBDMgmt: Failed to take claimed volume online during cleanup: %s{%Rrc}\n",
516 pDev->pszDevice, RTErrConvertFromWin32(GetLastError())));
517
518 CloseHandle(pDev->ahVolumes[i]);
519 }
520 if (pDev->pszDevice)
521 RTStrFree(pDev->pszDevice);
522 RTMemFree(pDev);
523 }
524 }
525 else
526 rc = VERR_NO_MEMORY;
527
528 for (unsigned i = 0; i < cVolumes; i++)
529 RTUtf16Free(papwszVolumes[i]);
530
531 RTMemFree(papwszVolumes);
532 }
533 }
534
535 RTUtf16Free(pwszVolume);
536 }
537 }
538 else
539 rc = VERR_ALREADY_EXISTS;
540
541 return rc;
542}
543
544DECLHIDDEN(int) HBDMgrUnclaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
545{
546 PHBDMGRINT pThis = hHbdMgr;
547 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
548
549 RTSemFastMutexRequest(pThis->hMtxList);
550 int rc = VINF_SUCCESS;
551 PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
552 if (pDev)
553 hbdMgrDevUnclaim(pDev);
554 else
555 rc = VERR_NOT_FOUND;
556 RTSemFastMutexRelease(pThis->hMtxList);
557
558 return rc;
559}
560
561DECLHIDDEN(bool) HBDMgrIsBlockDeviceClaimed(HBDMGR hHbdMgr, const char *pszFilename)
562{
563 PHBDMGRINT pThis = hHbdMgr;
564 AssertPtrReturn(pThis, false);
565
566 RTSemFastMutexRequest(pThis->hMtxList);
567 PHBDMGRDEV pIt = hbdMgrDevFindByName(pThis, pszFilename);
568 RTSemFastMutexRelease(pThis->hMtxList);
569
570 return pIt ? true : false;
571}
572
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette