16 #include <sys/types.h>
19 #include <sys/param.h>
21 #include "libmythbase/mythconfig.h"
26 #include <QDBusConnection>
27 #include <QDBusInterface>
28 #include <QDBusPendingReply>
30 #include <QVersionNumber>
31 #include <QXmlStreamReader>
34 #include <QTextStream>
36 #include <QRegularExpression>
57 #ifndef MNTTYPE_ISO9660
59 static constexpr
const char* MNTTYPE_ISO9660 {
"iso9660" };
60 # elif defined(__FreeBSD__) || defined(Q_OS_DARWIN) || defined(__OpenBSD__)
61 static constexpr
const char* MNTTYPE_ISO9660 {
"cd9660" };
73 #ifndef MNTTYPE_SUPERMOUNT
77 #endif // !Q_OS_ANDROID
81 static constexpr
const char* UDISKS2_SVC {
"org.freedesktop.UDisks2" };
82 static constexpr
const char* UDISKS2_SVC_DRIVE {
"org.freedesktop.UDisks2.Drive" };
83 static constexpr
const char* UDISKS2_SVC_BLOCK {
"org.freedesktop.UDisks2.Block" };
84 static constexpr
const char* UDISKS2_SVC_FILESYSTEM {
"org.freedesktop.UDisks2.Filesystem" };
85 static constexpr
const char* UDISKS2_SVC_MANAGER {
"org.freedesktop.UDisks2.Manager" };
86 static constexpr
const char* UDISKS2_PATH {
"/org/freedesktop/UDisks2" };
87 static constexpr
const char* UDISKS2_PATH_MANAGER {
"/org/freedesktop/UDisks2/Manager" };
88 static constexpr
const char* UDISKS2_PATH_BLOCK_DEVICES {
"/org/freedesktop/UDisks2/block_devices" };
89 static constexpr
const char* UDISKS2_MIN_VERSION {
"2.7.3" };
95 static const QString
LOC = QString(
"MMUnix:");
101 LOG(VB_GENERAL, LOG_ALERT,
102 LOC + methodName +
" Error: failed to open " + _PATH_FSTAB +
103 " for reading, " +
ENO);
107 static void statError(
const QString &methodName,
const QString &devPath)
109 LOG(VB_GENERAL, LOG_ALERT,
110 LOC + methodName +
" Error: failed to stat " + devPath +
119 unsigned long interval,
bool allowEject)
124 LOG(VB_GENERAL, LOG_NOTICE,
"MediaMonitor disabled by user setting.");
146 #endif // !CONFIG_QTDBUS
153 struct fstab * mep =
nullptr;
163 while ((mep = getfsent()) !=
nullptr)
176 static QVariant DriveProperty(
const QDBusObjectPath& o,
const std::string& kszProperty)
179 QDBusInterface iface(UDISKS2_SVC, o.path(), UDISKS2_SVC_DRIVE,
180 QDBusConnection::systemBus() );
183 v = iface.property(kszProperty.c_str());
184 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
185 "Udisks2:Drive:" + kszProperty.c_str() +
" : " +
186 (v.canConvert<QStringList>() ? v.toStringList().join(
", ") : v.toString()) );
192 static bool DetectDevice(
const QDBusObjectPath& entry, MythUdisksDevice& device,
193 QString& desc, QString& dev)
195 QDBusInterface block(UDISKS2_SVC, entry.path(), UDISKS2_SVC_BLOCK,
196 QDBusConnection::systemBus() );
198 if (!block.property(
"HintSystem").toBool() &&
199 !block.property(
"HintIgnore").toBool())
201 dev = block.property(
"Device").toString();
202 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
203 "DetectDevice: Device found: " + dev);
206 if (dev.startsWith(
"/dev/fd"))
210 QDBusInterface properties(UDISKS2_SVC, entry.path(),
211 "org.freedesktop.DBus.Properties", QDBusConnection::systemBus());
213 auto mountpointscall = properties.call(
"Get", UDISKS2_SVC_FILESYSTEM,
215 bool isfsmountable = (properties.lastError().type() == QDBusError::NoError);
217 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
218 QString(
" DetectDevice:Entry:isfsmountable : %1").arg(isfsmountable));
222 auto drivePath = block.property(
"Drive").value<QDBusObjectPath>();
223 desc = DriveProperty(drivePath,
"Vendor").toString();
226 desc += DriveProperty(drivePath,
"Model").toString();
227 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
228 QString(
"DetectDevice: Found drive '%1'").arg(desc));
229 const auto media = DriveProperty(drivePath,
"MediaCompatibility").toStringList();
230 const bool isOptical = !media.filter(
"optical", Qt::CaseInsensitive).isEmpty();
232 if (DriveProperty(drivePath,
"Removable").toBool())
248 #endif // CONFIG_QTDBUS
265 for (
int i = 0; i < 10; ++i, usleep(500000))
269 QDBusInterface ifacem(UDISKS2_SVC, UDISKS2_PATH_MANAGER,
270 UDISKS2_SVC_MANAGER, QDBusConnection::systemBus() );
272 if (ifacem.isValid())
274 if (!ifacem.property(
"Version").toString().isEmpty())
277 QString mversion = ifacem.property(
"Version").toString();
280 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
"Using UDisk2 version " + mversion);
282 if (QVersionNumber::compare(this_version, min_version) < 0)
284 LOG(VB_GENERAL, LOG_ERR,
LOC +
285 "CheckMountable: UDisks2 version too old: " + mversion);
291 LOG(VB_GENERAL, LOG_ERR,
LOC +
292 "Cannot retrieve UDisks2 version, stopping device discovery");
298 LOG(VB_GENERAL, LOG_WARNING,
LOC +
299 "Cannot interface to UDisks2, will retry");
304 using QDBusObjectPathList = QList<QDBusObjectPath>;
305 QDBusPendingReply<QDBusObjectPathList> reply = ifacem.call(
"GetBlockDevices",
307 reply.waitForFinished();
308 if (!reply.isValid())
310 LOG(VB_GENERAL, LOG_ALERT,
LOC +
311 "CheckMountable DBus GetBlockDevices error: " +
312 reply.error().message() );
316 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
317 "CheckMountable: Start listening on UDisks2 DBus");
320 (void)QDBusConnection::systemBus().connect(UDISKS2_SVC, UDISKS2_PATH,
321 "org.freedesktop.DBus.ObjectManager",
"InterfacesAdded",
322 this, SLOT(deviceAdded(QDBusObjectPath,QMap<QString,QVariant>)) );
323 (void)QDBusConnection::systemBus().connect(UDISKS2_SVC, UDISKS2_PATH,
324 "org.freedesktop.DBus.ObjectManager",
"InterfacesRemoved",
325 this, SLOT(deviceRemoved(QDBusObjectPath,QStringList)) );
328 const QDBusObjectPathList& list(reply.value());
329 for (
const auto& entry : std::as_const(list))
333 MythUdisksDevice mythdevice = UDisks2INVALID;
337 if (DetectDevice(entry, mythdevice, description, path))
339 if (mythdevice == UDisks2DVD)
342 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
343 "deviceAdded: Added MythCDROM: " + path);
345 else if (mythdevice == UDisks2HDD)
347 pDevice =
MythHDD::Get(
this, path.toLatin1(),
false,
false);
348 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
349 "deviceAdded: Added MythHDD: " + path);
355 pDevice -> setDeviceModel(description.toLatin1().constData());
357 pDevice->deleteLater();
368 #elif defined(__linux__)
373 QDir sysfs(
"/sys/block");
374 sysfs.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
376 auto list = sysfs.entryList();
377 for (
const auto& device : std::as_const(list))
380 if (device.startsWith(
"fd"))
384 QString path = sysfs.absolutePath();
390 #else // not CONFIG_QTDBUS and not linux
402 QString removablePath = dev +
"/removable";
403 QFile removable(removablePath);
404 if (removable.exists() && removable.open(QIODevice::ReadOnly))
407 QString msg =
LOC +
":CheckRemovable(" + dev +
")/removable ";
408 bool ok = removable.getChar(&c);
413 LOG(VB_MEDIA, LOG_DEBUG, msg + c);
419 LOG(VB_GENERAL, LOG_ALERT, msg +
"failed");
435 QString msg =
LOC +
":GetDeviceFile(" + sysfs +
")";
439 ret.replace(QRegularExpression(
".*/"),
"/dev/");
445 struct udev *udev = udev_new();
448 struct udev_device *device =
449 udev_device_new_from_syspath(udev, sysfs.toLatin1().constData());
450 if (device !=
nullptr)
452 const char *name = udev_device_get_devnode(device);
462 LOG(VB_MEDIA, LOG_DEBUG, msg +
" devnode not (yet) known");
465 udev_device_unref(device);
469 LOG(VB_GENERAL, LOG_ALERT,
470 msg +
" udev_device_new_from_syspath returned NULL");
477 LOG(VB_GENERAL, LOG_ALERT,
478 "MediaMonitorUnix::GetDeviceFile udev_new failed");
479 # else // !HAVE_LIBUDEV
482 args <<
"info" <<
"-q" <<
"name"
501 while( !estream.atEnd() )
502 LOG(VB_MEDIA, LOG_DEBUG,
503 msg +
" - udevadm info error...\n" + estream.readLine());
506 QTextStream ostream(udevinfo->
ReadAll());
507 QString udevLine = ostream.readLine();
508 if (!udevLine.startsWith(
"device not found in database") )
512 # endif // HAVE_LIBUDEV
515 LOG(VB_MEDIA, LOG_INFO, msg +
"->'" + ret +
"'");
518 #endif // !CONFIG_QTDBUS
530 QDBusInterface blocks(UDISKS2_SVC, UDISKS2_PATH_BLOCK_DEVICES,
531 "org.freedesktop.DBus.Introspectable", QDBusConnection::systemBus());
533 QDBusReply<QString> reply = blocks.call(
"Introspect");
534 QXmlStreamReader xml_parser(reply.value());
536 while (!xml_parser.atEnd())
538 xml_parser.readNext();
540 if (xml_parser.tokenType() == QXmlStreamReader::StartElement
541 && xml_parser.name().toString() ==
"node")
543 const QString &name = xml_parser.attributes().value(
"name").toString();
545 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
"GetCDROMBlockDevices: name: " + name);
549 QString sbdevices = QString::fromUtf8(UDISKS2_PATH_BLOCK_DEVICES);
550 QDBusObjectPath entry {sbdevices +
"/" + name};
551 MythUdisksDevice mythdevice = UDisks2INVALID;
555 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
556 "GetCDROMBlockDevices: path: " + entry.path());
558 if (DetectDevice(entry, mythdevice, description, dev))
560 if (mythdevice == UDisks2DVD)
562 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
563 "GetCDROMBlockDevices: Added: " + dev);
565 if (dev.startsWith(
"/dev/"))
574 #elif defined(__linux__)
575 QFile
file(
"/proc/sys/dev/cdrom/info");
576 if (
file.open(QIODevice::ReadOnly))
579 QTextStream stream(&
file);
582 line = stream.readLine();
583 if (line.startsWith(
"drive name:"))
585 l = line.split(
'\t', Qt::SkipEmptyParts);
590 while (!stream.atEnd());
595 LOG(VB_MEDIA, LOG_DEBUG,
596 LOC +
":GetCDROMBlockDevices()->'" + l.join(
", ") +
"'");
605 #if defined(__linux__)
609 if (devname.startsWith(
"hd"))
611 QFile
file(
"/proc/ide/" + devname.left(3) +
"/model");
612 if (
file.open(QIODevice::ReadOnly))
614 QTextStream stream(&
file);
616 desc.append(stream.readLine());
621 if (devname.startsWith(
"scd"))
622 devname.replace(
"scd",
"sr");
624 if (devname.startsWith(
"sd")
625 || devname.startsWith(
"sr"))
627 QString path = devname.prepend(
"/sys/block/");
628 path.append(
"/device/");
630 QFile
file(path +
"vendor");
631 if (
file.open(QIODevice::ReadOnly))
633 QTextStream stream(&
file);
635 desc.append(stream.readLine());
640 file.setFileName(path +
"model");
641 if (
file.open(QIODevice::ReadOnly))
643 QTextStream stream(&
file);
645 desc.append(stream.readLine());
652 LOG(VB_MEDIA, LOG_DEBUG, QString(
"LookupModel '%1' -> '%2'")
666 LOG(VB_GENERAL, LOG_ERR,
"MediaMonitorUnix::AddDevice(null)");
677 LOG(VB_GENERAL, LOG_ALERT,
678 "MediaMonitorUnix::AddDevice() - empty device path.");
683 if (stat(path.toLocal8Bit().constData(), &sb) < 0)
688 dev_t new_rdev = sb.st_rdev;
693 for (
const auto *device : std::as_const(
m_devices))
695 if (stat(device->getDevicePath().toLocal8Bit().constData(), &sb) < 0)
697 statError(
":AddDevice()", device->getDevicePath());
701 if (sb.st_rdev == new_rdev)
703 LOG(VB_MEDIA, LOG_INFO,
704 LOC +
":AddDevice() - not adding " + path +
706 "because it appears to be a duplicate of " +
707 device->getDevicePath());
721 LOG(VB_MEDIA, LOG_INFO,
LOC +
":AddDevice() - Added " + path);
734 QString devicePath( mep->fs_spec );
735 LOG(VB_GENERAL, LOG_DEBUG,
"AddDevice - " + devicePath);
741 bool is_supermount =
false;
742 bool is_cdrom =
false;
744 if (stat(mep->fs_spec, &sbuf) < 0)
748 if ( ! ( ((strstr(mep->fs_mntops,
"owner") &&
749 (sbuf.st_mode & S_IRUSR)) || strstr(mep->fs_mntops,
"user")) &&
750 (strstr(mep->fs_vfstype, MNTTYPE_ISO9660) ||
754 if (strstr(mep->fs_mntops, MNTTYPE_ISO9660) &&
757 is_supermount =
true;
765 if (strstr(mep->fs_mntops, MNTTYPE_ISO9660) ||
766 strstr(mep->fs_vfstype, MNTTYPE_ISO9660) ||
772 LOG(VB_GENERAL, LOG_DEBUG,
"Device is a CDROM");
784 QString dev(mep->fs_mntops);
785 int pos = dev.indexOf(QString::fromStdString(
kSuperOptDev));
789 static const QRegularExpression kSeparatorRE {
"[, ]" };
790 QStringList parts = dev.split(kSeparatorRE, Qt::SkipEmptyParts);
791 if (parts[0].isEmpty())
804 pDevice->deleteLater();
816 void MediaMonitorUnix::deviceAdded(
const QDBusObjectPath& o,
817 const QMap<QString, QVariant> &interfaces)
819 if (!interfaces.contains(QStringLiteral(
"org.freedesktop.UDisks2.Block")))
822 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
":deviceAdded " + o.path());
826 MythUdisksDevice mythdevice = UDisks2INVALID;
830 if (DetectDevice(o, mythdevice, description, path))
832 if (mythdevice == UDisks2DVD)
835 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
836 "deviceAdded: Added MythCDROM: " + path);
838 else if (mythdevice == UDisks2HDD)
840 pDevice =
MythHDD::Get(
this, path.toLatin1(),
false,
false);
841 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
842 "deviceAdded: Added MythHDD: " + path);
847 pDevice -> setDeviceModel(description.toLatin1().constData());
849 pDevice->deleteLater();
856 void MediaMonitorUnix::deviceRemoved(
const QDBusObjectPath& o,
const QStringList &interfaces)
858 if (!interfaces.contains(QStringLiteral(
"org.freedesktop.UDisks2.Block")))
861 LOG(VB_MEDIA, LOG_INFO,
LOC +
"deviceRemoved " + o.path());
863 QString dev = QFileInfo(o.path()).baseName();
864 dev.prepend(
"/dev/");
868 #else //CONFIG_QTDBUS
888 LOG(VB_MEDIA, LOG_DEBUG,
889 LOC +
":FindPartitions(" + dev +
890 QString(
",%1").arg(checkPartitions ?
" true" :
" false" ) +
")");
897 sysfs.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
899 bool found_partitions =
false;
900 QStringList parts = sysfs.entryList();
901 for (
const auto& part : std::as_const(parts))
904 if (part ==
"device" || part ==
"holders" || part ==
"queue"
905 || part ==
"slaves" || part ==
"subsystem"
906 || part ==
"bdi" || part ==
"power")
910 sysfs.absoluteFilePath(part),
false);
914 if (!found_partitions)
917 return found_partitions;
922 if (device_file.isEmpty())
927 if (cdroms.contains(dev.section(
'/', -1)))
931 this, device_file.toLatin1().constData(),
false,
m_allowEject);
937 this, device_file.toLatin1().constData(),
false,
false);
944 pDevice->deleteLater();
956 std::string buffer(256,
'\0');
967 qBuffer.append(QString::fromStdString(buffer));
970 const QStringList list = qBuffer.split(
'\n', Qt::SkipEmptyParts);
971 for (
const auto& notif : std::as_const(list))
973 if (notif.startsWith(
"add"))
975 QString dev = notif.section(
' ', 1, 1);
976 LOG(VB_MEDIA, LOG_INFO,
"Udev add " + dev);
981 else if (notif.startsWith(
"remove"))
983 QString dev = notif.section(
' ', 2, 2);
984 LOG(VB_MEDIA, LOG_INFO,
"Udev remove " + dev);