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