16 #include <sys/types.h>
19 #include <sys/param.h>
21 #include "libmythbase/mythconfig.h"
27 #include <QDBusConnection>
28 #include <QXmlStreamReader>
31 #include <QTextStream>
33 #include <QRegularExpression>
54 #ifndef MNTTYPE_ISO9660
56 static constexpr
const char* MNTTYPE_ISO9660 {
"iso9660" };
57 # elif defined(__FreeBSD__) || defined(Q_OS_DARWIN) || defined(__OpenBSD__)
58 static constexpr
const char* MNTTYPE_ISO9660 {
"cd9660" };
70 #ifndef MNTTYPE_SUPERMOUNT
74 #endif // !Q_OS_ANDROID
78 static constexpr
const char* UDISKS2_SVC {
"org.freedesktop.UDisks2" };
79 static constexpr
const char* UDISKS2_SVC_DRIVE {
"org.freedesktop.UDisks2.Drive" };
80 static constexpr
const char* UDISKS2_SVC_BLOCK {
"org.freedesktop.UDisks2.Block" };
81 static constexpr
const char* UDISKS2_SVC_FILESYSTEM {
"org.freedesktop.UDisks2.Filesystem" };
82 static constexpr
const char* UDISKS2_SVC_MANAGER {
"org.freedesktop.UDisks2.Manager" };
83 static constexpr
const char* UDISKS2_PATH {
"/org/freedesktop/UDisks2" };
84 static constexpr
const char* UDISKS2_PATH_MANAGER {
"/org/freedesktop/UDisks2/Manager" };
85 static constexpr
const char* UDISKS2_PATH_BLOCK_DEVICES {
"/org/freedesktop/UDisks2/block_devices" };
86 static constexpr
const char* UDISKS2_MIN_VERSION {
"2.7.3" };
92 static const QString
LOC = QString(
"MMUnix:");
98 LOG(VB_GENERAL, LOG_ALERT,
99 LOC + methodName +
" Error: failed to open " + _PATH_FSTAB +
100 " for reading, " +
ENO);
104 static void statError(
const QString &methodName,
const QString &devPath)
106 LOG(VB_GENERAL, LOG_ALERT,
107 LOC + methodName +
" Error: failed to stat " + devPath +
116 unsigned long interval,
bool allowEject)
121 LOG(VB_GENERAL, LOG_NOTICE,
"MediaMonitor disabled by user setting.");
143 #endif // !CONFIG_QTDBUS
150 struct fstab * mep =
nullptr;
160 while ((mep = getfsent()) !=
nullptr)
173 static QVariant DriveProperty(
const QDBusObjectPath& o,
const std::string& kszProperty)
176 QDBusInterface iface(UDISKS2_SVC, o.path(), UDISKS2_SVC_DRIVE,
177 QDBusConnection::systemBus() );
180 v = iface.property(kszProperty.c_str());
181 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
182 "Udisks2:Drive:" + kszProperty.c_str() +
" : " +
183 (v.canConvert<QStringList>() ? v.toStringList().join(
", ") : v.toString()) );
189 static bool DetectDevice(
const QDBusObjectPath& entry, MythUdisksDevice& device,
190 QString& desc, QString& dev)
192 QDBusInterface block(UDISKS2_SVC, entry.path(), UDISKS2_SVC_BLOCK,
193 QDBusConnection::systemBus() );
195 if (!block.property(
"HintSystem").toBool() &&
196 !block.property(
"HintIgnore").toBool())
198 dev = block.property(
"Device").toString();
199 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
200 "DetectDevice: Device found: " + dev);
203 if (dev.startsWith(
"/dev/fd"))
207 QDBusInterface properties(UDISKS2_SVC, entry.path(),
208 "org.freedesktop.DBus.Properties", QDBusConnection::systemBus());
210 auto mountpointscall = properties.call(
"Get", UDISKS2_SVC_FILESYSTEM,
212 bool isfsmountable = (properties.lastError().type() == QDBusError::NoError);
214 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
215 QString(
" DetectDevice:Entry:isfsmountable : %1").arg(isfsmountable));
219 auto drivePath = block.property(
"Drive").value<QDBusObjectPath>();
220 desc = DriveProperty(drivePath,
"Vendor").toString();
223 desc += DriveProperty(drivePath,
"Model").toString();
224 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
225 QString(
"DetectDevice: Found drive '%1'").arg(desc));
226 const auto media = DriveProperty(drivePath,
"MediaCompatibility").toStringList();
227 const bool isOptical = !media.filter(
"optical", Qt::CaseInsensitive).isEmpty();
229 if (DriveProperty(drivePath,
"Removable").toBool())
245 #endif // CONFIG_QTDBUS
262 for (
int i = 0; i < 10; ++i, usleep(500000))
266 QDBusInterface ifacem(UDISKS2_SVC, UDISKS2_PATH_MANAGER,
267 UDISKS2_SVC_MANAGER, QDBusConnection::systemBus() );
269 if (ifacem.isValid())
271 if (!ifacem.property(
"Version").toString().isEmpty())
274 QString mversion = ifacem.property(
"Version").toString();
277 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
"Using UDisk2 version " + mversion);
279 if (QVersionNumber::compare(this_version, min_version) < 0)
281 LOG(VB_GENERAL, LOG_ERR,
LOC +
282 "CheckMountable: UDisks2 version too old: " + mversion);
288 LOG(VB_GENERAL, LOG_ERR,
LOC +
289 "Cannot retrieve UDisks2 version, stopping device discovery");
295 LOG(VB_GENERAL, LOG_WARNING,
LOC +
296 "Cannot interface to UDisks2, will retry");
301 using QDBusObjectPathList = QList<QDBusObjectPath>;
302 QDBusPendingReply<QDBusObjectPathList> reply = ifacem.call(
"GetBlockDevices",
304 reply.waitForFinished();
305 if (!reply.isValid())
307 LOG(VB_GENERAL, LOG_ALERT,
LOC +
308 "CheckMountable DBus GetBlockDevices error: " +
309 reply.error().message() );
313 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
314 "CheckMountable: Start listening on UDisks2 DBus");
317 (void)QDBusConnection::systemBus().connect(UDISKS2_SVC, UDISKS2_PATH,
318 "org.freedesktop.DBus.ObjectManager",
"InterfacesAdded",
319 this, SLOT(deviceAdded(QDBusObjectPath,QMap<QString,QVariant>)) );
320 (void)QDBusConnection::systemBus().connect(UDISKS2_SVC, UDISKS2_PATH,
321 "org.freedesktop.DBus.ObjectManager",
"InterfacesRemoved",
322 this, SLOT(deviceRemoved(QDBusObjectPath,QStringList)) );
325 const QDBusObjectPathList& list(reply.value());
326 for (
const auto& entry : qAsConst(list))
330 MythUdisksDevice mythdevice = UDisks2INVALID;
334 if (DetectDevice(entry, mythdevice, description, path))
336 if (mythdevice == UDisks2DVD)
339 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
340 "deviceAdded: Added MythCDROM: " + path);
342 else if (mythdevice == UDisks2HDD)
344 pDevice =
MythHDD::Get(
this, path.toLatin1(),
false,
false);
345 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
346 "deviceAdded: Added MythHDD: " + path);
352 pDevice -> setDeviceModel(description.toLatin1().constData());
354 pDevice->deleteLater();
365 #elif defined(__linux__)
370 QDir sysfs(
"/sys/block");
371 sysfs.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
373 auto list = sysfs.entryList();
374 for (
const auto& device : qAsConst(list))
377 if (device.startsWith(
"fd"))
381 QString path = sysfs.absolutePath();
387 #else // not CONFIG_QTDBUS and not linux
399 QString removablePath = dev +
"/removable";
400 QFile removable(removablePath);
401 if (removable.exists() && removable.open(QIODevice::ReadOnly))
404 QString msg =
LOC +
":CheckRemovable(" + dev +
")/removable ";
405 bool ok = removable.getChar(&c);
410 LOG(VB_MEDIA, LOG_DEBUG, msg + c);
416 LOG(VB_GENERAL, LOG_ALERT, msg +
"failed");
432 QString msg =
LOC +
":GetDeviceFile(" + sysfs +
")";
436 ret.replace(QRegularExpression(
".*/"),
"/dev/");
442 struct udev *udev = udev_new();
445 struct udev_device *device =
446 udev_device_new_from_syspath(udev, sysfs.toLatin1().constData());
447 if (device !=
nullptr)
449 const char *name = udev_device_get_devnode(device);
459 LOG(VB_MEDIA, LOG_DEBUG, msg +
" devnode not (yet) known");
462 udev_device_unref(device);
466 LOG(VB_GENERAL, LOG_ALERT,
467 msg +
" udev_device_new_from_syspath returned NULL");
474 LOG(VB_GENERAL, LOG_ALERT,
475 "MediaMonitorUnix::GetDeviceFile udev_new failed");
476 # else // !HAVE_LIBUDEV
479 args <<
"info" <<
"-q" <<
"name"
498 while( !estream.atEnd() )
499 LOG(VB_MEDIA, LOG_DEBUG,
500 msg +
" - udevadm info error...\n" + estream.readLine());
503 QTextStream ostream(udevinfo->
ReadAll());
504 QString udevLine = ostream.readLine();
505 if (!udevLine.startsWith(
"device not found in database") )
509 # endif // HAVE_LIBUDEV
512 LOG(VB_MEDIA, LOG_INFO, msg +
"->'" + ret +
"'");
515 #endif // !CONFIG_QTDBUS
527 QDBusInterface blocks(UDISKS2_SVC, UDISKS2_PATH_BLOCK_DEVICES,
528 "org.freedesktop.DBus.Introspectable", QDBusConnection::systemBus());
530 QDBusReply<QString> reply = blocks.call(
"Introspect");
531 QXmlStreamReader xml_parser(reply.value());
533 while (!xml_parser.atEnd())
535 xml_parser.readNext();
537 if (xml_parser.tokenType() == QXmlStreamReader::StartElement
538 && xml_parser.name().toString() ==
"node")
540 const QString &name = xml_parser.attributes().value(
"name").toString();
542 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
"GetCDROMBlockDevices: name: " + name);
546 QString sbdevices = QString::fromUtf8(UDISKS2_PATH_BLOCK_DEVICES);
547 QDBusObjectPath entry {sbdevices +
"/" + name};
548 MythUdisksDevice mythdevice = UDisks2INVALID;
552 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
553 "GetCDROMBlockDevices: path: " + entry.path());
555 if (DetectDevice(entry, mythdevice, description, dev))
557 if (mythdevice == UDisks2DVD)
559 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
560 "GetCDROMBlockDevices: Added: " + dev);
562 if (dev.startsWith(
"/dev/"))
571 #elif defined(__linux__)
572 QFile
file(
"/proc/sys/dev/cdrom/info");
573 if (
file.open(QIODevice::ReadOnly))
576 QTextStream stream(&
file);
579 line = stream.readLine();
580 if (line.startsWith(
"drive name:"))
582 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
583 l = line.split(
'\t', QString::SkipEmptyParts);
585 l = line.split(
'\t', Qt::SkipEmptyParts);
591 while (!stream.atEnd());
596 LOG(VB_MEDIA, LOG_DEBUG,
597 LOC +
":GetCDROMBlockDevices()->'" + l.join(
", ") +
"'");
606 #if defined(__linux__)
610 if (devname.startsWith(
"hd"))
612 QFile
file(
"/proc/ide/" + devname.left(3) +
"/model");
613 if (
file.open(QIODevice::ReadOnly))
615 QTextStream stream(&
file);
617 desc.append(stream.readLine());
622 if (devname.startsWith(
"scd"))
623 devname.replace(
"scd",
"sr");
625 if (devname.startsWith(
"sd")
626 || devname.startsWith(
"sr"))
628 QString path = devname.prepend(
"/sys/block/");
629 path.append(
"/device/");
631 QFile
file(path +
"vendor");
632 if (
file.open(QIODevice::ReadOnly))
634 QTextStream stream(&
file);
636 desc.append(stream.readLine());
641 file.setFileName(path +
"model");
642 if (
file.open(QIODevice::ReadOnly))
644 QTextStream stream(&
file);
646 desc.append(stream.readLine());
653 LOG(VB_MEDIA, LOG_DEBUG, QString(
"LookupModel '%1' -> '%2'")
667 LOG(VB_GENERAL, LOG_ERR,
"MediaMonitorUnix::AddDevice(null)");
678 LOG(VB_GENERAL, LOG_ALERT,
679 "MediaMonitorUnix::AddDevice() - empty device path.");
684 if (stat(path.toLocal8Bit().constData(), &sb) < 0)
689 dev_t new_rdev = sb.st_rdev;
694 for (
const auto *device : qAsConst(
m_devices))
696 if (stat(device->getDevicePath().toLocal8Bit().constData(), &sb) < 0)
698 statError(
":AddDevice()", device->getDevicePath());
702 if (sb.st_rdev == new_rdev)
704 LOG(VB_MEDIA, LOG_INFO,
705 LOC +
":AddDevice() - not adding " + path +
707 "because it appears to be a duplicate of " +
708 device->getDevicePath());
722 LOG(VB_MEDIA, LOG_INFO,
LOC +
":AddDevice() - Added " + path);
735 QString devicePath( mep->fs_spec );
736 LOG(VB_GENERAL, LOG_DEBUG,
"AddDevice - " + devicePath);
742 bool is_supermount =
false;
743 bool is_cdrom =
false;
745 if (stat(mep->fs_spec, &sbuf) < 0)
749 if ( ! ( ((strstr(mep->fs_mntops,
"owner") &&
750 (sbuf.st_mode & S_IRUSR)) || strstr(mep->fs_mntops,
"user")) &&
751 (strstr(mep->fs_vfstype, MNTTYPE_ISO9660) ||
755 if (strstr(mep->fs_mntops, MNTTYPE_ISO9660) &&
758 is_supermount =
true;
766 if (strstr(mep->fs_mntops, MNTTYPE_ISO9660) ||
767 strstr(mep->fs_vfstype, MNTTYPE_ISO9660) ||
773 LOG(VB_GENERAL, LOG_DEBUG,
"Device is a CDROM");
785 QString dev(mep->fs_mntops);
786 int pos = dev.indexOf(QString::fromStdString(
kSuperOptDev));
790 static const QRegularExpression kSeparatorRE {
"[, ]" };
791 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
792 QStringList parts = dev.split(kSeparatorRE, QString::SkipEmptyParts);
794 QStringList parts = dev.split(kSeparatorRE, Qt::SkipEmptyParts);
796 if (parts[0].isEmpty())
809 pDevice->deleteLater();
821 void MediaMonitorUnix::deviceAdded(
const QDBusObjectPath& o,
822 const QMap<QString, QVariant> &interfaces)
824 if (!interfaces.contains(QStringLiteral(
"org.freedesktop.UDisks2.Block")))
827 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
":deviceAdded " + o.path());
831 MythUdisksDevice mythdevice = UDisks2INVALID;
835 if (DetectDevice(o, mythdevice, description, path))
837 if (mythdevice == UDisks2DVD)
840 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
841 "deviceAdded: Added MythCDROM: " + path);
843 else if (mythdevice == UDisks2HDD)
845 pDevice =
MythHDD::Get(
this, path.toLatin1(),
false,
false);
846 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
847 "deviceAdded: Added MythHDD: " + path);
852 pDevice -> setDeviceModel(description.toLatin1().constData());
854 pDevice->deleteLater();
861 void MediaMonitorUnix::deviceRemoved(
const QDBusObjectPath& o,
const QStringList &interfaces)
863 if (!interfaces.contains(QStringLiteral(
"org.freedesktop.UDisks2.Block")))
866 LOG(VB_MEDIA, LOG_INFO,
LOC +
"deviceRemoved " + o.path());
868 QString dev = QFileInfo(o.path()).baseName();
869 dev.prepend(
"/dev/");
873 #else //CONFIG_QTDBUS
893 LOG(VB_MEDIA, LOG_DEBUG,
894 LOC +
":FindPartitions(" + dev +
895 QString(
",%1").arg(checkPartitions ?
" true" :
" false" ) +
")");
902 sysfs.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
904 bool found_partitions =
false;
905 QStringList parts = sysfs.entryList();
906 for (
const auto& part : qAsConst(parts))
909 if (part ==
"device" || part ==
"holders" || part ==
"queue"
910 || part ==
"slaves" || part ==
"subsystem"
911 || part ==
"bdi" || part ==
"power")
915 sysfs.absoluteFilePath(part),
false);
919 if (!found_partitions)
922 return found_partitions;
927 if (device_file.isEmpty())
932 if (cdroms.contains(dev.section(
'/', -1)))
936 this, device_file.toLatin1().constData(),
false,
m_allowEject);
942 this, device_file.toLatin1().constData(),
false,
false);
949 pDevice->deleteLater();
961 std::string buffer(256,
'\0');
972 qBuffer.append(QString::fromStdString(buffer));
975 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
976 const QStringList list = qBuffer.split(
'\n', QString::SkipEmptyParts);
978 const QStringList list = qBuffer.split(
'\n', Qt::SkipEmptyParts);
980 for (
const auto& notif : qAsConst(list))
982 if (notif.startsWith(
"add"))
984 QString dev = notif.section(
' ', 1, 1);
985 LOG(VB_MEDIA, LOG_INFO,
"Udev add " + dev);
990 else if (notif.startsWith(
"remove"))
992 QString dev = notif.section(
' ', 2, 2);
993 LOG(VB_MEDIA, LOG_INFO,
"Udev remove " + dev);