VirtualBox

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

Last change on this file since 98110 was 98103, checked in by vboxsync, 23 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 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * VBox storage devices: Host block device management API.
4 */
5
6/*
7 * Copyright (C) 2015-2023 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 * @returns nothing.
98 * @param pDev The block device to unclaim.
99 */
100static void hbdMgrDevUnclaim(PHBDMGRDEV pDev)
101{
102 LogFlowFunc(("pDev=%p{%s} cVolumes=%u\n", pDev, pDev->pszDevice, pDev->cVolumes));
103
104 for (unsigned i = 0; i < pDev->cVolumes; i++)
105 {
106 DWORD dwReturned = 0;
107
108 LogFlowFunc(("Taking volume %u online\n", i));
109 BOOL bRet = DeviceIoControl(pDev->ahVolumes[i], IOCTL_VOLUME_ONLINE, NULL, 0, NULL, 0, &dwReturned, NULL);
110 if (!bRet)
111 LogRel(("HBDMgmt: Failed to take claimed volume online during cleanup: %s{%Rrc}\n",
112 pDev->pszDevice, RTErrConvertFromWin32(GetLastError())));
113
114 CloseHandle(pDev->ahVolumes[i]);
115 }
116
117 RTListNodeRemove(&pDev->ListNode);
118 RTStrFree(pDev->pszDevice);
119 RTMemFree(pDev);
120}
121
122/**
123 * Returns the block device given by the filename if claimed or NULL.
124 *
125 * @returns Pointer to the claimed block device or NULL if not claimed.
126 * @param pThis The block device manager.
127 * @param pszFilename The name to look for.
128 */
129static PHBDMGRDEV hbdMgrDevFindByName(PHBDMGRINT pThis, const char *pszFilename)
130{
131 bool fFound = false;
132
133 PHBDMGRDEV pIt;
134 RTListForEach(&pThis->ListClaimed, pIt, HBDMGRDEV, ListNode)
135 {
136 if (!RTStrCmp(pszFilename, pIt->pszDevice))
137 {
138 fFound = true;
139 break;
140 }
141 }
142
143 return fFound ? pIt : NULL;
144}
145
146/**
147 * Queries the target in the NT namespace of the given symbolic link.
148 *
149 * @returns VBox status code.
150 * @param pwszLinkNt The symbolic link to query the target for.
151 * @param ppwszLinkTarget Where to store the link target in the NT namespace on success.
152 * Must be freed with RTUtf16Free().
153 */
154static int hbdMgrQueryNtLinkTarget(PRTUTF16 pwszLinkNt, PRTUTF16 *ppwszLinkTarget)
155{
156 int rc = VINF_SUCCESS;
157 HANDLE hFile = RTNT_INVALID_HANDLE_VALUE;
158 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
159 UNICODE_STRING NtName;
160
161 NtName.Buffer = (PWSTR)pwszLinkNt;
162 NtName.Length = (USHORT)(RTUtf16Len(pwszLinkNt) * sizeof(RTUTF16));
163 NtName.MaximumLength = NtName.Length + sizeof(RTUTF16);
164
165 OBJECT_ATTRIBUTES ObjAttr;
166 InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
167
168 NTSTATUS rcNt = NtOpenSymbolicLinkObject(&hFile, SYMBOLIC_LINK_QUERY, &ObjAttr);
169 if (NT_SUCCESS(rcNt))
170 {
171 UNICODE_STRING UniStr;
172 RTUTF16 awszBuf[1024];
173 RT_ZERO(awszBuf);
174 UniStr.Buffer = awszBuf;
175 UniStr.MaximumLength = sizeof(awszBuf);
176 rcNt = NtQuerySymbolicLinkObject(hFile, &UniStr, NULL);
177 if (NT_SUCCESS(rcNt))
178 {
179 *ppwszLinkTarget = RTUtf16Dup((PRTUTF16)UniStr.Buffer);
180 if (!*ppwszLinkTarget)
181 rc = VERR_NO_STR_MEMORY;
182 }
183 else
184 rc = RTErrConvertFromNtStatus(rcNt);
185
186 CloseHandle(hFile);
187 }
188 else
189 rc = RTErrConvertFromNtStatus(rcNt);
190
191 return rc;
192}
193
194/**
195 * Queries the harddisk volume device in the NT namespace for the given Win32
196 * block device path.
197 *
198 * @returns VBox status code.
199 * @param pwszDriveWin32 The Win32 path to the block device (e.g. "\\.\PhysicalDrive0" for example)
200 * @param ppwszDriveNt Where to store the NT path to the volume on success.
201 * Must be freed with RTUtf16Free().
202 */
203static int hbdMgrQueryNtName(PRTUTF16 pwszDriveWin32, PRTUTF16 *ppwszDriveNt)
204{
205 int rc = VINF_SUCCESS;
206 RTUTF16 awszFileNt[1024];
207
208 /*
209 * Make sure the path is at least 5 characters long so we can safely access
210 * the part following \\.\ below.
211 */
212 AssertReturn(RTUtf16Len(pwszDriveWin32) >= 5, VERR_INVALID_STATE);
213
214 RT_ZERO(awszFileNt);
215 RTUtf16CatAscii(awszFileNt, RT_ELEMENTS(awszFileNt), "\\??\\");
216 RTUtf16Cat(awszFileNt, RT_ELEMENTS(awszFileNt), &pwszDriveWin32[4]);
217
218 /* Make sure there is no trailing \ at the end or we will fail. */
219 size_t cwcPath = RTUtf16Len(awszFileNt);
220 if (awszFileNt[cwcPath - 1] == L'\\')
221 awszFileNt[cwcPath - 1] = L'\0';
222
223 return hbdMgrQueryNtLinkTarget(awszFileNt, ppwszDriveNt);
224}
225
226static int hbdMgrQueryAllMountpointsForDisk(PRTUTF16 pwszDiskNt, PRTUTF16 **ppapwszVolumes,
227 unsigned *pcVolumes)
228{
229 /*
230 * Try to get the symlink target for every partition, we will take the easy approach
231 * and just try to open every partition starting with \Device\Harddisk<N>\Partition1
232 * (0 is a special reserved partition linking to the complete disk).
233 *
234 * For every partition we get the target \Device\HarddiskVolume<N> and query all mountpoints
235 * with that.
236 */
237 int rc = VINF_SUCCESS;
238 char *pszDiskNt = NULL;
239 unsigned cVolumes = 0;
240 unsigned cVolumesMax = 10;
241 PRTUTF16 *papwszVolumes = (PRTUTF16 *)RTMemAllocZ(cVolumesMax * sizeof(PRTUTF16));
242
243 if (!papwszVolumes)
244 return VERR_NO_MEMORY;
245
246 rc = RTUtf16ToUtf8(pwszDiskNt, &pszDiskNt);
247 if (RT_SUCCESS(rc))
248 {
249 /* Check that the path matches our expectation \Device\Harddisk<N>\DR<N>. */
250 if (!RTStrNCmp(pszDiskNt, HBDMGR_NT_HARDDISK_START, sizeof(HBDMGR_NT_HARDDISK_START) - 1))
251 {
252 uint32_t iDisk = 0;
253
254 rc = RTStrToUInt32Ex(pszDiskNt + sizeof(HBDMGR_NT_HARDDISK_START) - 1, NULL, 10, &iDisk);
255 if (RT_SUCCESS(rc) || rc == VWRN_TRAILING_CHARS)
256 {
257 uint32_t iPart = 1;
258
259 /* Try to query all mount points for all partitions, the simple way. */
260 do
261 {
262 char aszDisk[1024];
263 RT_ZERO(aszDisk);
264
265 size_t cchWritten = RTStrPrintf(&aszDisk[0], sizeof(aszDisk), "\\Device\\Harddisk%u\\Partition%u", iDisk, iPart);
266 if (cchWritten < sizeof(aszDisk))
267 {
268 PRTUTF16 pwszDisk = NULL;
269 rc = RTStrToUtf16(&aszDisk[0], &pwszDisk);
270 if (RT_SUCCESS(rc))
271 {
272 PRTUTF16 pwszTargetNt = NULL;
273
274 rc = hbdMgrQueryNtLinkTarget(pwszDisk, &pwszTargetNt);
275 if (RT_SUCCESS(rc))
276 {
277 if (cVolumes == cVolumesMax)
278 {
279 /* Increase array of volumes. */
280 PRTUTF16 *papwszVolumesNew = (PRTUTF16 *)RTMemAllocZ((cVolumesMax + 10) * sizeof(PRTUTF16));
281 if (papwszVolumesNew)
282 {
283 cVolumesMax += 10;
284 papwszVolumes = papwszVolumesNew;
285 }
286 else
287 {
288 RTUtf16Free(pwszTargetNt);
289 rc = VERR_NO_MEMORY;
290 }
291 }
292
293 if (RT_SUCCESS(rc))
294 {
295 Assert(cVolumes < cVolumesMax);
296 papwszVolumes[cVolumes++] = pwszTargetNt;
297 iPart++;
298 }
299 }
300 else if (rc == VERR_FILE_NOT_FOUND)
301 {
302 /* The partition does not exist, so stop trying. */
303 rc = VINF_SUCCESS;
304 break;
305 }
306
307 RTUtf16Free(pwszDisk);
308 }
309 }
310 else
311 rc = VERR_BUFFER_OVERFLOW;
312
313 } while (RT_SUCCESS(rc));
314 }
315 }
316 else
317 rc = VERR_INVALID_STATE;
318
319 RTStrFree(pszDiskNt);
320 }
321
322 if (RT_SUCCESS(rc))
323 {
324 *pcVolumes = cVolumes;
325 *ppapwszVolumes = papwszVolumes;
326 LogFlowFunc(("rc=%Rrc cVolumes=%u ppapwszVolumes=%p\n", rc, cVolumes, papwszVolumes));
327 }
328 else
329 {
330 for (unsigned i = 0; i < cVolumes; i++)
331 RTUtf16Free(papwszVolumes[i]);
332
333 RTMemFree(papwszVolumes);
334 }
335
336 return rc;
337}
338
339static NTSTATUS hbdMgrNtCreateFileWrapper(PRTUTF16 pwszVolume, HANDLE *phVolume)
340{
341 HANDLE hVolume = RTNT_INVALID_HANDLE_VALUE;
342 IO_STATUS_BLOCK Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
343 UNICODE_STRING NtName;
344
345 NtName.Buffer = (PWSTR)pwszVolume;
346 NtName.Length = (USHORT)(RTUtf16Len(pwszVolume) * sizeof(RTUTF16));
347 NtName.MaximumLength = NtName.Length + sizeof(WCHAR);
348
349 OBJECT_ATTRIBUTES ObjAttr;
350 InitializeObjectAttributes(&ObjAttr, &NtName, OBJ_CASE_INSENSITIVE, NULL /*hRootDir*/, NULL /*pSecDesc*/);
351
352 NTSTATUS rcNt = NtCreateFile(&hVolume,
353 FILE_READ_DATA | FILE_WRITE_DATA | SYNCHRONIZE,
354 &ObjAttr,
355 &Ios,
356 NULL /* Allocation Size*/,
357 FILE_ATTRIBUTE_NORMAL,
358 FILE_SHARE_READ | FILE_SHARE_WRITE,
359 FILE_OPEN,
360 FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT,
361 NULL /*EaBuffer*/,
362 0 /*EaLength*/);
363 if (NT_SUCCESS(rcNt))
364 rcNt = Ios.Status;
365
366 if (NT_SUCCESS(rcNt))
367 *phVolume = hVolume;
368
369 return rcNt;
370}
371
372DECLHIDDEN(int) HBDMgrCreate(PHBDMGR phHbdMgr)
373{
374 AssertPtrReturn(phHbdMgr, VERR_INVALID_POINTER);
375
376 PHBDMGRINT pThis = (PHBDMGRINT)RTMemAllocZ(sizeof(HBDMGRINT));
377 if (RT_UNLIKELY(!pThis))
378 return VERR_NO_MEMORY;
379
380 int rc = VINF_SUCCESS;
381 RTListInit(&pThis->ListClaimed);
382
383 rc = RTSemFastMutexCreate(&pThis->hMtxList);
384 if (RT_SUCCESS(rc))
385 {
386 *phHbdMgr = pThis;
387 return VINF_SUCCESS;
388 }
389
390 RTMemFree(pThis);
391 return rc;
392}
393
394DECLHIDDEN(void) HBDMgrDestroy(HBDMGR hHbdMgr)
395{
396 PHBDMGRINT pThis = hHbdMgr;
397 AssertPtrReturnVoid(pThis);
398
399 /* Go through all claimed block devices and release them. */
400 RTSemFastMutexRequest(pThis->hMtxList);
401 PHBDMGRDEV pIt, pItNext;
402 RTListForEachSafe(&pThis->ListClaimed, pIt, pItNext, HBDMGRDEV, ListNode)
403 {
404 hbdMgrDevUnclaim(pIt);
405 }
406 RTSemFastMutexRelease(pThis->hMtxList);
407
408 RTSemFastMutexDestroy(pThis->hMtxList);
409 RTMemFree(pThis);
410}
411
412DECLHIDDEN(bool) HBDMgrIsBlockDevice(const char *pszFilename)
413{
414 bool fIsBlockDevice = RTStrNICmp(pszFilename, "\\\\.\\PhysicalDrive", sizeof("\\\\.\\PhysicalDrive") - 1) == 0 ? true : false;
415 if (!fIsBlockDevice)
416 fIsBlockDevice = RTStrNICmp(pszFilename, "\\\\.\\Harddisk", sizeof("\\\\.\\Harddisk") - 1) == 0 ? true : false;
417
418 LogFlowFunc(("returns %s -> %RTbool\n", pszFilename, fIsBlockDevice));
419 return fIsBlockDevice;
420}
421
422DECLHIDDEN(int) HBDMgrClaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
423{
424 PHBDMGRINT pThis = hHbdMgr;
425 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
426 AssertReturn(HBDMgrIsBlockDevice(pszFilename), VERR_INVALID_PARAMETER);
427
428 int rc = VINF_SUCCESS;
429 PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
430 if (!pDev)
431 {
432 PRTUTF16 pwszVolume = NULL;
433
434 rc = RTStrToUtf16(pszFilename, &pwszVolume);
435 if (RT_SUCCESS(rc))
436 {
437 PRTUTF16 pwszVolNt = NULL;
438
439 rc = hbdMgrQueryNtName(pwszVolume, &pwszVolNt);
440 if (RT_SUCCESS(rc))
441 {
442 PRTUTF16 *papwszVolumes = NULL;
443 unsigned cVolumes = 0;
444
445 /* Complete disks need to be handled differently. */
446 if (!RTStrNCmp(pszFilename, "\\\\.\\PhysicalDrive", sizeof("\\\\.\\PhysicalDrive") - 1))
447 {
448 rc = hbdMgrQueryAllMountpointsForDisk(pwszVolNt, &papwszVolumes, &cVolumes);
449 RTUtf16Free(pwszVolNt);
450 }
451 else
452 {
453 papwszVolumes = &pwszVolNt;
454 cVolumes = 1;
455 }
456
457 if (RT_SUCCESS(rc))
458 {
459#ifdef LOG_ENABLED
460 for (unsigned i = 0; i < cVolumes; i++)
461 LogFlowFunc(("Volume %u: %ls\n", i, papwszVolumes[i]));
462#endif
463 pDev = (PHBDMGRDEV)RTMemAllocZ(RT_UOFFSETOF_DYN(HBDMGRDEV, ahVolumes[cVolumes]));
464 if (pDev)
465 {
466 pDev->cVolumes = 0;
467 pDev->pszDevice = RTStrDup(pszFilename);
468 if (pDev->pszDevice)
469 {
470 for (unsigned i = 0; i < cVolumes; i++)
471 {
472 HANDLE hVolume;
473
474 NTSTATUS rcNt = hbdMgrNtCreateFileWrapper(papwszVolumes[i], &hVolume);
475 if (NT_SUCCESS(rcNt))
476 {
477 DWORD dwReturned = 0;
478
479 Assert(hVolume != INVALID_HANDLE_VALUE);
480 BOOL bRet = DeviceIoControl(hVolume, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &dwReturned, NULL);
481 if (bRet)
482 {
483 bRet = DeviceIoControl(hVolume, IOCTL_VOLUME_OFFLINE, NULL, 0, NULL, 0, &dwReturned, NULL);
484 if (bRet)
485 pDev->ahVolumes[pDev->cVolumes++] = hVolume;
486 else
487 rc = RTErrConvertFromWin32(GetLastError());
488 }
489 else
490 rc = RTErrConvertFromWin32(GetLastError());
491
492 if (RT_FAILURE(rc))
493 CloseHandle(hVolume);
494 }
495 else
496 rc = RTErrConvertFromNtStatus(rcNt);
497 }
498 }
499 else
500 rc = VERR_NO_STR_MEMORY;
501
502 if (RT_SUCCESS(rc))
503 {
504 RTSemFastMutexRequest(pThis->hMtxList);
505 RTListAppend(&pThis->ListClaimed, &pDev->ListNode);
506 RTSemFastMutexRelease(pThis->hMtxList);
507 }
508 else
509 {
510 /* Close all open handles and take the volumes online again. */
511 for (unsigned i = 0; i < pDev->cVolumes; i++)
512 {
513 DWORD dwReturned = 0;
514 BOOL bRet = DeviceIoControl(pDev->ahVolumes[i], IOCTL_VOLUME_ONLINE, NULL, 0, NULL, 0, &dwReturned, NULL);
515 if (!bRet)
516 LogRel(("HBDMgmt: Failed to take claimed volume online during cleanup: %s{%Rrc}\n",
517 pDev->pszDevice, RTErrConvertFromWin32(GetLastError())));
518
519 CloseHandle(pDev->ahVolumes[i]);
520 }
521 if (pDev->pszDevice)
522 RTStrFree(pDev->pszDevice);
523 RTMemFree(pDev);
524 }
525 }
526 else
527 rc = VERR_NO_MEMORY;
528
529 for (unsigned i = 0; i < cVolumes; i++)
530 RTUtf16Free(papwszVolumes[i]);
531
532 RTMemFree(papwszVolumes);
533 }
534 }
535
536 RTUtf16Free(pwszVolume);
537 }
538 }
539 else
540 rc = VERR_ALREADY_EXISTS;
541
542 return rc;
543}
544
545DECLHIDDEN(int) HBDMgrUnclaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
546{
547 PHBDMGRINT pThis = hHbdMgr;
548 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
549
550 RTSemFastMutexRequest(pThis->hMtxList);
551 int rc = VINF_SUCCESS;
552 PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
553 if (pDev)
554 hbdMgrDevUnclaim(pDev);
555 else
556 rc = VERR_NOT_FOUND;
557 RTSemFastMutexRelease(pThis->hMtxList);
558
559 return rc;
560}
561
562DECLHIDDEN(bool) HBDMgrIsBlockDeviceClaimed(HBDMGR hHbdMgr, const char *pszFilename)
563{
564 PHBDMGRINT pThis = hHbdMgr;
565 AssertPtrReturn(pThis, false);
566
567 RTSemFastMutexRequest(pThis->hMtxList);
568 PHBDMGRDEV pIt = hbdMgrDevFindByName(pThis, pszFilename);
569 RTSemFastMutexRelease(pThis->hMtxList);
570
571 return pIt ? true : false;
572}
573
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