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