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
00029
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
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
00067
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
00138
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
00159
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
00209
00210 static const QString getModel(CFDictionaryRef diskDetails)
00211 {
00212 QString desc;
00213 const void *strRef;
00214
00215
00216 if (kCFBooleanTrue ==
00217 CFDictionaryGetValue(diskDetails,
00218 kDADiskDescriptionDeviceInternalKey))
00219 desc.append("Internal ");
00220
00221
00222 strRef = CFDictionaryGetValue(diskDetails,
00223 kDADiskDescriptionDeviceVendorKey);
00224 if (strRef)
00225 {
00226 desc.append(CFStringGetCStringPtr((CFStringRef)strRef,
00227 kCFStringEncodingMacRoman));
00228 desc.append(' ');
00229 }
00230
00231
00232 strRef = CFDictionaryGetValue(diskDetails,
00233 kDADiskDescriptionDeviceModelKey);
00234 if (strRef)
00235 {
00236 desc.append(CFStringGetCStringPtr((CFStringRef)strRef,
00237 kCFStringEncodingMacRoman));
00238 desc.append(' ');
00239 }
00240
00241
00242 desc.truncate(desc.length() - 1);
00243
00244
00245 desc.remove(" ");
00246
00247 return desc;
00248 }
00249
00250
00251
00252
00253
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
00284
00285
00286
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
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
00315
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
00377
00378 while (m_Monitor && m_Monitor->IsActive())
00379 {
00380
00381
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
00420 media->setVolumeID(volName);
00421 media->setDeviceModel(model.toAscii());
00422
00423
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
00447
00448 if (m_Monitor->shouldIgnore(media))
00449 return;
00450
00451
00452
00453
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)
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
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
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
00521 if (m_Active)
00522 return;
00523
00524 if (!m_StartThread)
00525 return;
00526
00527
00528
00529
00530
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
00558 if (shouldIgnore(pDevice))
00559 return false;
00560
00561 m_Devices.push_back( pDevice );
00562 m_UseCount[pDevice] = 0;
00563
00564
00565
00566
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
00581
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
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
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;
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 }