VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 7 weeks 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.0 KB
Line 
1/* $Id: HBDMgmt-darwin.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * VBox storage devices: Host block device management API - darwin specifics.
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
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 * @param pDev The block device to unclaim.
127 */
128static void hbdMgrDevUnclaim(PHBDMGRDEV pDev)
129{
130 DADiskUnclaim(pDev->hDiskRef);
131 CFRelease(pDev->hDiskRef);
132 RTListNodeRemove(&pDev->ListNode);
133 RTMemFree(pDev);
134}
135
136/**
137 * Returns the block device given by the filename if claimed or NULL.
138 *
139 * @returns Pointer to the claimed block device or NULL if not claimed.
140 * @param pThis The block device manager.
141 * @param pszFilename The name to look for.
142 */
143static PHBDMGRDEV hbdMgrDevFindByName(PHBDMGRINT pThis, const char *pszFilename)
144{
145 bool fFound = false;
146 const char *pszFilenameStripped = RTPathFilename(pszFilename);
147
148 AssertPtrReturn(pszFilenameStripped, NULL);
149
150 PHBDMGRDEV pIt;
151 RTListForEach(&pThis->ListClaimed, pIt, HBDMGRDEV, ListNode)
152 {
153 const char *pszBSDName = DADiskGetBSDName(pIt->hDiskRef);
154 if (!RTStrCmp(pszFilenameStripped, pszBSDName))
155 {
156 fFound = true;
157 break;
158 }
159 }
160
161 return fFound ? pIt : NULL;
162}
163
164/**
165 * Converts a given DA return code to a VBox status code.
166 *
167 * @returns VBox status code.
168 * @param hReturn The status code returned by a DA API call.
169 */
170static int hbdMgrDAReturn2VBoxStatus(DAReturn hReturn)
171{
172 int rc = VERR_UNRESOLVED_ERROR;
173
174 switch (hReturn)
175 {
176 case kDAReturnBusy:
177 rc = VERR_RESOURCE_BUSY;
178 break;
179 case kDAReturnNotMounted:
180 case kDAReturnBadArgument:
181 rc = VERR_INVALID_PARAMETER;
182 break;
183 case kDAReturnNotPermitted:
184 case kDAReturnNotPrivileged:
185 case kDAReturnExclusiveAccess:
186 rc = VERR_ACCESS_DENIED;
187 break;
188 case kDAReturnNoResources:
189 rc = VERR_NO_MEMORY;
190 break;
191 case kDAReturnNotFound:
192 rc = VERR_NOT_FOUND;
193 break;
194 case kDAReturnNotReady:
195 rc = VERR_TRY_AGAIN;
196 break;
197 case kDAReturnNotWritable:
198 rc = VERR_WRITE_PROTECT;
199 break;
200 case kDAReturnUnsupported:
201 rc = VERR_NOT_SUPPORTED;
202 break;
203 case kDAReturnError:
204 default:
205 rc = VERR_UNRESOLVED_ERROR;
206 }
207
208 return rc;
209}
210
211/**
212 * Implements the OS X callback DADiskClaimCallback.
213 *
214 * This notifies us that the async DADiskClaim()/DADiskUnmount call has
215 * completed.
216 *
217 * @param hDiskRef The disk that was attempted claimed / unmounted.
218 * @param hDissenterRef NULL on success, contains details on failure.
219 * @param pvContext Pointer to the return code variable.
220 */
221static void hbdMgrDACallbackComplete(DADiskRef hDiskRef, DADissenterRef hDissenterRef, void *pvContext)
222{
223 RT_NOREF(hDiskRef);
224 PHBDMGRDACLBKARGS pArgs = (PHBDMGRDACLBKARGS)pvContext;
225 pArgs->pszErrDetail = NULL;
226
227 if (!hDissenterRef)
228 pArgs->rcDA = kDAReturnSuccess;
229 else
230 {
231 CFStringRef hStrErr = DADissenterGetStatusString(hDissenterRef);
232 if (hStrErr)
233 {
234 const char *pszErrDetail = CFStringGetCStringPtr(hStrErr, kCFStringEncodingUTF8);
235 if (pszErrDetail)
236 pArgs->pszErrDetail = RTStrDup(pszErrDetail);
237 CFRelease(hStrErr);
238 }
239 pArgs->rcDA = DADissenterGetStatus(hDissenterRef);
240
241 }
242 RTSemEventSignal(pArgs->pThis->hEvtCallback);
243}
244
245/**
246 * Implements the OS X callback DADiskMountApprovalCallback.
247 *
248 * This notifies us about any attempt to mount a volume. If we claimed the
249 * volume or the complete disk containing the volume we will deny the attempt.
250 *
251 * @returns Reference to a DADissenter object which contains the result.
252 * @param hDiskRef The disk that is about to be mounted.
253 * @param pvContext Pointer to the block device manager.
254 */
255static DADissenterRef hbdMgrDAMountApprovalCallback(DADiskRef hDiskRef, void *pvContext)
256{
257 PHBDMGRINT pThis = (PHBDMGRINT)pvContext;
258 DADiskRef hDiskParentRef = DADiskCopyWholeDisk(hDiskRef);
259 const char *pszBSDName = DADiskGetBSDName(hDiskRef);
260 const char *pszBSDNameParent = hDiskParentRef ? DADiskGetBSDName(hDiskParentRef) : NULL;
261 DADissenterRef hDissenterRef = NULL;
262
263 RTSemFastMutexRequest(pThis->hMtxList);
264 PHBDMGRDEV pIt;
265 RTListForEach(&pThis->ListClaimed, pIt, HBDMGRDEV, ListNode)
266 {
267 const char *pszBSDNameCur = DADiskGetBSDName(pIt->hDiskRef);
268 /*
269 * Prevent mounting any volume we have in use. This applies to the case
270 * where we have the whole disk occupied but a single volume is about to be
271 * mounted.
272 */
273 if ( !RTStrCmp(pszBSDNameCur, pszBSDName)
274 || ( pszBSDNameParent
275 && !RTStrCmp(pszBSDNameParent, pszBSDNameCur)))
276 {
277 CFStringRef hStrReason = CFStringCreateWithCString(kCFAllocatorDefault, "The disk is currently in use by VirtualBox and cannot be mounted", kCFStringEncodingUTF8);
278 hDissenterRef = DADissenterCreate(kCFAllocatorDefault, kDAReturnExclusiveAccess, hStrReason);
279 break;
280 }
281 }
282
283 RTSemFastMutexRelease(pThis->hMtxList);
284
285 if (hDiskParentRef)
286 CFRelease(hDiskParentRef);
287 return hDissenterRef;
288}
289
290
291/**
292 * Implements OS X callback CFRunLoopSourceContext::perform.
293 *
294 * Dummy handler for the wakeup source to kick the worker thread.
295 *
296 * @param pInfo Opaque user data given during source creation, unused.
297 */
298static void hbdMgrDAPerformWakeup(void *pInfo)
299{
300 RT_NOREF(pInfo);
301}
302
303
304/**
305 * Worker function of the thread processing messages from the Disk Arbitration daemon.
306 *
307 * @returns IPRT status code.
308 * @param hThreadSelf The thread handle.
309 * @param pvUser Opaque user data, the block device manager instance.
310 */
311static DECLCALLBACK(int) hbdMgrDAWorker(RTTHREAD hThreadSelf, void *pvUser)
312{
313 PHBDMGRINT pThis = (PHBDMGRINT)pvUser;
314
315 /* Provide the runloop reference. */
316 pThis->hRunLoopRef = CFRunLoopGetCurrent();
317 RTThreadUserSignal(hThreadSelf);
318
319 /* Add the wake source to our runloop so we get notified about state changes. */
320 CFRunLoopAddSource(pThis->hRunLoopRef, pThis->hRunLoopSrcWakeRef, kCFRunLoopCommonModes);
321
322 /* Do what we are here for. */
323 while (ASMAtomicReadBool(&pThis->fRunning))
324 {
325 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10.0, true);
326 }
327
328 /* Remove the wakeup source form our runloop. */
329 CFRunLoopRemoveSource(pThis->hRunLoopRef, pThis->hRunLoopSrcWakeRef, kCFRunLoopCommonModes);
330
331 return VINF_SUCCESS;
332}
333
334DECLHIDDEN(int) HBDMgrCreate(PHBDMGR phHbdMgr)
335{
336 AssertPtrReturn(phHbdMgr, VERR_INVALID_POINTER);
337
338 PHBDMGRINT pThis = (PHBDMGRINT)RTMemAllocZ(sizeof(HBDMGRINT));
339 if (RT_UNLIKELY(!pThis))
340 return VERR_NO_MEMORY;
341
342 int rc = VINF_SUCCESS;
343 RTListInit(&pThis->ListClaimed);
344 pThis->fRunning = true;
345 pThis->hSessionRef = DASessionCreate(kCFAllocatorDefault);
346 if (pThis->hSessionRef)
347 {
348 rc = RTSemFastMutexCreate(&pThis->hMtxList);
349 if (RT_SUCCESS(rc))
350 {
351 rc = RTSemEventCreate(&pThis->hEvtCallback);
352 if (RT_SUCCESS(rc))
353 {
354 CFRunLoopSourceContext CtxRunLoopSource;
355 CtxRunLoopSource.version = 0;
356 CtxRunLoopSource.info = NULL;
357 CtxRunLoopSource.retain = NULL;
358 CtxRunLoopSource.release = NULL;
359 CtxRunLoopSource.copyDescription = NULL;
360 CtxRunLoopSource.equal = NULL;
361 CtxRunLoopSource.hash = NULL;
362 CtxRunLoopSource.schedule = NULL;
363 CtxRunLoopSource.cancel = NULL;
364 CtxRunLoopSource.perform = hbdMgrDAPerformWakeup;
365 pThis->hRunLoopSrcWakeRef = CFRunLoopSourceCreate(NULL, 0, &CtxRunLoopSource);
366 if (CFRunLoopSourceIsValid(pThis->hRunLoopSrcWakeRef))
367 {
368 rc = RTThreadCreate(&pThis->hThrdDAEvts, hbdMgrDAWorker, pThis, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "HbdDA-Wrk");
369 if (RT_SUCCESS(rc))
370 {
371 /* Wait for the thread to start up and provide the runloop reference. */
372 rc = RTThreadUserWait(pThis->hThrdDAEvts, RT_INDEFINITE_WAIT);
373 AssertRC(rc);
374 AssertPtr(pThis->hRunLoopRef);
375
376 DARegisterDiskMountApprovalCallback(pThis->hSessionRef, NULL, hbdMgrDAMountApprovalCallback, pThis);
377 DASessionScheduleWithRunLoop(pThis->hSessionRef, pThis->hRunLoopRef, kCFRunLoopDefaultMode);
378 *phHbdMgr = pThis;
379 return VINF_SUCCESS;
380 }
381 CFRelease(pThis->hRunLoopSrcWakeRef);
382 }
383 }
384
385 RTSemFastMutexDestroy(pThis->hMtxList);
386 }
387
388 CFRelease(pThis->hSessionRef);
389 }
390 else
391 rc = VERR_NO_MEMORY;
392
393 RTMemFree(pThis);
394 return rc;
395}
396
397
398DECLHIDDEN(void) HBDMgrDestroy(HBDMGR hHbdMgr)
399{
400 PHBDMGRINT pThis = hHbdMgr;
401 AssertPtrReturnVoid(pThis);
402
403 /* Unregister the mount approval and DA session from the runloop. */
404 DASessionUnscheduleFromRunLoop(pThis->hSessionRef, pThis->hRunLoopRef, kCFRunLoopDefaultMode);
405 DAUnregisterApprovalCallback(pThis->hSessionRef, (void *)hbdMgrDAMountApprovalCallback, pThis);
406
407 /* Kick the worker thread to exit. */
408 ASMAtomicXchgBool(&pThis->fRunning, false);
409 CFRunLoopSourceSignal(pThis->hRunLoopSrcWakeRef);
410 CFRunLoopWakeUp(pThis->hRunLoopRef);
411 int rcThrd = VINF_SUCCESS;
412 int rc = RTThreadWait(pThis->hThrdDAEvts, RT_INDEFINITE_WAIT, &rcThrd);
413 AssertRC(rc); AssertRC(rcThrd);
414
415 CFRelease(pThis->hRunLoopSrcWakeRef);
416
417 /* Go through all claimed block devices and release them. */
418 RTSemFastMutexRequest(pThis->hMtxList);
419 PHBDMGRDEV pIt, pItNext;
420 RTListForEachSafe(&pThis->ListClaimed, pIt, pItNext, HBDMGRDEV, ListNode)
421 {
422 hbdMgrDevUnclaim(pIt);
423 }
424 RTSemFastMutexRelease(pThis->hMtxList);
425
426 CFRelease(pThis->hSessionRef);
427 RTSemFastMutexDestroy(pThis->hMtxList);
428 RTSemEventDestroy(pThis->hEvtCallback);
429 RTMemFree(pThis);
430}
431
432
433DECLHIDDEN(bool) HBDMgrIsBlockDevice(const char *pszFilename)
434{
435 bool fIsBlockDevice = RTStrNCmp(pszFilename, "/dev/disk", sizeof("/dev/disk") - 1) == 0 ? true : false;
436 if (!fIsBlockDevice)
437 fIsBlockDevice = RTStrNCmp(pszFilename, "/dev/rdisk", sizeof("/dev/rdisk") - 1) == 0 ? true : false;
438 return fIsBlockDevice;
439}
440
441
442DECLHIDDEN(int) HBDMgrClaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
443{
444 PHBDMGRINT pThis = hHbdMgr;
445 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
446 AssertReturn(HBDMgrIsBlockDevice(pszFilename), VERR_INVALID_PARAMETER);
447
448 int rc = VINF_SUCCESS;
449 PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
450 if (!pDev)
451 {
452 DADiskRef hDiskRef = DADiskCreateFromBSDName(kCFAllocatorDefault, pThis->hSessionRef, pszFilename);
453 if (hDiskRef)
454 {
455 HBDMGRDACLBKARGS CalllbackArgs;
456 CalllbackArgs.pThis = pThis;
457 CalllbackArgs.rcDA = kDAReturnSuccess;
458
459 /* Claim the device. */
460 DADiskClaim(hDiskRef, kDADiskClaimOptionDefault, NULL, NULL, hbdMgrDACallbackComplete, &CalllbackArgs);
461 rc = RTSemEventWait(pThis->hEvtCallback, 120 * RT_MS_1SEC);
462 if ( RT_SUCCESS(rc)
463 && CalllbackArgs.rcDA == kDAReturnSuccess)
464 {
465 /* Unmount anything which might be mounted. */
466 DADiskUnmount(hDiskRef, kDADiskUnmountOptionWhole, hbdMgrDACallbackComplete, &CalllbackArgs);
467 rc = RTSemEventWait(pThis->hEvtCallback, 120 * RT_MS_1SEC);
468 if ( RT_SUCCESS(rc)
469 && ( CalllbackArgs.rcDA == kDAReturnSuccess
470 || CalllbackArgs.rcDA == kDAReturnNotMounted))
471 {
472 pDev = (PHBDMGRDEV)RTMemAllocZ(sizeof(HBDMGRDEV));
473 if (RT_LIKELY(pDev))
474 {
475 pDev->hDiskRef = hDiskRef;
476 RTSemFastMutexRequest(pThis->hMtxList);
477 RTListAppend(&pThis->ListClaimed, &pDev->ListNode);
478 RTSemFastMutexRelease(pThis->hMtxList);
479 rc = VINF_SUCCESS;
480 }
481 else
482 rc = VERR_NO_MEMORY;
483 }
484 else if (RT_SUCCESS(rc))
485 {
486 rc = hbdMgrDAReturn2VBoxStatus(CalllbackArgs.rcDA);
487 LogRel(("HBDMgrClaimBlockDevice: DADiskUnmount(\"%s\") failed with %Rrc (%s)\n",
488 pszFilename, rc, CalllbackArgs.pszErrDetail ? CalllbackArgs.pszErrDetail : "<no detail>"));
489 if (CalllbackArgs.pszErrDetail)
490 RTStrFree(CalllbackArgs.pszErrDetail);
491 }
492 }
493 else if (RT_SUCCESS(rc))
494 {
495 rc = hbdMgrDAReturn2VBoxStatus(CalllbackArgs.rcDA);
496 LogRel(("HBDMgrClaimBlockDevice: DADiskClaim(\"%s\") failed with %Rrc (%s)\n",
497 pszFilename, rc, CalllbackArgs.pszErrDetail ? CalllbackArgs.pszErrDetail : "<no detail>"));
498 if (CalllbackArgs.pszErrDetail)
499 RTStrFree(CalllbackArgs.pszErrDetail);
500 }
501 if (RT_FAILURE(rc))
502 CFRelease(hDiskRef);
503 }
504 else
505 rc = VERR_NO_MEMORY;
506 }
507 else
508 rc = VERR_ALREADY_EXISTS;
509
510 return rc;
511}
512
513
514DECLHIDDEN(int) HBDMgrUnclaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
515{
516 PHBDMGRINT pThis = hHbdMgr;
517 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
518
519 RTSemFastMutexRequest(pThis->hMtxList);
520 int rc = VINF_SUCCESS;
521 PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
522 if (pDev)
523 hbdMgrDevUnclaim(pDev);
524 else
525 rc = VERR_NOT_FOUND;
526 RTSemFastMutexRelease(pThis->hMtxList);
527
528 return rc;
529}
530
531
532DECLHIDDEN(bool) HBDMgrIsBlockDeviceClaimed(HBDMGR hHbdMgr, const char *pszFilename)
533{
534 PHBDMGRINT pThis = hHbdMgr;
535 AssertPtrReturn(pThis, false);
536
537 RTSemFastMutexRequest(pThis->hMtxList);
538 PHBDMGRDEV pIt = hbdMgrDevFindByName(pThis, pszFilename);
539 RTSemFastMutexRelease(pThis->hMtxList);
540
541 return pIt ? true : false;
542}
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