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 MythMediaDevice::MythMediaDevice(QObject* par, const char* DevicePath,
70  bool SuperMount, bool AllowEject)
71  : QObject(par), m_DevicePath(DevicePath),
72  m_AllowEject(AllowEject), m_SuperMount(SuperMount)
73 {
75 }
76 
78 {
79  // Sanity check
80  if (isDeviceOpen())
81  return true;
82 
83  QByteArray dev = m_DevicePath.toLocal8Bit();
84  m_DeviceHandle = open(dev.constData(), O_RDONLY | O_NONBLOCK);
85 
86  return isDeviceOpen();
87 }
88 
90 {
91  // Sanity check
92  if (!isDeviceOpen())
93  return true;
94 
95  int ret = close(m_DeviceHandle);
96  m_DeviceHandle = -1;
97 
98  return ret != -1;
99 }
100 
102 {
103  return m_DeviceHandle >= 0;
104 }
105 
107 {
108  if (DoMount && isMounted())
109  {
110 #ifdef Q_OS_MAC
111  // Not an error - DiskArbitration has already mounted the device.
112  // AddDevice calls mount() so onDeviceMounted() can get mediaType.
113  onDeviceMounted();
114 #else
115  LOG(VB_MEDIA, LOG_ERR, "MythMediaDevice::performMountCmd(true)"
116  " - Logic Error? Device already mounted.");
117  return true;
118 #endif
119  }
120 
121  if (isDeviceOpen())
122  closeDevice();
123 
124  if (!m_SuperMount)
125  {
126  QString MountCommand;
127 
128  // Build a command line for mount/unmount and execute it...
129  // Is there a better way to do this?
130  if (QFile(PATHTO_PMOUNT).exists() && QFile(PATHTO_PUMOUNT).exists())
131  {
132  MountCommand = QString("%1 %2")
133  .arg((DoMount) ? PATHTO_PMOUNT : PATHTO_PUMOUNT)
134  .arg(m_DevicePath);
135  }
136  else
137  {
138  MountCommand = QString("%1 %2")
139  .arg((DoMount) ? PATHTO_MOUNT : PATHTO_UNMOUNT)
140  .arg(m_DevicePath);
141  }
142 
143  LOG(VB_MEDIA, LOG_INFO, QString("Executing '%1'").arg(MountCommand));
144  int ret = myth_system(MountCommand, kMSDontBlockInputDevs);
145  if (ret != GENERIC_EXIT_OK)
146  {
147  usleep(300000);
148  LOG(VB_MEDIA, LOG_INFO, QString("Retrying '%1'").arg(MountCommand));
149  ret = myth_system(MountCommand, kMSDontBlockInputDevs);
150  }
151  if (ret == GENERIC_EXIT_OK)
152  {
153  if (DoMount)
154  {
155  // we cannot tell beforehand what the pmount mount point is
156  // so verify the mount status of the device
157  // In the case that m_DevicePath is a symlink to a device
158  // in /etc/fstab then pmount delegates to mount which
159  // performs the mount asynchronously so we must wait a bit
160  usleep(1000000-1);
161  for (int tries = 2; !findMountPath() && tries > 0; --tries)
162  {
163  LOG(VB_MEDIA, LOG_INFO,
164  QString("Repeating '%1'").arg(MountCommand));
165  myth_system(MountCommand, kMSDontBlockInputDevs);
166  usleep(500000);
167  }
168  if (!findMountPath())
169  {
170  LOG(VB_MEDIA, LOG_ERR, "performMountCmd() attempted to"
171  " find mounted media, but failed?");
172  return false;
173  }
174  onDeviceMounted(); // Identify disk type & content
175  LOG(VB_GENERAL, LOG_INFO,
176  QString("Detected MediaType ") + MediaTypeString());
177  }
178  else
180 
181  return true;
182  }
183  LOG(VB_GENERAL, LOG_ERR, QString("Failed to %1 %2.")
184  .arg(DoMount ? "mount" : "unmount").arg(m_DevicePath));
185  }
186  else
187  {
188  LOG(VB_MEDIA, LOG_INFO, "Disk inserted on a supermount device");
189  // If it's a super mount then the OS will handle mounting / unmounting.
190  // We just need to give derived classes a chance to perform their
191  // mount / unmount logic.
192  if (DoMount)
193  {
194  onDeviceMounted();
195  LOG(VB_GENERAL, LOG_INFO,
196  QString("Detected MediaType ") + MediaTypeString());
197  }
198  else
200 
201  return true;
202  }
203  return false;
204 }
205 
210 {
211  ext_cnt_t ext_cnt;
212 
213  if (!ScanMediaType(m_MountPath, ext_cnt))
214  {
215  LOG(VB_MEDIA, LOG_NOTICE,
216  QString("No files with extensions found in '%1'")
217  .arg(m_MountPath));
218  return MEDIATYPE_UNKNOWN;
219  }
220 
221  QMap<uint, uint> media_cnts;
222 
223  // convert raw counts to composite mediatype counts
224  ext_cnt_t::const_iterator it = ext_cnt.begin();
225  for (; it != ext_cnt.end(); ++it)
226  {
227  ext_to_media_t::const_iterator found = s_ext_to_media.find(it.key());
228  if (found != s_ext_to_media.end())
229  {
230  LOG(VB_MEDIA, LOG_INFO, QString("DetectMediaType %1 (%2)")
231  .arg(MediaTypeString(found.value())).arg(it.key()));
232  media_cnts[*found] += *it;
233  }
234  else
235  {
236  LOG(VB_MEDIA, LOG_NOTICE, QString(
237  "DetectMediaType(this=0x%1) unknown file type %1")
238  .arg(quintptr(this),0,16).arg(it.key()));
239  }
240  }
241 
242  // break composite mediatypes into constituent components
243  uint mediatype = 0;
244 
245  QMap<uint, uint>::const_iterator cit = media_cnts.begin();
246  for (; cit != media_cnts.end(); ++cit)
247  {
248  for (uint key = 1; key != MEDIATYPE_END; key <<= 1)
249  {
250  if (key & cit.key())
251  mediatype |= key;
252  }
253  }
254 
256 }
257 
262 bool MythMediaDevice::ScanMediaType(const QString &directory, ext_cnt_t &cnt)
263 {
264  QDir d(directory);
265  if (!d.exists())
266  return false;
267 
268  d.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
269  foreach (auto & fi, d.entryInfoList())
270  {
271  if (fi.isSymLink())
272  continue;
273 
274  if (fi.isDir())
275  {
276  ScanMediaType(fi.absoluteFilePath(), cnt);
277  continue;
278  }
279 
280  const QString ext = fi.suffix();
281  if (!ext.isEmpty())
282  cnt[ext.toLower()]++;
283  }
284 
285  return !cnt.empty();
286 }
287 
294 // static
296  const QString &extensions)
297 {
298  foreach (const auto & ext, extensions.split(","))
299  s_ext_to_media[ext] |= mediatype;
300 }
301 
303 {
304  (void) open_close;
305 
306 #if CONFIG_DARWIN
307  QString command = "diskutil eject " + m_DevicePath;
308 
309  myth_system(command, kMSRunBackground);
310  return MEDIAERR_OK;
311 #endif
312 
313  return MEDIAERR_UNSUPPORTED;
314 }
315 
316 bool MythMediaDevice::isSameDevice(const QString &path)
317 {
318 #ifdef Q_OS_MAC
319  // The caller may be using a raw device instead of the BSD 'leaf' name
320  if (path == "/dev/r" + m_DevicePath)
321  return true;
322 #endif
323 
324  return (path == m_DevicePath);
325 }
326 
328 {
329  setDeviceSpeed(m_DevicePath.toLocal8Bit().constData(), speed);
330 }
331 
333 {
334  // We just open the device here, which may or may not do the trick,
335  // derived classes can do more...
336  if (openDevice())
337  {
338  m_Locked = true;
339  return MEDIAERR_OK;
340  }
341  m_Locked = false;
342  return MEDIAERR_FAILED;
343 }
344 
346 {
347  m_Locked = false;
348 
349  return MEDIAERR_OK;
350 }
351 
353 bool MythMediaDevice::isMounted(bool Verify)
354 {
355  if (Verify)
356  return findMountPath();
357  return (m_Status == MEDIASTAT_MOUNTED);
358 }
359 
362 {
363  if (m_DevicePath.isEmpty())
364  {
365  LOG(VB_MEDIA, LOG_ERR, "findMountPath() - logic error, no device path");
366  return false;
367  }
368 
369 #ifdef USE_MOUNT_COMMAND
370  // HACK. TODO: replace with something using popen()?
371  if (myth_system(PATHTO_MOUNT + " > /tmp/mounts") != GENERIC_EXIT_OK)
372  return false;
373  QFile mountFile("/tmp/mounts");
374 #else
375  QFile mountFile(PATHTO_MOUNTS);
376 #endif
377 
378  // Try to open the mounts file so we can search it for our device.
379  if (!mountFile.open(QIODevice::ReadOnly))
380  return false;
381 
382  QString debug;
383  QTextStream stream(&mountFile);
384 
385  for (;;)
386  {
387  QString mountPoint;
388  QString deviceName;
389 
390 
391 #ifdef USE_MOUNT_COMMAND
392  // Extract mount point and device name from something like:
393  // /dev/disk0s3 on / (hfs, local, journaled) - Mac OS X
394  // /dev/hdd on /tmp/AAA BBB type udf (ro) - Linux
395  stream >> deviceName;
396  mountPoint = stream.readLine();
397  mountPoint.remove(" on ");
398  mountPoint.remove(QRegExp(" type \\w.*")); // Linux
399  mountPoint.remove(QRegExp(" \\(\\w.*")); // Mac OS X
400 #else
401  // Extract the mount point and device name.
402  stream >> deviceName >> mountPoint;
403  stream.readLine(); // skip the rest of the line
404 #endif
405 
406  if (deviceName.isNull())
407  break;
408 
409  if (deviceName.isEmpty())
410  continue;
411 
412  if (!deviceName.startsWith("/dev/"))
413  continue;
414 
415  QStringList deviceNames;
416  getSymlinkTarget(deviceName, &deviceNames);
417 
418 #if CONFIG_DARWIN
419  // match short-style BSD node names:
420  if (m_DevicePath.startsWith("disk"))
421  deviceNames << deviceName.mid(5); // remove 5 chars - /dev/
422 #endif
423 
424  // Deal with escaped spaces
425  if (mountPoint.contains("\\040"))
426  mountPoint.replace("\\040", " ");
427 
428 
429  if (deviceNames.contains(m_DevicePath) ||
430  deviceNames.contains(m_RealDevice) )
431  {
432  m_MountPath = mountPoint;
433  mountFile.close();
434  return true;
435  }
436 
437  if (VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_DEBUG))
438  debug += QString(" %1 | %2\n")
439  .arg(deviceName, 16).arg(mountPoint);
440  }
441 
442  mountFile.close();
443 
444  if (VERBOSE_LEVEL_CHECK(VB_MEDIA, LOG_DEBUG))
445  {
446  debug = LOC + ":findMountPath() - mount of '"
447  + m_DevicePath + "' not found.\n"
448  + " Device name/type | Current mountpoint\n"
449  + " -----------------+-------------------\n"
450  + debug
451  + " =================+===================";
452  LOG(VB_MEDIA, LOG_DEBUG, debug);
453  }
454 
455  return false;
456 }
457 
459  bool CloseIt )
460 {
461  MythMediaStatus OldStatus = m_Status;
462 
463  m_Status = NewStatus;
464 
465  // If the status is changed we need to take some actions
466  // depending on the old and new status.
467  if (NewStatus != OldStatus)
468  {
469  LOG(VB_MEDIA, LOG_DEBUG,
470  QString("MythMediaDevice::setStatus %1 %2->%3")
471  .arg(getDevicePath()).arg(MediaStatusStrings[OldStatus])
472  .arg(MediaStatusStrings[NewStatus]));
473  switch (NewStatus)
474  {
475  // the disk is not / should not be mounted.
476  case MEDIASTAT_ERROR:
477  case MEDIASTAT_OPEN:
478  case MEDIASTAT_NODISK:
480  if (isMounted())
481  unmount();
482  break;
483  case MEDIASTAT_UNKNOWN:
484  case MEDIASTAT_USEABLE:
485  case MEDIASTAT_MOUNTED:
486  case MEDIASTAT_UNPLUGGED:
488  // get rid of the compiler warning...
489  break;
490  }
491 
492  // Don't fire off transitions to / from unknown states
493  if (m_Status != MEDIASTAT_UNKNOWN && OldStatus != MEDIASTAT_UNKNOWN)
494  emit statusChanged(OldStatus, this);
495  }
496 
497 
498  if (CloseIt)
499  closeDevice();
500 
501  return m_Status;
502 }
503 
505 {
506  m_VolumeID.clear();
507  m_KeyID.clear();
509 }
510 
512 {
514 }
515 
517 {
518  // MediaType is a bitmask.
519  QString mediatype;
520  for (uint u = MEDIATYPE_UNKNOWN; u != MEDIATYPE_END; u <<= 1)
521  {
522  QString s;
523  if (u & type & MEDIATYPE_UNKNOWN)
524  s = "MEDIATYPE_UNKNOWN";
525  else if (u & type & MEDIATYPE_DATA)
526  s = "MEDIATYPE_DATA";
527  else if (u & type & MEDIATYPE_MIXED)
528  s = "MEDIATYPE_MIXED";
529  else if (u & type & MEDIATYPE_AUDIO)
530  s = "MEDIATYPE_AUDIO";
531  else if (u & type & MEDIATYPE_DVD)
532  s = "MEDIATYPE_DVD";
533  else if (u & type & MEDIATYPE_BD)
534  s = "MEDIATYPE_BD";
535  else if (u & type & MEDIATYPE_VCD)
536  s = "MEDIATYPE_VCD";
537  else if (u & type & MEDIATYPE_MMUSIC)
538  s = "MEDIATYPE_MMUSIC";
539  else if (u & type & MEDIATYPE_MVIDEO)
540  s = "MEDIATYPE_MVIDEO";
541  else if (u & type & MEDIATYPE_MGALLERY)
542  s = "MEDIATYPE_MGALLERY";
543  else
544  continue;
545 
546  if (mediatype.isEmpty())
547  mediatype = s;
548  else
549  mediatype += "|" + s;
550  }
551 
552  return mediatype;
553 }
virtual bool performMountCmd(bool DoMount)
Definition: mythmedia.cpp:106
static const QString PATHTO_PMOUNT("/usr/bin/pmount")
int mediatype
Definition: mythburn.py:179
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:89
#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:262
virtual bool openDevice()
Definition: mythmedia.cpp:77
static void RegisterMediaExtensions(uint mediatype, const QString &extensions)
Used to register media types with extensions.
Definition: mythmedia.cpp:295
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:458
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:345
#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:332
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:209
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:327
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:302
static Type kEventType
Definition: mythmedia.h:192
#define LOG(_MASK_, _LEVEL_, _STRING_)
Definition: mythlogging.h:41
bool findMountPath()
Try to find a mount of m_DevicePath in the mounts file.
Definition: mythmedia.cpp:361
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:511
bool isMounted(bool bVerify=true)
Tells us if m_DevicePath is a mounted device.
Definition: mythmedia.cpp:353
CD/DVD tray closed but empty, device unusable.
Definition: mythmedia.h:17
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:101
MythMediaDevice(QObject *par, const char *DevicePath, bool SuperMount, bool AllowEject)
Definition: mythmedia.cpp:69
bool unmount()
Definition: mythmedia.h:108
virtual bool isSameDevice(const QString &path)
Definition: mythmedia.cpp:316
static const QString PATHTO_PUMOUNT("/usr/bin/pumount")