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() + " : " +
183  (v.canConvert<QStringList>() ? v.toStringList().join(", ") : v.toString()) );
184  }
185  return v;
186 }
187 
188 // Uses a QDBusObjectPath for a block device as input parameter
189 static bool DetectDevice(const QDBusObjectPath& entry, MythUdisksDevice& device,
190  QString& desc, QString& dev)
191 {
192  QDBusInterface block(UDISKS2_SVC, entry.path(), UDISKS2_SVC_BLOCK,
193  QDBusConnection::systemBus() );
194 
195  if (!block.property("HintSystem").toBool() &&
196  !block.property("HintIgnore").toBool())
197  {
198  dev = block.property("Device").toString();
199  LOG(VB_MEDIA, LOG_DEBUG, LOC +
200  "DetectDevice: Device found: " + dev);
201 
202  // ignore floppies, too slow
203  if (dev.startsWith("/dev/fd"))
204  return false;
205 
206  // Check if device has partitions
207  QDBusInterface properties(UDISKS2_SVC, entry.path(),
208  "org.freedesktop.DBus.Properties", QDBusConnection::systemBus());
209 
210  auto mountpointscall = properties.call("Get", UDISKS2_SVC_FILESYSTEM,
211  "MountPoints");
212  bool isfsmountable = (properties.lastError().type() == QDBusError::NoError);
213 
214  LOG(VB_MEDIA, LOG_DEBUG, LOC +
215  QString(" DetectDevice:Entry:isfsmountable : %1").arg(isfsmountable));
216 
217  // Get properties of the corresponding drive
218  // Note: the properties 'Optical' and 'OpticalBlank' needs a medium inserted
219  auto drivePath = block.property("Drive").value<QDBusObjectPath>();
220  desc = DriveProperty(drivePath, "Vendor").toString();
221  if (!desc.isEmpty())
222  desc += " ";
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();
228 
229  if (DriveProperty(drivePath, "Removable").toBool())
230  {
231  if (isOptical)
232  {
233  device = UDisks2DVD;
234  return true;
235  }
236  if (isfsmountable)
237  {
238  device = UDisks2HDD;
239  return true;
240  }
241  }
242  }
243  return false;
244 }
245 #endif // CONFIG_QTDBUS
246 
247 
260 {
261 #if CONFIG_QTDBUS
262  for (int i = 0; i < 10; ++i, usleep(500000))
263  {
264  // Connect to UDisks2. This can sometimes fail if mythfrontend
265  // is started during system init
266  QDBusInterface ifacem(UDISKS2_SVC, UDISKS2_PATH_MANAGER,
267  UDISKS2_SVC_MANAGER, QDBusConnection::systemBus() );
268 
269  if (ifacem.isValid())
270  {
271  if (!ifacem.property("Version").toString().isEmpty())
272  {
273  // Minimal supported UDisk2 version: UDISKS2_MIN_VERSION
274  QString mversion = ifacem.property("Version").toString();
275  QVersionNumber this_version = QVersionNumber::fromString(mversion);
276  QVersionNumber min_version = QVersionNumber::fromString(UDISKS2_MIN_VERSION);
277  LOG(VB_MEDIA, LOG_DEBUG, LOC + "Using UDisk2 version " + mversion);
278 
279  if (QVersionNumber::compare(this_version, min_version) < 0)
280  {
281  LOG(VB_GENERAL, LOG_ERR, LOC +
282  "CheckMountable: UDisks2 version too old: " + mversion);
283  return false;
284  }
285  }
286  else
287  {
288  LOG(VB_GENERAL, LOG_ERR, LOC +
289  "Cannot retrieve UDisks2 version, stopping device discovery");
290  return false;
291  }
292  }
293  else
294  {
295  LOG(VB_GENERAL, LOG_WARNING, LOC +
296  "Cannot interface to UDisks2, will retry");
297  continue;
298  }
299 
300  // Get device paths
301  using QDBusObjectPathList = QList<QDBusObjectPath>;
302  QDBusPendingReply<QDBusObjectPathList> reply = ifacem.call("GetBlockDevices",
303  QVariantMap{});
304  reply.waitForFinished();
305  if (!reply.isValid())
306  {
307  LOG(VB_GENERAL, LOG_ALERT, LOC +
308  "CheckMountable DBus GetBlockDevices error: " +
309  reply.error().message() );
310  continue;
311  }
312 
313  LOG(VB_MEDIA, LOG_DEBUG, LOC +
314  "CheckMountable: Start listening on UDisks2 DBus");
315 
316  // Listen on DBus for UDisk add/remove device messages
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)) );
323 
324  // Parse the returned device array
325  const QDBusObjectPathList& list(reply.value());
326  for (const auto& entry : qAsConst(list))
327  {
328  // Create the MythMediaDevice
329  MythMediaDevice* pDevice = nullptr;
330  MythUdisksDevice mythdevice = UDisks2INVALID;
331  QString description;
332  QString path;
333 
334  if (DetectDevice(entry, mythdevice, description, path))
335  {
336  if (mythdevice == UDisks2DVD)
337  {
338  pDevice = MythCDROM::get(this, path.toLatin1(), false, m_allowEject);
339  LOG(VB_MEDIA, LOG_DEBUG, LOC +
340  "deviceAdded: Added MythCDROM: " + path);
341  }
342  else if (mythdevice == UDisks2HDD)
343  {
344  pDevice = MythHDD::Get(this, path.toLatin1(), false, false);
345  LOG(VB_MEDIA, LOG_DEBUG, LOC +
346  "deviceAdded: Added MythHDD: " + path);
347  }
348  }
349  if (pDevice)
350  {
351  // Set the device model and try to add the device
352  pDevice -> setDeviceModel(description.toLatin1().constData());
353  if (!MediaMonitorUnix::AddDevice(pDevice))
354  pDevice->deleteLater();
355  }
356  }
357 
358  // Success
359  return true;
360  }
361 
362  // Timed out
363  return false;
364 
365 #elif defined(__linux__)
366  // NB needs script in /etc/udev/rules.d
367  mkfifo(kUDEV_FIFO, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
368  m_fifo = open(kUDEV_FIFO, O_RDONLY | O_NONBLOCK);
369 
370  QDir sysfs("/sys/block");
371  sysfs.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
372 
373  auto list = sysfs.entryList();
374  for (const auto& device : qAsConst(list))
375  {
376  // ignore floppies, too slow
377  if (device.startsWith("fd"))
378  continue;
379 
380  sysfs.cd(device);
381  QString path = sysfs.absolutePath();
382  if (CheckRemovable(path))
383  FindPartitions(path, true);
384  sysfs.cdUp();
385  }
386  return true;
387 #else // not CONFIG_QTDBUS and not linux
388  return false;
389 #endif
390 }
391 
392 #if !CONFIG_QTDBUS
393 
396 bool MediaMonitorUnix::CheckRemovable(const QString &dev)
397 {
398 #ifdef __linux__
399  QString removablePath = dev + "/removable";
400  QFile removable(removablePath);
401  if (removable.exists() && removable.open(QIODevice::ReadOnly))
402  {
403  char c = 0;
404  QString msg = LOC + ":CheckRemovable(" + dev + ")/removable ";
405  bool ok = removable.getChar(&c);
406  removable.close();
407 
408  if (ok)
409  {
410  LOG(VB_MEDIA, LOG_DEBUG, msg + c);
411  if (c == '1')
412  return true;
413  }
414  else
415  {
416  LOG(VB_GENERAL, LOG_ALERT, msg + "failed");
417  }
418  }
419  return false;
420 #else // if !linux
421  return false;
422 #endif // !linux
423 }
424 
430 QString MediaMonitorUnix::GetDeviceFile(const QString &sysfs)
431 {
432  QString msg = LOC + ":GetDeviceFile(" + sysfs + ")";
433  QString ret = sysfs;
434 
435  // In case of error, a working default? (device names usually match)
436  ret.replace(QRegularExpression(".*/"), "/dev/");
437 
438 #ifdef __linux__
439 # if HAVE_LIBUDEV
440  // Use libudev to determine the name
441  ret.clear();
442  struct udev *udev = udev_new();
443  if (udev != nullptr)
444  {
445  struct udev_device *device =
446  udev_device_new_from_syspath(udev, sysfs.toLatin1().constData());
447  if (device != nullptr)
448  {
449  const char *name = udev_device_get_devnode(device);
450 
451  if (name != nullptr)
452  ret = tr(name);
453  else
454  {
455  // This can happen when udev sends an AddDevice for a block
456  // device with partitions. FindPartition locates a partition
457  // in sysfs but udev hasn't created the devnode for it yet.
458  // Udev will send another AddDevice for the partition later.
459  LOG(VB_MEDIA, LOG_DEBUG, msg + " devnode not (yet) known");
460  }
461 
462  udev_device_unref(device);
463  }
464  else
465  {
466  LOG(VB_GENERAL, LOG_ALERT,
467  msg + " udev_device_new_from_syspath returned NULL");
468  ret = "";
469  }
470 
471  udev_unref(udev);
472  }
473  else
474  LOG(VB_GENERAL, LOG_ALERT,
475  "MediaMonitorUnix::GetDeviceFile udev_new failed");
476 # else // !HAVE_LIBUDEV
477  // Use udevadm info to determine the name
478  QStringList args;
479  args << "info" << "-q" << "name"
480  << "-rp" << sysfs;
481 
482  uint flags = kMSStdOut;
483  if (VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_DEBUG))
484  flags |= kMSStdErr;
485 
486  // TODO: change this to a MythSystemLegacy on the stack?
487  MythSystemLegacy *udevinfo = new MythSystemLegacy("udevinfo", args, flags);
488  udevinfo->Run(4s);
489  if( udevinfo->Wait() != GENERIC_EXIT_OK )
490  {
491  delete udevinfo;
492  return ret;
493  }
494 
495  if (VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_DEBUG))
496  {
497  QTextStream estream(udevinfo->ReadAllErr());
498  while( !estream.atEnd() )
499  LOG(VB_MEDIA, LOG_DEBUG,
500  msg + " - udevadm info error...\n" + estream.readLine());
501  }
502 
503  QTextStream ostream(udevinfo->ReadAll());
504  QString udevLine = ostream.readLine();
505  if (!udevLine.startsWith("device not found in database") )
506  ret = udevLine;
507 
508  delete udevinfo;
509 # endif // HAVE_LIBUDEV
510 #endif // linux
511 
512  LOG(VB_MEDIA, LOG_INFO, msg + "->'" + ret + "'");
513  return ret;
514 }
515 #endif // !CONFIG_QTDBUS
516 
517 /*
518  * \brief Reads the list devices known to be CD or DVD devices.
519  * \return list of CD and DVD device names.
520  */
521 // pure virtual
523 {
524  QStringList l;
525 
526 #if CONFIG_QTDBUS
527  QDBusInterface blocks(UDISKS2_SVC, UDISKS2_PATH_BLOCK_DEVICES,
528  "org.freedesktop.DBus.Introspectable", QDBusConnection::systemBus());
529 
530  QDBusReply<QString> reply = blocks.call("Introspect");
531  QXmlStreamReader xml_parser(reply.value());
532 
533  while (!xml_parser.atEnd())
534  {
535  xml_parser.readNext();
536 
537  if (xml_parser.tokenType() == QXmlStreamReader::StartElement
538  && xml_parser.name().toString() == "node")
539  {
540  const QString &name = xml_parser.attributes().value("name").toString();
541 #if 0
542  LOG(VB_MEDIA, LOG_DEBUG, LOC + "GetCDROMBlockDevices: name: " + name);
543 #endif
544  if (!name.isEmpty())
545  {
546  QString sbdevices = QString::fromUtf8(UDISKS2_PATH_BLOCK_DEVICES);
547  QDBusObjectPath entry {sbdevices + "/" + name};
548  MythUdisksDevice mythdevice = UDisks2INVALID;
549  QString description;
550  QString dev;
551 
552  LOG(VB_MEDIA, LOG_DEBUG, LOC +
553  "GetCDROMBlockDevices: path: " + entry.path());
554 
555  if (DetectDevice(entry, mythdevice, description, dev))
556  {
557  if (mythdevice == UDisks2DVD)
558  {
559  LOG(VB_MEDIA, LOG_DEBUG, LOC +
560  "GetCDROMBlockDevices: Added: " + dev);
561 
562  if (dev.startsWith("/dev/"))
563  dev.remove(0,5);
564  l.push_back(dev);
565  }
566  }
567  }
568  }
569  }
570 
571 #elif defined(__linux__)
572  QFile file("/proc/sys/dev/cdrom/info");
573  if (file.open(QIODevice::ReadOnly))
574  {
575  QString line;
576  QTextStream stream(&file);
577  do
578  {
579  line = stream.readLine();
580  if (line.startsWith("drive name:"))
581  {
582 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
583  l = line.split('\t', QString::SkipEmptyParts);
584 #else
585  l = line.split('\t', Qt::SkipEmptyParts);
586 #endif
587  l.pop_front(); // Remove 'drive name:' field
588  break; // file should only contain one drive table?
589  }
590  }
591  while (!stream.atEnd());
592  file.close();
593  }
594 #endif // linux
595 
596  LOG(VB_MEDIA, LOG_DEBUG,
597  LOC + ":GetCDROMBlockDevices()->'" + l.join(", ") + "'");
598  return l;
599 }
600 
601 #if !CONFIG_QTDBUS
602 static void LookupModel(MythMediaDevice* device)
603 {
604  QString desc;
605 
606 #if defined(__linux__)
607  // Given something like /dev/hda1, extract hda1
608  QString devname = device->getRealDevice().mid(5,5);
609 
610  if (devname.startsWith("hd")) // IDE drive
611  {
612  QFile file("/proc/ide/" + devname.left(3) + "/model");
613  if (file.open(QIODevice::ReadOnly))
614  {
615  QTextStream stream(&file);
616 
617  desc.append(stream.readLine());
618  file.close();
619  }
620  }
621 
622  if (devname.startsWith("scd")) // scd0 doesn't appear in /sys/block,
623  devname.replace("scd", "sr"); // use sr0 instead
624 
625  if (devname.startsWith("sd") // SATA/USB/FireWire
626  || devname.startsWith("sr")) // SCSI CD-ROM?
627  {
628  QString path = devname.prepend("/sys/block/");
629  path.append("/device/");
630 
631  QFile file(path + "vendor");
632  if (file.open(QIODevice::ReadOnly))
633  {
634  QTextStream stream(&file);
635 
636  desc.append(stream.readLine());
637  desc.append(' ');
638  file.close();
639  }
640 
641  file.setFileName(path + "model");
642  if (file.open(QIODevice::ReadOnly))
643  {
644  QTextStream stream(&file);
645 
646  desc.append(stream.readLine());
647  desc.append(' ');
648  file.close();
649  }
650  }
651 #endif
652 
653  LOG(VB_MEDIA, LOG_DEBUG, QString("LookupModel '%1' -> '%2'")
654  .arg(device->getRealDevice(), desc) );
655  device->setDeviceModel(desc.toLatin1().constData());
656 }
657 #endif
658 
659 
664 {
665  if ( ! pDevice )
666  {
667  LOG(VB_GENERAL, LOG_ERR, "MediaMonitorUnix::AddDevice(null)");
668  return false;
669  }
670 
671  // If the user doesn't want this device to be monitored, stop now:
672  if (shouldIgnore(pDevice))
673  return false;
674 
675  QString path = pDevice->getDevicePath();
676  if (path.isEmpty())
677  {
678  LOG(VB_GENERAL, LOG_ALERT,
679  "MediaMonitorUnix::AddDevice() - empty device path.");
680  return false;
681  }
682 
683  struct stat sb {};
684  if (stat(path.toLocal8Bit().constData(), &sb) < 0)
685  {
686  statError(":AddDevice()", path);
687  return false;
688  }
689  dev_t new_rdev = sb.st_rdev;
690 
691  //
692  // Check if this is a duplicate of a device we have already added
693  //
694  for (const auto *device : qAsConst(m_devices))
695  {
696  if (stat(device->getDevicePath().toLocal8Bit().constData(), &sb) < 0)
697  {
698  statError(":AddDevice()", device->getDevicePath());
699  return false;
700  }
701 
702  if (sb.st_rdev == new_rdev)
703  {
704  LOG(VB_MEDIA, LOG_INFO,
705  LOC + ":AddDevice() - not adding " + path +
706  "\n "
707  "because it appears to be a duplicate of " +
708  device->getDevicePath());
709  return false;
710  }
711  }
712 #if !CONFIG_QTDBUS
713  // Device vendor and name already fetched by UDisks2
714  LookupModel(pDevice);
715 #endif
716  QMutexLocker locker(&m_devicesLock);
717 
718  connect(pDevice, &MythMediaDevice::statusChanged,
720  m_devices.push_back( pDevice );
721  m_useCount[pDevice] = 0;
722  LOG(VB_MEDIA, LOG_INFO, LOC + ":AddDevice() - Added " + path);
723 
724  return true;
725 }
726 
727 // Given a fstab entry to a media device determine what type of device it is
728 bool MediaMonitorUnix::AddDevice(struct fstab * mep)
729 {
730  if (!mep)
731  return false;
732 
733 #ifndef Q_OS_ANDROID
734 #if 0
735  QString devicePath( mep->fs_spec );
736  LOG(VB_GENERAL, LOG_DEBUG, "AddDevice - " + devicePath);
737 #endif
738 
739  MythMediaDevice* pDevice = nullptr;
740  struct stat sbuf {};
741 
742  bool is_supermount = false;
743  bool is_cdrom = false;
744 
745  if (stat(mep->fs_spec, &sbuf) < 0)
746  return false;
747 
748  // Can it be mounted?
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) ||
752  strstr(mep->fs_vfstype, MNTTYPE_UDF) ||
753  strstr(mep->fs_vfstype, MNTTYPE_AUTO)) ) )
754  {
755  if (strstr(mep->fs_mntops, MNTTYPE_ISO9660) &&
756  strstr(mep->fs_vfstype, MNTTYPE_SUPERMOUNT))
757  {
758  is_supermount = true;
759  }
760  else
761  {
762  return false;
763  }
764  }
765 
766  if (strstr(mep->fs_mntops, MNTTYPE_ISO9660) ||
767  strstr(mep->fs_vfstype, MNTTYPE_ISO9660) ||
768  strstr(mep->fs_vfstype, MNTTYPE_UDF) ||
769  strstr(mep->fs_vfstype, MNTTYPE_AUTO))
770  {
771  is_cdrom = true;
772 #if 0
773  LOG(VB_GENERAL, LOG_DEBUG, "Device is a CDROM");
774 #endif
775  }
776 
777  if (!is_supermount)
778  {
779  if (is_cdrom)
780  pDevice = MythCDROM::get(this, mep->fs_spec,
781  is_supermount, m_allowEject);
782  }
783  else
784  {
785  QString dev(mep->fs_mntops);
786  int pos = dev.indexOf(QString::fromStdString(kSuperOptDev));
787  if (pos == -1)
788  return false;
789  dev = dev.mid(pos+kSuperOptDev.size());
790  static const QRegularExpression kSeparatorRE { "[, ]" };
791 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
792  QStringList parts = dev.split(kSeparatorRE, QString::SkipEmptyParts);
793 #else
794  QStringList parts = dev.split(kSeparatorRE, Qt::SkipEmptyParts);
795 #endif
796  if (parts[0].isEmpty())
797  return false;
798  pDevice = MythCDROM::get(this, dev, is_supermount, m_allowEject);
799  }
800 
801  if (pDevice)
802  {
803  pDevice->setMountPath(mep->fs_file);
804  if (pDevice->testMedia() == MEDIAERR_OK)
805  {
806  if (MediaMonitorUnix::AddDevice(pDevice))
807  return true;
808  }
809  pDevice->deleteLater();
810  }
811 #endif
812 
813  return false;
814 }
815 
816 #if CONFIG_QTDBUS
817 /*
818  * DBus UDisk AddDevice handler
819  */
820 
821 void MediaMonitorUnix::deviceAdded( const QDBusObjectPath& o,
822  const QMap<QString, QVariant> &interfaces)
823 {
824  if (!interfaces.contains(QStringLiteral("org.freedesktop.UDisks2.Block")))
825  return;
826 
827  LOG(VB_MEDIA, LOG_DEBUG, LOC + ":deviceAdded " + o.path());
828 
829  // Create the MythMediaDevice
830  MythMediaDevice* pDevice = nullptr;
831  MythUdisksDevice mythdevice = UDisks2INVALID;
832  QString description;
833  QString path;
834 
835  if (DetectDevice(o, mythdevice, description, path))
836  {
837  if (mythdevice == UDisks2DVD)
838  {
839  pDevice = MythCDROM::get(this, path.toLatin1(), false, m_allowEject);
840  LOG(VB_MEDIA, LOG_DEBUG, LOC +
841  "deviceAdded: Added MythCDROM: " + path);
842  }
843  else if (mythdevice == UDisks2HDD)
844  {
845  pDevice = MythHDD::Get(this, path.toLatin1(), false, false);
846  LOG(VB_MEDIA, LOG_DEBUG, LOC +
847  "deviceAdded: Added MythHDD: " + path);
848  }
849  }
850  if (pDevice)
851  {
852  pDevice -> setDeviceModel(description.toLatin1().constData());
853  if (!MediaMonitorUnix::AddDevice(pDevice))
854  pDevice->deleteLater();
855  }
856 }
857 
858 /*
859  * DBus UDisk RemoveDevice handler
860  */
861 void MediaMonitorUnix::deviceRemoved(const QDBusObjectPath& o, const QStringList &interfaces)
862 {
863  if (!interfaces.contains(QStringLiteral("org.freedesktop.UDisks2.Block")))
864  return;
865 
866  LOG(VB_MEDIA, LOG_INFO, LOC + "deviceRemoved " + o.path());
867 
868  QString dev = QFileInfo(o.path()).baseName();
869  dev.prepend("/dev/");
870  RemoveDevice(dev);
871 }
872 
873 #else //CONFIG_QTDBUS
874 
891 bool MediaMonitorUnix::FindPartitions(const QString &dev, bool checkPartitions)
892 {
893  LOG(VB_MEDIA, LOG_DEBUG,
894  LOC + ":FindPartitions(" + dev +
895  QString(",%1").arg(checkPartitions ? " true" : " false" ) + ")");
896  MythMediaDevice* pDevice = nullptr;
897 
898  if (checkPartitions)
899  {
900  // check for partitions
901  QDir sysfs(dev);
902  sysfs.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
903 
904  bool found_partitions = false;
905  QStringList parts = sysfs.entryList();
906  for (const auto& part : qAsConst(parts))
907  {
908  // skip some sysfs dirs that are _not_ sub-partitions
909  if (part == "device" || part == "holders" || part == "queue"
910  || part == "slaves" || part == "subsystem"
911  || part == "bdi" || part == "power")
912  continue;
913 
914  found_partitions |= FindPartitions(
915  sysfs.absoluteFilePath(part), false);
916  }
917 
918  // no partitions on block device, use main device
919  if (!found_partitions)
920  found_partitions |= FindPartitions(sysfs.absolutePath(), false);
921 
922  return found_partitions;
923  }
924 
925  QString device_file = GetDeviceFile(dev);
926 
927  if (device_file.isEmpty())
928  return false;
929 
930  QStringList cdroms = GetCDROMBlockDevices();
931 
932  if (cdroms.contains(dev.section('/', -1)))
933  {
934  // found cdrom device
935  pDevice = MythCDROM::get(
936  this, device_file.toLatin1().constData(), false, m_allowEject);
937  }
938  else
939  {
940  // found block or partition device
941  pDevice = MythHDD::Get(
942  this, device_file.toLatin1().constData(), false, false);
943  }
944 
945  if (AddDevice(pDevice))
946  return true;
947 
948  if (pDevice)
949  pDevice->deleteLater();
950 
951  return false;
952 }
953 
960 {
961  std::string buffer(256,'\0');
962  QString qBuffer;
963 
964  if (m_fifo == -1)
965  return;
966 
967  int size = read(m_fifo, buffer.data(), 255);
968  while (size > 0)
969  {
970  // append buffer to QString
971  buffer[size] = '\0';
972  qBuffer.append(QString::fromStdString(buffer));
973  size = read(m_fifo, buffer.data(), 255);
974  }
975 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
976  const QStringList list = qBuffer.split('\n', QString::SkipEmptyParts);
977 #else
978  const QStringList list = qBuffer.split('\n', Qt::SkipEmptyParts);
979 #endif
980  for (const auto& notif : qAsConst(list))
981  {
982  if (notif.startsWith("add"))
983  {
984  QString dev = notif.section(' ', 1, 1);
985  LOG(VB_MEDIA, LOG_INFO, "Udev add " + dev);
986 
987  if (CheckRemovable(dev))
988  FindPartitions(dev, true);
989  }
990  else if (notif.startsWith("remove"))
991  {
992  QString dev = notif.section(' ', 2, 2);
993  LOG(VB_MEDIA, LOG_INFO, "Udev remove " + dev);
994  RemoveDevice(dev);
995  }
996  }
997 }
998 #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:959
O_NONBLOCK
#define O_NONBLOCK
Definition: compat.h:341
MythSystemLegacy::ReadAllErr
QByteArray & ReadAllErr()
Definition: mythsystemlegacy.cpp:407
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:259
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:396
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:891
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:602
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:522
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:81
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:55
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:905
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:663
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:430
mkfifo
#define mkfifo(path, mode)
Definition: compat.h:148
S_IROTH
#define S_IROTH
Definition: replex.cpp:57
mythhdd.h