MythTV master
mythmedia.cpp
Go to the documentation of this file.
1// C header
2#include <fcntl.h>
3#include <unistd.h>
4#include <thread>
5#include <utility>
6#include <sys/types.h>
7#include <sys/stat.h>
8#include <sys/param.h>
9
10// Qt Headers
11#include <QtGlobal>
12#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
13#include <QtSystemDetection>
14#endif
15#include <QDir>
16#include <QFileInfo>
17#include <QFileInfoList>
18#include <QRegularExpression>
19#include <QTextStream>
20
21// MythTV headers
22#include "mythmedia.h"
23#include "mythlogging.h"
24#include "mythmiscutil.h"
25#include "mythsystemlegacy.h"
26#include "exitcodes.h"
27
28#ifdef Q_OS_WINDOWS
29# undef O_NONBLOCK
30# define O_NONBLOCK 0
31#endif
32
33#define LOC QString("MythMediaDevice:")
34
35static const QString PATHTO_PMOUNT("/usr/bin/pmount");
36static const QString PATHTO_PUMOUNT("/usr/bin/pumount");
37#ifdef Q_OS_DARWIN
38 static const QString PATHTO_MOUNT("/sbin/mount");
39#else
40 static const QString PATHTO_MOUNT("/bin/mount");
41#endif
42static const QString PATHTO_UNMOUNT("/bin/umount");
43static const QString PATHTO_MOUNTS("/proc/mounts");
44
45#ifdef Q_OS_DARWIN
46# define USE_MOUNT_COMMAND
47#endif
48
49const std::array<const QString,9> MythMediaDevice::kMediaStatusStrings
50{
51 "MEDIASTAT_ERROR",
52 "MEDIASTAT_UNKNOWN",
53 "MEDIASTAT_UNPLUGGED",
54 "MEDIASTAT_OPEN",
55 "MEDIASTAT_NODISK",
56 "MEDIASTAT_UNFORMATTED",
57 "MEDIASTAT_USEABLE",
58 "MEDIASTAT_NOTMOUNTED",
59 "MEDIASTAT_MOUNTED"
60};
61
62const std::array<const QString,3> MythMediaDevice::kMediaErrorStrings
63{
64 "MEDIAERR_OK",
65 "MEDIAERR_FAILED",
66 "MEDIAERR_UNSUPPORTED"
67};
68
69const QEvent::Type MythMediaEvent::kEventType =
70 (QEvent::Type) QEvent::registerEventType();
71
72// Force this class to have a vtable so that dynamic_cast works.
73// NOLINTNEXTLINE(modernize-use-equals-default)
75{
76}
77
79
80MythMediaDevice::MythMediaDevice(QObject* par, QString DevicePath,
81 bool SuperMount, bool AllowEject)
82 : QObject(par), m_devicePath(std::move(DevicePath)),
83 m_allowEject(AllowEject), m_superMount(SuperMount)
84{
86}
87
89{
90 // Sanity check
91 if (isDeviceOpen())
92 return true;
93
94 QByteArray dev = m_devicePath.toLocal8Bit();
95 m_deviceHandle = open(dev.constData(), O_RDONLY | O_NONBLOCK);
96
97 return isDeviceOpen();
98}
99
101{
102 // Sanity check
103 if (!isDeviceOpen())
104 return true;
105
106 int ret = close(m_deviceHandle);
107 m_deviceHandle = -1;
108
109 return ret != -1;
110}
111
113{
114 return m_deviceHandle >= 0;
115}
116
118{
119 if (DoMount && isMounted())
120 {
121#ifdef Q_OS_DARWIN
122 // Not an error - DiskArbitration has already mounted the device.
123 // AddDevice calls mount() so onDeviceMounted() can get mediaType.
125#else
126 LOG(VB_MEDIA, LOG_ERR, "MythMediaDevice::performMountCmd(true)"
127 " - Logic Error? Device already mounted.");
128 return true;
129#endif
130 }
131
132 if (isDeviceOpen())
133 closeDevice();
134
135 if (!m_superMount)
136 {
137 QString MountCommand;
138
139 // Build a command line for mount/unmount and execute it...
140 // Is there a better way to do this?
141 if (QFile(PATHTO_PMOUNT).exists() && QFile(PATHTO_PUMOUNT).exists())
142 {
143 MountCommand = QString("%1 %2")
144 .arg(DoMount ? PATHTO_PMOUNT : PATHTO_PUMOUNT, m_devicePath);
145 }
146 else
147 {
148 MountCommand = QString("%1 %2")
149 .arg(DoMount ? PATHTO_MOUNT : PATHTO_UNMOUNT, m_devicePath);
150 }
151
152 LOG(VB_MEDIA, LOG_INFO, QString("Executing '%1'").arg(MountCommand));
153 int ret = myth_system(MountCommand, kMSDontBlockInputDevs);
154 if (ret != GENERIC_EXIT_OK)
155 {
156 std::this_thread::sleep_for(300ms);
157 LOG(VB_MEDIA, LOG_INFO, QString("Retrying '%1'").arg(MountCommand));
158 ret = myth_system(MountCommand, kMSDontBlockInputDevs);
159 }
160 if (ret == GENERIC_EXIT_OK)
161 {
162 if (DoMount)
163 {
164 // we cannot tell beforehand what the pmount mount point is
165 // so verify the mount status of the device
166 // In the case that m_devicePath is a symlink to a device
167 // in /etc/fstab then pmount delegates to mount which
168 // performs the mount asynchronously so we must wait a bit
169 std::this_thread::sleep_for(1s);
170 for (int tries = 2; !findMountPath() && tries > 0; --tries)
171 {
172 LOG(VB_MEDIA, LOG_INFO,
173 QString("Repeating '%1'").arg(MountCommand));
174 myth_system(MountCommand, kMSDontBlockInputDevs);
175 std::this_thread::sleep_for(500ms);
176 }
177 if (!findMountPath())
178 {
179 LOG(VB_MEDIA, LOG_ERR, "performMountCmd() attempted to"
180 " find mounted media, but failed?");
181 return false;
182 }
183 onDeviceMounted(); // Identify disk type & content
184 LOG(VB_GENERAL, LOG_INFO,
185 QString("Detected MediaType ") + MediaTypeString());
186 }
187 else
188 {
190 }
191
192 return true;
193 }
194 LOG(VB_GENERAL, LOG_ERR, QString("Failed to %1 %2.")
195 .arg(DoMount ? "mount" : "unmount", m_devicePath));
196 }
197 else
198 {
199 LOG(VB_MEDIA, LOG_INFO, "Disk inserted on a supermount device");
200 // If it's a super mount then the OS will handle mounting / unmounting.
201 // We just need to give derived classes a chance to perform their
202 // mount / unmount logic.
203 if (DoMount)
204 {
206 LOG(VB_GENERAL, LOG_INFO,
207 QString("Detected MediaType ") + MediaTypeString());
208 }
209 else
210 {
212 }
213
214 return true;
215 }
216 return false;
217}
218
223{
224 ext_cnt_t ext_cnt;
225
226 if (!ScanMediaType(m_mountPath, ext_cnt))
227 {
228 LOG(VB_MEDIA, LOG_NOTICE,
229 QString("No files with extensions found in '%1'")
230 .arg(m_mountPath));
231 return MEDIATYPE_UNKNOWN;
232 }
233
234 QMap<uint, uint> media_cnts;
235
236 // convert raw counts to composite mediatype counts
237 for (auto it = ext_cnt.cbegin(); it != ext_cnt.cend(); ++it)
238 {
239 ext_to_media_t::const_iterator found = s_ext_to_media.constFind(it.key());
240 if (found != s_ext_to_media.constEnd())
241 {
242 LOG(VB_MEDIA, LOG_INFO, QString("DetectMediaType %1 (%2)")
243 .arg(MediaTypeString(found.value()), it.key()));
244 media_cnts[*found] += *it;
245 }
246 else
247 {
248 LOG(VB_MEDIA, LOG_NOTICE, QString(
249 "DetectMediaType(this=0x%1) unknown file type %1")
250 .arg(quintptr(this),0,16).arg(it.key()));
251 }
252 }
253
254 // break composite mediatypes into constituent components
255 uint mediatype = 0;
256
257 for (auto cit = media_cnts.cbegin(); cit != media_cnts.cend(); ++cit)
258 {
259 for (uint key = 1; key != MEDIATYPE_END; key <<= 1)
260 {
261 if (key & cit.key())
262 mediatype |= key;
263 }
264 }
265
267}
268
273bool MythMediaDevice::ScanMediaType(const QString &directory, ext_cnt_t &cnt)
274{
275 QDir d(directory);
276 if (!d.exists())
277 return false;
278
279 d.setFilter(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
280 QFileInfoList entries = d.entryInfoList();
281 for (const auto& fi : std::as_const(entries))
282 {
283 if (fi.isSymLink())
284 continue;
285
286 if (fi.isDir())
287 {
288 ScanMediaType(fi.absoluteFilePath(), cnt);
289 continue;
290 }
291
292 const QString ext = fi.suffix();
293 if (!ext.isEmpty())
294 cnt[ext.toLower()]++;
295 }
296
297 return !cnt.empty();
298}
299
306// static
308 const QString &extensions)
309{
310 QStringList exts = extensions.split(",");
311 for (const auto& ext : std::as_const(exts))
313}
314
315MythMediaError MythMediaDevice::eject([[maybe_unused]] bool open_close)
316{
317#ifdef Q_OS_DARWIN
318 QString command = "diskutil eject " + m_devicePath;
319
321 return MEDIAERR_OK;
322#endif
323
325}
326
327bool MythMediaDevice::isSameDevice(const QString &path)
328{
329#ifdef Q_OS_DARWIN
330 // The caller may be using a raw device instead of the BSD 'leaf' name
331 if (path == "/dev/r" + m_devicePath)
332 return true;
333#endif
334
335 return (path == m_devicePath);
336}
337
339{
340 setDeviceSpeed(m_devicePath.toLocal8Bit().constData(), speed);
341}
342
344{
345 // We just open the device here, which may or may not do the trick,
346 // derived classes can do more...
347 if (openDevice())
348 {
349 m_locked = true;
350 return MEDIAERR_OK;
351 }
352 m_locked = false;
353 return MEDIAERR_FAILED;
354}
355
357{
358 m_locked = false;
359
360 return MEDIAERR_OK;
361}
362
365{
366 if (Verify)
367 return findMountPath();
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(QRegularExpression(" type \\w.*")); // Linux
410 mountPoint.remove(QRegularExpression(" \\(\\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#ifdef Q_OS_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(), kMediaStatusStrings[OldStatus],
483 kMediaStatusStrings[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;
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.clear();
518 m_keyID.clear();
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}
MythMediaStatus setStatus(MythMediaStatus newStat, bool CloseIt=false)
Definition: mythmedia.cpp:469
bool m_superMount
Is this a supermount device?.
Definition: mythmedia.h:168
QString MediaTypeString()
Definition: mythmedia.cpp:522
MythMediaDevice(QObject *par, QString DevicePath, bool SuperMount, bool AllowEject)
Definition: mythmedia.cpp:80
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:327
void statusChanged(MythMediaStatus oldStatus, MythMediaDevice *pMedia)
static void RegisterMediaExtensions(uint mediatype, const QString &extensions)
Used to register media types with extensions.
Definition: mythmedia.cpp:307
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:100
virtual MythMediaError lock()
Definition: mythmedia.cpp:343
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:117
virtual MythMediaError unlock()
Definition: mythmedia.cpp:356
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:372
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:315
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:112
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:273
virtual void setSpeed(int speed)
Definition: mythmedia.cpp:338
QString m_volumeID
The volume ID of the media. Read/write.
Definition: mythmedia.h:157
virtual bool openDevice()
Definition: mythmedia.cpp:88
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:364
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:222
~MythMediaEvent() override
Definition: mythmedia.cpp:74
static const Type kEventType
Definition: mythmedia.h:193
unsigned int uint
Definition: compat.h:60
#define close
Definition: compat.h:28
@ GENERIC_EXIT_OK
Exited with no error.
Definition: exitcodes.h:13
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:33
#define O_NONBLOCK
Definition: mythmedia.cpp:30
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:206
bool exists(str path)
Definition: xbmcvfs.py:51
VERBOSE_PREAMBLE Most debug(nodatabase, notimestamp, noextra)") VERBOSE_MAP(VB_GENERAL