5#include "libmythbase/mythconfig.h"
29#include <QDBusConnection>
30#include <QDBusInterface>
31#include <QDBusPendingReply>
33#include <QVersionNumber>
34#include <QXmlStreamReader>
39#include <QRegularExpression>
57#ifndef MNTTYPE_ISO9660
59 static constexpr const char* MNTTYPE_ISO9660 {
"iso9660" };
60# elif defined(Q_OS_BSD4)
61 static constexpr const char* MNTTYPE_ISO9660 {
"cd9660" };
66static constexpr const char* MNTTYPE_UDF {
"udf" };
70static constexpr const char* MNTTYPE_AUTO {
"auto" };
73#ifndef MNTTYPE_SUPERMOUNT
74static constexpr const char* MNTTYPE_SUPERMOUNT {
"supermount" };
76static const std::string kSuperOptDev {
"dev=" };
81static constexpr const char* UDISKS2_SVC {
"org.freedesktop.UDisks2" };
82static constexpr const char* UDISKS2_SVC_DRIVE {
"org.freedesktop.UDisks2.Drive" };
83static constexpr const char* UDISKS2_SVC_BLOCK {
"org.freedesktop.UDisks2.Block" };
84static constexpr const char* UDISKS2_SVC_FILESYSTEM {
"org.freedesktop.UDisks2.Filesystem" };
85static constexpr const char* UDISKS2_SVC_MANAGER {
"org.freedesktop.UDisks2.Manager" };
86static constexpr const char* UDISKS2_PATH {
"/org/freedesktop/UDisks2" };
87static constexpr const char* UDISKS2_PATH_MANAGER {
"/org/freedesktop/UDisks2/Manager" };
88static constexpr const char* UDISKS2_PATH_BLOCK_DEVICES {
"/org/freedesktop/UDisks2/block_devices" };
89static constexpr const char* UDISKS2_MIN_VERSION {
"2.7.3" };
95static const QString
LOC = QString(
"MMUnix:");
99static void fstabError(
const QString &methodName)
101 LOG(VB_GENERAL, LOG_ALERT,
102 LOC + methodName +
" Error: failed to open " + _PATH_FSTAB +
103 " for reading, " +
ENO);
107static 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.");
154 struct fstab * mep =
nullptr;
159 fstabError(
":CheckFileSystemTable()");
164 while ((mep = getfsent()) !=
nullptr)
177static QVariant DriveProperty(
const QDBusObjectPath& o,
const std::string& kszProperty)
180 QDBusInterface iface(UDISKS2_SVC, o.path(), UDISKS2_SVC_DRIVE,
181 QDBusConnection::systemBus() );
184 v = iface.property(kszProperty.c_str());
185 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
186 "Udisks2:Drive:" + kszProperty.c_str() +
" : " +
187 (v.canConvert<QStringList>() ? v.toStringList().join(
", ") : v.toString()) );
193static bool DetectDevice(
const QDBusObjectPath& entry, MythUdisksDevice& device,
194 QString& desc, QString& dev)
196 QDBusInterface block(UDISKS2_SVC, entry.path(), UDISKS2_SVC_BLOCK,
197 QDBusConnection::systemBus() );
199 if (!block.property(
"HintSystem").toBool() &&
200 !block.property(
"HintIgnore").toBool())
202 dev = block.property(
"Device").toString();
203 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
204 "DetectDevice: Device found: " + dev);
207 if (dev.startsWith(
"/dev/fd"))
211 QDBusInterface properties(UDISKS2_SVC, entry.path(),
212 "org.freedesktop.DBus.Properties", QDBusConnection::systemBus());
214 auto mountpointscall = properties.call(
"Get", UDISKS2_SVC_FILESYSTEM,
216 bool isfsmountable = (properties.lastError().type() == QDBusError::NoError);
218 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
219 QString(
" DetectDevice:Entry:isfsmountable : %1").arg(isfsmountable));
223 auto drivePath = block.property(
"Drive").value<QDBusObjectPath>();
224 desc = DriveProperty(drivePath,
"Vendor").toString();
227 desc += DriveProperty(drivePath,
"Model").toString();
228 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
229 QString(
"DetectDevice: Found drive '%1'").arg(desc));
230 const auto media = DriveProperty(drivePath,
"MediaCompatibility").toStringList();
231 const bool isOptical = !media.filter(
"optical", Qt::CaseInsensitive).isEmpty();
233 if (DriveProperty(drivePath,
"Removable").toBool())
266 for (
int i = 0; i < 10; ++i, std::this_thread::sleep_for(500ms))
270 QDBusInterface ifacem(UDISKS2_SVC, UDISKS2_PATH_MANAGER,
271 UDISKS2_SVC_MANAGER, QDBusConnection::systemBus() );
273 if (ifacem.isValid())
275 if (!ifacem.property(
"Version").toString().isEmpty())
278 QString mversion = ifacem.property(
"Version").toString();
281 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
"Using UDisk2 version " + mversion);
283 if (QVersionNumber::compare(this_version, min_version) < 0)
285 LOG(VB_GENERAL, LOG_ERR,
LOC +
286 "CheckMountable: UDisks2 version too old: " + mversion);
292 LOG(VB_GENERAL, LOG_ERR,
LOC +
293 "Cannot retrieve UDisks2 version, stopping device discovery");
299 LOG(VB_GENERAL, LOG_WARNING,
LOC +
300 "Cannot interface to UDisks2, will retry");
305 using QDBusObjectPathList = QList<QDBusObjectPath>;
306 QDBusPendingReply<QDBusObjectPathList> reply = ifacem.call(
"GetBlockDevices",
308 reply.waitForFinished();
309 if (!reply.isValid())
311 LOG(VB_GENERAL, LOG_ALERT,
LOC +
312 "CheckMountable DBus GetBlockDevices error: " +
313 reply.error().message() );
317 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
318 "CheckMountable: Start listening on UDisks2 DBus");
321 (void)QDBusConnection::systemBus().connect(UDISKS2_SVC, UDISKS2_PATH,
322 "org.freedesktop.DBus.ObjectManager",
"InterfacesAdded",
323 this, SLOT(deviceAdded(QDBusObjectPath,QMap<QString,QVariant>)) );
324 (void)QDBusConnection::systemBus().connect(UDISKS2_SVC, UDISKS2_PATH,
325 "org.freedesktop.DBus.ObjectManager",
"InterfacesRemoved",
326 this, SLOT(deviceRemoved(QDBusObjectPath,QStringList)) );
329 const QDBusObjectPathList& list(reply.value());
330 for (
const auto& entry : std::as_const(list))
334 MythUdisksDevice mythdevice = UDisks2INVALID;
338 if (DetectDevice(entry, mythdevice, description, path))
340 if (mythdevice == UDisks2DVD)
343 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
344 "deviceAdded: Added MythCDROM: " + path);
346 else if (mythdevice == UDisks2HDD)
348 pDevice =
MythHDD::Get(
this, path.toLatin1().constData(),
false,
false);
349 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
350 "deviceAdded: Added MythHDD: " + path);
356 pDevice -> setDeviceModel(description.toLatin1().constData());
358 pDevice->deleteLater();
369#elif defined(Q_OS_LINUX)
374 QDir sysfs(
"/sys/block");
375 sysfs.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
377 auto list = sysfs.entryList();
378 for (
const auto& device : std::as_const(list))
381 if (device.startsWith(
"fd"))
385 QString path = sysfs.absolutePath();
403 QString removablePath = dev +
"/removable";
404 QFile removable(removablePath);
405 if (removable.exists() && removable.open(QIODevice::ReadOnly))
408 QString msg =
LOC +
":CheckRemovable(" + dev +
")/removable ";
409 bool ok = removable.getChar(&c);
414 LOG(VB_MEDIA, LOG_DEBUG, msg + c);
420 LOG(VB_GENERAL, LOG_ALERT, msg +
"failed");
436 QString msg =
LOC +
":GetDeviceFile(" + sysfs +
")";
440 ret.replace(QRegularExpression(
".*/"),
"/dev/");
446 struct udev *udev = udev_new();
449 struct udev_device *device =
450 udev_device_new_from_syspath(udev, sysfs.toLatin1().constData());
451 if (device !=
nullptr)
453 const char *name = udev_device_get_devnode(device);
463 LOG(VB_MEDIA, LOG_DEBUG, msg +
" devnode not (yet) known");
466 udev_device_unref(device);
470 LOG(VB_GENERAL, LOG_ALERT,
471 msg +
" udev_device_new_from_syspath returned NULL");
478 LOG(VB_GENERAL, LOG_ALERT,
479 "MediaMonitorUnix::GetDeviceFile udev_new failed");
483 args <<
"info" <<
"-q" <<
"name"
501 QTextStream estream(udevinfo->ReadAllErr());
502 while( !estream.atEnd() )
503 LOG(VB_MEDIA, LOG_DEBUG,
504 msg +
" - udevadm info error...\n" + estream.readLine());
507 QTextStream ostream(udevinfo->ReadAll());
508 QString udevLine = ostream.readLine();
509 if (!udevLine.startsWith(
"device not found in database") )
516 LOG(VB_MEDIA, LOG_INFO, msg +
"->'" + ret +
"'");
531 QDBusInterface blocks(UDISKS2_SVC, UDISKS2_PATH_BLOCK_DEVICES,
532 "org.freedesktop.DBus.Introspectable", QDBusConnection::systemBus());
534 QDBusReply<QString> reply = blocks.call(
"Introspect");
535 QXmlStreamReader xml_parser(reply.value());
537 while (!xml_parser.atEnd())
539 xml_parser.readNext();
541 if (xml_parser.tokenType() == QXmlStreamReader::StartElement
542 && xml_parser.name().toString() ==
"node")
544 const QString &name = xml_parser.attributes().value(
"name").toString();
546 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
"GetCDROMBlockDevices: name: " + name);
550 QString sbdevices = QString::fromUtf8(UDISKS2_PATH_BLOCK_DEVICES);
551 QDBusObjectPath entry {sbdevices +
"/" + name};
552 MythUdisksDevice mythdevice = UDisks2INVALID;
556 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
557 "GetCDROMBlockDevices: path: " + entry.path());
559 if (DetectDevice(entry, mythdevice, description, dev))
561 if (mythdevice == UDisks2DVD)
563 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
564 "GetCDROMBlockDevices: Added: " + dev);
566 if (dev.startsWith(
"/dev/"))
575#elif defined(Q_OS_LINUX)
576 QFile
file(
"/proc/sys/dev/cdrom/info");
577 if (
file.open(QIODevice::ReadOnly))
580 QTextStream stream(&
file);
581 while (!stream.atEnd())
583 line = stream.readLine();
584 if (line.startsWith(
"drive name:"))
586 l = line.split(
'\t', Qt::SkipEmptyParts);
595 LOG(VB_MEDIA, LOG_DEBUG,
596 LOC +
":GetCDROMBlockDevices()->'" + l.join(
", ") +
"'");
605#if defined(Q_OS_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) ||
751 strstr(mep->fs_vfstype, MNTTYPE_UDF) ||
752 strstr(mep->fs_vfstype, MNTTYPE_AUTO)) ) )
754 if (strstr(mep->fs_mntops, MNTTYPE_ISO9660) &&
755 strstr(mep->fs_vfstype, MNTTYPE_SUPERMOUNT))
757 is_supermount =
true;
765 if (strstr(mep->fs_mntops, MNTTYPE_ISO9660) ||
766 strstr(mep->fs_vfstype, MNTTYPE_ISO9660) ||
767 strstr(mep->fs_vfstype, MNTTYPE_UDF) ||
768 strstr(mep->fs_vfstype, MNTTYPE_AUTO))
772 LOG(VB_GENERAL, LOG_DEBUG,
"Device is a CDROM");
784 QString dev(mep->fs_mntops);
785 int pos = dev.indexOf(QString::fromStdString(kSuperOptDev));
788 dev = dev.mid(pos+kSuperOptDev.size());
789 static const QRegularExpression kSeparatorRE {
"[, ]" };
790 QStringList parts = dev.split(kSeparatorRE, Qt::SkipEmptyParts);
791 if (parts[0].isEmpty())
804 pDevice->deleteLater();
816void 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().constData(),
false,
false);
841 LOG(VB_MEDIA, LOG_DEBUG,
LOC +
842 "deviceAdded: Added MythHDD: " + path);
847 pDevice -> setDeviceModel(description.toLatin1().constData());
849 pDevice->deleteLater();
856void 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/");
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);
992#include "moc_mediamonitor-unix.cpp"
static MythCDROM * get(QObject *par, const QString &devicePath, bool SuperMount, bool AllowEject)
bool GetBoolSetting(const QString &key, bool defaultval=false)
static MythHDD * Get(QObject *par, const char *devicePath, bool SuperMount, bool AllowEject)
Helper function used to create a new instance of a hard disk device.
#define mkfifo(path, mode)
@ GENERIC_EXIT_OK
Exited with no error.
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
#define ENO
This can be appended to the LOG args with "+".
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
@ kMSStdErr
allow access to stderr
@ kMSStdOut
allow access to stdout
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
def read(device=None, features=[])