1 /* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */
2 /*
3  * This file is part of libaacs
4  * Copyright (C) 2009-2010  Obliter0n
5  * Copyright (C) 2010-2015  npzacs
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library. If not, see
19  * <http://www.gnu.org/licenses/>.
20  */
21 
22 #if HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 
26 #include "mmc_device.h"
27 
28 #include "util/logging.h"
29 #include "util/macro.h"
30 #include "util/strutl.h"
31 
32 #include <stdlib.h>
33 #include <string.h>
34 
35 #include <IOKit/IOKitLib.h>
36 #include <IOKit/IOBSD.h>
37 #include <IOKit/IOCFPlugIn.h>
38 
39 #include <DiskArbitration/DiskArbitration.h>
40 
41 /* need to undefine VERSION as one of the members of struct
42    SCSICmd_INQUIRY_StandardData is named VERSION (see
43    IOKit/scsi/SCSICmds_INQUIRY_Definitions.h) */
44 #undef VERSION
45 #include <IOKit/scsi/SCSITaskLib.h>
46 
47 #include <IOKit/storage/IOBDMediaBSDClient.h>
48 
49 #ifdef HAVE_SYS_TYPES_H
50 #include <sys/types.h>
51 #endif
52 
53 #ifdef HAVE_SYS_PARAM_H
54 #include <sys/param.h>
55 #endif
56 
57 #ifdef HAVE_SYS_MOUNT_H
58 #include <sys/mount.h>
59 #endif
60 
61 #ifdef HAVE_LIBGEN_H
62 #include <libgen.h>
63 #endif
64 
65 #ifdef HAVE_LIMITS_H
66 #include <limits.h>
67 #endif
68 
69 
70 /*
71  *
72  */
73 enum disk_state_e {
74     disk_mounted,
75     disk_unmounted,
76     disk_appeared,
77     disk_mounting
78 };
79 
80 struct mmcdev {
81     /* Interfaces required for low-level device communication */
82     IOCFPlugInInterface **plugInInterface;
83     MMCDeviceInterface **mmcInterface;
84     SCSITaskDeviceInterface **taskInterface;
85 
86     /* device short name (ie disk1) */
87     char bsd_name[MNAMELEN];
88 
89     /* for mounting/unmounting the disc */
90     DADiskRef disk;
91     DASessionRef session;
92     enum disk_state_e disk_state;
93     dispatch_semaphore_t sync_sem;
94     dispatch_queue_t background_queue;
95 };
96 
device_send_cmd(MMCDEV * mmc,const uint8_t * cmd,uint8_t * buf,size_t tx,size_t rx)97 int device_send_cmd(MMCDEV *mmc, const uint8_t *cmd, uint8_t *buf, size_t tx, size_t rx)
98 {
99     SCSITaskInterface **task = NULL;
100     SCSI_Sense_Data sense;
101     SCSITaskStatus status;
102     SCSITaskSGElement iov;
103     UInt8 direction;
104     UInt64 sent;
105     int rc;
106 
107     if (NULL == mmc->taskInterface) {
108         return 0;
109     }
110 
111     do {
112         task = (*mmc->taskInterface)->CreateSCSITask(mmc->taskInterface);
113         if (NULL == task) {
114             BD_DEBUG(DBG_MMC, "Could not create SCSI Task\n");
115             break;
116         }
117 
118         iov.address = (uintptr_t) buf;
119         iov.length  = tx ? tx : rx;
120 
121         if (buf) {
122             direction = tx ? kSCSIDataTransfer_FromInitiatorToTarget :
123                 kSCSIDataTransfer_FromTargetToInitiator;
124         } else {
125             direction = kSCSIDataTransfer_NoDataTransfer;
126         }
127 
128         SCSICommandDescriptorBlock cdb = {0};
129         memcpy(cdb, cmd, sizeof(cdb));
130 
131         rc = (*task)->SetCommandDescriptorBlock(task, cdb, kSCSICDBSize_16Byte);
132         if (kIOReturnSuccess != rc) {
133             BD_DEBUG(DBG_MMC, "Error setting SCSI command\n");
134             break;
135         }
136 
137         rc = (*task)->SetScatterGatherEntries(task, &iov, 1, iov.length, direction);
138         if (kIOReturnSuccess != rc) {
139             BD_DEBUG(DBG_MMC, "Error setting SCSI scatter gather entries\n");
140             break;
141         }
142 
143         rc = (*task)->SetTimeoutDuration(task, 5000000);
144         if (kIOReturnSuccess != rc) {
145             BD_DEBUG(DBG_MMC, "Error setting SCSI command timeout\n");
146             break;
147         }
148 
149         memset(&sense, 0, sizeof (sense));
150 
151         rc = (*task)->ExecuteTaskSync(task, &sense, &status, &sent);
152 
153         char str[512];
154         BD_DEBUG(DBG_MMC, "Send SCSI MMC cmd %s:\n", str_print_hex(str, cmd, 16));
155         if (tx) {
156             BD_DEBUG(DBG_MMC, "  Buffer: %s ->\n", str_print_hex(str, buf, tx>255?255:tx));
157         } else {
158             BD_DEBUG(DBG_MMC, "  Buffer: %s <-\n", str_print_hex(str, buf, rx>255?255:rx));
159         }
160 
161         if (kIOReturnSuccess != rc || status != 0) {
162             BD_DEBUG(DBG_MMC, "  Send failed!\n");
163             break;
164         } else {
165             BD_DEBUG(DBG_MMC, "  Send succeeded! sent = %lld status = %u. response = %x\n",
166                   (unsigned long long) sent, status, sense.VALID_RESPONSE_CODE);
167         }
168 
169         (*task)->Release(task);
170 
171         return 1;
172     } while (0);
173 
174     if (task) {
175         (*task)->Release(task);
176     }
177 
178     return 0;
179 }
180 
get_mounted_device_from_path(MMCDEV * mmc,const char * path)181 static int get_mounted_device_from_path(MMCDEV *mmc, const char *path) {
182   struct statfs stat_info;
183   int rc;
184 
185   rc = statfs(path, &stat_info);
186   if (0 != rc) {
187     return rc;
188   }
189 
190   strncpy(mmc->bsd_name, basename (stat_info.f_mntfromname), sizeof (mmc->bsd_name));
191 
192   return 0;
193 }
194 
iokit_unmount_complete(DADiskRef disk,DADissenterRef dissenter,void * context)195 static void iokit_unmount_complete(DADiskRef disk, DADissenterRef dissenter,
196                                    void *context) {
197     (void)disk; /* suppress warning */
198     MMCDEV *mmc = context;
199 
200     if (dissenter) {
201         BD_DEBUG(DBG_MMC, "Could not unmount the disc\n");
202     } else {
203         BD_DEBUG(DBG_MMC, "Disc unmounted\n");
204         mmc->disk_state = disk_unmounted;
205     }
206     dispatch_semaphore_signal(mmc->sync_sem);
207 }
208 
iokit_mount_complete(DADiskRef disk,DADissenterRef dissenter,void * context)209 static void iokit_mount_complete(DADiskRef disk, DADissenterRef dissenter,
210                                  void *context) {
211     (void) disk; /* suppress warning */
212     MMCDEV *mmc = context;
213 
214     if (dissenter) {
215         DAReturn code = DADissenterGetStatus(dissenter);
216         BD_DEBUG(DBG_MMC, "Could not mount the disc (%8X)\n", code);
217         mmc->disk_state = disk_unmounted;
218     } else {
219         BD_DEBUG(DBG_MMC, "Disc mounted\n");
220         mmc->disk_state = disk_mounted;
221     }
222     dispatch_semaphore_signal(mmc->sync_sem);
223 }
224 
225 /* Unmount the disk at mmc->disk
226  * Note: This MAY NOT be called on the background queue,
227  *       as that would lead to a deadlock.
228  */
iokit_unmount(MMCDEV * mmc)229 static int iokit_unmount(MMCDEV *mmc) {
230     if (disk_unmounted == mmc->disk_state) {
231         return 0; /* nothing to do */
232     }
233 
234     BD_DEBUG(DBG_MMC, "Unmounting disk\n");
235 
236     DADiskUnmount(mmc->disk, kDADiskUnmountOptionForce, iokit_unmount_complete, mmc);
237     dispatch_semaphore_wait(mmc->sync_sem, DISPATCH_TIME_FOREVER);
238 
239     return (mmc->disk_state == disk_unmounted) ? 0 : -1;
240 }
241 
242 /* Mount the disk at mmc->disk
243  * Note: This MAY NOT be called on the background queue,
244  *       as that would lead to a deadlock.
245  */
iokit_mount(MMCDEV * mmc)246 static int iokit_mount(MMCDEV *mmc) {
247     if (disk_mounted != mmc->disk_state) {
248         if (mmc->disk && mmc->session) {
249             mmc->disk_state = disk_mounting;
250             DADiskMount(mmc->disk, NULL, kDADiskMountOptionDefault, iokit_mount_complete, mmc);
251             dispatch_semaphore_wait(mmc->sync_sem, DISPATCH_TIME_FOREVER);
252         }
253     }
254     return (mmc->disk_state == disk_unmounted) ? 0 : -1;
255 }
256 
iokit_find_service_matching(MMCDEV * mmc,io_service_t * servp)257 static int iokit_find_service_matching(MMCDEV *mmc, io_service_t *servp) {
258     CFMutableDictionaryRef matchingDict = IOServiceMatching("IOBDServices");
259     io_iterator_t deviceIterator;
260     io_service_t service;
261     int rc;
262 
263     assert(NULL != servp);
264 
265     *servp = IO_OBJECT_NULL;
266 
267     if (!matchingDict) {
268         BD_DEBUG(DBG_MMC, "Could not create a matching dictionary for IOBDServices\n");
269         return -1;
270     }
271 
272     /* this call consumes the reference to the matchingDict. we do not need to release it */
273     rc = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &deviceIterator);
274     if (kIOReturnSuccess != rc) {
275         BD_DEBUG(DBG_MMC, "Could not create device iterator\n");
276         return -1;
277     }
278 
279     while (0 != (service = IOIteratorNext(deviceIterator))) {
280         CFStringRef data;
281         char name[MNAMELEN] = "";
282 
283         data = IORegistryEntrySearchCFProperty(service, kIOServicePlane, CFSTR(kIOBSDNameKey),
284                                                kCFAllocatorDefault, kIORegistryIterateRecursively);
285 
286         if (NULL != data) {
287             rc = CFStringGetCString(data, name, sizeof (name), kCFStringEncodingASCII);
288             CFRelease(data);
289             if (0 == strcmp(name, mmc->bsd_name)) {
290                 break;
291             }
292         }
293 
294         (void) IOObjectRelease(service);
295     }
296 
297     IOObjectRelease(deviceIterator);
298 
299     *servp = service;
300 
301     return (service != IO_OBJECT_NULL) ? 0 : -1;
302 }
303 
iokit_find_interfaces(MMCDEV * mmc,io_service_t service)304 static int iokit_find_interfaces(MMCDEV *mmc, io_service_t service) {
305     SInt32 score;
306     int rc;
307 
308     rc = IOCreatePlugInInterfaceForService(service, kIOMMCDeviceUserClientTypeID,
309                                            kIOCFPlugInInterfaceID, &mmc->plugInInterface,
310                                            &score);
311     if (kIOReturnSuccess != rc || NULL == mmc->plugInInterface) {
312         return -1;
313     }
314 
315     BD_DEBUG(DBG_MMC, "Getting MMC interface\n");
316     IOCFPlugInInterface **plugInInterface = mmc->plugInInterface;
317 
318     rc = (*plugInInterface)->QueryInterface(plugInInterface,
319                                             CFUUIDGetUUIDBytes(kIOMMCDeviceInterfaceID),
320                                             (LPVOID)&mmc->mmcInterface);
321 
322     if (kIOReturnSuccess != rc || NULL == mmc->mmcInterface) {
323         BD_DEBUG(DBG_MMC, "Could not get multimedia commands (MMC) interface\n");
324         return -1;
325     }
326 
327     BD_DEBUG(DBG_MMC, "Have an MMC interface (%p). Getting a SCSI task interface...\n", (void*)mmc->mmcInterface);
328 
329     mmc->taskInterface = (*mmc->mmcInterface)->GetSCSITaskDeviceInterface (mmc->mmcInterface);
330     if (NULL == mmc->taskInterface) {
331         BD_DEBUG(DBG_MMC, "Could not get SCSI task device interface\n");
332         return -1;
333     }
334 
335     return 0;
336 }
337 
iokit_mount_approval_cb(DADiskRef disk,void * context)338 static DADissenterRef iokit_mount_approval_cb(DADiskRef disk, void *context)
339 {
340     MMCDEV *mmc = context;
341 
342     /* If the disk state is mounted, there is nothing to do here. */
343     if (disk_mounted == mmc->disk_state) {
344         return NULL;
345     }
346 
347     /* Check if the disk that is to be mounted matches ours,
348      * if not, we do not need to reject mounting.
349      */
350     if (!CFEqual(disk, mmc->disk)) {
351         return NULL;
352     }
353 
354     BD_DEBUG(DBG_MMC, "Mount approval request for matching disc\n");
355 
356     /* When we are trying to mount, the mount approval callback is
357      * called too, so we need to allow mounting for ourselves here.
358      */
359     if (disk_mounting == mmc->disk_state) {
360         BD_DEBUG(DBG_MMC, "Allowing ourselves to mount\n");
361         return NULL;
362     }
363 
364     mmc->disk_state = disk_appeared;
365     dispatch_semaphore_signal(mmc->sync_sem);
366 
367     CFStringRef reason = CFSTR("Disk is going to be mounted libaacs");
368     return DADissenterCreate(kCFAllocatorDefault, kDAReturnBusy, reason);
369 }
370 
iokit_da_init(MMCDEV * mmc)371 static int iokit_da_init(MMCDEV *mmc) {
372     mmc->session = DASessionCreate(kCFAllocatorDefault);
373     if (NULL == mmc->session) {
374         BD_DEBUG(DBG_MMC | DBG_CRIT, "Could not create a disc arbitration session\n");
375         return -1;
376     }
377 
378     mmc->disk = DADiskCreateFromBSDName(kCFAllocatorDefault, mmc->session, mmc->bsd_name);
379     if (NULL == mmc->disk) {
380         BD_DEBUG(DBG_MMC | DBG_CRIT, "Could not create a disc arbitration disc for the device\n");
381         CFRelease(mmc->session);
382         mmc->session = NULL;
383         return -1;
384     }
385 
386     mmc->background_queue = dispatch_queue_create("org.videolan.libaacs", DISPATCH_QUEUE_SERIAL);
387     DASessionSetDispatchQueue(mmc->session, mmc->background_queue);
388 
389     // Register event callbacks
390     DARegisterDiskMountApprovalCallback(mmc->session, NULL, iokit_mount_approval_cb, mmc);
391 
392     mmc->sync_sem = dispatch_semaphore_create(0);
393 
394     return 0;
395 }
396 
iokit_da_destroy(MMCDEV * mmc)397 static void iokit_da_destroy(MMCDEV *mmc) {
398     if (mmc->session) {
399         /* The approval callback must be unregistered here, doing it in the
400          * mount approval callback instead after we got a matching disk would
401          * cause the OS to immediately re-try to mount the disk faster than we
402          * can mount it.
403          */
404         DAUnregisterApprovalCallback(mmc->session, iokit_mount_approval_cb, mmc);
405         DASessionSetDispatchQueue(mmc->session, NULL);
406         CFRelease(mmc->session);
407         mmc->session = NULL;
408     }
409 
410     if (mmc->disk) {
411         CFRelease(mmc->disk);
412         mmc->disk = NULL;
413     }
414 
415     dispatch_release(mmc->sync_sem);
416 }
417 
mmc_open_iokit(const char * path,MMCDEV * mmc)418 static int mmc_open_iokit(const char *path, MMCDEV *mmc) {
419     io_service_t service;
420     int rc;
421 
422     mmc->plugInInterface = NULL;
423     mmc->mmcInterface = NULL;
424     mmc->taskInterface = NULL;
425     mmc->disk = NULL;
426     mmc->session = NULL;
427     mmc->disk_state = disk_mounted;
428 
429     /* get the bsd name associated with this mount */
430     rc = get_mounted_device_from_path(mmc, path);
431     if (0 != rc) {
432         BD_DEBUG(DBG_MMC | DBG_CRIT, "Could not locate mounted device associated with %s\n", path);
433         return rc;
434     }
435 
436     /* find a matching io service (IOBDServices) */
437     rc = iokit_find_service_matching(mmc, &service);
438     if (0 != rc) {
439         BD_DEBUG(DBG_MMC | DBG_CRIT, "Could not find matching IOBDServices mounted @ %s\n", path);
440         return rc;
441     }
442 
443     /* find mmc and scsi task interfaces */
444     rc = iokit_find_interfaces(mmc, service);
445 
446     /* done with the ioservice. release it */
447     (void) IOObjectRelease(service);
448 
449     if (0 != rc) {
450         return rc;
451     }
452 
453     /* Init DiskArbitration */
454     rc = iokit_da_init(mmc);
455     if (0 != rc) {
456         return rc;
457     }
458 
459     /* unmount the disk so exclusive access can be obtained (this is required
460        to use the scsi task interface) */
461     rc = iokit_unmount(mmc);
462     if (0 != rc) {
463         BD_DEBUG(DBG_MMC | DBG_CRIT, "Failed to unmount the disc at %s\n", path);
464         return rc;
465     }
466 
467     /* finally, obtain exclusive access */
468     rc = (*mmc->taskInterface)->ObtainExclusiveAccess(mmc->taskInterface);
469     if (kIOReturnSuccess != rc) {
470         BD_DEBUG(DBG_MMC | DBG_CRIT, "Failed to obtain exclusive access. rc = %x\n", rc);
471         return -1;
472     }
473 
474     BD_DEBUG(DBG_MMC, "MMC Open complete\n");
475 
476     return 0;
477 }
478 
device_open(const char * path)479 MMCDEV *device_open(const char *path)
480 {
481     MMCDEV *mmc;
482     int     rc;
483 
484     mmc = calloc(1, sizeof(MMCDEV));
485     if (!mmc) {
486         BD_DEBUG(DBG_MKB | DBG_CRIT, "out of memory\n");
487         return NULL;
488     }
489 
490     rc = mmc_open_iokit(path, mmc);
491     if (0 != rc) {
492         device_close(&mmc);
493         return NULL;
494     }
495 
496     return mmc;
497 }
498 
device_close(MMCDEV ** pp)499 void device_close(MMCDEV **pp)
500 {
501     __block int rc = 0;
502     if (pp && *pp) {
503         MMCDEV *mmc = *pp;
504 
505         /* When the exclusive access to the drive is released,
506          * the OS will see the device like a "new" device and
507          * try to mount it. Therefore we can't just mount the
508          * disk we previously got immediately here as it would
509          * fail with kDAReturnBadArgument as the disk is not
510          * available yet.
511          * Trying to mount the disk after it appears in peek
512          * does not work either as the disk is not yet ready
513          * or in the process of being mounted by the OS so
514          * that would return an kDAReturnBusy error.
515          * The only way that seems to reliably work is to use
516          * a mount approval callback. When the OS tries to
517          * mount the disk, the mount approval callback is
518          * called and we can reject mounting and then proceed
519          * to mount the disk ourselves.
520          * Claiming exclusive access using DADiskClaim in order
521          * to prevent the OS form mounting the disk does not work
522          * either!
523          */
524 
525         if (mmc->taskInterface) {
526             (*mmc->taskInterface)->ReleaseExclusiveAccess(mmc->taskInterface);
527             (*mmc->taskInterface)->Release(mmc->taskInterface);
528             mmc->taskInterface = NULL;
529         }
530 
531         if (mmc->mmcInterface) {
532             (*mmc->mmcInterface)->Release(mmc->mmcInterface);
533             mmc->mmcInterface = NULL;
534         }
535 
536         if (mmc->plugInInterface) {
537             IODestroyPlugInInterface(mmc->plugInInterface);
538         }
539 
540         if (!mmc->sync_sem) {
541             /* open failed before iokit_da_init() */
542             X_FREE(*pp);
543             return;
544         }
545 
546         /* Wait for disc to re-appear for 20 seconds.
547          * This timeout was figured out by experimentation with
548          * a USB BD drive which sometimes can take really long to
549          * be in a mountable state again.
550          * For internal drives this is probably much faster
551          * so the long timeout shouldnt do much harm for thse
552          * cases.
553          */
554         dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, 20 * 1E+9);
555         dispatch_semaphore_wait(mmc->sync_sem, timeout);
556 
557         /* It is crucial that this is done on the event handling queue
558          * else callbacks could be received while this code runs.
559          */
560         dispatch_sync(mmc->background_queue, ^{
561             if (disk_appeared != mmc->disk_state) {
562                 BD_DEBUG(DBG_MMC | DBG_CRIT, "Timeout waiting for the disc to appear again!\n");
563                 iokit_da_destroy(mmc);
564                 rc = -1;
565                 return;
566             }
567             rc = 0;
568         });
569 
570         if (rc == 0) {
571             /* Disk appeared successfully, mount it.
572              * Return value is ignored as logging of success or
573              * error takes place in the callback already and there
574              * is nothing we can do really if mounting fails.
575              */
576             (void) iokit_mount(mmc);
577             iokit_da_destroy(mmc);
578         }
579         X_FREE(*pp);
580     }
581 }
582