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
39 #include "libmythbase/exitcodes.h"
40 #include "libmythbase/mythcdrom.h"
42 #include "libmythbase/mythhdd.h"
45 
46 #include "mediamonitor-unix.h"
47 #include "mythmediamonitor.h"
48 
49 #if HAVE_LIBUDEV
50 extern "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
66 static constexpr const char* MNTTYPE_UDF { "udf" };
67 #endif
68 
69 #ifndef MNTTYPE_AUTO
70 static constexpr const char* MNTTYPE_AUTO { "auto" };
71 #endif
72 
73 #ifndef MNTTYPE_SUPERMOUNT
74 static constexpr const char* MNTTYPE_SUPERMOUNT { "supermount" };
75 #endif
76 static const std::string kSuperOptDev { "dev=" };
77 #endif // !Q_OS_ANDROID
78 
79 #if CONFIG_QTDBUS
80 // DBus UDisks2 service - https://udisks.freedesktop.org/
81 static constexpr const char* UDISKS2_SVC { "org.freedesktop.UDisks2" };
82 static constexpr const char* UDISKS2_SVC_DRIVE { "org.freedesktop.UDisks2.Drive" };
83 static constexpr const char* UDISKS2_SVC_BLOCK { "org.freedesktop.UDisks2.Block" };
84 static constexpr const char* UDISKS2_SVC_FILESYSTEM { "org.freedesktop.UDisks2.Filesystem" };
85 static constexpr const char* UDISKS2_SVC_MANAGER { "org.freedesktop.UDisks2.Manager" };
86 static constexpr const char* UDISKS2_PATH { "/org/freedesktop/UDisks2" };
87 static constexpr const char* UDISKS2_PATH_MANAGER { "/org/freedesktop/UDisks2/Manager" };
88 static constexpr const char* UDISKS2_PATH_BLOCK_DEVICES { "/org/freedesktop/UDisks2/block_devices" };
89 static constexpr const char* UDISKS2_MIN_VERSION { "2.7.3" };
90 #endif
91 
92 
93 // Some helpers for debugging:
94 
95 static const QString LOC = QString("MMUnix:");
96 
97 #ifndef Q_OS_ANDROID
98 // TODO: are these used?
99 static 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 
107 static 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  {
128  CheckMountable();
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
176 static 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
192 static 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
370  mkfifo(kUDEV_FIFO, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
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
396 
399 bool 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 
433 QString 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
601 static 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
727 bool 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 
816 void 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  */
856 void 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 
886 bool 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
MediaMonitor::m_devices
QList< MythMediaDevice * > m_devices
Definition: mythmediamonitor.h:119
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:954
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:262
LOC
static const QString LOC
Definition: mediamonitor-unix.cpp:95
MediaMonitor::m_allowEject
bool m_allowEject
Definition: mythmediamonitor.h:130
MediaMonitorUnix::CheckRemovable
static bool CheckRemovable(const QString &dev)
Is /sys/block/dev a removable device?
Definition: mediamonitor-unix.cpp:399
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:886
statError
static void statError(const QString &methodName, const QString &devPath)
Definition: mediamonitor-unix.cpp:107
LookupModel
static void LookupModel(MythMediaDevice *device)
Definition: mediamonitor-unix.cpp:601
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:136
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:711
MediaMonitorUnix::GetCDROMBlockDevices
QStringList GetCDROMBlockDevices(void) override
Definition: mediamonitor-unix.cpp:525
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:150
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:118
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:99
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:70
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:45
kSuperOptDev
static const std::string kSuperOptDev
Definition: mediamonitor-unix.cpp:76
MediaMonitor::listDevices
QString listDevices(void)
A string summarising the current devices, for debugging.
Definition: mythmediamonitor.cpp:936
MediaMonitor::RemoveDevice
bool RemoveDevice(const QString &dev)
Remove a device from the media monitor.
Definition: mythmediamonitor.cpp:386
MediaMonitor::shouldIgnore
bool shouldIgnore(const MythMediaDevice *device)
Check user preferences to see if this device should be monitored.
Definition: mythmediamonitor.cpp:758
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:906
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:121
MediaMonitorUnix::AddDevice
bool AddDevice(MythMediaDevice *pDevice) override
CONFIG_QTDBUS.
Definition: mediamonitor-unix.cpp:662
mythcorecontext.h
MNTTYPE_SUPERMOUNT
static constexpr const char * MNTTYPE_SUPERMOUNT
Definition: mediamonitor-unix.cpp:74
MNTTYPE_UDF
static constexpr const char * MNTTYPE_UDF
Definition: mediamonitor-unix.cpp:66
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:367
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:118
MediaMonitorUnix::GetDeviceFile
static QString GetDeviceFile(const QString &sysfs)
Returns the device special file associated with the /sys/block node.
Definition: mediamonitor-unix.cpp:433
mkfifo
#define mkfifo(path, mode)
Definition: compat.h:148
S_IROTH
#define S_IROTH
Definition: replex.cpp:57
mythhdd.h