MythTV  master
mediamonitor-unix.cpp
Go to the documentation of this file.
1 // -*- Mode: c++ -*-
2 #include "config.h"
3 
4 // Standard C headers
5 #include <cstdio>
6 
7 // POSIX headers
8 #include <dirent.h>
9 #include <unistd.h>
10 #include <fcntl.h>
11 #ifndef ANDROID
12 #include <fstab.h>
13 #endif
14 
15 // UNIX System headers
16 #include <sys/file.h>
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <sys/wait.h>
20 #include <sys/param.h>
21 
22 // C++ headers
23 #include <iostream>
24 
25 // Qt headers
26 #if CONFIG_QTDBUS
27 #include <QtDBus>
28 #include <QDBusConnection>
29 #endif
30 #include <QList>
31 #include <QTextStream>
32 #include <QDir>
33 #include <QFile>
34 
35 // MythTV headers
36 #include "mythmediamonitor.h"
37 #include "mediamonitor-unix.h"
38 #include "mythconfig.h"
39 #include "mythcorecontext.h"
40 #include "mythcdrom.h"
41 #include "mythhdd.h"
42 #include "mythlogging.h"
43 #include "mythsystemlegacy.h"
44 #include "exitcodes.h"
45 
46 #if HAVE_LIBUDEV
47 extern "C" {
48  #include <libudev.h>
49 }
50 #endif
51 
52 
53 #ifndef MNTTYPE_ISO9660
54 #ifdef linux
55 #define MNTTYPE_ISO9660 "iso9660"
56 #elif defined(__FreeBSD__) || CONFIG_DARWIN || defined(__OpenBSD__)
57 #define MNTTYPE_ISO9660 "cd9660"
58 #endif
59 #endif
60 
61 #ifndef MNTTYPE_UDF
62 #define MNTTYPE_UDF "udf"
63 #endif
64 
65 #ifndef MNTTYPE_AUTO
66 #define MNTTYPE_AUTO "auto"
67 #endif
68 
69 #ifndef MNTTYPE_SUPERMOUNT
70 #define MNTTYPE_SUPERMOUNT "supermount"
71 #endif
72 static const std::string kSuperOptDev { "dev=" };
73 
74 #if CONFIG_QTDBUS
75 // DBus UDisk service - http://hal.freedesktop.org/docs/udisks/
76 #define UDISKS_SVC "org.freedesktop.UDisks"
77 #define UDISKS_PATH "/org/freedesktop/UDisks"
78 #define UDISKS_IFACE "org.freedesktop.UDisks"
79 #define UDISKS_DEVADD "DeviceAdded"
80 #define UDISKS_DEVRMV "DeviceRemoved"
81 #define UDISKS_DEVSIG "o" // OBJECT_PATH
82 // DBus UDisks2 service - https://udisks.freedesktop.org/
83 #define UDISKS2_SVC "org.freedesktop.UDisks2"
84 #define UDISKS2_PATH "/org/freedesktop/UDisks2"
85 #define UDISKS2_IFACE "org.freedesktop.UDisks2.Drive"
86 #endif
87 
88 const char * MediaMonitorUnix::kUDEV_FIFO = "/tmp/mythtv_media";
89 
90 
91 // Some helpers for debugging:
92 
93 static const QString LOC = QString("MMUnix:");
94 
95 #ifndef Q_OS_ANDROID
96 // TODO: are these used?
97 static void fstabError(const QString &methodName)
98 {
99  LOG(VB_GENERAL, LOG_ALERT,
100  LOC + methodName + " Error: failed to open " + _PATH_FSTAB +
101  " for reading, " + ENO);
102 }
103 #endif
104 
105 static void statError(const QString &methodName, const QString &devPath)
106 {
107  LOG(VB_GENERAL, LOG_ALERT,
108  LOC + methodName + " Error: failed to stat " + devPath +
109  ", " + ENO);
110 }
111 
113 // MediaMonitor
114 
115 
117  unsigned long interval, bool allowEject)
118  : MediaMonitor(par, interval, allowEject)
119 {
121  if (!gCoreContext->GetBoolSetting("MonitorDrives", false)) {
122  LOG(VB_GENERAL, LOG_NOTICE, "MediaMonitor disabled by user setting.");
123  }
124  else
125  {
126  CheckMountable();
127  }
128 
129  LOG(VB_MEDIA, LOG_INFO, "Initial device list...\n" + listDevices());
130 }
131 
132 
133 #if !CONFIG_QTDBUS
135 {
136  if (m_fifo >= 0)
137  {
138  close(m_fifo);
139  m_fifo = -1;
140  unlink(kUDEV_FIFO);
141  }
143 }
144 #endif // !CONFIG_QTDBUS
145 
146 
147 // Loop through the file system table and add any supported devices.
149 {
150 #ifndef Q_OS_ANDROID
151  struct fstab * mep = nullptr;
152 
153  // Attempt to open the file system descriptor entry.
154  if (!setfsent())
155  {
156  fstabError(":CheckFileSystemTable()");
157  return false;
158  }
159 
160  // Add all the entries
161  while ((mep = getfsent()) != nullptr)
162  AddDevice(mep);
163 
164  endfsent();
165 
166  return !m_devices.isEmpty();
167 #else
168  return false;
169 #endif
170 }
171 
172 #if CONFIG_QTDBUS
173 // Get a device property by name
174 static QVariant DeviceProperty(const QDBusObjectPath& o, const std::string& kszProperty)
175 {
176  QVariant v;
177 
178  QDBusInterface iface(UDISKS_SVC, o.path(), UDISKS_IFACE".Device",
179  QDBusConnection::systemBus() );
180  if (iface.isValid())
181  v = iface.property(kszProperty.c_str());
182 
183  return v;
184 }
185 #endif
186 
199 {
200 #if CONFIG_QTDBUS
201  for (int i = 0; i < 10; ++i, usleep(500000))
202  {
203  // Connect to UDisks. This can sometimes fail if mythfrontend
204  // is started during system init
205  QDBusInterface iface(UDISKS_SVC, UDISKS_PATH, UDISKS_IFACE,
206  QDBusConnection::systemBus() );
207  if (!iface.isValid())
208  {
209  LOG(VB_GENERAL, LOG_ALERT, LOC +
210  "CheckMountable: DBus interface error: " +
211  iface.lastError().message() );
212  QDBusInterface iface2(UDISKS2_SVC, UDISKS2_PATH, UDISKS2_IFACE,
213  QDBusConnection::systemBus() );
214  if (iface2.isValid()) {
215  // We have udisks2 service on this system, give up quickly
216  // TODO: Implement device monitoring via udisks2 service
217  LOG(VB_GENERAL, LOG_WARNING, LOC +
218  "UDisks2 service found. Media Monitor does not support this yet!");
219  return false;
220  }
221  continue;
222  }
223 
224  // Enumerate devices
225  using QDBusObjectPathList = QList<QDBusObjectPath>;
226  QDBusReply<QDBusObjectPathList> reply = iface.call("EnumerateDevices");
227  if (!reply.isValid())
228  {
229  LOG(VB_GENERAL, LOG_ALERT, LOC +
230  "CheckMountable DBus EnumerateDevices error: " +
231  reply.error().message() );
232  continue;
233  }
234 
235  // Listen on DBus for UDisk add/remove device messages
236  (void)QDBusConnection::systemBus().connect(
237  UDISKS_SVC, UDISKS_PATH, UDISKS_IFACE, UDISKS_DEVADD, UDISKS_DEVSIG,
238  this, SLOT(deviceAdded(QDBusObjectPath)) );
239  (void)QDBusConnection::systemBus().connect(
240  UDISKS_SVC, UDISKS_PATH, UDISKS_IFACE, UDISKS_DEVRMV, UDISKS_DEVSIG,
241  this, SLOT(deviceRemoved(QDBusObjectPath)) );
242 
243  // Parse the returned device array
244  const QDBusObjectPathList& list(reply.value());
245  for (const auto& entry : qAsConst(list))
246  {
247  if (!DeviceProperty(entry, "DeviceIsSystemInternal").toBool() &&
248  !DeviceProperty(entry, "DeviceIsPartitionTable").toBool() )
249  {
250  QString dev = DeviceProperty(entry, "DeviceFile").toString();
251 
252  // ignore floppies, too slow
253  if (dev.startsWith("/dev/fd"))
254  continue;
255 
256  MythMediaDevice* pDevice = nullptr;
257  if (DeviceProperty(entry, "DeviceIsRemovable").toBool())
258  pDevice = MythCDROM::get(this, dev.toLatin1(), false, m_allowEject);
259  else
260  pDevice = MythHDD::Get(this, dev.toLatin1(), false, false);
261 
262  if (pDevice && !MediaMonitorUnix::AddDevice(pDevice))
263  pDevice->deleteLater();
264  }
265  }
266 
267  // Success
268  return true;
269  }
270 
271  // Timed out
272  return false;
273 
274 #elif defined linux
275  // NB needs script in /etc/udev/rules.d
276  mkfifo(kUDEV_FIFO, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
277  m_fifo = open(kUDEV_FIFO, O_RDONLY | O_NONBLOCK);
278 
279  QDir sysfs("/sys/block");
280  sysfs.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
281 
282  for (const auto& device : sysfs.entryList())
283  {
284  // ignore floppies, too slow
285  if (device.startsWith("fd"))
286  continue;
287 
288  sysfs.cd(device);
289  QString path = sysfs.absolutePath();
290  if (CheckRemovable(path))
291  FindPartitions(path, true);
292  sysfs.cdUp();
293  }
294  return true;
295 #else // linux
296  return false;
297 #endif
298 }
299 
300 #if !CONFIG_QTDBUS
301 
304 bool MediaMonitorUnix::CheckRemovable(const QString &dev)
305 {
306 #ifdef linux
307  QString removablePath = dev + "/removable";
308  QFile removable(removablePath);
309  if (removable.exists() && removable.open(QIODevice::ReadOnly))
310  {
311  char c = 0;
312  QString msg = LOC + ":CheckRemovable(" + dev + ")/removable ";
313  bool ok = removable.getChar(&c);
314  removable.close();
315 
316  if (ok)
317  {
318  LOG(VB_MEDIA, LOG_DEBUG, msg + c);
319  if (c == '1')
320  return true;
321  }
322  else
323  {
324  LOG(VB_GENERAL, LOG_ALERT, msg + "failed");
325  }
326  }
327  return false;
328 #else // if !linux
329  return false;
330 #endif // !linux
331 }
332 
338 QString MediaMonitorUnix::GetDeviceFile(const QString &sysfs)
339 {
340  QString msg = LOC + ":GetDeviceFile(" + sysfs + ")";
341  QString ret = sysfs;
342 
343  // In case of error, a working default? (device names usually match)
344  ret.replace(QRegExp(".*/"), "/dev/");
345 
346 #ifdef linux
347  #if HAVE_LIBUDEV
348  // Use libudev to determine the name
349  ret.clear();
350  struct udev *udev = udev_new();
351  if (udev != nullptr)
352  {
353  struct udev_device *device =
354  udev_device_new_from_syspath(udev, sysfs.toLatin1().constData());
355  if (device != nullptr)
356  {
357  const char *name = udev_device_get_devnode(device);
358 
359  if (name != nullptr)
360  ret = tr(name);
361  else
362  {
363  // This can happen when udev sends an AddDevice for a block
364  // device with partitions. FindPartition locates a partition
365  // in sysfs but udev hasn't created the devnode for it yet.
366  // Udev will send another AddDevice for the partition later.
367  LOG(VB_MEDIA, LOG_DEBUG, msg + " devnode not (yet) known");
368  }
369 
370  udev_device_unref(device);
371  }
372  else
373  {
374  LOG(VB_GENERAL, LOG_ALERT,
375  msg + " udev_device_new_from_syspath returned NULL");
376  ret = "";
377  }
378 
379  udev_unref(udev);
380  }
381  else
382  LOG(VB_GENERAL, LOG_ALERT,
383  "MediaMonitorUnix::GetDeviceFile udev_new failed");
384  #else // HAVE_LIBUDEV
385  // Use udevadm info to determine the name
386  QStringList args;
387  args << "info" << "-q" << "name"
388  << "-rp" << sysfs;
389 
390  uint flags = kMSStdOut;
391  if (VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_DEBUG))
392  flags |= kMSStdErr;
393 
394  // TODO: change this to a MythSystemLegacy on the stack?
395  MythSystemLegacy *udevinfo = new MythSystemLegacy("udevinfo", args, flags);
396  udevinfo->Run(4);
397  if( udevinfo->Wait() != GENERIC_EXIT_OK )
398  {
399  delete udevinfo;
400  return ret;
401  }
402 
403  if (VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_DEBUG))
404  {
405  QTextStream estream(udevinfo->ReadAllErr());
406  while( !estream.atEnd() )
407  LOG(VB_MEDIA, LOG_DEBUG,
408  msg + " - udevadm info error...\n" + estream.readLine());
409  }
410 
411  QTextStream ostream(udevinfo->ReadAll());
412  QString udevLine = ostream.readLine();
413  if (!udevLine.startsWith("device not found in database") )
414  ret = udevLine;
415 
416  delete udevinfo;
417  #endif // HAVE_LIBUDEV
418 #endif // linux
419 
420  LOG(VB_MEDIA, LOG_INFO, msg + "->'" + ret + "'");
421  return ret;
422 }
423 #endif // !CONFIG_QTDBUS
424 
425 /*
426  * \brief Reads the list devices known to be CD or DVD devices.
427  * \return list of CD and DVD device names.
428  */
429 // pure virtual
431 {
432  QStringList l;
433 
434 #if CONFIG_QTDBUS
435  QDBusInterface iface(UDISKS_SVC, UDISKS_PATH, UDISKS_IFACE,
436  QDBusConnection::systemBus() );
437  if (iface.isValid())
438  {
439  // Enumerate devices
440  using QDBusObjectPathList = QList<QDBusObjectPath>;
441  QDBusReply<QDBusObjectPathList> reply = iface.call("EnumerateDevices");
442  if (reply.isValid())
443  {
444  const QDBusObjectPathList& list(reply.value());
445  for (const auto& entry : qAsConst(list))
446  {
447  if (DeviceProperty(entry, "DeviceIsRemovable").toBool())
448  {
449  QString dev = DeviceProperty(entry, "DeviceFile").toString();
450  if (dev.startsWith("/dev/"))
451  dev.remove(0,5);
452  l.push_back(dev);
453  }
454  }
455  }
456  }
457 
458 #elif defined linux
459  QFile file("/proc/sys/dev/cdrom/info");
460  if (file.open(QIODevice::ReadOnly))
461  {
462  QString line;
463  QTextStream stream(&file);
464  do
465  {
466  line = stream.readLine();
467  if (line.startsWith("drive name:"))
468  {
469 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
470  l = line.split('\t', QString::SkipEmptyParts);
471 #else
472  l = line.split('\t', Qt::SkipEmptyParts);
473 #endif
474  l.pop_front(); // Remove 'drive name:' field
475  break; // file should only contain one drive table?
476  }
477  }
478  while (!stream.atEnd());
479  file.close();
480  }
481 #endif // linux
482 
483  LOG(VB_MEDIA, LOG_DEBUG,
484  LOC + ":GetCDROMBlockDevices()->'" + l.join(", ") + "'");
485  return l;
486 }
487 
488 static void LookupModel(MythMediaDevice* device)
489 {
490  QString desc;
491 
492 #if CONFIG_QTDBUS
493  QDBusInterface iface(UDISKS_SVC, UDISKS_PATH, UDISKS_IFACE,
494  QDBusConnection::systemBus() );
495  if (iface.isValid())
496  {
497  QDBusReply<QDBusObjectPath> reply = iface.call(
498  "FindDeviceByDeviceFile", device->getRealDevice());
499  if (reply.isValid())
500  {
501  desc = DeviceProperty(reply, "DriveVendor").toString();
502  if (!desc.isEmpty())
503  desc += " ";
504  desc += DeviceProperty(reply, "DriveModel").toString();
505  }
506  }
507 
508 #elif defined linux
509 
510  // Given something like /dev/hda1, extract hda1
511  QString devname = device->getRealDevice().mid(5,5);
512 
513  if (devname.startsWith("hd")) // IDE drive
514  {
515  QFile file("/proc/ide/" + devname.left(3) + "/model");
516  if (file.open(QIODevice::ReadOnly))
517  {
518  QTextStream stream(&file);
519 
520  desc.append(stream.readLine());
521  file.close();
522  }
523  }
524 
525  if (devname.startsWith("scd")) // scd0 doesn't appear in /sys/block,
526  devname.replace("scd", "sr"); // use sr0 instead
527 
528  if (devname.startsWith("sd") // SATA/USB/FireWire
529  || devname.startsWith("sr")) // SCSI CD-ROM?
530  {
531  QString path = devname.prepend("/sys/block/");
532  path.append("/device/");
533 
534  QFile file(path + "vendor");
535  if (file.open(QIODevice::ReadOnly))
536  {
537  QTextStream stream(&file);
538 
539  desc.append(stream.readLine());
540  desc.append(' ');
541  file.close();
542  }
543 
544  file.setFileName(path + "model");
545  if (file.open(QIODevice::ReadOnly))
546  {
547  QTextStream stream(&file);
548 
549  desc.append(stream.readLine());
550  desc.append(' ');
551  file.close();
552  }
553  }
554 #endif
555 
556  LOG(VB_MEDIA, LOG_DEBUG, QString("LookupModel '%1' -> '%2'")
557  .arg(device->getRealDevice()).arg(desc) );
558  device->setDeviceModel(desc.toLatin1().constData());
559 }
560 
565 {
566  if ( ! pDevice )
567  {
568  LOG(VB_GENERAL, LOG_ERR, "MediaMonitorUnix::AddDevice(null)");
569  return false;
570  }
571 
572  // If the user doesn't want this device to be monitored, stop now:
573  if (shouldIgnore(pDevice))
574  return false;
575 
576  QString path = pDevice->getDevicePath();
577  if (!path.length())
578  {
579  LOG(VB_GENERAL, LOG_ALERT,
580  "MediaMonitorUnix::AddDevice() - empty device path.");
581  return false;
582  }
583 
584  struct stat sb {};
585  if (stat(path.toLocal8Bit().constData(), &sb) < 0)
586  {
587  statError(":AddDevice()", path);
588  return false;
589  }
590  dev_t new_rdev = sb.st_rdev;
591 
592  //
593  // Check if this is a duplicate of a device we have already added
594  //
595  for (const auto *device : qAsConst(m_devices))
596  {
597  if (stat(device->getDevicePath().toLocal8Bit().constData(), &sb) < 0)
598  {
599  statError(":AddDevice()", device->getDevicePath());
600  return false;
601  }
602 
603  if (sb.st_rdev == new_rdev)
604  {
605  LOG(VB_MEDIA, LOG_INFO,
606  LOC + ":AddDevice() - not adding " + path +
607  "\n "
608  "because it appears to be a duplicate of " +
609  device->getDevicePath());
610  return false;
611  }
612  }
613 
614  LookupModel(pDevice);
615 
616  QMutexLocker locker(&m_devicesLock);
617 
618  connect(pDevice, &MythMediaDevice::statusChanged,
620  m_devices.push_back( pDevice );
621  m_useCount[pDevice] = 0;
622  LOG(VB_MEDIA, LOG_INFO, LOC + ":AddDevice() - Added " + path);
623 
624  return true;
625 }
626 
627 // Given a fstab entry to a media device determine what type of device it is
628 bool MediaMonitorUnix::AddDevice(struct fstab * mep)
629 {
630  if (!mep)
631  return false;
632 
633 #ifndef Q_OS_ANDROID
634 #if 0
635  QString devicePath( mep->fs_spec );
636  LOG(VB_GENERAL, LOG_DEBUG, "AddDevice - " + devicePath);
637 #endif
638 
639  MythMediaDevice* pDevice = nullptr;
640  struct stat sbuf {};
641 
642  bool is_supermount = false;
643  bool is_cdrom = false;
644 
645  if (stat(mep->fs_spec, &sbuf) < 0)
646  return false;
647 
648  // Can it be mounted?
649  if ( ! ( ((strstr(mep->fs_mntops, "owner") &&
650  (sbuf.st_mode & S_IRUSR)) || strstr(mep->fs_mntops, "user")) &&
651  (strstr(mep->fs_vfstype, MNTTYPE_ISO9660) ||
652  strstr(mep->fs_vfstype, MNTTYPE_UDF) ||
653  strstr(mep->fs_vfstype, MNTTYPE_AUTO)) ) )
654  {
655  if (strstr(mep->fs_mntops, MNTTYPE_ISO9660) &&
656  strstr(mep->fs_vfstype, MNTTYPE_SUPERMOUNT))
657  {
658  is_supermount = true;
659  }
660  else
661  {
662  return false;
663  }
664  }
665 
666  if (strstr(mep->fs_mntops, MNTTYPE_ISO9660) ||
667  strstr(mep->fs_vfstype, MNTTYPE_ISO9660) ||
668  strstr(mep->fs_vfstype, MNTTYPE_UDF) ||
669  strstr(mep->fs_vfstype, MNTTYPE_AUTO))
670  {
671  is_cdrom = true;
672 #if 0
673  LOG(VB_GENERAL, LOG_DEBUG, "Device is a CDROM");
674 #endif
675  }
676 
677  if (!is_supermount)
678  {
679  if (is_cdrom)
680  pDevice = MythCDROM::get(this, mep->fs_spec,
681  is_supermount, m_allowEject);
682  }
683  else
684  {
685  QString dev(mep->fs_mntops);
686  int pos = dev.indexOf(QString::fromStdString(kSuperOptDev));
687  if (pos == -1)
688  return false;
689  dev = dev.mid(pos+kSuperOptDev.size());
690 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
691  QStringList parts = dev.split(QRegularExpression("[, ]"),
692  QString::SkipEmptyParts);
693 #else
694  QStringList parts = dev.split(QRegularExpression("[, ]"),
695  Qt::SkipEmptyParts);
696 #endif
697  if (parts[0].isEmpty())
698  return false;
699  pDevice = MythCDROM::get(this, dev, is_supermount, m_allowEject);
700  }
701 
702  if (pDevice)
703  {
704  pDevice->setMountPath(mep->fs_file);
705  if (pDevice->testMedia() == MEDIAERR_OK)
706  {
707  if (MediaMonitorUnix::AddDevice(pDevice))
708  return true;
709  }
710  pDevice->deleteLater();
711  }
712 #endif
713 
714  return false;
715 }
716 
717 #if CONFIG_QTDBUS
718 /*
719  * DBus UDisk AddDevice handler
720  */
721 void MediaMonitorUnix::deviceAdded( const QDBusObjectPath& o)
722 {
723  LOG(VB_MEDIA, LOG_INFO, LOC + ":deviceAdded " + o.path());
724 
725  // Don't add devices with partition tables, just the partitions
726  if (!DeviceProperty(o, "DeviceIsPartitionTable").toBool())
727  {
728  QString dev = DeviceProperty(o, "DeviceFile").toString();
729 
730  MythMediaDevice* pDevice = nullptr;
731  if (DeviceProperty(o, "DeviceIsRemovable").toBool())
732  pDevice = MythCDROM::get(this, dev.toLatin1(), false, m_allowEject);
733  else
734  pDevice = MythHDD::Get(this, dev.toLatin1(), false, false);
735 
736  if (pDevice && !AddDevice(pDevice))
737  pDevice->deleteLater();
738  }
739 }
740 
741 /*
742  * DBus UDisk RemoveDevice handler
743  */
744 void MediaMonitorUnix::deviceRemoved( const QDBusObjectPath& o)
745 {
746  LOG(VB_MEDIA, LOG_INFO, LOC + "deviceRemoved " + o.path());
747 #if 0 // This fails because the DeviceFile has just been deleted
748  QString dev = DeviceProperty(o, "DeviceFile");
749  if (!dev.isEmpty())
750  RemoveDevice(dev);
751 #else
752  QString dev = QFileInfo(o.path()).baseName();
753  dev.prepend("/dev/");
754  RemoveDevice(dev);
755 #endif
756 }
757 
758 #else //CONFIG_QTDBUS
759 
776 bool MediaMonitorUnix::FindPartitions(const QString &dev, bool checkPartitions)
777 {
778  LOG(VB_MEDIA, LOG_DEBUG,
779  LOC + ":FindPartitions(" + dev +
780  QString(",%1").arg(checkPartitions ? " true" : " false" ) + ")");
781  MythMediaDevice* pDevice = nullptr;
782 
783  if (checkPartitions)
784  {
785  // check for partitions
786  QDir sysfs(dev);
787  sysfs.setFilter(QDir::Dirs | QDir::NoDotAndDotDot);
788 
789  bool found_partitions = false;
790  QStringList parts = sysfs.entryList();
791  for (const auto& part : qAsConst(parts))
792  {
793  // skip some sysfs dirs that are _not_ sub-partitions
794  if (part == "device" || part == "holders" || part == "queue"
795  || part == "slaves" || part == "subsystem"
796  || part == "bdi" || part == "power")
797  continue;
798 
799  found_partitions |= FindPartitions(
800  sysfs.absoluteFilePath(part), false);
801  }
802 
803  // no partitions on block device, use main device
804  if (!found_partitions)
805  found_partitions |= FindPartitions(sysfs.absolutePath(), false);
806 
807  return found_partitions;
808  }
809 
810  QString device_file = GetDeviceFile(dev);
811 
812  if (device_file.isEmpty())
813  return false;
814 
815  QStringList cdroms = GetCDROMBlockDevices();
816 
817  if (cdroms.contains(dev.section('/', -1)))
818  {
819  // found cdrom device
820  pDevice = MythCDROM::get(
821  this, device_file.toLatin1().constData(), false, m_allowEject);
822  }
823  else
824  {
825  // found block or partition device
826  pDevice = MythHDD::Get(
827  this, device_file.toLatin1().constData(), false, false);
828  }
829 
830  if (AddDevice(pDevice))
831  return true;
832 
833  if (pDevice)
834  pDevice->deleteLater();
835 
836  return false;
837 }
838 
845 {
846  char buffer[256];
847  QString qBuffer;
848 
849  if (m_fifo == -1)
850  return;
851 
852  int size = read(m_fifo, buffer, 255);
853  while (size > 0)
854  {
855  // append buffer to QString
856  buffer[size] = '\0';
857  qBuffer.append(buffer);
858  size = read(m_fifo, buffer, 255);
859  }
860 #if QT_VERSION < QT_VERSION_CHECK(5,14,0)
861  const QStringList list = qBuffer.split('\n', QString::SkipEmptyParts);
862 #else
863  const QStringList list = qBuffer.split('\n', Qt::SkipEmptyParts);
864 #endif
865  for (const auto& notif : qAsConst(list))
866  {
867  if (notif.startsWith("add"))
868  {
869  QString dev = notif.section(' ', 1, 1);
870  LOG(VB_MEDIA, LOG_INFO, "Udev add " + dev);
871 
872  if (CheckRemovable(dev))
873  FindPartitions(dev, true);
874  }
875  else if (notif.startsWith("remove"))
876  {
877  QString dev = notif.section(' ', 2, 2);
878  LOG(VB_MEDIA, LOG_INFO, "Udev remove " + dev);
879  RemoveDevice(dev);
880  }
881  }
882 }
883 #endif
MediaMonitor::m_devices
QList< MythMediaDevice * > m_devices
Definition: mythmediamonitor.h:117
kMSStdErr
@ kMSStdErr
allow access to stderr
Definition: mythsystem.h:40
MNTTYPE_AUTO
#define MNTTYPE_AUTO
Definition: mediamonitor-unix.cpp:66
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:844
MythSystemLegacy::ReadAllErr
QByteArray & ReadAllErr()
Definition: mythsystemlegacy.cpp:402
ENO
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:72
GENERIC_EXIT_OK
#define GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:10
MediaMonitorUnix::CheckMountable
bool CheckMountable(void)
Search /sys/block for valid removable media devices.
Definition: mediamonitor-unix.cpp:198
LOC
static const QString LOC
Definition: mediamonitor-unix.cpp:93
S_IROTH
#define S_IROTH
Definition: compat.h:225
MediaMonitor::m_allowEject
bool m_allowEject
Definition: mythmediamonitor.h:128
MediaMonitorUnix::CheckRemovable
bool CheckRemovable(const QString &dev)
Is /sys/block/dev a removable device?
Definition: mediamonitor-unix.cpp:304
MythSystemLegacy
Definition: mythsystemlegacy.h:68
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:776
statError
static void statError(const QString &methodName, const QString &devPath)
Definition: mediamonitor-unix.cpp:105
LookupModel
static void LookupModel(MythMediaDevice *device)
Definition: mediamonitor-unix.cpp:488
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:134
arg
arg(title).arg(filename).arg(doDelete))
mythmediamonitor.h
MediaMonitor::m_devicesLock
QMutex m_devicesLock
Definition: mythmediamonitor.h:116
MythMediaDevice::getDevicePath
const QString & getDevicePath() const
Definition: mythmedia.h:61
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
MythSystemLegacy::ReadAll
QByteArray & ReadAll()
Definition: mythsystemlegacy.cpp:397
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:731
MediaMonitorUnix::GetCDROMBlockDevices
QStringList GetCDROMBlockDevices(void) override
Definition: mediamonitor-unix.cpp:430
build_compdb.file
file
Definition: build_compdb.py:55
MythSystemLegacy::Run
void Run(time_t timeout=0)
Runs a command inside the /bin/sh shell. Returns immediately.
Definition: mythsystemlegacy.cpp:212
MediaMonitorUnix::CheckFileSystemTable
bool CheckFileSystemTable(void)
Definition: mediamonitor-unix.cpp:148
MythMediaDevice::statusChanged
void statusChanged(MythMediaStatus oldStatus, MythMediaDevice *pMedia)
close
#define close
Definition: compat.h:16
mythsystemlegacy.h
MediaMonitorUnix::m_fifo
int m_fifo
Definition: mediamonitor-unix.h:54
MediaMonitorUnix::MediaMonitorUnix
MediaMonitorUnix(QObject *par, unsigned long interval, bool allowEject)
Definition: mediamonitor-unix.cpp:116
mythlogging.h
MythMediaDevice::getRealDevice
const QString & getRealDevice() const
Definition: mythmedia.h:63
MythMediaDevice::setDeviceModel
void setDeviceModel(const char *model)
Definition: mythmedia.h:68
O_NONBLOCK
#define O_NONBLOCK
Definition: mythmedia.cpp:24
MediaMonitorUnix::kUDEV_FIFO
static const char * kUDEV_FIFO
Definition: mediamonitor-unix.h:55
fstabError
static void fstabError(const QString &methodName)
Definition: mediamonitor-unix.cpp:97
S_IWOTH
#define S_IWOTH
Definition: replex.cpp:58
S_IRGRP
#define S_IRGRP
Definition: compat.h:224
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
uint
unsigned int uint
Definition: compat.h:140
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:56
MediaMonitor
Definition: mythmediamonitor.h:44
kSuperOptDev
static const std::string kSuperOptDev
Definition: mediamonitor-unix.cpp:72
MediaMonitor::listDevices
QString listDevices(void)
A string summarising the current devices, for debugging.
Definition: mythmediamonitor.cpp:956
MediaMonitor::RemoveDevice
bool RemoveDevice(const QString &dev)
Remove a device from the media monitor.
Definition: mythmediamonitor.cpp:406
MediaMonitor::shouldIgnore
bool shouldIgnore(const MythMediaDevice *device)
Check user preferences to see if this device should be monitored.
Definition: mythmediamonitor.cpp:778
MNTTYPE_UDF
#define MNTTYPE_UDF
Definition: mediamonitor-unix.cpp:62
MythCoreContext::GetBoolSetting
bool GetBoolSetting(const QString &key, bool defaultval=false)
Definition: mythcorecontext.cpp:923
MythCDROM::get
static MythCDROM * get(QObject *par, const QString &devicePath, bool SuperMount, bool AllowEject)
Definition: mythcdrom.cpp:35
VERBOSE_LEVEL_CHECK
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:14
MediaMonitor::m_useCount
QMap< MythMediaDevice *, int > m_useCount
Definition: mythmediamonitor.h:119
MediaMonitorUnix::AddDevice
bool AddDevice(MythMediaDevice *pDevice) override
Given a media device, add it to our collection.
Definition: mediamonitor-unix.cpp:564
mythcorecontext.h
MythMediaDevice
Definition: mythmedia.h:49
mediamonitor-unix.h
MediaMonitor::deleteLater
virtual void deleteLater(void)
Definition: mythmediamonitor.cpp:387
MEDIAERR_OK
@ MEDIAERR_OK
Definition: mythmedia.h:40
exitcodes.h
kMSStdOut
@ kMSStdOut
allow access to stdout
Definition: mythsystem.h:39
MediaMonitorUnix::GetDeviceFile
QString GetDeviceFile(const QString &sysfs)
Returns the device special file associated with the /sys/block node.
Definition: mediamonitor-unix.cpp:338
mkfifo
#define mkfifo(path, mode)
Definition: compat.h:228
MythSystemLegacy::Wait
uint Wait(time_t timeout=0)
Definition: mythsystemlegacy.cpp:242
mythhdd.h
MNTTYPE_SUPERMOUNT
#define MNTTYPE_SUPERMOUNT
Definition: mediamonitor-unix.cpp:70