MythTV  0.26-pre
mediamonitor-darwin.cpp
Go to the documentation of this file.
00001 
00008 #include <QDir>
00009 #include <QMetaType>
00010 
00011 #include "mythmediamonitor.h"
00012 #include "mediamonitor-darwin.h"
00013 #include "mythcdrom.h"
00014 #include "mythhdd.h"
00015 
00016 #include "mythlogging.h"
00017 
00018 #include <IOKit/IOKitLib.h>
00019 #include <IOKit/storage/IOMedia.h>
00020 #include <IOKit/storage/IOCDMedia.h>
00021 #include <IOKit/storage/IODVDMedia.h>
00022 #include <IOKit/storage/IOBlockStorageDevice.h>
00023 #include <IOKit/storage/IOStorageDeviceCharacteristics.h>
00024 #include <IOKit/storage/IOStorageProtocolCharacteristics.h>
00025 #include <DiskArbitration/DiskArbitration.h>
00026 
00027 
00028 // These aren't external, they are defined in this file.
00029 // The 'extern "C"' forces them in the C namespace, not the C++
00030 extern "C" void diskAppearedCallback(DADiskRef disk, void *context);
00031 extern "C" void diskDisappearedCallback(DADiskRef disk, void *context);
00032 extern "C" void diskChangedCallback(DADiskRef disk,
00033                                     CFArrayRef keys, void *context);
00034 extern "C" MythMediaType MediaTypeForBSDName(const char *bsdName);
00035 
00036 static mach_port_t sMasterPort;
00037 
00038 
00042 MythMediaType FindMediaType(io_service_t service)
00043 {
00044     kern_return_t  kernResult;
00045     io_iterator_t  iter;
00046     MythMediaType  mediaType = MEDIATYPE_UNKNOWN;
00047     QString        msg = QString("FindMediaType() - ");
00048     bool           isWholeMedia = false;
00049 
00050     // Create an iterator across all parents of the service object passed in.
00051     kernResult = IORegistryEntryCreateIterator(service,
00052                                                kIOServicePlane,
00053                                                kIORegistryIterateRecursively
00054                                                | kIORegistryIterateParents,
00055                                                &iter);
00056 
00057     if (KERN_SUCCESS != kernResult)
00058         LOG(VB_GENERAL, LOG_CRIT, msg +
00059             QString("IORegistryEntryCreateIterator returned %1")
00060                 .arg(kernResult));
00061     else if (!iter)
00062         LOG(VB_GENERAL, LOG_CRIT, msg +
00063             "IORegistryEntryCreateIterator returned NULL iterator");
00064     else
00065     {
00066         // A reference on the initial service object is released in
00067         // the do-while loop below, so add a reference to balance
00068         IOObjectRetain(service);
00069         
00070         do
00071         {
00072             isWholeMedia = false;
00073             if (IOObjectConformsTo(service, kIOMediaClass))
00074             {
00075                 CFTypeRef wholeMedia;
00076 
00077                 wholeMedia = IORegistryEntryCreateCFProperty
00078                              (service, CFSTR(kIOMediaWholeKey), 
00079                               kCFAllocatorDefault, 0);
00080 
00081                 if (!wholeMedia)
00082                     LOG(VB_GENERAL, LOG_ALERT, msg +
00083                         "Could not retrieve Whole property");
00084                 else
00085                 {
00086                     isWholeMedia = CFBooleanGetValue((CFBooleanRef)wholeMedia);
00087                     CFRelease(wholeMedia);
00088                 }
00089             }
00090 
00091             if (isWholeMedia)
00092             {
00093                 if (IOObjectConformsTo(service, kIODVDMediaClass))
00094                     mediaType = MEDIATYPE_DVD;
00095                 else if (IOObjectConformsTo(service, kIOCDMediaClass))
00096                     mediaType = MEDIATYPE_AUDIO;
00097             }
00098 
00099             IOObjectRelease(service);
00100 
00101         } while ((service = IOIteratorNext(iter))
00102                  && (mediaType == MEDIATYPE_UNKNOWN));
00103 
00104         IOObjectRelease(iter);
00105     }
00106     return mediaType;
00107 }
00108 
00112 MythMediaType MediaTypeForBSDName(const char *bsdName)
00113 {
00114     CFMutableDictionaryRef  matchingDict;
00115     kern_return_t           kernResult;
00116     io_iterator_t           iter;
00117     io_service_t            service;
00118     QString                 msg = QString("MediaTypeForBSDName(%1)")
00119                                   .arg(bsdName);
00120     MythMediaType           mediaType;
00121 
00122 
00123     if (!bsdName || !*bsdName)
00124     {
00125         LOG(VB_GENERAL, LOG_ALERT, msg + " - No name supplied?");
00126         return MEDIATYPE_UNKNOWN;
00127     }
00128 
00129     matchingDict = IOBSDNameMatching(sMasterPort, 0, bsdName);
00130     if (!matchingDict)
00131     {
00132         LOG(VB_GENERAL, LOG_ALERT, 
00133                  msg + " - IOBSDNameMatching() returned a NULL dictionary.");
00134         return MEDIATYPE_UNKNOWN;
00135     }
00136 
00137     // Return an iterator across all objects with the matching
00138     // BSD node name. Note that there should only be one match!
00139     kernResult = IOServiceGetMatchingServices(sMasterPort, matchingDict, &iter);
00140 
00141     if (KERN_SUCCESS != kernResult)
00142     {
00143         LOG(VB_GENERAL, LOG_ALERT,
00144                  QString(msg + " - IOServiceGetMatchingServices() returned %2")
00145                          .arg(kernResult));
00146         return MEDIATYPE_UNKNOWN;
00147     }
00148     if (!iter)
00149     {
00150         LOG(VB_GENERAL, LOG_ALERT,
00151                  msg + " - IOServiceGetMatchingServices() returned a NULL "
00152                        "iterator");
00153         return MEDIATYPE_UNKNOWN;
00154     }
00155 
00156     service = IOIteratorNext(iter);
00157 
00158     // Release this now because we only expect
00159     // the iterator to contain a single io_service_t.
00160     IOObjectRelease(iter);
00161 
00162     if (!service)
00163     {
00164         LOG(VB_GENERAL, LOG_ALERT, 
00165                  msg + " - IOIteratorNext() returned a NULL iterator");
00166         return MEDIATYPE_UNKNOWN;
00167     }
00168     mediaType = FindMediaType(service);
00169     IOObjectRelease(service);
00170     return mediaType;
00171 }
00172 
00173 
00177 static char * getVolName(CFDictionaryRef diskDetails)
00178 {
00179     CFStringRef name;
00180     CFIndex     size;
00181     char       *volName;
00182 
00183     name = (CFStringRef)
00184            CFDictionaryGetValue(diskDetails, kDADiskDescriptionVolumeNameKey);
00185 
00186     if (!name)
00187         return NULL;
00188 
00189     size = CFStringGetLength(name) + 1;
00190     volName = (char *) malloc(size);
00191     if (!volName)
00192     {
00193         LOG(VB_GENERAL, LOG_ALERT, 
00194                 QString("getVolName() - Can't malloc(%1)?").arg(size));
00195         return NULL;
00196     }
00197 
00198     if (!CFStringGetCString(name, volName, size, kCFStringEncodingUTF8))
00199     {
00200         free(volName);
00201         return NULL;
00202     }
00203 
00204     return volName;
00205 }
00206 
00207 /*
00208  * Given a DA description, return a compound description to help identify it.
00209  */
00210 static const QString getModel(CFDictionaryRef diskDetails)
00211 {
00212     QString     desc;
00213     const void  *strRef;
00214 
00215     // Location
00216     if (kCFBooleanTrue ==
00217         CFDictionaryGetValue(diskDetails,
00218                              kDADiskDescriptionDeviceInternalKey))
00219         desc.append("Internal ");
00220 
00221     // Manufacturer
00222     strRef = CFDictionaryGetValue(diskDetails,
00223                                   kDADiskDescriptionDeviceVendorKey);
00224     if (strRef)
00225     {
00226         desc.append(CFStringGetCStringPtr((CFStringRef)strRef,
00227                                           kCFStringEncodingMacRoman));
00228         desc.append(' ');
00229     }
00230 
00231     // Product
00232     strRef = CFDictionaryGetValue(diskDetails,
00233                                kDADiskDescriptionDeviceModelKey);
00234     if (strRef)
00235     {
00236         desc.append(CFStringGetCStringPtr((CFStringRef)strRef,
00237                                           kCFStringEncodingMacRoman));
00238         desc.append(' ');
00239     }
00240 
00241     // Remove the trailing space
00242     desc.truncate(desc.length() - 1);
00243 
00244     // and multiple spaces
00245     desc.remove("  ");
00246 
00247     return desc;
00248 }
00249 
00250 
00251 /*
00252  * Callbacks which the Disk Arbitration session invokes
00253  * whenever a disk comes or goes, or is renamed
00254  */
00255 
00256 void diskAppearedCallback(DADiskRef disk, void *context)
00257 {
00258     const char          *BSDname = DADiskGetBSDName(disk);
00259     CFDictionaryRef      details;
00260     bool                 isCDorDVD;
00261     MythMediaType        mediaType;
00262     QString              model;
00263     MonitorThreadDarwin *mtd;
00264     QString              msg = "diskAppearedCallback() - ";
00265     char                *volName;
00266 
00267 
00268     if (!BSDname)
00269     {
00270         LOG(VB_MEDIA, LOG_INFO, msg + "Skipping non-local device");
00271         return;
00272     }
00273 
00274     if (!context)
00275     {
00276         LOG(VB_GENERAL, LOG_ALERT, msg + "Error. Invoked with a NULL context.");
00277         return;
00278     }
00279 
00280     mtd = reinterpret_cast<MonitorThreadDarwin*>(context);
00281 
00282 
00283     // We want to monitor CDs/DVDs and USB cameras or flash drives,
00284     // but probably not hard disk or network drives. For now, ignore
00285     // any disk or partitions that are not on removable media.
00286     // Seems OK for hot-plug USB/FireWire disks (i.e. they are removable)
00287 
00288     details = DADiskCopyDescription(disk);
00289 
00290     if (kCFBooleanFalse ==
00291         CFDictionaryGetValue(details, kDADiskDescriptionMediaRemovableKey))
00292     {
00293         LOG(VB_MEDIA, LOG_INFO, msg + QString("Skipping non-removable %1")
00294             .arg(BSDname));
00295         CFRelease(details);
00296         return;
00297     }
00298 
00299     // Get the volume and model name for more user-friendly interaction
00300     volName = getVolName(details);
00301     if (!volName)
00302     {
00303         LOG(VB_MEDIA, LOG_INFO, msg + QString("No volume name for dev %1")
00304             .arg(BSDname));
00305         CFRelease(details);
00306         return;
00307     }
00308 
00309     model     = getModel(details);
00310     mediaType = MediaTypeForBSDName(BSDname);
00311     isCDorDVD = (mediaType == MEDIATYPE_DVD) || (mediaType == MEDIATYPE_AUDIO);
00312 
00313 
00314     // We know it is removable, and have guessed the type.
00315     // Call a helper function to create appropriate objects and insert 
00316 
00317     LOG(VB_MEDIA, LOG_INFO, QString("Found disk %1 - volume name '%2'.")
00318                       .arg(BSDname).arg(volName));
00319 
00320     mtd->diskInsert(BSDname, volName, model, isCDorDVD);
00321 
00322     CFRelease(details);
00323     free(volName);
00324 }
00325 
00326 void diskDisappearedCallback(DADiskRef disk, void *context)
00327 {
00328     const char  *BSDname = DADiskGetBSDName(disk);
00329 
00330     if (context)
00331         reinterpret_cast<MonitorThreadDarwin *>(context)->diskRemove(BSDname);
00332 }
00333 
00334 void diskChangedCallback(DADiskRef disk, CFArrayRef keys, void *context)
00335 {
00336     if (CFArrayContainsValue(keys, CFRangeMake(0, CFArrayGetCount(keys)),
00337                              kDADiskDescriptionVolumeNameKey))
00338     {
00339         const char     *BSDname = DADiskGetBSDName(disk);
00340         CFDictionaryRef details = DADiskCopyDescription(disk);
00341         char           *volName = getVolName(details);
00342 
00343         LOG(VB_MEDIA, LOG_INFO, QString("Disk %1 - changed name to '%2'.")
00344                           .arg(BSDname).arg(volName));
00345 
00346         reinterpret_cast<MonitorThreadDarwin *>(context)
00347             ->diskRename(BSDname, volName);
00348         CFRelease(details);
00349         free(volName);
00350     }
00351 }
00352 
00353 
00357 void MonitorThreadDarwin::run(void)
00358 {
00359     CFDictionaryRef match     = kDADiskDescriptionMatchVolumeMountable;
00360     DASessionRef    daSession = DASessionCreate(kCFAllocatorDefault);
00361 
00362     IOMasterPort(MACH_PORT_NULL, &sMasterPort);
00363 
00364     DARegisterDiskAppearedCallback(daSession, match,
00365                                    diskAppearedCallback, this);
00366     DARegisterDiskDisappearedCallback(daSession, match,
00367                                       diskDisappearedCallback, this);
00368     DARegisterDiskDescriptionChangedCallback(daSession, match,
00369                                              kDADiskDescriptionWatchVolumeName,
00370                                              diskChangedCallback, this);
00371 
00372     DASessionScheduleWithRunLoop(daSession,
00373                                  CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
00374 
00375 
00376     // Nice and simple, as long as our monitor is valid and active, 
00377     // loop and let daSession check the devices.
00378     while (m_Monitor && m_Monitor->IsActive())        
00379     {
00380         // Run the run loop for interval (milliseconds) - this will
00381         // handle any disk arbitration appeared/dissappeared events
00382         CFRunLoopRunInMode(kCFRunLoopDefaultMode,
00383                            (float) m_Interval / 1000.0f, false );
00384     }
00385 
00386     DAUnregisterCallback(daSession, (void(*))diskChangedCallback,     this);
00387     DAUnregisterCallback(daSession, (void(*))diskDisappearedCallback, this);
00388     DAUnregisterCallback(daSession, (void(*))diskAppearedCallback,    this);
00389     CFRelease(daSession);
00390 }
00391 
00398 void MonitorThreadDarwin::diskInsert(const char *devName,
00399                                      const char *volName,
00400                                      QString model, bool isCDorDVD)
00401 {
00402     MythMediaDevice  *media;
00403     QString           msg = "MonitorThreadDarwin::diskInsert";
00404 
00405     LOG(VB_MEDIA, LOG_DEBUG, msg + QString("(%1,%2,'%3',%4)")
00406                       .arg(devName).arg(volName).arg(model).arg(isCDorDVD));
00407 
00408     if (isCDorDVD)
00409         media = MythCDROM::get(NULL, devName, true, m_Monitor->m_AllowEject);
00410     else
00411         media = MythHDD::Get(NULL, devName, true, false);
00412 
00413     if (!media)
00414     {
00415         LOG(VB_GENERAL, LOG_ALERT, msg + "Couldn't create MythMediaDevice.");
00416         return;
00417     }
00418 
00419     // We store the volume name for user activities like ChooseAndEjectMedia().
00420     media->setVolumeID(volName);
00421     media->setDeviceModel(model.toAscii());  // Same for the Manufacturer and model
00422 
00423     // Mac OS X devices are pre-mounted here:
00424     QString mnt = "/Volumes/"; mnt += volName;
00425     media->setMountPath(mnt.toAscii());
00426 
00427     int  attempts = 0;
00428     QDir d(mnt);
00429     while (!d.exists())
00430     {
00431         LOG(VB_MEDIA, LOG_WARNING,
00432             (msg + "() - Waiting for mount '%1' to become stable.").arg(mnt));
00433         usleep(120000);
00434         if ( ++attempts > 4 )
00435             usleep(200000);
00436         if ( attempts > 8 )
00437         {
00438             delete media;
00439             LOG(VB_MEDIA, LOG_ALERT, msg + "() - Giving up");
00440             return;
00441         }
00442     }
00443 
00444     media->setStatus(MEDIASTAT_MOUNTED);
00445 
00446     // This is checked in AddDevice(), but checking earlier means
00447     // we can avoid scanning all the files to determine its type
00448     if (m_Monitor->shouldIgnore(media))
00449         return;
00450 
00451     // We want to use MythMedia's code to work out the mediaType.
00452     // media->onDeviceMounted() is protected,
00453     // so to call it indirectly, we pretend to mount it here.
00454     media->mount();
00455 
00456     m_Monitor->AddDevice(media);
00457 }
00458 
00459 void MonitorThreadDarwin::diskRemove(QString devName)
00460 {
00461     LOG(VB_MEDIA, LOG_DEBUG,
00462             QString("MonitorThreadDarwin::diskRemove(%1)").arg(devName));
00463 
00464     if (m_Monitor->m_SendEvent)
00465     {
00466         MythMediaDevice *pDevice = m_Monitor->GetMedia(devName);
00467 
00468         if (pDevice)  // Probably should ValidateAndLock() here?
00469             pDevice->setStatus(MEDIASTAT_NODISK);
00470         else
00471             LOG(VB_MEDIA, LOG_INFO,
00472                      "Couldn't find MythMediaDevice: " + devName);
00473     }
00474 
00475     m_Monitor->RemoveDevice(devName);
00476 }
00477 
00484 void MonitorThreadDarwin::diskRename(const char *devName, const char *volName)
00485 {
00486     LOG(VB_MEDIA, LOG_DEBUG,
00487              QString("MonitorThreadDarwin::diskRename(%1,%2)")
00488                       .arg(devName).arg(volName));
00489 
00490     MythMediaDevice *pDevice = m_Monitor->GetMedia(devName);
00491 
00492     if (m_Monitor->ValidateAndLock(pDevice))
00493     {
00494         // Send message to plugins to ignore this drive:
00495         if (m_Monitor->m_SendEvent)
00496             pDevice->setStatus(MEDIASTAT_NODISK);
00497 
00498         pDevice->setVolumeID(volName);
00499         pDevice->setMountPath((QString("/Volumes/") + volName).toAscii());
00500 
00501         // Plugins can now use it again:
00502         if (m_Monitor->m_SendEvent)
00503             pDevice->setStatus(MEDIASTAT_USEABLE);
00504 
00505         m_Monitor->Unlock(pDevice);
00506     }
00507     else
00508         LOG(VB_MEDIA, LOG_INFO,
00509                  QString("Couldn't find MythMediaDevice: %1").arg(devName));
00510 }
00511 
00518 void MediaMonitorDarwin::StartMonitoring(void)
00519 {
00520     // Sanity check
00521     if (m_Active)
00522         return;
00523 
00524     if (!m_StartThread)
00525         return;
00526 
00527 
00528     // If something (like the MythMusic plugin) stops and starts monitoring,
00529     // DiskArbitration would re-add the same drives several times over.
00530     // So, we make sure the device list is deleted.
00531     m_Devices.clear();
00532 
00533 
00534     if (!m_Thread)
00535         m_Thread = new MonitorThreadDarwin(this, m_MonitorPollingInterval);
00536 
00537     qRegisterMetaType<MythMediaStatus>("MythMediaStatus");
00538 
00539     LOG(VB_MEDIA, LOG_NOTICE, "Starting MediaMonitor");
00540     m_Active = true;
00541     m_Thread->start();
00542 }
00543 
00549 bool MediaMonitorDarwin::AddDevice(MythMediaDevice* pDevice)
00550 {
00551     if ( !pDevice )
00552     {
00553         LOG(VB_GENERAL, LOG_ERR, "MediaMonitor::AddDevice(null)");
00554         return false;
00555     }
00556 
00557     // If the user doesn't want this device to be monitored, stop now: 
00558     if (shouldIgnore(pDevice)) 
00559         return false;
00560 
00561     m_Devices.push_back( pDevice );
00562     m_UseCount[pDevice] = 0;
00563 
00564 
00565     // Devices on Mac OS X don't change status the way Linux ones do,
00566     // so we force a status change for mediaStatusChanged() to send an event
00567     if (m_SendEvent)
00568     {
00569         pDevice->setStatus(MEDIASTAT_NODISK);
00570         connect(pDevice, SIGNAL(statusChanged(MythMediaStatus, MythMediaDevice*)),
00571                 this, SLOT(mediaStatusChanged(MythMediaStatus, MythMediaDevice*)));
00572         pDevice->setStatus(MEDIASTAT_USEABLE);
00573     }
00574 
00575 
00576     return true;
00577 }
00578 
00579 /*
00580  * Given a device, return a compound description to help identify it.
00581  * We try to find out if it is internal, its manufacturer, and model.
00582  */
00583 static const QString getModel(io_object_t drive)
00584 {
00585     QString                 desc;
00586     CFMutableDictionaryRef  props = NULL;
00587 
00588     props = (CFMutableDictionaryRef) IORegistryEntrySearchCFProperty(drive, kIOServicePlane, CFSTR(kIOPropertyProtocolCharacteristicsKey), kCFAllocatorDefault, kIORegistryIterateParents | kIORegistryIterateRecursively);
00589 CFShow(props);
00590     if (props)
00591     {
00592         const void  *location = CFDictionaryGetValue(props, CFSTR(kIOPropertyPhysicalInterconnectLocationKey));
00593         if (CFEqual(location, CFSTR("Internal")))
00594             desc.append("Internal ");
00595     }
00596 
00597     props = (CFMutableDictionaryRef) IORegistryEntrySearchCFProperty(drive, kIOServicePlane, CFSTR(kIOPropertyDeviceCharacteristicsKey), kCFAllocatorDefault, kIORegistryIterateParents | kIORegistryIterateRecursively);
00598     if (props)
00599     {
00600         const void *product = CFDictionaryGetValue(props, CFSTR(kIOPropertyProductNameKey));
00601         const void *vendor  = CFDictionaryGetValue(props, CFSTR(kIOPropertyVendorNameKey));
00602         if (vendor)
00603         {
00604             desc.append(CFStringGetCStringPtr((CFStringRef)vendor, kCFStringEncodingMacRoman));
00605             desc.append(" ");
00606         }
00607         if (product)
00608         {
00609             desc.append(CFStringGetCStringPtr((CFStringRef)product, kCFStringEncodingMacRoman));
00610             desc.append(" ");
00611         }
00612     }
00613 
00614     // Omit the trailing space
00615     desc.truncate(desc.length() - 1);
00616 
00617     return desc;
00618 }
00619 
00629 QStringList MediaMonitorDarwin::GetCDROMBlockDevices()
00630 {
00631     kern_return_t          kernResult;
00632     CFMutableDictionaryRef devices;
00633     io_iterator_t          iter;
00634     QStringList            list;
00635     QString                msg = QString("GetCDRomBlockDevices() - ");
00636 
00637 
00638     devices = IOServiceMatching(kIOBlockStorageDeviceClass);
00639     if (!devices)
00640     {
00641         LOG(VB_GENERAL, LOG_ALERT, msg + "No Storage Devices? Unlikely!");
00642         return list;
00643     }
00644 
00645     // Create an iterator across all parents of the service object passed in.
00646     kernResult = IOServiceGetMatchingServices(sMasterPort, devices, &iter);
00647 
00648     if (KERN_SUCCESS != kernResult)
00649     {
00650         LOG(VB_GENERAL, LOG_ALERT, msg +
00651             QString("IORegistryEntryCreateIterator returned %1")
00652                 .arg(kernResult));
00653         return list;
00654     }
00655     if (!iter)
00656     {
00657         LOG(VB_GENERAL, LOG_ALERT, msg +
00658             "IORegistryEntryCreateIterator returned a NULL iterator");
00659         return list;
00660     }
00661 
00662     io_object_t  drive;
00663 
00664     while ((drive = IOIteratorNext(iter)))
00665     {
00666         CFMutableDictionaryRef  p = NULL;  // properties of drive
00667 
00668         IORegistryEntryCreateCFProperties(drive, &p, kCFAllocatorDefault, 0);
00669         if (p)
00670         {
00671             const void  *type = CFDictionaryGetValue(p, CFSTR("device-type"));
00672 
00673             if (CFEqual(type, CFSTR("DVD")) || CFEqual(type, CFSTR("CD")))
00674             {
00675                 QString  desc = getModel(drive);
00676 
00677                 list.append(desc);
00678                 LOG(VB_MEDIA, LOG_INFO, desc.prepend("Found CD/DVD: "));
00679                 CFRelease(p);
00680             }
00681         }
00682         else
00683             LOG(VB_GENERAL, LOG_ALERT, 
00684                      msg + "Could not retrieve drive properties");
00685 
00686         IOObjectRelease(drive);
00687     }
00688 
00689     IOObjectRelease(iter);
00690 
00691     return list;
00692 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends