VirtualBox

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

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