2  * Copyright (c) 2016 Apple Inc. All Rights Reserved. 
   4  * @APPLE_LICENSE_HEADER_START@ 
   6  * This file contains Original Code and/or Modifications of Original Code 
   7  * as defined in and that are subject to the Apple Public Source License 
   8  * Version 2.0 (the 'License'). You may not use this file except in 
   9  * compliance with the License. Please obtain a copy of the License at 
  10  * http://www.opensource.apple.com/apsl/ and read it before using this 
  13  * The Original Code and all software distributed under the License are 
  14  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 
  15  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 
  16  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 
  17  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 
  18  * Please see the License for the specific language governing rights and 
  19  * limitations under the License. 
  21  * @APPLE_LICENSE_HEADER_END@ 
  26 #include <dispatch/dispatch.h> 
  27 #include <DiskArbitration/DiskArbitration.h> 
  29 #include <security_utilities/logging.h> 
  30 #include <security_utilities/cfutilities.h> 
  31 #include <security_utilities/unix++.h> 
  33 #include "SecTranslocateDANotification.hpp" 
  34 #include "SecTranslocateShared.hpp" 
  35 #include "SecTranslocateUtilities.hpp" 
  37 #define DA_FRAMEWORK_PATH "/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration" 
  40 namespace SecTranslocate 
{ 
  42 typedef CFDictionaryRef (*DiskCopyDescription_t
)(DADiskRef
); 
  43 typedef DASessionRef (*SessionCreate_t
) (CFAllocatorRef
); 
  44 typedef void (*SessionSetDispatchQueue_t
)(DASessionRef
,dispatch_queue_t
); 
  45 typedef void (*RegisterDiskDisappearedCallback_t
) (DASessionRef
, CFDictionaryRef
, DADiskDisappearedCallback
, void*); 
  46 typedef void (*RegisterDiskUnmountApprovalCallback_t
) (DASessionRef
, CFDictionaryRef
, DADiskUnmountApprovalCallback
, void*); 
  47 typedef void (*UnregisterCallback_t
)(DASessionRef
, void*, void*); 
  49 class DiskArbitrationProxy
 
  52     static DiskArbitrationProxy
* get(); 
  54     inline CFDictionaryRef 
diskCopyDescription(DADiskRef disk
) const 
  55         { return pDiskCopyDescription 
? pDiskCopyDescription(disk
) : NULL
; }; 
  56     inline DASessionRef 
sessionCreate (CFAllocatorRef allocator
) const 
  57         { return pSessionCreate 
? pSessionCreate(allocator
) : NULL
; }; 
  58     inline void sessionSetDispatchQueue (DASessionRef s
, dispatch_queue_t q
) const 
  59         { if(pSessionSetDispatchQueue
) pSessionSetDispatchQueue(s
,q
); }; 
  60     inline void registerDiskDisappearedCallback (DASessionRef s
, CFDictionaryRef d
, DADiskDisappearedCallback c
, void* x
) const 
  61         { if(pRegisterDiskDisappearedCallback
) pRegisterDiskDisappearedCallback(s
,d
,c
,x
); }; 
  62     inline void registerDiskUnmountApprovalCallback (DASessionRef s
, CFDictionaryRef d
, DADiskUnmountApprovalCallback c
, void* x
) const 
  63         { if(pRegisterDiskUnmountApprovalCallback
) pRegisterDiskUnmountApprovalCallback(s
,d
,c
,x
); }; 
  64     inline void unregisterCallback (DASessionRef s
, void* c
, void* x
) const 
  65         { if(pUnregisterCallback
) pUnregisterCallback(s
,c
,x
); }; 
  66     inline CFDictionaryRef 
diskDescriptionMatchVolumeMountable() const 
  67         { return pDiskDescriptionMatchVolumeMountable 
? *pDiskDescriptionMatchVolumeMountable 
: NULL
; }; 
  68     inline CFStringRef 
diskDescriptionVolumePathKey() const 
  69         { return pDiskDescriptionVolumePathKey 
? *pDiskDescriptionVolumePathKey 
: NULL
; }; 
  72     DiskArbitrationProxy(); 
  75     DiskCopyDescription_t pDiskCopyDescription
; 
  76     SessionCreate_t pSessionCreate
; 
  77     SessionSetDispatchQueue_t pSessionSetDispatchQueue
; 
  78     RegisterDiskDisappearedCallback_t pRegisterDiskDisappearedCallback
; 
  79     RegisterDiskUnmountApprovalCallback_t pRegisterDiskUnmountApprovalCallback
; 
  80     UnregisterCallback_t pUnregisterCallback
; 
  81     CFDictionaryRef
* pDiskDescriptionMatchVolumeMountable
; 
  82     CFStringRef
* pDiskDescriptionVolumePathKey
; 
  85 DiskArbitrationProxy::DiskArbitrationProxy() 
  87     handle 
= checkedDlopen(DA_FRAMEWORK_PATH
, RTLD_LAZY 
| RTLD_NOLOAD
); 
  89     pDiskCopyDescription 
= (DiskCopyDescription_t
) checkedDlsym(handle
, "DADiskCopyDescription"); 
  90     pSessionCreate 
= (SessionCreate_t
) checkedDlsym(handle
, "DASessionCreate"); 
  92     pSessionSetDispatchQueue 
= (SessionSetDispatchQueue_t
) checkedDlsym(handle
, "DASessionSetDispatchQueue"); 
  93     pRegisterDiskDisappearedCallback 
= (RegisterDiskDisappearedCallback_t
) checkedDlsym(handle
, "DARegisterDiskDisappearedCallback"); 
  94     pRegisterDiskUnmountApprovalCallback 
= (RegisterDiskUnmountApprovalCallback_t
) checkedDlsym(handle
, "DARegisterDiskUnmountApprovalCallback"); 
  95     pUnregisterCallback 
= (UnregisterCallback_t
) checkedDlsym(handle
, "DAUnregisterCallback"); 
  96     pDiskDescriptionMatchVolumeMountable 
= (CFDictionaryRef
*) checkedDlsym(handle
, "kDADiskDescriptionMatchVolumeMountable"); 
  97     pDiskDescriptionVolumePathKey 
= (CFStringRef
*) checkedDlsym(handle
, "kDADiskDescriptionVolumePathKey"); 
 100 DiskArbitrationProxy
* DiskArbitrationProxy::get() 
 102     static dispatch_once_t initialized
; 
 103     static DiskArbitrationProxy
* me 
= NULL
; 
 104     __block exception_ptr 
exception(0); 
 106     dispatch_once(&initialized
, ^{ 
 109             me 
= new DiskArbitrationProxy(); 
 113             Syslog::critical("SecTranslocate: error while creating DiskArbitrationProxy"); 
 114             exception 
= current_exception(); 
 122             rethrow_exception(exception
); //already logged in this case 
 126             Syslog::critical("SecTranslocate: DiskArbitrationProxy initialization has failed"); 
 127             UnixError::throwMe(EINVAL
); 
 134  For Disk Arbitration need to 
 135  1. create a session and hold on to it. 
 136  2. associate it with a queue 
 137  3. register for call backs  (DADiskDisappearedCallback and DADiskUnmountApprovalCallback) 
 138  4. provide a function to get the mounton path from DADiskref 
 139  5. Return a dissenter if unmount is gonna fail because something is in use (i.e. if my unmount fails) 
 142 /* Returns false if we failed an unmount call. anything else returns true */ 
 143 static bool cleanupDisksOnVolume(DADiskRef disk
) 
 149         DiskArbitrationProxy 
*dap 
= DiskArbitrationProxy::get(); 
 150         CFRef
<CFDictionaryRef
> dict 
= dap
->diskCopyDescription(disk
); 
 154             Syslog::error("SecTranslocate:disk cleanup, failed to get disk description"); 
 155             UnixError::throwMe(EINVAL
); 
 158         CFURLRef fspath 
= (CFURLRef
)CFDictionaryGetValue(dict
, dap
->diskDescriptionVolumePathKey()); 
 162             //For the disk disappeared call back, it looks like we won't get a volume path so we'll keep the empty string 
 163             fspathString 
= cfString(fspath
); 
 166         result 
= destroyTranslocatedPathsForUserOnVolume(fspathString
); 
 170         // This function is called from inside a DiskArbitration callback so we need to consume the exception 
 171         // more specific errors are assumed to be logged by the thrower 
 172         Syslog::error("SecTranslocate: DiskArbitration callback: failed to clean up mountpoint(s) related to volume: %s", 
 173                       fspathString
.empty() ? "unknown" : fspathString
.c_str()); 
 179 static void diskDisappearedCallback(DADiskRef disk
, void* context
) 
 181     (void)cleanupDisksOnVolume(disk
); 
 184 static DADissenterRef 
unmountApprovalCallback(DADiskRef disk
, void *context
) 
 186     (void)cleanupDisksOnVolume(disk
); 
 187     return NULL
; //For now, we won't raise a dissent, just let the unmount fail. The dissent text would get used by UI. 
 190 DANotificationMonitor::DANotificationMonitor(dispatch_queue_t q
) 
 192     DiskArbitrationProxy 
*dap 
= DiskArbitrationProxy::get(); 
 195         Syslog::critical("SecTranslocate::DANotificationMonitor initialized without a queue."); 
 196         UnixError::throwMe(EINVAL
); 
 199     diskArbitrationSession 
= dap
->sessionCreate(kCFAllocatorDefault
); 
 200     if(!diskArbitrationSession
) 
 202         Syslog::critical("SecTranslocate: Failed to create the disk arbitration session"); 
 203         UnixError::throwMe(ENOMEM
); 
 206     dap
->sessionSetDispatchQueue(diskArbitrationSession
, q
); 
 207     /* register so we can cleanup from force unmounts */ 
 208     dap
->registerDiskDisappearedCallback( diskArbitrationSession
, dap
->diskDescriptionMatchVolumeMountable(), diskDisappearedCallback
, NULL 
); 
 209     /* register so we can clean up pre-unmount */ 
 210     dap
->registerDiskUnmountApprovalCallback( diskArbitrationSession
, dap
->diskDescriptionMatchVolumeMountable(), unmountApprovalCallback
, NULL 
); 
 213 DANotificationMonitor::~DANotificationMonitor() 
 215     DiskArbitrationProxy::get()->unregisterCallback(diskArbitrationSession
,(void*)diskDisappearedCallback
, NULL
); 
 216     DiskArbitrationProxy::get()->unregisterCallback(diskArbitrationSession
,(void*)unmountApprovalCallback
, NULL
); 
 217     CFRelease(diskArbitrationSession
); 
 220 } //namespace SecTranslocate 
 221 } //namespace Security