VirtualBox

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

Last change on this file since 98110 was 98103, checked in by vboxsync, 2 years ago

Copyright year updates by scm.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.1 KB
Line 
1/* $Id: HBDMgmt-darwin.cpp 98103 2023-01-17 14:15:46Z vboxsync $ */
2/** @file
3 * VBox storage devices: Host block device management API - darwin specifics.
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
28
29/*********************************************************************************************************************************
30* Header Files *
31*********************************************************************************************************************************/
32#define LOG_GROUP LOG_GROUP_DRV_VD
33#include <VBox/cdefs.h>
34#include <VBox/err.h>
35#include <VBox/log.h>
36#include <iprt/assert.h>
37#include <iprt/list.h>
38#include <iprt/mem.h>
39#include <iprt/string.h>
40#include <iprt/once.h>
41#include <iprt/semaphore.h>
42#include <iprt/path.h>
43#include <iprt/thread.h>
44
45#include <DiskArbitration/DiskArbitration.h>
46
47#include "HBDMgmt.h"
48
49
50/*********************************************************************************************************************************
51* Defined Constants And Macros *
52*********************************************************************************************************************************/
53
54
55/*********************************************************************************************************************************
56* Structures and Typedefs *
57*********************************************************************************************************************************/
58
59/**
60 * Claimed block device state.
61 */
62typedef struct HBDMGRDEV
63{
64 /** List node. */
65 RTLISTNODE ListNode;
66 /** Handle to the DA Disk object. */
67 DADiskRef hDiskRef;
68} HBDMGRDEV;
69/** Pointer to a claimed block device. */
70typedef HBDMGRDEV *PHBDMGRDEV;
71
72/**
73 * Internal Host block device manager state.
74 */
75typedef struct HBDMGRINT
76{
77 /** Session handle to the DiskArbitration daemon. */
78 DASessionRef hSessionRef;
79 /** Runloop reference of the worker thread. */
80 CFRunLoopRef hRunLoopRef;
81 /** Runloop source for waking up the worker thread. */
82 CFRunLoopSourceRef hRunLoopSrcWakeRef;
83 /** List of claimed block devices. */
84 RTLISTANCHOR ListClaimed;
85 /** Fast mutex protecting the list. */
86 RTSEMFASTMUTEX hMtxList;
87 /** Event sempahore to signal callback completion. */
88 RTSEMEVENT hEvtCallback;
89 /** Thread processing DA events. */
90 RTTHREAD hThrdDAEvts;
91 /** Flag whether the thread should keep running. */
92 volatile bool fRunning;
93} HBDMGRINT;
94/** Pointer to an interal block device manager state. */
95typedef HBDMGRINT *PHBDMGRINT;
96
97/**
98 * Helper structure containing the arguments
99 * for the claim/unmount callbacks.
100 */
101typedef struct HBDMGRDACLBKARGS
102{
103 /** Pointer to the block device manager. */
104 PHBDMGRINT pThis;
105 /** The status code returned by the callback, after the operation completed. */
106 DAReturn rcDA;
107 /** A detailed error string in case of an error, can be NULL.
108 * Must be freed with RTStrFree(). */
109 char *pszErrDetail;
110} HBDMGRDACLBKARGS;
111typedef HBDMGRDACLBKARGS *PHBDMGRDACLBKARGS;
112
113
114/*********************************************************************************************************************************
115* Global Variables *
116*********************************************************************************************************************************/
117
118
119/*********************************************************************************************************************************
120* Internal Functions *
121*********************************************************************************************************************************/
122
123/**
124 * Unclaims the given block device and frees its state removing it from the list.
125 *
126 * @returns nothing.
127 * @param pDev The block device to unclaim.
128 */
129static void hbdMgrDevUnclaim(PHBDMGRDEV pDev)
130{
131 DADiskUnclaim(pDev->hDiskRef);
132 CFRelease(pDev->hDiskRef);
133 RTListNodeRemove(&pDev->ListNode);
134 RTMemFree(pDev);
135}
136
137/**
138 * Returns the block device given by the filename if claimed or NULL.
139 *
140 * @returns Pointer to the claimed block device or NULL if not claimed.
141 * @param pThis The block device manager.
142 * @param pszFilename The name to look for.
143 */
144static PHBDMGRDEV hbdMgrDevFindByName(PHBDMGRINT pThis, const char *pszFilename)
145{
146 bool fFound = false;
147 const char *pszFilenameStripped = RTPathFilename(pszFilename);
148
149 AssertPtrReturn(pszFilenameStripped, NULL);
150
151 PHBDMGRDEV pIt;
152 RTListForEach(&pThis->ListClaimed, pIt, HBDMGRDEV, ListNode)
153 {
154 const char *pszBSDName = DADiskGetBSDName(pIt->hDiskRef);
155 if (!RTStrCmp(pszFilenameStripped, pszBSDName))
156 {
157 fFound = true;
158 break;
159 }
160 }
161
162 return fFound ? pIt : NULL;
163}
164
165/**
166 * Converts a given DA return code to a VBox status code.
167 *
168 * @returns VBox status code.
169 * @param hReturn The status code returned by a DA API call.
170 */
171static int hbdMgrDAReturn2VBoxStatus(DAReturn hReturn)
172{
173 int rc = VERR_UNRESOLVED_ERROR;
174
175 switch (hReturn)
176 {
177 case kDAReturnBusy:
178 rc = VERR_RESOURCE_BUSY;
179 break;
180 case kDAReturnNotMounted:
181 case kDAReturnBadArgument:
182 rc = VERR_INVALID_PARAMETER;
183 break;
184 case kDAReturnNotPermitted:
185 case kDAReturnNotPrivileged:
186 case kDAReturnExclusiveAccess:
187 rc = VERR_ACCESS_DENIED;
188 break;
189 case kDAReturnNoResources:
190 rc = VERR_NO_MEMORY;
191 break;
192 case kDAReturnNotFound:
193 rc = VERR_NOT_FOUND;
194 break;
195 case kDAReturnNotReady:
196 rc = VERR_TRY_AGAIN;
197 break;
198 case kDAReturnNotWritable:
199 rc = VERR_WRITE_PROTECT;
200 break;
201 case kDAReturnUnsupported:
202 rc = VERR_NOT_SUPPORTED;
203 break;
204 case kDAReturnError:
205 default:
206 rc = VERR_UNRESOLVED_ERROR;
207 }
208
209 return rc;
210}
211
212/**
213 * Implements the OS X callback DADiskClaimCallback.
214 *
215 * This notifies us that the async DADiskClaim()/DADiskUnmount call has
216 * completed.
217 *
218 * @param hDiskRef The disk that was attempted claimed / unmounted.
219 * @param hDissenterRef NULL on success, contains details on failure.
220 * @param pvContext Pointer to the return code variable.
221 */
222static void hbdMgrDACallbackComplete(DADiskRef hDiskRef, DADissenterRef hDissenterRef, void *pvContext)
223{
224 RT_NOREF(hDiskRef);
225 PHBDMGRDACLBKARGS pArgs = (PHBDMGRDACLBKARGS)pvContext;
226 pArgs->pszErrDetail = NULL;
227
228 if (!hDissenterRef)
229 pArgs->rcDA = kDAReturnSuccess;
230 else
231 {
232 CFStringRef hStrErr = DADissenterGetStatusString(hDissenterRef);
233 if (hStrErr)
234 {
235 const char *pszErrDetail = CFStringGetCStringPtr(hStrErr, kCFStringEncodingUTF8);
236 if (pszErrDetail)
237 pArgs->pszErrDetail = RTStrDup(pszErrDetail);
238 CFRelease(hStrErr);
239 }
240 pArgs->rcDA = DADissenterGetStatus(hDissenterRef);
241
242 }
243 RTSemEventSignal(pArgs->pThis->hEvtCallback);
244}
245
246/**
247 * Implements the OS X callback DADiskMountApprovalCallback.
248 *
249 * This notifies us about any attempt to mount a volume. If we claimed the
250 * volume or the complete disk containing the volume we will deny the attempt.
251 *
252 * @returns Reference to a DADissenter object which contains the result.
253 * @param hDiskRef The disk that is about to be mounted.
254 * @param pvContext Pointer to the block device manager.
255 */
256static DADissenterRef hbdMgrDAMountApprovalCallback(DADiskRef hDiskRef, void *pvContext)
257{
258 PHBDMGRINT pThis = (PHBDMGRINT)pvContext;
259 DADiskRef hDiskParentRef = DADiskCopyWholeDisk(hDiskRef);
260 const char *pszBSDName = DADiskGetBSDName(hDiskRef);
261 const char *pszBSDNameParent = hDiskParentRef ? DADiskGetBSDName(hDiskParentRef) : NULL;
262 DADissenterRef hDissenterRef = NULL;
263
264 RTSemFastMutexRequest(pThis->hMtxList);
265 PHBDMGRDEV pIt;
266 RTListForEach(&pThis->ListClaimed, pIt, HBDMGRDEV, ListNode)
267 {
268 const char *pszBSDNameCur = DADiskGetBSDName(pIt->hDiskRef);
269 /*
270 * Prevent mounting any volume we have in use. This applies to the case
271 * where we have the whole disk occupied but a single volume is about to be
272 * mounted.
273 */
274 if ( !RTStrCmp(pszBSDNameCur, pszBSDName)
275 || ( pszBSDNameParent
276 && !RTStrCmp(pszBSDNameParent, pszBSDNameCur)))
277 {
278 CFStringRef hStrReason = CFStringCreateWithCString(kCFAllocatorDefault, "The disk is currently in use by VirtualBox and cannot be mounted", kCFStringEncodingUTF8);
279 hDissenterRef = DADissenterCreate(kCFAllocatorDefault, kDAReturnExclusiveAccess, hStrReason);
280 break;
281 }
282 }
283
284 RTSemFastMutexRelease(pThis->hMtxList);
285
286 if (hDiskParentRef)
287 CFRelease(hDiskParentRef);
288 return hDissenterRef;
289}
290
291
292/**
293 * Implements OS X callback CFRunLoopSourceContext::perform.
294 *
295 * Dummy handler for the wakeup source to kick the worker thread.
296 *
297 * @returns nothing.
298 * @param pInfo Opaque user data given during source creation, unused.
299 */
300static void hbdMgrDAPerformWakeup(void *pInfo)
301{
302 RT_NOREF(pInfo);
303}
304
305
306/**
307 * Worker function of the thread processing messages from the Disk Arbitration daemon.
308 *
309 * @returns IPRT status code.
310 * @param hThreadSelf The thread handle.
311 * @param pvUser Opaque user data, the block device manager instance.
312 */
313static DECLCALLBACK(int) hbdMgrDAWorker(RTTHREAD hThreadSelf, void *pvUser)
314{
315 PHBDMGRINT pThis = (PHBDMGRINT)pvUser;
316
317 /* Provide the runloop reference. */
318 pThis->hRunLoopRef = CFRunLoopGetCurrent();
319 RTThreadUserSignal(hThreadSelf);
320
321 /* Add the wake source to our runloop so we get notified about state changes. */
322 CFRunLoopAddSource(pThis->hRunLoopRef, pThis->hRunLoopSrcWakeRef, kCFRunLoopCommonModes);
323
324 /* Do what we are here for. */
325 while (ASMAtomicReadBool(&pThis->fRunning))
326 {
327 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10.0, true);
328 }
329
330 /* Remove the wakeup source form our runloop. */
331 CFRunLoopRemoveSource(pThis->hRunLoopRef, pThis->hRunLoopSrcWakeRef, kCFRunLoopCommonModes);
332
333 return VINF_SUCCESS;
334}
335
336DECLHIDDEN(int) HBDMgrCreate(PHBDMGR phHbdMgr)
337{
338 AssertPtrReturn(phHbdMgr, VERR_INVALID_POINTER);
339
340 PHBDMGRINT pThis = (PHBDMGRINT)RTMemAllocZ(sizeof(HBDMGRINT));
341 if (RT_UNLIKELY(!pThis))
342 return VERR_NO_MEMORY;
343
344 int rc = VINF_SUCCESS;
345 RTListInit(&pThis->ListClaimed);
346 pThis->fRunning = true;
347 pThis->hSessionRef = DASessionCreate(kCFAllocatorDefault);
348 if (pThis->hSessionRef)
349 {
350 rc = RTSemFastMutexCreate(&pThis->hMtxList);
351 if (RT_SUCCESS(rc))
352 {
353 rc = RTSemEventCreate(&pThis->hEvtCallback);
354 if (RT_SUCCESS(rc))
355 {
356 CFRunLoopSourceContext CtxRunLoopSource;
357 CtxRunLoopSource.version = 0;
358 CtxRunLoopSource.info = NULL;
359 CtxRunLoopSource.retain = NULL;
360 CtxRunLoopSource.release = NULL;
361 CtxRunLoopSource.copyDescription = NULL;
362 CtxRunLoopSource.equal = NULL;
363 CtxRunLoopSource.hash = NULL;
364 CtxRunLoopSource.schedule = NULL;
365 CtxRunLoopSource.cancel = NULL;
366 CtxRunLoopSource.perform = hbdMgrDAPerformWakeup;
367 pThis->hRunLoopSrcWakeRef = CFRunLoopSourceCreate(NULL, 0, &CtxRunLoopSource);
368 if (CFRunLoopSourceIsValid(pThis->hRunLoopSrcWakeRef))
369 {
370 rc = RTThreadCreate(&pThis->hThrdDAEvts, hbdMgrDAWorker, pThis, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "HbdDA-Wrk");
371 if (RT_SUCCESS(rc))
372 {
373 /* Wait for the thread to start up and provide the runloop reference. */
374 rc = RTThreadUserWait(pThis->hThrdDAEvts, RT_INDEFINITE_WAIT);
375 AssertRC(rc);
376 AssertPtr(pThis->hRunLoopRef);
377
378 DARegisterDiskMountApprovalCallback(pThis->hSessionRef, NULL, hbdMgrDAMountApprovalCallback, pThis);
379 DASessionScheduleWithRunLoop(pThis->hSessionRef, pThis->hRunLoopRef, kCFRunLoopDefaultMode);
380 *phHbdMgr = pThis;
381 return VINF_SUCCESS;
382 }
383 CFRelease(pThis->hRunLoopSrcWakeRef);
384 }
385 }
386
387 RTSemFastMutexDestroy(pThis->hMtxList);
388 }
389
390 CFRelease(pThis->hSessionRef);
391 }
392 else
393 rc = VERR_NO_MEMORY;
394
395 RTMemFree(pThis);
396 return rc;
397}
398
399
400DECLHIDDEN(void) HBDMgrDestroy(HBDMGR hHbdMgr)
401{
402 PHBDMGRINT pThis = hHbdMgr;
403 AssertPtrReturnVoid(pThis);
404
405 /* Unregister the mount approval and DA session from the runloop. */
406 DASessionUnscheduleFromRunLoop(pThis->hSessionRef, pThis->hRunLoopRef, kCFRunLoopDefaultMode);
407 DAUnregisterApprovalCallback(pThis->hSessionRef, (void *)hbdMgrDAMountApprovalCallback, pThis);
408
409 /* Kick the worker thread to exit. */
410 ASMAtomicXchgBool(&pThis->fRunning, false);
411 CFRunLoopSourceSignal(pThis->hRunLoopSrcWakeRef);
412 CFRunLoopWakeUp(pThis->hRunLoopRef);
413 int rcThrd = VINF_SUCCESS;
414 int rc = RTThreadWait(pThis->hThrdDAEvts, RT_INDEFINITE_WAIT, &rcThrd);
415 AssertRC(rc); AssertRC(rcThrd);
416
417 CFRelease(pThis->hRunLoopSrcWakeRef);
418
419 /* Go through all claimed block devices and release them. */
420 RTSemFastMutexRequest(pThis->hMtxList);
421 PHBDMGRDEV pIt, pItNext;
422 RTListForEachSafe(&pThis->ListClaimed, pIt, pItNext, HBDMGRDEV, ListNode)
423 {
424 hbdMgrDevUnclaim(pIt);
425 }
426 RTSemFastMutexRelease(pThis->hMtxList);
427
428 CFRelease(pThis->hSessionRef);
429 RTSemFastMutexDestroy(pThis->hMtxList);
430 RTSemEventDestroy(pThis->hEvtCallback);
431 RTMemFree(pThis);
432}
433
434
435DECLHIDDEN(bool) HBDMgrIsBlockDevice(const char *pszFilename)
436{
437 bool fIsBlockDevice = RTStrNCmp(pszFilename, "/dev/disk", sizeof("/dev/disk") - 1) == 0 ? true : false;
438 if (!fIsBlockDevice)
439 fIsBlockDevice = RTStrNCmp(pszFilename, "/dev/rdisk", sizeof("/dev/rdisk") - 1) == 0 ? true : false;
440 return fIsBlockDevice;
441}
442
443
444DECLHIDDEN(int) HBDMgrClaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
445{
446 PHBDMGRINT pThis = hHbdMgr;
447 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
448 AssertReturn(HBDMgrIsBlockDevice(pszFilename), VERR_INVALID_PARAMETER);
449
450 int rc = VINF_SUCCESS;
451 PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
452 if (!pDev)
453 {
454 DADiskRef hDiskRef = DADiskCreateFromBSDName(kCFAllocatorDefault, pThis->hSessionRef, pszFilename);
455 if (hDiskRef)
456 {
457 HBDMGRDACLBKARGS CalllbackArgs;
458 CalllbackArgs.pThis = pThis;
459 CalllbackArgs.rcDA = kDAReturnSuccess;
460
461 /* Claim the device. */
462 DADiskClaim(hDiskRef, kDADiskClaimOptionDefault, NULL, NULL, hbdMgrDACallbackComplete, &CalllbackArgs);
463 rc = RTSemEventWait(pThis->hEvtCallback, 120 * RT_MS_1SEC);
464 if ( RT_SUCCESS(rc)
465 && CalllbackArgs.rcDA == kDAReturnSuccess)
466 {
467 /* Unmount anything which might be mounted. */
468 DADiskUnmount(hDiskRef, kDADiskUnmountOptionWhole, hbdMgrDACallbackComplete, &CalllbackArgs);
469 rc = RTSemEventWait(pThis->hEvtCallback, 120 * RT_MS_1SEC);
470 if ( RT_SUCCESS(rc)
471 && ( CalllbackArgs.rcDA == kDAReturnSuccess
472 || CalllbackArgs.rcDA == kDAReturnNotMounted))
473 {
474 pDev = (PHBDMGRDEV)RTMemAllocZ(sizeof(HBDMGRDEV));
475 if (RT_LIKELY(pDev))
476 {
477 pDev->hDiskRef = hDiskRef;
478 RTSemFastMutexRequest(pThis->hMtxList);
479 RTListAppend(&pThis->ListClaimed, &pDev->ListNode);
480 RTSemFastMutexRelease(pThis->hMtxList);
481 rc = VINF_SUCCESS;
482 }
483 else
484 rc = VERR_NO_MEMORY;
485 }
486 else if (RT_SUCCESS(rc))
487 {
488 rc = hbdMgrDAReturn2VBoxStatus(CalllbackArgs.rcDA);
489 LogRel(("HBDMgrClaimBlockDevice: DADiskUnmount(\"%s\") failed with %Rrc (%s)\n",
490 pszFilename, rc, CalllbackArgs.pszErrDetail ? CalllbackArgs.pszErrDetail : "<no detail>"));
491 if (CalllbackArgs.pszErrDetail)
492 RTStrFree(CalllbackArgs.pszErrDetail);
493 }
494 }
495 else if (RT_SUCCESS(rc))
496 {
497 rc = hbdMgrDAReturn2VBoxStatus(CalllbackArgs.rcDA);
498 LogRel(("HBDMgrClaimBlockDevice: DADiskClaim(\"%s\") failed with %Rrc (%s)\n",
499 pszFilename, rc, CalllbackArgs.pszErrDetail ? CalllbackArgs.pszErrDetail : "<no detail>"));
500 if (CalllbackArgs.pszErrDetail)
501 RTStrFree(CalllbackArgs.pszErrDetail);
502 }
503 if (RT_FAILURE(rc))
504 CFRelease(hDiskRef);
505 }
506 else
507 rc = VERR_NO_MEMORY;
508 }
509 else
510 rc = VERR_ALREADY_EXISTS;
511
512 return rc;
513}
514
515
516DECLHIDDEN(int) HBDMgrUnclaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
517{
518 PHBDMGRINT pThis = hHbdMgr;
519 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
520
521 RTSemFastMutexRequest(pThis->hMtxList);
522 int rc = VINF_SUCCESS;
523 PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
524 if (pDev)
525 hbdMgrDevUnclaim(pDev);
526 else
527 rc = VERR_NOT_FOUND;
528 RTSemFastMutexRelease(pThis->hMtxList);
529
530 return rc;
531}
532
533
534DECLHIDDEN(bool) HBDMgrIsBlockDeviceClaimed(HBDMGR hHbdMgr, const char *pszFilename)
535{
536 PHBDMGRINT pThis = hHbdMgr;
537 AssertPtrReturn(pThis, false);
538
539 RTSemFastMutexRequest(pThis->hMtxList);
540 PHBDMGRDEV pIt = hbdMgrDevFindByName(pThis, pszFilename);
541 RTSemFastMutexRelease(pThis->hMtxList);
542
543 return pIt ? true : false;
544}
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