MythTV master
mediamonitor-unix.cpp
Go to the documentation of this file.
1// -*- Mode: c++ -*-
2
3// Standard C headers
4#include <cstdio>
5
6// POSIX headers
7#include <dirent.h>
8#include <unistd.h>
9#include <fcntl.h>
10#ifndef ANDROID
11#include <fstab.h>
12#endif
13
14// UNIX System headers
15#include <sys/file.h>
16#include <sys/types.h>
17#include <sys/stat.h>
18#include <sys/wait.h>
19#include <sys/param.h>
20
21#include "libmythbase/mythconfig.h"
22
23// Qt headers
24#include <QtGlobal>
25#if CONFIG_QTDBUS
26#include <QDBusConnection>
27#include <QDBusInterface>
28#include <QDBusPendingReply>
29#include <QDBusReply>
30#include <QVersionNumber>
31#include <QXmlStreamReader>
32#endif
33#include <QList>
34#include <QTextStream>
35#include <QDir>
36#include <QRegularExpression>
37
38// MythTV headers
42#include "libmythbase/mythhdd.h"
45
46#include "mediamonitor-unix.h"
47#include "mediamonitor.h"
48
49#if HAVE_LIBUDEV
50extern "C" {
51 #include <libudev.h>
52}
53#endif
54
55
56#ifndef Q_OS_ANDROID
57#ifndef MNTTYPE_ISO9660
58# ifdef __linux__
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" };
62# endif
63#endif
64
65#ifndef MNTTYPE_UDF
66static constexpr const char* MNTTYPE_UDF { "udf" };
67#endif
68
69#ifndef MNTTYPE_AUTO
70static constexpr const char* MNTTYPE_AUTO { "auto" };
71#endif
72
73#ifndef MNTTYPE_SUPERMOUNT
74static constexpr const char* MNTTYPE_SUPERMOUNT { "supermount" };
75#endif
76static const std::string kSuperOptDev { "dev=" };
77#endif // !Q_OS_ANDROID
78
79#if CONFIG_QTDBUS
80// DBus UDisks2 service - https://udisks.freedesktop.org/
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" };
90#endif
91
92
93// Some helpers for debugging:
94
95static const QString LOC = QString("MMUnix:");
96
97#ifndef Q_OS_ANDROID
98// TODO: are these used?
99static void fstabError(const QString &methodName)
100{
101 LOG(VB_GENERAL, LOG_ALERT,
102 LOC + methodName + " Error: failed to open " + _PATH_FSTAB +
103 " for reading, " + ENO);
104}
105#endif
106
107static void statError(const QString &methodName, const QString &devPath)
108{
109 LOG(VB_GENERAL, LOG_ALERT,
110 LOC + methodName + " Error: failed to stat " + devPath +
111 ", " + ENO);
112}
113
115// MediaMonitor
116
117
119 unsigned long interval, bool allowEject)
120 : MediaMonitor(par, interval, allowEject)
121{
123 if (!gCoreContext->GetBoolSetting("MonitorDrives", false)) {
124 LOG(VB_GENERAL, LOG_NOTICE, "MediaMonitor disabled by user setting.");
125 }
126 else
127 {
129 }
130
131 LOG(VB_MEDIA, LOG_INFO, LOC +"Initial device list...\n" + listDevices());
132}
133
134
135#if !CONFIG_QTDBUS
137{
138 if (m_fifo >= 0)
139 {
140 close(m_fifo);
141 m_fifo = -1;
142 unlink(kUDEV_FIFO);
143 }
145}
146#endif // !CONFIG_QTDBUS
147
148
149// Loop through the file system table and add any supported devices.
151{
152#ifndef Q_OS_ANDROID
153 struct fstab * mep = nullptr;
154
155 // Attempt to open the file system descriptor entry.
156 if (!setfsent())
157 {
158 fstabError(":CheckFileSystemTable()");
159 return false;
160 }
161
162 // Add all the entries
163 while ((mep = getfsent()) != nullptr)
164 AddDevice(mep);
165
166 endfsent();
167
168 return !m_devices.isEmpty();
169#else
170 return false;
171#endif
172}
173
174#if CONFIG_QTDBUS
175// Get a device property by name
176static QVariant DriveProperty(const QDBusObjectPath& o, const std::string& kszProperty)
177{
178 QVariant v;
179 QDBusInterface iface(UDISKS2_SVC, o.path(), UDISKS2_SVC_DRIVE,
180 QDBusConnection::systemBus() );
181 if (iface.isValid())
182 {
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()) );
187 }
188 return v;
189}
190
191// Uses a QDBusObjectPath for a block device as input parameter
192static bool DetectDevice(const QDBusObjectPath& entry, MythUdisksDevice& device,
193 QString& desc, QString& dev)
194{
195 QDBusInterface block(UDISKS2_SVC, entry.path(), UDISKS2_SVC_BLOCK,
196 QDBusConnection::systemBus() );
197
198 if (!block.property("HintSystem").toBool() &&
199 !block.property("HintIgnore").toBool())
200 {
201 dev = block.property("Device").toString();
202 LOG(VB_MEDIA, LOG_DEBUG, LOC +
203 "DetectDevice: Device found: " + dev);
204
205 // ignore floppies, too slow
206 if (dev.startsWith("/dev/fd"))
207 return false;
208
209 // Check if device has partitions
210 QDBusInterface properties(UDISKS2_SVC, entry.path(),
211 "org.freedesktop.DBus.Properties", QDBusConnection::systemBus());
212
213 auto mountpointscall = properties.call("Get", UDISKS2_SVC_FILESYSTEM,
214 "MountPoints");
215 bool isfsmountable = (properties.lastError().type() == QDBusError::NoError);
216
217 LOG(VB_MEDIA, LOG_DEBUG, LOC +
218 QString(" DetectDevice:Entry:isfsmountable : %1").arg(isfsmountable));
219
220 // Get properties of the corresponding drive
221 // Note: the properties 'Optical' and 'OpticalBlank' needs a medium inserted
222 auto drivePath = block.property("Drive").value<QDBusObjectPath>();
223 desc = DriveProperty(drivePath, "Vendor").toString();
224 if (!desc.isEmpty())
225 desc += " ";
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();
231
232 if (DriveProperty(drivePath, "Removable").toBool())
233 {
234 if (isOptical)
235 {
236 device = UDisks2DVD;
237 return true;
238 }
239 if (isfsmountable)
240 {
241 device = UDisks2HDD;
242 return true;
243 }
244 }
245 }
246 return false;
247}
248#endif // CONFIG_QTDBUS
249
250
263{
264#if CONFIG_QTDBUS
265 for (int i = 0; i < 10; ++i, usleep(500000))
266 {
267 // Connect to UDisks2. This can sometimes fail if mythfrontend
268 // is started during system init
269 QDBusInterface ifacem(UDISKS2_SVC, UDISKS2_PATH_MANAGER,
270 UDISKS2_SVC_MANAGER, QDBusConnection::systemBus() );
271
272 if (ifacem.isValid())
273 {
274 if (!ifacem.property("Version").toString().isEmpty())
275 {
276 // Minimal supported UDisk2 version: UDISKS2_MIN_VERSION
277 QString mversion = ifacem.property("Version").toString();
278 QVersionNumber this_version = QVersionNumber::fromString(mversion);
279 QVersionNumber min_version = QVersionNumber::fromString(UDISKS2_MIN_VERSION);
280 LOG(VB_MEDIA, LOG_DEBUG, LOC + "Using UDisk2 version " + mversion);
281
282 if (QVersionNumber::compare(this_version, min_version) < 0)
283 {
284 LOG(VB_GENERAL, LOG_ERR, LOC +
285 "CheckMountable: UDisks2 version too old: " + mversion);
286 return false;
287 }
288 }
289 else
290 {
291 LOG(VB_GENERAL, LOG_ERR, LOC +
292 "Cannot retrieve UDisks2 version, stopping device discovery");
293 return false;
294 }
295 }
296 else
297 {
298 LOG(VB_GENERAL, LOG_WARNING, LOC +
299 "Cannot interface to UDisks2, will retry");
300 continue;
301 }
302
303 // Get device paths
304 using QDBusObjectPathList = QList<QDBusObjectPath>;
305 QDBusPendingReply<QDBusObjectPathList> reply = ifacem.call("GetBlockDevices",
306 QVariantMap{});
307 reply.waitForFinished();
308 if (!reply.isValid())
309 {
310 LOG(VB_GENERAL, LOG_ALERT, LOC +
311 "CheckMountable DBus GetBlockDevices error: " +
312 reply.error().message() );
313 continue;
314 }
315
316 LOG(VB_MEDIA, LOG_DEBUG, LOC +
317 "CheckMountable: Start listening on UDisks2 DBus");
318
319 // Listen on DBus for UDisk add/remove device messages
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)) );
326
327 // Parse the returned device array
328 const QDBusObjectPathList& list(reply.value());
329 for (const auto& entry : std::as_const(list))
330 {
331 // Create the MythMediaDevice
332 MythMediaDevice* pDevice = nullptr;
333 MythUdisksDevice mythdevice = UDisks2INVALID;
334 QString description;
335 QString path;
336
337 if (DetectDevice(entry, mythdevice, description, path))
338 {
339 if (mythdevice == UDisks2DVD)
340 {
341 pDevice = MythCDROM::get(this, path.toLatin1(), false, m_allowEject);
342 LOG(VB_MEDIA, LOG_DEBUG, LOC +
343 "deviceAdded: Added MythCDROM: " + path);
344 }
345 else if (mythdevice == UDisks2HDD)
346 {
347 pDevice = MythHDD::Get(this, path.toLatin1(), false, false);
348 LOG(VB_MEDIA, LOG_DEBUG, LOC +
349 "deviceAdded: Added MythHDD: " + path);
350 }
351 }
352 if (pDevice)
353 {
354 // Set the device model and try to add the device
355 pDevice -> setDeviceModel(description.toLatin1().constData());
356 if (!MediaMonitorUnix::AddDevice(pDevice))
357 pDevice->deleteLater();
358 }
359 }
360
361 // Success
362 return true;
363 }
364
365 // Timed out
366 return false;
367
368#elif defined(__linux__)
369 // NB needs script in /etc/udev/rules.d
371 m_fifo = open(kUDEV_FIFO, O_RDONLY | O_NONBLOCK);
372
373 QDir sysfs("/sys/block");
374 sysfs.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
375
376 auto list = sysfs.entryList();
377 for (const auto& device : std::as_const(list))
378 {
379 // ignore floppies, too slow
380 if (device.startsWith("fd"))
381 continue;
382
383 sysfs.cd(device);
384 QString path = sysfs.absolutePath();
385 if (CheckRemovable(path))
386 FindPartitions(path, true);
387 sysfs.cdUp();
388 }
389 return true;
390#else // not CONFIG_QTDBUS and not linux
391 return false;
392#endif
393}
394
395#if !CONFIG_QTDBUS
399bool MediaMonitorUnix::CheckRemovable(const QString &dev)
400{
401#ifdef __linux__
402 QString removablePath = dev + "/removable";
403 QFile removable(removablePath);
404 if (removable.exists() && removable.open(QIODevice::ReadOnly))
405 {
406 char c = 0;
407 QString msg = LOC + ":CheckRemovable(" + dev + ")/removable ";
408 bool ok = removable.getChar(&c);
409 removable.close();
410
411 if (ok)
412 {
413 LOG(VB_MEDIA, LOG_DEBUG, msg + c);
414 if (c == '1')
415 return true;
416 }
417 else
418 {
419 LOG(VB_GENERAL, LOG_ALERT, msg + "failed");
420 }
421 }
422 return false;
423#else // if !linux
424 return false;
425#endif // !linux
426}
427
433QString MediaMonitorUnix::GetDeviceFile(const QString &sysfs)
434{
435 QString msg = LOC + ":GetDeviceFile(" + sysfs + ")";
436 QString ret = sysfs;
437
438 // In case of error, a working default? (device names usually match)
439 ret.replace(QRegularExpression(".*/"), "/dev/");
440
441#ifdef __linux__
442# if HAVE_LIBUDEV
443 // Use libudev to determine the name
444 ret.clear();
445 struct udev *udev = udev_new();
446 if (udev != nullptr)
447 {
448 struct udev_device *device =
449 udev_device_new_from_syspath(udev, sysfs.toLatin1().constData());
450 if (device != nullptr)
451 {
452 const char *name = udev_device_get_devnode(device);
453
454 if (name != nullptr)
455 ret = tr(name);
456 else
457 {
458 // This can happen when udev sends an AddDevice for a block
459 // device with partitions. FindPartition locates a partition
460 // in sysfs but udev hasn't created the devnode for it yet.
461 // Udev will send another AddDevice for the partition later.
462 LOG(VB_MEDIA, LOG_DEBUG, msg + " devnode not (yet) known");
463 }
464
465 udev_device_unref(device);
466 }
467 else
468 {
469 LOG(VB_GENERAL, LOG_ALERT,
470 msg + " udev_device_new_from_syspath returned NULL");
471 ret = "";
472 }
473
474 udev_unref(udev);
475 }
476 else
477 LOG(VB_GENERAL, LOG_ALERT,
478 "MediaMonitorUnix::GetDeviceFile udev_new failed");
479# else // !HAVE_LIBUDEV
480 // Use udevadm info to determine the name
481 QStringList args;
482 args << "info" << "-q" << "name"
483 << "-rp" << sysfs;
484
485 uint flags = kMSStdOut;
486 if (VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_DEBUG))
487 flags |= kMSStdErr;
488
489 // TODO: change this to a MythSystemLegacy on the stack?
490 MythSystemLegacy *udevinfo = new MythSystemLegacy("udevinfo", args, flags);
491 udevinfo->Run(4s);
492 if( udevinfo->Wait() != GENERIC_EXIT_OK )
493 {
494 delete udevinfo;
495 return ret;
496 }
497
498 if (VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_DEBUG))
499 {
500 QTextStream estream(udevinfo->ReadAllErr());
501 while( !estream.atEnd() )
502 LOG(VB_MEDIA, LOG_DEBUG,
503 msg + " - udevadm info error...\n" + estream.readLine());
504 }
505
506 QTextStream ostream(udevinfo->ReadAll());
507 QString udevLine = ostream.readLine();
508 if (!udevLine.startsWith("device not found in database") )
509 ret = udevLine;
510
511 delete udevinfo;
512# endif // HAVE_LIBUDEV
513#endif // linux
514
515 LOG(VB_MEDIA, LOG_INFO, msg + "->'" + ret + "'");
516 return ret;
517}
518#endif // !CONFIG_QTDBUS
519
520/*
521 * \brief Reads the list devices known to be CD or DVD devices.
522 * \return list of CD and DVD device names.
523 */
524// pure virtual
526{
527 QStringList l;
528
529#if CONFIG_QTDBUS
530 QDBusInterface blocks(UDISKS2_SVC, UDISKS2_PATH_BLOCK_DEVICES,
531 "org.freedesktop.DBus.Introspectable", QDBusConnection::systemBus());
532
533 QDBusReply<QString> reply = blocks.call("Introspect");
534 QXmlStreamReader xml_parser(reply.value());
535
536 while (!xml_parser.atEnd())
537 {
538 xml_parser.readNext();
539
540 if (xml_parser.tokenType() == QXmlStreamReader::StartElement
541 && xml_parser.name().toString() == "node")
542 {
543 const QString &name = xml_parser.attributes().value("name").toString();
544#if 0
545 LOG(VB_MEDIA, LOG_DEBUG, LOC + "GetCDROMBlockDevices: name: " + name);
546#endif
547 if (!name.isEmpty())
548 {
549 QString sbdevices = QString::fromUtf8(UDISKS2_PATH_BLOCK_DEVICES);
550 QDBusObjectPath entry {sbdevices + "/" + name};
551 MythUdisksDevice mythdevice = UDisks2INVALID;
552 QString description;
553 QString dev;
554
555 LOG(VB_MEDIA, LOG_DEBUG, LOC +
556 "GetCDROMBlockDevices: path: " + entry.path());
557
558 if (DetectDevice(entry, mythdevice, description, dev))
559 {
560 if (mythdevice == UDisks2DVD)
561 {
562 LOG(VB_MEDIA, LOG_DEBUG, LOC +
563 "GetCDROMBlockDevices: Added: " + dev);
564
565 if (dev.startsWith("/dev/"))
566 dev.remove(0,5);
567 l.push_back(dev);
568 }
569 }
570 }
571 }
572 }
573
574#elif defined(__linux__)
575 QFile file("/proc/sys/dev/cdrom/info");
576 if (file.open(QIODevice::ReadOnly))
577 {
578 QString line;
579 QTextStream stream(&file);
580 do
581 {
582 line = stream.readLine();
583 if (line.startsWith("drive name:"))
584 {
585 l = line.split('\t', Qt::SkipEmptyParts);
586 l.pop_front(); // Remove 'drive name:' field
587 break; // file should only contain one drive table?
588 }
589 }
590 while (!stream.atEnd());
591 file.close();
592 }
593#endif // linux
594
595 LOG(VB_MEDIA, LOG_DEBUG,
596 LOC + ":GetCDROMBlockDevices()->'" + l.join(", ") + "'");
597 return l;
598}
599
600#if !CONFIG_QTDBUS
601static void LookupModel(MythMediaDevice* device)
602{
603 QString desc;
604
605#if defined(__linux__)
606 // Given something like /dev/hda1, extract hda1
607 QString devname = device->getRealDevice().mid(5,5);
608
609 if (devname.startsWith("hd")) // IDE drive
610 {
611 QFile file("/proc/ide/" + devname.left(3) + "/model");
612 if (file.open(QIODevice::ReadOnly))
613 {
614 QTextStream stream(&file);
615
616 desc.append(stream.readLine());
617 file.close();
618 }
619 }
620
621 if (devname.startsWith("scd")) // scd0 doesn't appear in /sys/block,
622 devname.replace("scd", "sr"); // use sr0 instead
623
624 if (devname.startsWith("sd") // SATA/USB/FireWire
625 || devname.startsWith("sr")) // SCSI CD-ROM?
626 {
627 QString path = devname.prepend("/sys/block/");
628 path.append("/device/");
629
630 QFile file(path + "vendor");
631 if (file.open(QIODevice::ReadOnly))
632 {
633 QTextStream stream(&file);
634
635 desc.append(stream.readLine());
636 desc.append(' ');
637 file.close();
638 }
639
640 file.setFileName(path + "model");
641 if (file.open(QIODevice::ReadOnly))
642 {
643 QTextStream stream(&file);
644
645 desc.append(stream.readLine());
646 desc.append(' ');
647 file.close();
648 }
649 }
650#endif
651
652 LOG(VB_MEDIA, LOG_DEBUG, QString("LookupModel '%1' -> '%2'")
653 .arg(device->getRealDevice(), desc) );
654 device->setDeviceModel(desc.toLatin1().constData());
655}
656#endif
657
658
663{
664 if ( ! pDevice )
665 {
666 LOG(VB_GENERAL, LOG_ERR, "MediaMonitorUnix::AddDevice(null)");
667 return false;
668 }
669
670 // If the user doesn't want this device to be monitored, stop now:
671 if (shouldIgnore(pDevice))
672 return false;
673
674 QString path = pDevice->getDevicePath();
675 if (path.isEmpty())
676 {
677 LOG(VB_GENERAL, LOG_ALERT,
678 "MediaMonitorUnix::AddDevice() - empty device path.");
679 return false;
680 }
681
682 struct stat sb {};
683 if (stat(path.toLocal8Bit().constData(), &sb) < 0)
684 {
685 statError(":AddDevice()", path);
686 return false;
687 }
688 dev_t new_rdev = sb.st_rdev;
689
690 //
691 // Check if this is a duplicate of a device we have already added
692 //
693 for (const auto *device : std::as_const(m_devices))
694 {
695 if (stat(device->getDevicePath().toLocal8Bit().constData(), &sb) < 0)
696 {
697 statError(":AddDevice()", device->getDevicePath());
698 return false;
699 }
700
701 if (sb.st_rdev == new_rdev)
702 {
703 LOG(VB_MEDIA, LOG_INFO,
704 LOC + ":AddDevice() - not adding " + path +
705 "\n "
706 "because it appears to be a duplicate of " +
707 device->getDevicePath());
708 return false;
709 }
710 }
711#if !CONFIG_QTDBUS
712 // Device vendor and name already fetched by UDisks2
713 LookupModel(pDevice);
714#endif
715 QMutexLocker locker(&m_devicesLock);
716
717 connect(pDevice, &MythMediaDevice::statusChanged,
719 m_devices.push_back( pDevice );
720 m_useCount[pDevice] = 0;
721 LOG(VB_MEDIA, LOG_INFO, LOC + ":AddDevice() - Added " + path);
722
723 return true;
724}
725
726// Given a fstab entry to a media device determine what type of device it is
727bool MediaMonitorUnix::AddDevice(struct fstab * mep)
728{
729 if (!mep)
730 return false;
731
732#ifndef Q_OS_ANDROID
733#if 0
734 QString devicePath( mep->fs_spec );
735 LOG(VB_GENERAL, LOG_DEBUG, "AddDevice - " + devicePath);
736#endif
737
738 MythMediaDevice* pDevice = nullptr;
739 struct stat sbuf {};
740
741 bool is_supermount = false;
742 bool is_cdrom = false;
743
744 if (stat(mep->fs_spec, &sbuf) < 0)
745 return false;
746
747 // Can it be mounted?
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)) ) )
753 {
754 if (strstr(mep->fs_mntops, MNTTYPE_ISO9660) &&
755 strstr(mep->fs_vfstype, MNTTYPE_SUPERMOUNT))
756 {
757 is_supermount = true;
758 }
759 else
760 {
761 return false;
762 }
763 }
764
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))
769 {
770 is_cdrom = true;
771#if 0
772 LOG(VB_GENERAL, LOG_DEBUG, "Device is a CDROM");
773#endif
774 }
775
776 if (!is_supermount)
777 {
778 if (is_cdrom)
779 pDevice = MythCDROM::get(this, mep->fs_spec,
780 is_supermount, m_allowEject);
781 }
782 else
783 {
784 QString dev(mep->fs_mntops);
785 int pos = dev.indexOf(QString::fromStdString(kSuperOptDev));
786 if (pos == -1)
787 return false;
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())
792 return false;
793 pDevice = MythCDROM::get(this, dev, is_supermount, m_allowEject);
794 }
795
796 if (pDevice)
797 {
798 pDevice->setMountPath(mep->fs_file);
799 if (pDevice->testMedia() == MEDIAERR_OK)
800 {
801 if (MediaMonitorUnix::AddDevice(pDevice))
802 return true;
803 }
804 pDevice->deleteLater();
805 }
806#endif
807
808 return false;
809}
810
811#if CONFIG_QTDBUS
812/*
813 * DBus UDisk AddDevice handler
814 */
815
816void MediaMonitorUnix::deviceAdded( const QDBusObjectPath& o,
817 const QMap<QString, QVariant> &interfaces)
818{
819 if (!interfaces.contains(QStringLiteral("org.freedesktop.UDisks2.Block")))
820 return;
821
822 LOG(VB_MEDIA, LOG_DEBUG, LOC + ":deviceAdded " + o.path());
823
824 // Create the MythMediaDevice
825 MythMediaDevice* pDevice = nullptr;
826 MythUdisksDevice mythdevice = UDisks2INVALID;
827 QString description;
828 QString path;
829
830 if (DetectDevice(o, mythdevice, description, path))
831 {
832 if (mythdevice == UDisks2DVD)
833 {
834 pDevice = MythCDROM::get(this, path.toLatin1(), false, m_allowEject);
835 LOG(VB_MEDIA, LOG_DEBUG, LOC +
836 "deviceAdded: Added MythCDROM: " + path);
837 }
838 else if (mythdevice == UDisks2HDD)
839 {
840 pDevice = MythHDD::Get(this, path.toLatin1(), false, false);
841 LOG(VB_MEDIA, LOG_DEBUG, LOC +
842 "deviceAdded: Added MythHDD: " + path);
843 }
844 }
845 if (pDevice)
846 {
847 pDevice -> setDeviceModel(description.toLatin1().constData());
848 if (!MediaMonitorUnix::AddDevice(pDevice))
849 pDevice->deleteLater();
850 }
851}
852
853/*
854 * DBus UDisk RemoveDevice handler
855 */
856void MediaMonitorUnix::deviceRemoved(const QDBusObjectPath& o, const QStringList &interfaces)
857{
858 if (!interfaces.contains(QStringLiteral("org.freedesktop.UDisks2.Block")))
859 return;
860
861 LOG(VB_MEDIA, LOG_INFO, LOC + "deviceRemoved " + o.path());
862
863 QString dev = QFileInfo(o.path()).baseName();
864 dev.prepend("/dev/");
865 RemoveDevice(dev);
866}
867
868#else //CONFIG_QTDBUS
869
886bool MediaMonitorUnix::FindPartitions(const QString &dev, bool checkPartitions)
887{
888 LOG(VB_MEDIA, LOG_DEBUG,
889 LOC + ":FindPartitions(" + dev +
890 QString(",%1").arg(checkPartitions ? " true" : " false" ) + ")");
891 MythMediaDevice* pDevice = nullptr;
892
893 if (checkPartitions)
894 {
895 // check for partitions
896 QDir sysfs(dev);
897 sysfs.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
898
899 bool found_partitions = false;
900 QStringList parts = sysfs.entryList();
901 for (const auto& part : std::as_const(parts))
902 {
903 // skip some sysfs dirs that are _not_ sub-partitions
904 if (part == "device" || part == "holders" || part == "queue"
905 || part == "slaves" || part == "subsystem"
906 || part == "bdi" || part == "power")
907 continue;
908
909 found_partitions |= FindPartitions(
910 sysfs.absoluteFilePath(part), false);
911 }
912
913 // no partitions on block device, use main device
914 if (!found_partitions)
915 found_partitions |= FindPartitions(sysfs.absolutePath(), false);
916
917 return found_partitions;
918 }
919
920 QString device_file = GetDeviceFile(dev);
921
922 if (device_file.isEmpty())
923 return false;
924
925 QStringList cdroms = GetCDROMBlockDevices();
926
927 if (cdroms.contains(dev.section('/', -1)))
928 {
929 // found cdrom device
930 pDevice = MythCDROM::get(
931 this, device_file.toLatin1().constData(), false, m_allowEject);
932 }
933 else
934 {
935 // found block or partition device
936 pDevice = MythHDD::Get(
937 this, device_file.toLatin1().constData(), false, false);
938 }
939
940 if (AddDevice(pDevice))
941 return true;
942
943 if (pDevice)
944 pDevice->deleteLater();
945
946 return false;
947}
948
955{
956 std::string buffer(256,'\0');
957 QString qBuffer;
958
959 if (m_fifo == -1)
960 return;
961
962 int size = read(m_fifo, buffer.data(), 255);
963 while (size > 0)
964 {
965 // append buffer to QString
966 buffer[size] = '\0';
967 qBuffer.append(QString::fromStdString(buffer));
968 size = read(m_fifo, buffer.data(), 255);
969 }
970 const QStringList list = qBuffer.split('\n', Qt::SkipEmptyParts);
971 for (const auto& notif : std::as_const(list))
972 {
973 if (notif.startsWith("add"))
974 {
975 QString dev = notif.section(' ', 1, 1);
976 LOG(VB_MEDIA, LOG_INFO, "Udev add " + dev);
977
978 if (CheckRemovable(dev))
979 FindPartitions(dev, true);
980 }
981 else if (notif.startsWith("remove"))
982 {
983 QString dev = notif.section(' ', 2, 2);
984 LOG(VB_MEDIA, LOG_INFO, "Udev remove " + dev);
985 RemoveDevice(dev);
986 }
987 }
988}
989#endif
static bool CheckRemovable(const QString &dev)
Is /sys/block/dev a removable device?
bool AddDevice(MythMediaDevice *pDevice) override
CONFIG_QTDBUS.
MediaMonitorUnix(QObject *par, unsigned long interval, bool allowEject)
bool CheckMountable(void)
Search /sys/block for valid removable media devices.
bool FindPartitions(const QString &dev, bool checkPartitions)
Creates MythMedia instances for sysfs removable media devices.
QStringList GetCDROMBlockDevices(void) override
bool CheckFileSystemTable(void)
void deleteLater(void) override
void CheckDeviceNotifications(void) override
Checks the named pipe, kUDEV_FIFO, for hotplug events from the udev system.
static QString GetDeviceFile(const QString &sysfs)
Returns the device special file associated with the /sys/block node.
static constexpr const char * kUDEV_FIFO
void mediaStatusChanged(MythMediaStatus oldStatus, MythMediaDevice *pMedia) const
Slot which is called when the device status changes and posts a media event to the mainwindow.
bool RemoveDevice(const QString &dev)
Remove a device from the media monitor.
QRecursiveMutex m_devicesLock
Definition: mediamonitor.h:121
bool shouldIgnore(const MythMediaDevice *device)
Check user preferences to see if this device should be monitored.
QList< MythMediaDevice * > m_devices
Definition: mediamonitor.h:122
virtual void deleteLater(void)
QMap< MythMediaDevice *, int > m_useCount
Definition: mediamonitor.h:124
QString listDevices(void)
A string summarising the current devices, for debugging.
static MythCDROM * get(QObject *par, const QString &devicePath, bool SuperMount, bool AllowEject)
Definition: mythcdrom.cpp:43
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.
Definition: mythhdd.cpp:15
const QString & getRealDevice() const
Definition: mythmedia.h:63
void statusChanged(MythMediaStatus oldStatus, MythMediaDevice *pMedia)
const QString & getDevicePath() const
Definition: mythmedia.h:61
virtual MythMediaError testMedia()
Definition: mythmedia.h:95
void setMountPath(const char *path)
Definition: mythmedia.h:59
void setDeviceModel(const char *model)
Definition: mythmedia.h:68
uint Wait(std::chrono::seconds timeout=0s)
QByteArray & ReadAllErr()
void Run(std::chrono::seconds timeout=0s)
Runs a command inside the /bin/sh shell. Returns immediately.
QByteArray & ReadAll()
#define mkfifo(path, mode)
Definition: compat.h:147
#define O_NONBLOCK
Definition: compat.h:292
#define close
Definition: compat.h:39
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:13
unsigned int uint
Definition: freesurround.h:24
static void LookupModel(MythMediaDevice *device)
static constexpr const char * MNTTYPE_UDF
static void statError(const QString &methodName, const QString &devPath)
static constexpr const char * MNTTYPE_SUPERMOUNT
static void fstabError(const QString &methodName)
static const std::string kSuperOptDev
static const QString LOC
static constexpr const char * MNTTYPE_AUTO
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
@ MEDIAERR_OK
Definition: mythmedia.h:40
@ kMSStdErr
allow access to stderr
Definition: mythsystem.h:42
@ kMSStdOut
allow access to stdout
Definition: mythsystem.h:41
QDateTime fromString(const QString &dtstr)
Converts kFilename && kISODate formats to QDateTime.
Definition: mythdate.cpp:39
def read(device=None, features=[])
Definition: disc.py:35
#define S_IROTH
Definition: replex.cpp:57
#define S_IRGRP
Definition: replex.cpp:55
#define S_IWOTH
Definition: replex.cpp:58
#define S_IWGRP
Definition: replex.cpp:56