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