VirtualBox

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

Last change on this file since 56992 was 56967, checked in by vboxsync, 9 years ago

Devices/Storage: Keep track of used host block devices and prevent the host from opening any device while it is still in use by VBox on OS X

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