VirtualBox

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

Last change on this file since 59248 was 59049, checked in by vboxsync, 9 years ago

HBDMgmt-darwin.cpp: Log more details about the error if available

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.6 KB
Line 
1/* $Id: HBDMgmt-darwin.cpp 59049 2015-12-08 10:50:26Z vboxsync $ */
2/** @file
3 * VBox storage devices: Host block device management API - darwin specifics.
4 */
5
6/*
7 * Copyright (C) 2015 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 * Callback notifying us that the async DADiskClaim()/DADiskUnmount call has completed.
204 *
205 * @param hDiskRef The disk that was attempted claimed / unmounted.
206 * @param hDissenterRef NULL on success, contains details on failure.
207 * @param pvContext Pointer to the return code variable.
208 */
209static DECLCALLBACK(void) hbdMgrDACallbackComplete(DADiskRef hDiskRef, DADissenterRef hDissenterRef, void *pvContext)
210{
211 PHBDMGRDACLBKARGS pArgs = (PHBDMGRDACLBKARGS)pvContext;
212 pArgs->pszErrDetail = NULL;
213
214 if (!hDissenterRef)
215 pArgs->rcDA = kDAReturnSuccess;
216 else
217 {
218 CFStringRef hStrErr = DADissenterGetStatusString(hDissenterRef);
219 if (hStrErr)
220 {
221 const char *pszErrDetail = CFStringGetCStringPtr(hStrErr, kCFStringEncodingUTF8);
222 if (pszErrDetail)
223 pArgs->pszErrDetail = RTStrDup(pszErrDetail);
224 CFRelease(hStrErr);
225 }
226 pArgs->rcDA = DADissenterGetStatus(hDissenterRef);
227
228 }
229 RTSemEventSignal(pArgs->pThis->hEvtCallback);
230}
231
232/**
233 * Callback notifying us about any attempt to mount a volume. If we claimed the volume
234 * or the complete disk containing the volume we will deny the attempt.
235 *
236 * @returns Reference to a DADissenter object which contains the result.
237 * @param hDiskRef The disk that is about to be mounted.
238 * @param pvCOntext Pointer to the block device manager.
239 */
240static DECLCALLBACK(DADissenterRef) hbdMgrDAMountApprovalCallback(DADiskRef hDiskRef, void *pvContext)
241{
242 PHBDMGRINT pThis = (PHBDMGRINT)pvContext;
243 DADiskRef hDiskParentRef = DADiskCopyWholeDisk(hDiskRef);
244 const char *pszBSDName = DADiskGetBSDName(hDiskRef);
245 const char *pszBSDNameParent = hDiskParentRef ? DADiskGetBSDName(hDiskParentRef) : NULL;
246 DADissenterRef hDissenterRef = NULL;
247
248 RTSemFastMutexRequest(pThis->hMtxList);
249 PHBDMGRDEV pIt;
250 RTListForEach(&pThis->ListClaimed, pIt, HBDMGRDEV, ListNode)
251 {
252 const char *pszBSDNameCur = DADiskGetBSDName(pIt->hDiskRef);
253 /*
254 * Prevent mounting any volume we have in use. This applies to the case
255 * where we have the whole disk occupied but a single volume is about to be
256 * mounted.
257 */
258 if ( !RTStrCmp(pszBSDNameCur, pszBSDName)
259 || ( pszBSDNameParent
260 && !RTStrCmp(pszBSDNameParent, pszBSDNameCur)))
261 {
262 CFStringRef hStrReason = CFStringCreateWithCString(kCFAllocatorDefault, "The disk is currently in use by VirtualBox and cannot be mounted", kCFStringEncodingUTF8);
263 hDissenterRef = DADissenterCreate(kCFAllocatorDefault, kDAReturnExclusiveAccess, hStrReason);
264 break;
265 }
266 }
267
268 RTSemFastMutexRelease(pThis->hMtxList);
269
270 if (hDiskParentRef)
271 CFRelease(hDiskParentRef);
272 return hDissenterRef;
273}
274
275
276/**
277 * Dummy handler for the wakeup source to kick the worker thread.
278 *
279 * @returns nothing.
280 * @param pInfo Opaque user data given during source creation, unused.
281 */
282static DECLCALLBACK(void) hbdMgrDAPerformWakeup(void *pInfo)
283{
284 return;
285}
286
287
288/**
289 * Worker function of the thread processing messages from the Disk Arbitration daemon.
290 *
291 * @returns IPRT status code.
292 * @param hThreadSelf The thread handle.
293 * @param pvUser Opaque user data, the block device manager instance.
294 */
295static DECLCALLBACK(int) hbdMgrDAWorker(RTTHREAD hThreadSelf, void *pvUser)
296{
297 PHBDMGRINT pThis = (PHBDMGRINT)pvUser;
298
299 /* Provide the runloop reference. */
300 pThis->hRunLoopRef = CFRunLoopGetCurrent();
301 RTThreadUserSignal(hThreadSelf);
302
303 /* Add the wake source to our runloop so we get notified about state changes. */
304 CFRunLoopAddSource(pThis->hRunLoopRef, pThis->hRunLoopSrcWakeRef, kCFRunLoopCommonModes);
305
306 /* Do what we are here for. */
307 while (ASMAtomicReadBool(&pThis->fRunning))
308 {
309 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10.0, true);
310 }
311
312 /* Remove the wakeup source form our runloop. */
313 CFRunLoopRemoveSource(pThis->hRunLoopRef, pThis->hRunLoopSrcWakeRef, kCFRunLoopCommonModes);
314
315 return VINF_SUCCESS;
316}
317
318DECLHIDDEN(int) HBDMgrCreate(PHBDMGR phHbdMgr)
319{
320 AssertPtrReturn(phHbdMgr, VERR_INVALID_POINTER);
321
322 PHBDMGRINT pThis = (PHBDMGRINT)RTMemAllocZ(sizeof(HBDMGRINT));
323 if (RT_UNLIKELY(!pThis))
324 return VERR_NO_MEMORY;
325
326 int rc = VINF_SUCCESS;
327 RTListInit(&pThis->ListClaimed);
328 pThis->fRunning = true;
329 pThis->hSessionRef = DASessionCreate(kCFAllocatorDefault);
330 if (pThis->hSessionRef)
331 {
332 rc = RTSemFastMutexCreate(&pThis->hMtxList);
333 if (RT_SUCCESS(rc))
334 {
335 rc = RTSemEventCreate(&pThis->hEvtCallback);
336 if (RT_SUCCESS(rc))
337 {
338 CFRunLoopSourceContext CtxRunLoopSource;
339 CtxRunLoopSource.version = 0;
340 CtxRunLoopSource.info = NULL;
341 CtxRunLoopSource.retain = NULL;
342 CtxRunLoopSource.release = NULL;
343 CtxRunLoopSource.copyDescription = NULL;
344 CtxRunLoopSource.equal = NULL;
345 CtxRunLoopSource.hash = NULL;
346 CtxRunLoopSource.schedule = NULL;
347 CtxRunLoopSource.cancel = NULL;
348 CtxRunLoopSource.perform = hbdMgrDAPerformWakeup;
349 pThis->hRunLoopSrcWakeRef = CFRunLoopSourceCreate(NULL, 0, &CtxRunLoopSource);
350 if (CFRunLoopSourceIsValid(pThis->hRunLoopSrcWakeRef))
351 {
352 rc = RTThreadCreate(&pThis->hThrdDAEvts, hbdMgrDAWorker, pThis, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "HbdDA-Wrk");
353 if (RT_SUCCESS(rc))
354 {
355 /* Wait for the thread to start up and provide the runloop reference. */
356 rc = RTThreadUserWait(pThis->hThrdDAEvts, RT_INDEFINITE_WAIT);
357 AssertRC(rc);
358 AssertPtr(pThis->hRunLoopRef);
359
360 DARegisterDiskMountApprovalCallback(pThis->hSessionRef, NULL, hbdMgrDAMountApprovalCallback, pThis);
361 DASessionScheduleWithRunLoop(pThis->hSessionRef, pThis->hRunLoopRef, kCFRunLoopDefaultMode);
362 *phHbdMgr = pThis;
363 return VINF_SUCCESS;
364 }
365 CFRelease(pThis->hRunLoopSrcWakeRef);
366 }
367 }
368
369 RTSemFastMutexDestroy(pThis->hMtxList);
370 }
371
372 CFRelease(pThis->hSessionRef);
373 }
374 else
375 rc = VERR_NO_MEMORY;
376
377 RTMemFree(pThis);
378 return rc;
379}
380
381
382DECLHIDDEN(void) HBDMgrDestroy(HBDMGR hHbdMgr)
383{
384 PHBDMGRINT pThis = hHbdMgr;
385 AssertPtrReturnVoid(pThis);
386
387 /* Unregister the mount approval and DA session from the runloop. */
388 DASessionUnscheduleFromRunLoop(pThis->hSessionRef, pThis->hRunLoopRef, kCFRunLoopDefaultMode);
389 DAUnregisterApprovalCallback(pThis->hSessionRef, (void *)hbdMgrDAMountApprovalCallback, pThis);
390
391 /* Kick the worker thread to exit. */
392 ASMAtomicXchgBool(&pThis->fRunning, false);
393 CFRunLoopSourceSignal(pThis->hRunLoopSrcWakeRef);
394 CFRunLoopWakeUp(pThis->hRunLoopRef);
395 int rcThrd = VINF_SUCCESS;
396 int rc = RTThreadWait(pThis->hThrdDAEvts, RT_INDEFINITE_WAIT, &rcThrd);
397 AssertRC(rc); AssertRC(rcThrd);
398
399 CFRelease(pThis->hRunLoopSrcWakeRef);
400
401 /* Go through all claimed block devices and release them. */
402 RTSemFastMutexRequest(pThis->hMtxList);
403 PHBDMGRDEV pIt, pItNext;
404 RTListForEachSafe(&pThis->ListClaimed, pIt, pItNext, HBDMGRDEV, ListNode)
405 {
406 hbdMgrDevUnclaim(pIt);
407 }
408 RTSemFastMutexRelease(pThis->hMtxList);
409
410 CFRelease(pThis->hSessionRef);
411 RTSemFastMutexDestroy(pThis->hMtxList);
412 RTSemEventDestroy(pThis->hEvtCallback);
413 RTMemFree(pThis);
414}
415
416
417DECLHIDDEN(bool) HBDMgrIsBlockDevice(const char *pszFilename)
418{
419 bool fIsBlockDevice = RTStrNCmp(pszFilename, "/dev/disk", sizeof("/dev/disk") - 1) == 0 ? true : false;
420 if (!fIsBlockDevice)
421 fIsBlockDevice = RTStrNCmp(pszFilename, "/dev/rdisk", sizeof("/dev/rdisk") - 1) == 0 ? true : false;
422 return fIsBlockDevice;
423}
424
425
426DECLHIDDEN(int) HBDMgrClaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
427{
428 PHBDMGRINT pThis = hHbdMgr;
429 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
430 AssertReturn(HBDMgrIsBlockDevice(pszFilename), VERR_INVALID_PARAMETER);
431
432 int rc = VINF_SUCCESS;
433 PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
434 if (!pDev)
435 {
436 DADiskRef hDiskRef = DADiskCreateFromBSDName(kCFAllocatorDefault, pThis->hSessionRef, pszFilename);
437 if (hDiskRef)
438 {
439 HBDMGRDACLBKARGS CalllbackArgs;
440 CalllbackArgs.pThis = pThis;
441 CalllbackArgs.rcDA = kDAReturnSuccess;
442
443 /* Claim the device. */
444 DADiskClaim(hDiskRef, kDADiskClaimOptionDefault, NULL, NULL, hbdMgrDACallbackComplete, &CalllbackArgs);
445 rc = RTSemEventWait(pThis->hEvtCallback, 120 * RT_MS_1SEC);
446 if ( RT_SUCCESS(rc)
447 && CalllbackArgs.rcDA == kDAReturnSuccess)
448 {
449 /* Unmount anything which might be mounted. */
450 DADiskUnmount(hDiskRef, kDADiskUnmountOptionWhole, hbdMgrDACallbackComplete, &CalllbackArgs);
451 rc = RTSemEventWait(pThis->hEvtCallback, 120 * RT_MS_1SEC);
452 if ( RT_SUCCESS(rc)
453 && ( CalllbackArgs.rcDA == kDAReturnSuccess
454 || CalllbackArgs.rcDA == kDAReturnNotMounted))
455 {
456 pDev = (PHBDMGRDEV)RTMemAllocZ(sizeof(HBDMGRDEV));
457 if (RT_LIKELY(pDev))
458 {
459 pDev->hDiskRef = hDiskRef;
460 RTSemFastMutexRequest(pThis->hMtxList);
461 RTListAppend(&pThis->ListClaimed, &pDev->ListNode);
462 RTSemFastMutexRelease(pThis->hMtxList);
463 rc = VINF_SUCCESS;
464 }
465 else
466 rc = VERR_NO_MEMORY;
467 }
468 else if (RT_SUCCESS(rc))
469 {
470 rc = hbdMgrDAReturn2VBoxStatus(CalllbackArgs.rcDA);
471 LogRel(("HBDMgrClaimBlockDevice: DADiskUnmount(\"%s\") failed with %Rrc (%s)\n",
472 pszFilename, rc, CalllbackArgs.pszErrDetail ? CalllbackArgs.pszErrDetail : "<no detail>"));
473 if (CalllbackArgs.pszErrDetail)
474 RTStrFree(CalllbackArgs.pszErrDetail);
475 }
476 }
477 else if (RT_SUCCESS(rc))
478 {
479 rc = hbdMgrDAReturn2VBoxStatus(CalllbackArgs.rcDA);
480 LogRel(("HBDMgrClaimBlockDevice: DADiskClaim(\"%s\") failed with %Rrc (%s)\n",
481 pszFilename, rc, CalllbackArgs.pszErrDetail ? CalllbackArgs.pszErrDetail : "<no detail>"));
482 if (CalllbackArgs.pszErrDetail)
483 RTStrFree(CalllbackArgs.pszErrDetail);
484 }
485 if (RT_FAILURE(rc))
486 CFRelease(hDiskRef);
487 }
488 else
489 rc = VERR_NO_MEMORY;
490 }
491 else
492 rc = VERR_ALREADY_EXISTS;
493
494 return rc;
495}
496
497
498DECLHIDDEN(int) HBDMgrUnclaimBlockDevice(HBDMGR hHbdMgr, const char *pszFilename)
499{
500 PHBDMGRINT pThis = hHbdMgr;
501 AssertPtrReturn(pThis, VERR_INVALID_HANDLE);
502
503 RTSemFastMutexRequest(pThis->hMtxList);
504 int rc = VINF_SUCCESS;
505 PHBDMGRDEV pDev = hbdMgrDevFindByName(pThis, pszFilename);
506 if (pDev)
507 hbdMgrDevUnclaim(pDev);
508 else
509 rc = VERR_NOT_FOUND;
510 RTSemFastMutexRelease(pThis->hMtxList);
511
512 return rc;
513}
514
515
516DECLHIDDEN(bool) HBDMgrIsBlockDeviceClaimed(HBDMGR hHbdMgr, const char *pszFilename)
517{
518 PHBDMGRINT pThis = hHbdMgr;
519 AssertPtrReturn(pThis, false);
520
521 RTSemFastMutexRequest(pThis->hMtxList);
522 PHBDMGRDEV pIt = hbdMgrDevFindByName(pThis, pszFilename);
523 RTSemFastMutexRelease(pThis->hMtxList);
524
525 return pIt ? true : false;
526}
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