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
{
43 SCT_DA_DISK_DISAPPEARED
,
44 SCT_DA_DISK_UNMOUNT_APPROVAL
,
45 SCT_DA_DISK_DESCRIPTION_CHANGE
46 } SecTranslocateDACallbackType_t
;
48 typedef CFDictionaryRef (*DiskCopyDescription_t
)(DADiskRef
);
49 typedef DASessionRef (*SessionCreate_t
) (CFAllocatorRef
);
50 typedef void (*SessionSetDispatchQueue_t
)(DASessionRef
,dispatch_queue_t
);
51 typedef void (*RegisterDiskDisappearedCallback_t
) (DASessionRef
, CFDictionaryRef
, DADiskDisappearedCallback
, void*);
52 typedef void (*RegisterDiskUnmountApprovalCallback_t
) (DASessionRef
, CFDictionaryRef
, DADiskUnmountApprovalCallback
, void*);
53 typedef void (*RegisterDiskDescriptionChangedCallback_t
) (DASessionRef
, CFDictionaryRef
, CFArrayRef
,
54 DADiskDescriptionChangedCallback
, void*);
55 typedef void (*UnregisterCallback_t
)(DASessionRef
, void*, void*);
57 class DiskArbitrationProxy
60 static DiskArbitrationProxy
* get();
62 inline CFDictionaryRef
diskCopyDescription(DADiskRef disk
) const
63 { return pDiskCopyDescription
? pDiskCopyDescription(disk
) : NULL
; };
64 inline DASessionRef
sessionCreate (CFAllocatorRef allocator
) const
65 { return pSessionCreate
? pSessionCreate(allocator
) : NULL
; };
66 inline void sessionSetDispatchQueue (DASessionRef s
, dispatch_queue_t q
) const
67 { if(pSessionSetDispatchQueue
) pSessionSetDispatchQueue(s
,q
); };
68 inline void registerDiskDisappearedCallback (DASessionRef s
, CFDictionaryRef d
, DADiskDisappearedCallback c
, void* x
) const
69 { if(pRegisterDiskDisappearedCallback
) pRegisterDiskDisappearedCallback(s
,d
,c
,x
); };
70 inline void registerDiskUnmountApprovalCallback (DASessionRef s
, CFDictionaryRef d
, DADiskUnmountApprovalCallback c
, void* x
) const
71 { if(pRegisterDiskUnmountApprovalCallback
) pRegisterDiskUnmountApprovalCallback(s
,d
,c
,x
); };
72 inline void registerDiskDescriptionChangedCallback (DASessionRef s
, CFDictionaryRef d
, CFArrayRef a
, DADiskDescriptionChangedCallback c
, void* x
) const
73 { if(pRegisterDiskDescriptionChangedCallback
) pRegisterDiskDescriptionChangedCallback(s
,d
,a
,c
,x
); };
74 inline void unregisterCallback (DASessionRef s
, void* c
, void* x
) const
75 { if(pUnregisterCallback
) pUnregisterCallback(s
,c
,x
); };
76 inline CFDictionaryRef
diskDescriptionMatchVolumeMountable() const
77 { return pDiskDescriptionMatchVolumeMountable
? *pDiskDescriptionMatchVolumeMountable
: NULL
; };
78 inline CFStringRef
diskDescriptionVolumePathKey() const
79 { return pDiskDescriptionVolumePathKey
? *pDiskDescriptionVolumePathKey
: NULL
; };
82 DiskArbitrationProxy();
85 DiskCopyDescription_t pDiskCopyDescription
;
86 SessionCreate_t pSessionCreate
;
87 SessionSetDispatchQueue_t pSessionSetDispatchQueue
;
88 RegisterDiskDisappearedCallback_t pRegisterDiskDisappearedCallback
;
89 RegisterDiskUnmountApprovalCallback_t pRegisterDiskUnmountApprovalCallback
;
90 RegisterDiskDescriptionChangedCallback_t pRegisterDiskDescriptionChangedCallback
;
91 UnregisterCallback_t pUnregisterCallback
;
92 CFDictionaryRef
* pDiskDescriptionMatchVolumeMountable
;
93 CFStringRef
* pDiskDescriptionVolumePathKey
;
96 DiskArbitrationProxy::DiskArbitrationProxy()
98 handle
= checkedDlopen(DA_FRAMEWORK_PATH
, RTLD_LAZY
| RTLD_NOLOAD
);
100 pDiskCopyDescription
= (DiskCopyDescription_t
) checkedDlsym(handle
, "DADiskCopyDescription");
101 pSessionCreate
= (SessionCreate_t
) checkedDlsym(handle
, "DASessionCreate");
103 pSessionSetDispatchQueue
= (SessionSetDispatchQueue_t
) checkedDlsym(handle
, "DASessionSetDispatchQueue");
104 pRegisterDiskDisappearedCallback
= (RegisterDiskDisappearedCallback_t
) checkedDlsym(handle
, "DARegisterDiskDisappearedCallback");
105 pRegisterDiskUnmountApprovalCallback
= (RegisterDiskUnmountApprovalCallback_t
) checkedDlsym(handle
, "DARegisterDiskUnmountApprovalCallback");
106 pRegisterDiskDescriptionChangedCallback
= (RegisterDiskDescriptionChangedCallback_t
) checkedDlsym(handle
,"DARegisterDiskDescriptionChangedCallback");
107 pUnregisterCallback
= (UnregisterCallback_t
) checkedDlsym(handle
, "DAUnregisterCallback");
108 pDiskDescriptionMatchVolumeMountable
= (CFDictionaryRef
*) checkedDlsym(handle
, "kDADiskDescriptionMatchVolumeMountable");
109 pDiskDescriptionVolumePathKey
= (CFStringRef
*) checkedDlsym(handle
, "kDADiskDescriptionVolumePathKey");
112 DiskArbitrationProxy
* DiskArbitrationProxy::get()
114 static dispatch_once_t initialized
;
115 static DiskArbitrationProxy
* me
= NULL
;
116 __block exception_ptr
exception(0);
118 dispatch_once(&initialized
, ^{
121 me
= new DiskArbitrationProxy();
125 Syslog::critical("SecTranslocate: error while creating DiskArbitrationProxy");
126 exception
= current_exception();
134 rethrow_exception(exception
); //already logged in this case
138 Syslog::critical("SecTranslocate: DiskArbitrationProxy initialization has failed");
139 UnixError::throwMe(EINVAL
);
146 For Disk Arbitration need to
147 1. create a session and hold on to it.
148 2. associate it with a queue
149 3. register for call backs (DADiskDisappearedCallback, DADiskDescriptionChangedCallback and DADiskUnmountApprovalCallback)
150 4. provide a function to get the mounton path from DADiskref
151 5. Return a dissenter if unmount is gonna fail because something is in use (i.e. if my unmount fails)
154 /* Returns false if we failed an unmount call. anything else returns true */
155 static bool cleanupDisksOnVolume(DADiskRef disk
, SecTranslocateDACallbackType_t type
)
161 DiskArbitrationProxy
*dap
= DiskArbitrationProxy::get();
162 CFRef
<CFDictionaryRef
> dict
= dap
->diskCopyDescription(disk
);
166 Syslog::error("SecTranslocate:disk cleanup, failed to get disk description");
167 UnixError::throwMe(EINVAL
);
170 CFURLRef fspath
= (CFURLRef
)CFDictionaryGetValue(dict
, dap
->diskDescriptionVolumePathKey());
174 if (type
!= SCT_DA_DISK_DESCRIPTION_CHANGE
) {
175 //For the disk disappeared call back, it looks like we won't get a volume path so we'll keep the empty string
176 fspathString
= cfString(fspath
);
178 // For disk description changed, volume path key will be populated on mount and null on unmount
179 // we only care about unmount, so bail if there is a volume path key
184 result
= destroyTranslocatedPathsForUserOnVolume(fspathString
);
188 // This function is called from inside a DiskArbitration callback so we need to consume the exception
189 // more specific errors are assumed to be logged by the thrower
190 Syslog::error("SecTranslocate: DiskArbitration callback: failed to clean up mountpoint(s) related to volume: %s",
191 fspathString
.empty() ? "unknown" : fspathString
.c_str());
197 static void diskDisappearedCallback(DADiskRef disk
, void* __unused context
)
199 (void)cleanupDisksOnVolume(disk
, SCT_DA_DISK_DISAPPEARED
);
202 static DADissenterRef
unmountApprovalCallback(DADiskRef disk
, void* __unused context
)
204 (void)cleanupDisksOnVolume(disk
, SCT_DA_DISK_UNMOUNT_APPROVAL
);
205 return NULL
; //For now, we won't raise a dissent, just let the unmount fail. The dissent text would get used by UI.
208 static void diskDescriptionChangedCallback(DADiskRef disk
, CFArrayRef __unused daKeys
, void* __unused context
)
210 (void)cleanupDisksOnVolume(disk
, SCT_DA_DISK_DESCRIPTION_CHANGE
);
213 DANotificationMonitor::DANotificationMonitor(dispatch_queue_t q
)
215 DiskArbitrationProxy
*dap
= DiskArbitrationProxy::get();
218 Syslog::critical("SecTranslocate::DANotificationMonitor initialized without a queue.");
219 UnixError::throwMe(EINVAL
);
222 diskArbitrationSession
= dap
->sessionCreate(kCFAllocatorDefault
);
223 if(!diskArbitrationSession
)
225 Syslog::critical("SecTranslocate: Failed to create the disk arbitration session");
226 UnixError::throwMe(ENOMEM
);
229 dap
->sessionSetDispatchQueue(diskArbitrationSession
, q
);
230 /* register so we can cleanup from force unmounts */
231 dap
->registerDiskDisappearedCallback( diskArbitrationSession
, dap
->diskDescriptionMatchVolumeMountable(), diskDisappearedCallback
, NULL
);
232 dap
->registerDiskDescriptionChangedCallback(diskArbitrationSession
, dap
->diskDescriptionMatchVolumeMountable(), NULL
, diskDescriptionChangedCallback
, NULL
);
233 /* register so we can clean up pre-unmount */
234 dap
->registerDiskUnmountApprovalCallback( diskArbitrationSession
, dap
->diskDescriptionMatchVolumeMountable(), unmountApprovalCallback
, NULL
);
237 DANotificationMonitor::~DANotificationMonitor()
239 DiskArbitrationProxy::get()->unregisterCallback(diskArbitrationSession
,(void*)diskDisappearedCallback
, NULL
);
240 DiskArbitrationProxy::get()->unregisterCallback(diskArbitrationSession
,(void*)unmountApprovalCallback
, NULL
);
241 CFRelease(diskArbitrationSession
);
244 } //namespace SecTranslocate
245 } //namespace Security