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