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