MythTV  master
mythmedia.cpp
Go to the documentation of this file.
1 // C header
2 #include <fcntl.h>
3 #include <unistd.h>
4 #include <sys/types.h>
5 #include <sys/stat.h>
6 #include <sys/param.h>
7 
8 // Qt Headers
9 #include <QDir>
10 #include <QFileInfo>
11 #include <QFileInfoList>
12 #include <QTextStream>
13 
14 // MythTV headers
15 #include "mythmedia.h"
16 #include "mythconfig.h"
17 #include "mythlogging.h"
18 #include "mythmiscutil.h"
19 #include "mythsystemlegacy.h"
20 #include "exitcodes.h"
21 
22 using namespace std;
23 
24 #ifdef _WIN32
25 # define O_NONBLOCK 0
26 #endif
27 
28 #define LOC QString("MythMediaDevice:")
29 
30 static const QString PATHTO_PMOUNT("/usr/bin/pmount");
31 static const QString PATHTO_PUMOUNT("/usr/bin/pumount");
32 #if CONFIG_DARWIN
33  static const QString PATHTO_MOUNT("/sbin/mount");
34 #else
35  static const QString PATHTO_MOUNT("/bin/mount");
36 #endif
37 static const QString PATHTO_UNMOUNT("/bin/umount");
38 static const QString PATHTO_MOUNTS("/proc/mounts");
39 
40 #if CONFIG_DARWIN
41 # define USE_MOUNT_COMMAND
42 #endif
43 
45 {
46  "MEDIASTAT_ERROR",
47  "MEDIASTAT_UNKNOWN",
48  "MEDIASTAT_UNPLUGGED",
49  "MEDIASTAT_OPEN",
50  "MEDIASTAT_NODISK",
51  "MEDIASTAT_UNFORMATTED",
52  "MEDIASTAT_USEABLE",
53  "MEDIASTAT_NOTMOUNTED",
54  "MEDIASTAT_MOUNTED"
55 };
56 
58 {
59  "MEDIAERR_OK",
60  "MEDIAERR_FAILED",
61  "MEDIAERR_UNSUPPORTED"
62 };
63 
64 QEvent::Type MythMediaEvent::kEventType =
65  (QEvent::Type) QEvent::registerEventType();
66 
68 {
69 }
70 
72 
73 MythMediaDevice::MythMediaDevice(QObject* par, const char* DevicePath,
74  bool SuperMount, bool AllowEject)
75  : QObject(par), m_DevicePath(DevicePath),
76  m_AllowEject(AllowEject), m_SuperMount(SuperMount)
77 {
79 }
80 
82 {
83  // Sanity check
84  if (isDeviceOpen())
85  return true;
86 
87  QByteArray dev = m_DevicePath.toLocal8Bit();
88  m_DeviceHandle = open(dev.constData(), O_RDONLY | O_NONBLOCK);
89 
90  return isDeviceOpen();
91 }
92 
94 {
95  // Sanity check
96  if (!isDeviceOpen())
97  return true;
98 
99  int ret = close(m_DeviceHandle);
100  m_DeviceHandle = -1;
101 
102  return ret != -1;
103 }
104 
106 {
107  return m_DeviceHandle >= 0;
108 }
109 
111 {
112  if (DoMount && isMounted())
113  {
114 #ifdef Q_OS_MAC
115  // Not an error - DiskArbitration has already mounted the device.
116  // AddDevice calls mount() so onDeviceMounted() can get mediaType.
117  onDeviceMounted();
118 #else
119  LOG(VB_MEDIA, LOG_ERR, "MythMediaDevice::performMountCmd(true)"
120  " - Logic Error? Device already mounted.");
121  return true;
122 #endif
123  }
124 
125  if (isDeviceOpen())
126  closeDevice();
127 
128  if (!m_SuperMount)
129  {
130  QString MountCommand;
131 
132  // Build a command line for mount/unmount and execute it...
133  // Is there a better way to do this?
134  if (QFile(PATHTO_PMOUNT).exists() && QFile(PATHTO_PUMOUNT).exists())
135  {
136  MountCommand = QString("%1 %2")
137  .arg((DoMount) ? PATHTO_PMOUNT : PATHTO_PUMOUNT)
138  .arg(m_DevicePath);
139  }
140  else
141  {
142  MountCommand = QString("%1 %2")
143  .arg((DoMount) ? PATHTO_MOUNT : PATHTO_UNMOUNT)
144  .arg(m_DevicePath);
145  }
146 
147  LOG(VB_MEDIA, LOG_INFO, QString("Executing '%1'").arg(MountCommand));
148  int ret = myth_system(MountCommand, kMSDontBlockInputDevs);
149  if (ret != GENERIC_EXIT_OK)
150  {
151  usleep(300000);
152  LOG(VB_MEDIA, LOG_INFO, QString("Retrying '%1'").arg(MountCommand));
153  ret = myth_system(MountCommand, kMSDontBlockInputDevs);
154  }
155  if (ret == GENERIC_EXIT_OK)
156  {
157  if (DoMount)
158  {
159  // we cannot tell beforehand what the pmount mount point is
160  // so verify the mount status of the device
161  // In the case that m_DevicePath is a symlink to a device
162  // in /etc/fstab then pmount delegates to mount which
163  // performs the mount asynchronously so we must wait a bit
164  usleep(1000000-1);
165  for (int tries = 2; !findMountPath() && tries > 0; --tries)
166  {
167  LOG(VB_MEDIA, LOG_INFO,
168  QString("Repeating '%1'").arg(MountCommand));
169  myth_system(MountCommand, kMSDontBlockInputDevs);
170  usleep(500000);
171  }
172  if (!findMountPath())
173  {
174  LOG(VB_MEDIA, LOG_ERR, "performMountCmd() attempted to"
175  " find mounted media, but failed?");
176  return false;
177  }
178  onDeviceMounted(); // Identify disk type & content
179  LOG(VB_GENERAL, LOG_INFO,
180  QString("Detected MediaType ") + MediaTypeString());
181  }
182  else
184 
185  return true;
186  }
187  LOG(VB_GENERAL, LOG_ERR, QString("Failed to %1 %2.")
188  .arg(DoMount ? "mount" : "unmount").arg(m_DevicePath));
189  }
190  else
191  {
192  LOG(VB_MEDIA, LOG_INFO, "Disk inserted on a supermount device");
193  // If it's a super mount then the OS will handle mounting / unmounting.
194  // We just need to give derived classes a chance to perform their
195  // mount / unmount logic.
196  if (DoMount)
197  {
198  onDeviceMounted();
199  LOG(VB_GENERAL, LOG_INFO,
200  QString("Detected MediaType ") + MediaTypeString());
201  }
202  else
204 
205  return true;
206  }
207  return false;
208 }
209 
214 {
215  ext_cnt_t ext_cnt;
216 
217  if (!ScanMediaType(m_MountPath, ext_cnt))
218  {
219  LOG(VB_MEDIA, LOG_NOTICE,
220  QString("No files with extensions found in '%1'")
221  .arg(m_MountPath));
222  return MEDIATYPE_UNKNOWN;
223  }
224 
225  QMap<uint, uint> media_cnts;
226 
227  // convert raw counts to composite mediatype counts
228  ext_cnt_t::const_iterator it = ext_cnt.begin();
229  for (; it != ext_cnt.end(); ++it)
230  {
231  ext_to_media_t::const_iterator found = s_ext_to_media.find(it.key());
232  if (found != s_ext_to_media.end())
233  {
234  LOG(VB_MEDIA, LOG_INFO, QString("DetectMediaType %1 (%2)")
235  .arg(MediaTypeString(found.value())).arg(it.key()));
236  media_cnts[*found] += *it;
237  }
238  else
239  {
240  LOG(VB_MEDIA, LOG_NOTICE, QString(
241  "DetectMediaType(this=0x%1) unknown file type %1")
242  .arg(quintptr(this),0,16).arg(it.key()));
243  }
244  }
245 
246  // break composite mediatypes into constituent components
247  uint mediatype = 0;
248 
249  QMap<uint, uint>::const_iterator cit = media_cnts.begin();
250  for (; cit != media_cnts.end(); ++cit)
251  {
252  for (uint key = 1; key != MEDIATYPE_END; key <<= 1)
253  {
254  if (key & cit.key())
255  mediatype |= key;
256  }
257  }
258 
260 }
261 
266 bool MythMediaDevice::ScanMediaType(const QString &directory, ext_cnt_t &cnt)
267 {
268  QDir d(directory);
269  if (!d.exists())
270  return false;
271 
272  d.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
273  foreach (auto & fi, d.entryInfoList())
274  {
275  if (fi.isSymLink())
276  continue;
277 
278  if (fi.isDir())
279  {
280  ScanMediaType(fi.absoluteFilePath(), cnt);
281  continue;
282  }
283 
284  const QString ext = fi.suffix();
285  if (!ext.isEmpty())
286  cnt[ext.toLower()]++;
287  }
288 
289  return !cnt.empty();
290 }
291 
298 // static
300  const QString &extensions)
301 {
302  foreach (const auto & ext, extensions.split(","))
303  s_ext_to_media[ext] |= mediatype;
304 }
305 
307 {
308  (void) open_close;
309 
310 #if CONFIG_DARWIN
311  QString command = "diskutil eject " + m_DevicePath;
312 
313  myth_system(command, kMSRunBackground);
314  return MEDIAERR_OK;
315 #endif
316 
317  return MEDIAERR_UNSUPPORTED;
318 }
319 
320 bool MythMediaDevice::isSameDevice(const QString &path)
321 {
322 #ifdef Q_OS_MAC
323  // The caller may be using a raw device instead of the BSD 'leaf' name
324  if (path == "/dev/r" + m_DevicePath)
325  return true;
326 #endif
327 
328  return (path == m_DevicePath);
329 }
330 
332 {
333  setDeviceSpeed(m_DevicePath.toLocal8Bit().constData(), speed);
334 }
335 
337 {
338  // We just open the device here, which may or may not do the trick,
339  // derived classes can do more...
340  if (openDevice())
341  {
342  m_Locked = true;
343  return MEDIAERR_OK;
344  }
345  m_Locked = false;
346  return MEDIAERR_FAILED;
347 }
348 
350 {
351  m_Locked = false;
352 
353  return MEDIAERR_OK;
354 }
355 
357 bool MythMediaDevice::isMounted(bool Verify)
358 {
359  if (Verify)
360  return findMountPath();
361  return (m_Status == MEDIASTAT_MOUNTED);
362 }
363 
366 {
367  if (m_DevicePath.isEmpty())
368  {
369  LOG(VB_MEDIA, LOG_ERR, "findMountPath() - logic error, no device path");
370  return false;
371  }
372 
373 #ifdef USE_MOUNT_COMMAND
374  // HACK. TODO: replace with something using popen()?
375  if (myth_system(PATHTO_MOUNT + " > /tmp/mounts") != GENERIC_EXIT_OK)
376  return false;
377  QFile mountFile("/tmp/mounts");
378 #else
379  QFile mountFile(PATHTO_MOUNTS);
380 #endif
381 
382  // Try to open the mounts file so we can search it for our device.
383  if (!mountFile.open(QIODevice::ReadOnly))
384  return false;
385 
386  QString debug;
387  QTextStream stream(&mountFile);
388 
389  for (;;)
390  {
391  QString mountPoint;
392  QString deviceName;
393 
394 
395 #ifdef USE_MOUNT_COMMAND
396  // Extract mount point and device name from something like:
397  // /dev/disk0s3 on / (hfs, local, journaled) - Mac OS X
398  // /dev/hdd on /tmp/AAA BBB type udf (ro) - Linux
399  stream >> deviceName;
400  mountPoint = stream.readLine();
401  mountPoint.remove(" on ");
402  mountPoint.remove(QRegExp(" type \\w.*")); // Linux
403  mountPoint.remove(QRegExp(" \\(\\w.*")); // Mac OS X
404 #else
405  // Extract the mount point and device name.
406  stream >> deviceName >> mountPoint;
407  stream.readLine(); // skip the rest of the line
408 #endif
409 
410  if (deviceName.isNull())
411  break;
412 
413  if (deviceName.isEmpty())
414  continue;
415 
416  if (!deviceName.startsWith("/dev/"))
417  continue;
418 
419  QStringList deviceNames;
420  getSymlinkTarget(deviceName, &deviceNames);
421 
422 #if CONFIG_DARWIN
423  // match short-style BSD node names:
424  if (m_DevicePath.startsWith("disk"))
425  deviceNames << deviceName.mid(5); // remove 5 chars - /dev/
426 #endif
427 
428  // Deal with escaped spaces
429  if (mountPoint.contains("\\040"))
430  mountPoint.replace("\\040", " ");
431 
432 
433  if (deviceNames.contains(m_DevicePath) ||
434  deviceNames.contains(m_RealDevice) )
435  {
436  m_MountPath = mountPoint;
437  mountFile.close();
438  return true;
439  }
440 
441  if (VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_DEBUG))
442  debug += QString(" %1 | %2\n")
443  .arg(deviceName, 16).arg(mountPoint);
444  }
445 
446  mountFile.close();
447 
448  if (VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_DEBUG))
449  {
450  debug = LOC + ":findMountPath() - mount of '"
451  + m_DevicePath + "' not found.\n"
452  + " Device name/type | Current mountpoint\n"
453  + " -----------------+-------------------\n"
454  + debug
455  + " =================+===================";
456  LOG(VB_MEDIA, LOG_DEBUG, debug);
457  }
458 
459  return false;
460 }
461 
463  bool CloseIt )
464 {
465  MythMediaStatus OldStatus = m_Status;
466 
467  m_Status = NewStatus;
468 
469  // If the status is changed we need to take some actions
470  // depending on the old and new status.
471  if (NewStatus != OldStatus)
472  {
473  LOG(VB_MEDIA, LOG_DEBUG,
474  QString("MythMediaDevice::setStatus %1 %2->%3")
475  .arg(getDevicePath()).arg(MediaStatusStrings[OldStatus])
476  .arg(MediaStatusStrings[NewStatus]));
477  switch (NewStatus)
478  {
479  // the disk is not / should not be mounted.
480  case MEDIASTAT_ERROR:
481  case MEDIASTAT_OPEN:
482  case MEDIASTAT_NODISK:
484  if (isMounted())
485  unmount();
486  break;
487  case MEDIASTAT_UNKNOWN:
488  case MEDIASTAT_USEABLE:
489  case MEDIASTAT_MOUNTED:
490  case MEDIASTAT_UNPLUGGED:
492  // get rid of the compiler warning...
493  break;
494  }
495 
496  // Don't fire off transitions to / from unknown states
497  if (m_Status != MEDIASTAT_UNKNOWN && OldStatus != MEDIASTAT_UNKNOWN)
498  emit statusChanged(OldStatus, this);
499  }
500 
501 
502  if (CloseIt)
503  closeDevice();
504 
505  return m_Status;
506 }
507 
509 {
510  m_VolumeID.clear();
511  m_KeyID.clear();
513 }
514 
516 {
518 }
519 
521 {
522  // MediaType is a bitmask.
523  QString mediatype;
524  for (uint u = MEDIATYPE_UNKNOWN; u != MEDIATYPE_END; u <<= 1)
525  {
526  QString s;
527  if (u & type & MEDIATYPE_UNKNOWN)
528  s = "MEDIATYPE_UNKNOWN";
529  else if (u & type & MEDIATYPE_DATA)
530  s = "MEDIATYPE_DATA";
531  else if (u & type & MEDIATYPE_MIXED)
532  s = "MEDIATYPE_MIXED";
533  else if (u & type & MEDIATYPE_AUDIO)
534  s = "MEDIATYPE_AUDIO";
535  else if (u & type & MEDIATYPE_DVD)
536  s = "MEDIATYPE_DVD";
537  else if (u & type & MEDIATYPE_BD)
538  s = "MEDIATYPE_BD";
539  else if (u & type & MEDIATYPE_VCD)
540  s = "MEDIATYPE_VCD";
541  else if (u & type & MEDIATYPE_MMUSIC)
542  s = "MEDIATYPE_MMUSIC";
543  else if (u & type & MEDIATYPE_MVIDEO)
544  s = "MEDIATYPE_MVIDEO";
545  else if (u & type & MEDIATYPE_MGALLERY)
546  s = "MEDIATYPE_MGALLERY";
547  else
548  continue;
549 
550  if (mediatype.isEmpty())
551  mediatype = s;
552  else
553  mediatype += "|" + s;
554  }
555 
556  return mediatype;
557 }
virtual bool performMountCmd(bool DoMount)
Definition: mythmedia.cpp:110
static const QString PATHTO_PMOUNT("/usr/bin/pmount")
int mediatype
Definition: mythburn.py:208
const QString & getDevicePath() const
Definition: mythmedia.h:61
CD/DVD tray open (meaningless for non-CDs?)
Definition: mythmedia.h:16
MythMediaType
Definition: mythmedia.h:24
static ext_to_media_t s_ext_to_media
Map of extension to media type.
Definition: mythmedia.h:180
virtual bool closeDevice()
Definition: mythmedia.cpp:93
#define O_NONBLOCK
Definition: mythmedia.cpp:25
#define GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:10
QMap< QString, uint > ext_cnt_t
Definition: mythmedia.h:45
QString m_VolumeID
The volume ID of the media. Read/write.
Definition: mythmedia.h:157
MythMediaStatus m_Status
The status of the media as of the last call to checkMedia.
Definition: mythmedia.h:159
QMap< QString, uint > ext_to_media_t
Definition: mythmedia.h:46
virtual void setDeviceSpeed(const char *, int)
Definition: mythmedia.h:100
bool m_Locked
Is this media locked?. Read only.
Definition: mythmedia.h:166
Unable to mount, but could be usable.
Definition: mythmedia.h:13
bool ScanMediaType(const QString &directory, ext_cnt_t &cnt)
Recursively scan directories and create an associative array with the number of times we've seen each...
Definition: mythmedia.cpp:266
virtual bool openDevice()
Definition: mythmedia.cpp:81
static void RegisterMediaExtensions(uint mediatype, const QString &extensions)
Used to register media types with extensions.
Definition: mythmedia.cpp:299
QString m_DevicePath
The path to this media's device.
Definition: mythmedia.h:149
QString getSymlinkTarget(const QString &start_file, QStringList *intermediaries, unsigned maxLinks)
bool m_SuperMount
Is this a supermount device?.
Definition: mythmedia.h:168
MythMediaStatus setStatus(MythMediaStatus newStat, bool CloseIt=false)
Definition: mythmedia.cpp:462
static const QString PATHTO_MOUNTS("/proc/mounts")
static const char * MediaStatusStrings[]
Definition: mythmedia.h:117
static const char * MediaErrorStrings[]
Definition: mythmedia.h:118
QString m_KeyID
KeyID of the media.
Definition: mythmedia.h:151
virtual MythMediaError unlock()
Definition: mythmedia.cpp:349
#define close
Definition: compat.h:16
#define VERBOSE_LEVEL_CHECK(_MASK_, _LEVEL_)
Definition: mythlogging.h:24
static const uint16_t * d
#define LOC
Definition: mythmedia.cpp:28
MythMediaError
Definition: mythmedia.h:39
virtual MythMediaError lock()
Definition: mythmedia.cpp:336
For devices/media a plugin might erase/format.
Definition: mythmedia.h:18
QString m_RealDevice
If m_DevicePath is a symlink, its target.
Definition: mythmedia.h:155
MythMediaType DetectMediaType(void)
Returns guessed media type based on file extensions.
Definition: mythmedia.cpp:213
static const QString PATHTO_MOUNT("/bin/mount")
unsigned int uint
Definition: compat.h:140
uint myth_system(const QString &command, uint flags, uint timeout)
int m_DeviceHandle
A file handle for opening and closing the device, ioctls(), et c.
Definition: mythmedia.h:175
virtual void setSpeed(int speed)
Definition: mythmedia.cpp:331
virtual void onDeviceUnmounted()
Override this to perform any post unmount logic.
Definition: mythmedia.h:141
static const QString PATHTO_UNMOUNT("/bin/umount")
virtual MythMediaError eject(bool open_close=true)
Definition: mythmedia.cpp:306
static Type kEventType
Definition: mythmedia.h:193
bool findMountPath()
Try to find a mount of m_DevicePath in the mounts file.
Definition: mythmedia.cpp:365
VERBOSE_PREAMBLE Most debug(nodatabase, notimestamp, noextra)") VERBOSE_MAP(VB_GENERAL
run child in the background
Definition: mythsystem.h:36
virtual void onDeviceMounted(void)
Override this to perform any post mount logic.
Definition: mythmedia.h:133
MythMediaType m_MediaType
The type of media. Read only.
Definition: mythmedia.h:162
QString MediaTypeString()
Definition: mythmedia.cpp:515
bool isMounted(bool bVerify=true)
Tells us if m_DevicePath is a mounted device.
Definition: mythmedia.cpp:357
CD/DVD tray closed but empty, device unusable.
Definition: mythmedia.h:17
~MythMediaEvent() override
Definition: mythmedia.cpp:67
MythMediaStatus
Definition: mythmedia.h:12
QString m_MountPath
The path to this media's mount point.
Definition: mythmedia.h:153
avoid blocking LIRC & Joystick Menu
Definition: mythsystem.h:34
void statusChanged(MythMediaStatus oldStatus, MythMediaDevice *pMedia)
bool isDeviceOpen() const
Definition: mythmedia.cpp:105
MythMediaDevice(QObject *par, const char *DevicePath, bool SuperMount, bool AllowEject)
Definition: mythmedia.cpp:73
bool unmount()
Definition: mythmedia.h:108
virtual bool isSameDevice(const QString &path)
Definition: mythmedia.cpp:320
static const QString PATHTO_PUMOUNT("/usr/bin/pumount")
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:41