MythTV master
mythcdrom-linux.cpp
Go to the documentation of this file.
1#include <cerrno>
2#include <climits>
3#include <cstdint>
4#include <fcntl.h>
5#include <linux/cdrom.h> // old ioctls for cdrom
6#include <linux/fs.h> // BLKRRPART
7#include <linux/iso_fs.h>
8#include <scsi/scsi.h>
9#include <scsi/sg.h>
10#include <sys/ioctl.h> // ioctls
11#include <sys/stat.h>
12#include <sys/types.h>
13#include <unistd.h>
14
15#include <QtGlobal>
16#include <QChar> // Fix Qt6 GCC SFINAE warning
17#include <QDateTime>
18
19#include "mythcdrom.h"
20#include "mythcdrom-linux.h"
21#include "mythlogging.h"
22#include "mythdate.h"
23
24#define LOC QString("MythCDROMLinux:")
25
26// On a mixed-mode disc (audio+data), set this to 0 to mount the data portion:
27#ifndef ASSUME_WANT_AUDIO
28#define ASSUME_WANT_AUDIO 1 // NOLINT(cppcoreguidelines-macro-usage)
29#endif
30
31
32// Some features cannot be detected (reliably) using the standard
33// Linux ioctl()s, so we use some direct low-level device queries.
34
35using CDROMgenericCmd = struct cdrom_generic_command;
36
37// Some structures stolen from the __KERNEL__ section of linux/cdrom.h.
38
39// Prevent clang-tidy modernize-avoid-c-arrays warnings in these
40// kernel structures
41extern "C" {
42
43// This contains the result of a GPCMD_GET_EVENT_STATUS_NOTIFICATION.
44// It is the joining of a struct event_header and a struct media_event_desc
46{
48#if (Q_BYTE_ORDER == Q_BIG_ENDIAN)
49 uint8_t m_nea : 1;
50 uint8_t m_reserved1 : 4;
52#else
53 uint8_t m_notificationClass : 3;
54 uint8_t m_reserved1 : 4;
55 uint8_t m_nea : 1;
56#endif
58#if (Q_BYTE_ORDER == Q_BIG_ENDIAN)
59 uint8_t m_reserved2 : 4;
60 uint8_t m_mediaEventCode : 4;
61 uint8_t m_reserved3 : 6;
62 uint8_t m_mediaPresent : 1;
63 uint8_t m_doorOpen : 1;
64#else
65 uint8_t m_mediaEventCode : 4;
66 uint8_t m_reserved2 : 4;
67 uint8_t m_doorOpen : 1;
68 uint8_t m_mediaPresent : 1;
69 uint8_t m_reserved3 : 6;
70#endif
71 uint8_t m_startSlot;
72 uint8_t m_endSlot;
73};
74
75// and this is returned by GPCMD_READ_DISC_INFO
78#if (Q_BYTE_ORDER == Q_BIG_ENDIAN)
79 uint8_t m_reserved1 : 3;
80 uint8_t m_erasable : 1;
81 uint8_t m_borderStatus : 2;
82 uint8_t m_discStatus : 2;
83#else
84 uint8_t m_discStatus : 2;
85 uint8_t m_borderStatus : 2;
86 uint8_t m_erasable : 1;
87 uint8_t m_reserved1 : 3;
88#endif
93#if (Q_BYTE_ORDER == Q_BIG_ENDIAN)
94 uint8_t m_didV : 1;
95 uint8_t m_dbcV : 1;
96 uint8_t m_uru : 1;
97 uint8_t m_reserved2 : 5;
98#else
99 uint8_t m_reserved2 : 5;
100 uint8_t m_uru : 1;
101 uint8_t m_dbcV : 1;
102 uint8_t m_didV : 1;
103#endif
104 uint8_t m_discType;
108 uint32_t m_discId;
109 uint32_t m_leadIn;
110 uint32_t m_leadOut;
111 uint8_t m_discBarCode[8];
112 uint8_t m_reserved3;
113 uint8_t m_nOpc;
114};
115
116// end of kernel structures.
117};
118
119enum CDROMdiscStatus : std::uint8_t
120{
124 MEDIA_IS_OTHER = 0x3
126
127
135{
136public:
137 MythCDROMLinux(QObject* par, const QString& DevicePath, bool SuperMount,
138 bool AllowEject):
139 MythCDROM(par, DevicePath, SuperMount, AllowEject) {
140 }
141
142 MythMediaError testMedia(void) override; // MythMediaDevice
143 bool mediaChanged(void) override; // MythCDROM
144 bool checkOK(void) override; // MythCDROM
145 MythMediaStatus checkMedia(void) override; // MythMediaDevice
146 MythMediaError eject(bool open_close = true) override; // MythMediaDevice
147 void setDeviceSpeed(const char *device, int speed) override; // MythMediaDevice
148 bool isSameDevice(const QString &path) override; // MythMediaDevice
149 MythMediaError lock(void) override; // MythMediaDevice
150 MythMediaError unlock(void) override; // MythMediaDevice
151
152protected:
153 MythMediaError ejectCDROM(bool open_close);
155
156private:
157 int driveStatus(void);
158 bool hasWritableMedia(void);
159 int SCSIstatus(void);
160};
161
162MythCDROM *GetMythCDROMLinux(QObject* par, const QString& devicePath,
163 bool SuperMount, bool AllowEject)
164{
165 return new MythCDROMLinux(par, devicePath, SuperMount, AllowEject);
166}
167
168
178{
179 int drive_status = ioctl(m_deviceHandle, CDROM_DRIVE_STATUS, CDSL_CURRENT);
180
181 if (drive_status == -1) // Very unlikely, but we should check
182 {
183 LOG(VB_MEDIA, LOG_ERR, LOC + ":driveStatus() - ioctl failed: " + ENO);
184 return CDS_NO_INFO;
185 }
186
187 if (drive_status == CDS_TRAY_OPEN && m_devicePath.contains("/dev/scd"))
188 return SCSIstatus();
189
190 return drive_status;
191}
192
197{
198 CDROMdiscInfo di {};
199 CDROMgenericCmd cgc {};
200
201 cgc.cmd[0] = GPCMD_READ_DISC_INFO;
202 cgc.cmd[8] = sizeof(CDROMdiscInfo);
203 cgc.quiet = 1;
204 cgc.buffer = reinterpret_cast<uint8_t*>(&di);
205 cgc.buflen = sizeof(CDROMdiscInfo);
206 cgc.data_direction = CGC_DATA_READ;
207
208 if (ioctl(m_deviceHandle, CDROM_SEND_PACKET, &cgc) < 0)
209 {
210 LOG(VB_MEDIA, LOG_ERR, LOC +
211 ":hasWritableMedia() - failed to send packet to " + m_devicePath + ENO);
212 return false;
213 }
214
215 switch (di.m_discStatus)
216 {
217 case MEDIA_IS_EMPTY:
218 return true;
219
221 // It is unlikely that any plugins will support multi-session
222 // writing, so we treat it just like a finished disc:
223
225 return di.m_erasable;
226
227 case MEDIA_IS_OTHER:
228 ;
229 }
230
231 return false;
232}
233
243{
244 CDROMeventStatus es {};
245 CDROMgenericCmd cgc {};
246
247 cgc.cmd[0] = GPCMD_GET_EVENT_STATUS_NOTIFICATION;
248 cgc.cmd[1] = 1; // Tell us immediately
249 cgc.cmd[4] = 1 << 4; // notification class of media
250 cgc.cmd[8] = sizeof(CDROMeventStatus);
251 cgc.quiet = 1;
252 cgc.buffer = reinterpret_cast<uint8_t*>(&es);
253 cgc.buflen = sizeof(CDROMeventStatus);
254 cgc.data_direction = CGC_DATA_READ;
255
256 if ((ioctl(m_deviceHandle, CDROM_SEND_PACKET, &cgc) < 0)
257 || es.m_nea // drive does not support request
258 || (es.m_notificationClass != 0x4)) // notification class mismatch
259 {
260 LOG(VB_MEDIA, LOG_ERR, LOC +
261 ":SCSIstatus() - failed to send SCSI packet to " + m_devicePath + ENO);
262 return CDS_TRAY_OPEN;
263 }
264
265 if (es.m_mediaPresent)
266 {
267 LOG(VB_MEDIA, LOG_DEBUG, LOC +
268 ":SCSIstatus() - ioctl said tray was open, "
269 "but drive is actually closed with a disc");
270 return CDS_DISC_OK;
271 }
272 if (es.m_doorOpen)
273 {
274 LOG(VB_MEDIA, LOG_DEBUG, LOC +
275 ":SCSIstatus() - tray is definitely open");
276 return CDS_TRAY_OPEN;
277 }
278
279 LOG(VB_MEDIA, LOG_DEBUG, LOC + ":SCSIstatus() - ioctl said tray was open, "
280 "but drive is actually closed with no disc");
281 return CDS_NO_DISC;
282}
283
284
286{
287 if (!isDeviceOpen())
288 {
289 if (!openDevice())
290 return MEDIAERR_FAILED;
291 }
292
293 MythMediaError err = ejectCDROM(open_close);
294 if (MEDIAERR_OK != err && open_close)
295 err = ejectSCSI();
296
297 return err;
298}
299
301{
302 if (open_close)
303 {
304 LOG(VB_MEDIA, LOG_DEBUG, LOC + ":eject - Ejecting CDROM");
305 int res = ioctl(m_deviceHandle, CDROMEJECT);
306
307 if (res < 0)
308 LOG(VB_MEDIA, LOG_DEBUG, "CDROMEJECT ioctl failed" + ENO);
309
310 return (res == 0) ? MEDIAERR_OK : MEDIAERR_FAILED;
311 }
312
313 LOG(VB_MEDIA, LOG_DEBUG, LOC + ":eject - Loading CDROM");
314 // If the tray is empty, this will fail (Input/Output error)
315 int res = ioctl(m_deviceHandle, CDROMCLOSETRAY);
316
317 if (res < 0)
318 LOG(VB_MEDIA, LOG_DEBUG, "CDROMCLOSETRAY ioctl failed" + ENO);
319
320 // This allows us to catch any drives that the OS has problems
321 // detecting the status of (some always report OPEN when empty)
322 if (driveStatus() == CDS_TRAY_OPEN)
323 return MEDIAERR_FAILED;
324 return MEDIAERR_OK;
325}
326
327struct StHandle {
328 const int m_fd;
329 explicit StHandle(const char *dev) : m_fd(open(dev, O_RDWR | O_NONBLOCK)) { }
331 operator int() const { return m_fd; } // NOLINT(google-explicit-constructor)
332};
333
334// This is copied from eject.c by Jeff Tranter (tranter@pobox.com)
336{
337 int k = 0;
338 sg_io_hdr_t io_hdr;
339 std::array<uint8_t,6> allowRmBlk {ALLOW_MEDIUM_REMOVAL, 0, 0, 0, 0, 0};
340 std::array<uint8_t,6> startStop1Blk {START_STOP, 0, 0, 0, 1, 0}; // start
341 std::array<uint8_t,6> startStop2Blk {START_STOP, 0, 0, 0, 2, 0}; // load eject
342 std::array<uint8_t,16> sense_buffer {};
343 const unsigned DID_OK = 0;
344 const unsigned DRIVER_OK = 0;
345
346 // ALLOW_MEDIUM_REMOVAL requires r/w access so re-open the device
347 struct StHandle fd(qPrintable(m_devicePath));
348
349 LOG(VB_MEDIA, LOG_DEBUG, LOC + ":ejectSCSI");
350 if ((ioctl(fd, SG_GET_VERSION_NUM, &k) < 0) || (k < 30000))
351 {
352 // not an sg device, or old sg driver
353 LOG(VB_MEDIA, LOG_DEBUG, "SG_GET_VERSION_NUM ioctl failed" + ENO);
354 return MEDIAERR_FAILED;
355 }
356
357 memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
358 io_hdr.interface_id = 'S';
359 io_hdr.cmd_len = 6;
360 io_hdr.mx_sb_len = sense_buffer.size();
361 io_hdr.dxfer_direction = SG_DXFER_NONE;
362 io_hdr.sbp = sense_buffer.data();
363 io_hdr.timeout = 10000; // millisecs
364
365 io_hdr.cmdp = allowRmBlk.data();
366 if (ioctl(fd, SG_IO, &io_hdr) < 0)
367 {
368 LOG(VB_MEDIA, LOG_DEBUG, "SG_IO allowRmBlk ioctl failed" + ENO);
369 return MEDIAERR_FAILED;
370 }
371 if (io_hdr.host_status != DID_OK || io_hdr.driver_status != DRIVER_OK)
372 {
373 LOG(VB_MEDIA, LOG_DEBUG, "SG_IO allowRmBlk failed");
374 return MEDIAERR_FAILED;
375 }
376
377 io_hdr.cmdp = startStop1Blk.data();
378 if (ioctl(fd, SG_IO, &io_hdr) < 0)
379 {
380 LOG(VB_MEDIA, LOG_DEBUG, "SG_IO START_STOP(start) ioctl failed" + ENO);
381 return MEDIAERR_FAILED;
382 }
383 if (io_hdr.host_status != DID_OK || io_hdr.driver_status != DRIVER_OK)
384 {
385 LOG(VB_MEDIA, LOG_DEBUG, "SG_IO START_STOP(start) failed");
386 return MEDIAERR_FAILED;
387 }
388
389 io_hdr.cmdp = startStop2Blk.data();
390 if (ioctl(fd, SG_IO, &io_hdr) < 0)
391 {
392 LOG(VB_MEDIA, LOG_DEBUG, "SG_IO START_STOP(eject) ioctl failed" + ENO);
393 return MEDIAERR_FAILED;
394 }
395 if (io_hdr.host_status != DID_OK || io_hdr.driver_status != DRIVER_OK)
396 {
397 LOG(VB_MEDIA, LOG_DEBUG, "SG_IO START_STOP(eject) failed");
398 return MEDIAERR_FAILED;
399 }
400
401 /* force kernel to reread partition table when new disc inserted */
402 (void)ioctl(fd, BLKRRPART);
403 return MEDIAERR_OK;
404}
405
406
408{
409 return (ioctl(m_deviceHandle, CDROM_MEDIA_CHANGED, CDSL_CURRENT) > 0);
410}
411
413{
414 return (ioctl(m_deviceHandle, CDROM_DRIVE_STATUS, CDSL_CURRENT) ==
415 CDS_DISC_OK);
416}
417
418// Helper function, perform a sanity check on the device
420{
421 bool OpenedHere = false;
422 if (!isDeviceOpen())
423 {
424 if (!openDevice())
425 {
426 LOG(VB_MEDIA, LOG_DEBUG, LOC + ":testMedia - failed to open '" +
427 m_devicePath + "' : " +ENO);
428 if (errno == EBUSY)
430 return MEDIAERR_FAILED;
431 }
432 LOG(VB_MEDIA, LOG_DEBUG, LOC + ":testMedia - Opened device");
433 OpenedHere = true;
434 }
435
436 // Since the device was is/was open we can get it's status...
437 int Stat = driveStatus();
438
439 // Be nice and close the device if we opened it,
440 // otherwise it might be locked when the user doesn't want it to be.
441 if (OpenedHere)
442 closeDevice();
443
444 if (Stat == -1)
445 {
446 LOG(VB_MEDIA, LOG_DEBUG, LOC +
447 ":testMedia - Failed to get drive status of '" + m_devicePath +
448 "' : " + ENO);
449 return MEDIAERR_FAILED;
450 }
451
452 return MEDIAERR_OK;
453}
454
456{
457 bool OpenedHere = false;
458
459 // If it's not already open we need to at least
460 // TRY to open it for most of these checks to work.
461 if (!isDeviceOpen())
462 {
463 OpenedHere = openDevice();
464
465 if (!OpenedHere)
466 {
467 LOG(VB_MEDIA, LOG_ERR, LOC +
468 ":checkMedia() - cannot open device '" + m_devicePath + "' : " +
469 ENO + "- returning UNKNOWN");
471 return setStatus(MEDIASTAT_UNKNOWN, false);
472 }
473 }
474
475 switch (driveStatus())
476 {
477 case CDS_DISC_OK:
478 LOG(VB_MEDIA, LOG_DEBUG, m_devicePath + " Disk OK, type = " +
480 // further checking is required
481 break;
482 case CDS_TRAY_OPEN:
483 LOG(VB_MEDIA, LOG_DEBUG, m_devicePath + " Tray open or no disc");
484 // First, send a message to the
485 // plugins to forget the current media type
486 setStatus(MEDIASTAT_OPEN, OpenedHere);
487 // then "clear out" this device
489 return MEDIASTAT_OPEN;
490 break;
491 case CDS_NO_DISC:
492 LOG(VB_MEDIA, LOG_DEBUG, m_devicePath + " No disc");
494 return setStatus(MEDIASTAT_NODISK, OpenedHere);
495 break;
496 case CDS_NO_INFO:
497 case CDS_DRIVE_NOT_READY:
498 LOG(VB_MEDIA, LOG_DEBUG, m_devicePath +
499 " No info or drive not ready");
501 return setStatus(MEDIASTAT_UNKNOWN, OpenedHere);
502 default:
503 LOG(VB_GENERAL, LOG_ERR, "Failed to get drive status of " +
504 m_devicePath + " : " + ENO);
506 return setStatus(MEDIASTAT_UNKNOWN, OpenedHere);
507 }
508
509 // NB must call mediaChanged before testing m_status otherwise will get
510 // an unwanted mediaChanged on next pass
512 {
513 LOG(VB_MEDIA, LOG_INFO, m_devicePath + " Media changed");
514 // Regardless of the actual status lie here and say
515 // it's open for now, so we can cover the case of a missed open.
516 return setStatus(MEDIASTAT_OPEN, OpenedHere);
517 }
518
519
520 if (isUsable())
521 {
522 LOG(VB_MEDIA, LOG_DEBUG, "Disc useable, media unchanged. All good!");
523 if (OpenedHere)
524 closeDevice();
525 return MEDIASTAT_USEABLE;
526 }
527
528 // If we have tried to mount and failed, don't keep trying
530 {
531 // Check if an external agent (like Gnome/KDE) mounted the disk
532 if (isMounted())
533 {
535 // pretend we're NOTMOUNTED so setStatus emits a signal
537 return setStatus(MEDIASTAT_MOUNTED, OpenedHere);
538 }
539
540 LOG(VB_MEDIA, LOG_DEBUG, "Disc is unmountable?");
541 if (OpenedHere)
542 closeDevice();
543 return m_status;
544 }
545
546 if ((m_status == MEDIASTAT_OPEN) ||
548 {
549 LOG(VB_MEDIA, LOG_INFO, m_devicePath + " Current status " +
551 int type = ioctl(m_deviceHandle, CDROM_DISC_STATUS, CDSL_CURRENT);
552 switch (type)
553 {
554 case CDS_DATA_1:
555 case CDS_DATA_2:
556 {
558 LOG(VB_MEDIA, LOG_INFO, "Found a data disk");
559
560 //grab information from iso9660 (& udf)
561 off_t sr = lseek(m_deviceHandle,
562 (off_t) 2048*16, SEEK_SET);
563
564 struct iso_primary_descriptor buf {};
565 ssize_t readin = 0;
566 while ((sr != (off_t) -1) && (readin < 2048))
567 {
568 ssize_t rr = read(
569 m_deviceHandle, ((char*)&buf) + readin, 2048 - readin);
570 if ((rr < 0) && ((EAGAIN == errno) || (EINTR == errno)))
571 continue;
572 if (rr < 0)
573 break;
574 readin += rr;
575 }
576
577 if (readin == 2048)
578 {
579 m_volumeID = QString(buf.volume_id).trimmed();
580 m_keyID = QString("%1%2")
581 .arg(m_volumeID,
582 QString(reinterpret_cast<char*>(buf.creation_date)).left(16));
583 }
584 else
585 {
586 m_volumeID = "UNKNOWN";
588 }
589
590 LOG(VB_MEDIA, LOG_INFO,
591 QString("Volume ID: %1").arg(m_volumeID));
592 {
594/*
595 if( imageType == MythCDROM::kBluray )
596 m_mediaType = MEDIATYPE_BD;
597 else
598*/
599 if( imageType == MythCDROM::kDVD )
601
603 {
604 // pretend we're NOTMOUNTED so setStatus emits a signal
606 return setStatus(MEDIASTAT_USEABLE, OpenedHere);
607 }
608 }
609
610 // the base class's onDeviceMounted will do fine
611 // grained detection of the type of data on this disc
612 if (isMounted())
614 else if (!mount()) // onDeviceMounted() called as side-effect
615 return setStatus(MEDIASTAT_NOTMOUNTED, OpenedHere);
616
617 if (isMounted())
618 {
619 // pretend we're NOTMOUNTED so setStatus emits a signal
621 return setStatus(MEDIASTAT_MOUNTED, OpenedHere);
622 }
624 {
625 // pretend we're NOTMOUNTED so setStatus emits a signal
627 return setStatus(MEDIASTAT_USEABLE, OpenedHere);
628 }
629 return setStatus(MEDIASTAT_NOTMOUNTED, OpenedHere);
630 }
631 case CDS_AUDIO:
632 LOG(VB_MEDIA, LOG_DEBUG, "found an audio disk");
633 // pretend we're NOTMOUNTED so setStatus emits a signal
636 return setStatus(MEDIASTAT_USEABLE, OpenedHere);
637 case CDS_MIXED:
638 LOG(VB_MEDIA, LOG_DEBUG, "found a mixed CD");
639 // Note: Mixed mode CDs require an explixit mount call
640 // since we'll usually want the audio portion.
641 // undefine ASSUME_WANT_AUDIO to change this behavior.
642 #if ASSUME_WANT_AUDIO
643 // pretend we're NOTMOUNTED so setStatus emits a signal
646 return setStatus(MEDIASTAT_USEABLE, OpenedHere);
647 #else
649 mount();
650 if (isMounted())
651 {
652 // pretend we're NOTMOUNTED so setStatus
653 // emits a signal
655 return setStatus(MEDIASTAT_MOUNTED, OpenedHere);
656 }
657 else
658 {
659 return setStatus(MEDIASTAT_USEABLE, OpenedHere);
660 }
661 #endif
662 break;
663 case CDS_NO_INFO:
664 case CDS_NO_DISC:
665 if (hasWritableMedia())
666 {
667 LOG(VB_MEDIA, LOG_DEBUG, "found a blank or writable disk");
668 return setStatus(MEDIASTAT_UNFORMATTED, OpenedHere);
669 }
670
671 LOG(VB_MEDIA, LOG_DEBUG, "found no disk");
673 return setStatus(MEDIASTAT_UNKNOWN, OpenedHere);
674 break;
675 default:
676 LOG(VB_MEDIA, LOG_DEBUG, "found unknown disk type: " +
677 QString::number(type));
679 return setStatus(MEDIASTAT_UNKNOWN, OpenedHere);
680 }
681 }
682
683 if (m_allowEject)
684 unlock();
685 else
686 lock();
687
688 if (OpenedHere)
689 closeDevice();
690
691 LOG(VB_MEDIA, LOG_DEBUG, QString("Returning %1")
693 return m_status;
694}
695
697{
699 if (ret == MEDIAERR_OK)
700 {
701 LOG(VB_MEDIA, LOG_DEBUG, LOC + ":lock - Locking CDROM door");
702 int res = ioctl(m_deviceHandle, CDROM_LOCKDOOR, 1);
703
704 if (res < 0)
705 LOG(VB_MEDIA, LOG_WARNING, "lock() - CDROM_LOCKDOOR ioctl failed" + ENO);
706 }
707
708 return ret;
709}
710
712{
713 if (isDeviceOpen() || openDevice())
714 {
715 LOG(VB_MEDIA, LOG_DEBUG, LOC + ":unlock - Unlocking CDROM door");
716 int res = ioctl(m_deviceHandle, CDROM_LOCKDOOR, 0);
717
718 if (res < 0)
719 LOG(VB_MEDIA, LOG_WARNING, "unlock() - CDROM_LOCKDOOR ioctl failed" + ENO);
720 }
721 else
722 {
723 LOG(VB_GENERAL, LOG_INFO, "Failed to open device, CDROM tray will "
724 "remain locked.");
725 }
726
728}
729
730bool MythCDROMLinux::isSameDevice(const QString &path)
731{
732 struct stat sb {};
733
734 if (stat(path.toLocal8Bit().constData(), &sb) < 0)
735 {
736 LOG(VB_GENERAL, LOG_ERR, LOC + ":isSameDevice() -- " +
737 QString("Failed to stat '%1'").arg(path) + ENO);
738 return false;
739 }
740 dev_t new_rdev = sb.st_rdev;
741
742 // Check against m_devicePath...
743 if (stat(m_devicePath.toLocal8Bit().constData(), &sb) < 0)
744 {
745 LOG(VB_GENERAL, LOG_ERR, LOC + ":isSameDevice() -- " +
746 QString("Failed to stat '%1'").arg(m_devicePath) + ENO);
747 return false;
748 }
749 return (sb.st_rdev == new_rdev);
750}
751
752#if defined(SG_IO) && defined(GPCMD_SET_STREAMING)
753/*
754 * \brief obtained from the mplayer project
755 */
756void MythCDROMLinux::setDeviceSpeed(const char *device, int speed)
757{
758 std::array<uint8_t,28> buffer {};
759 std::array<uint8_t,16> cmd {};
760 std::array<uint8_t,16> sense {};
761 struct sg_io_hdr sghdr {};
762 struct stat st {};
763 int rate = 0;
764
765 int fd = open(device, O_RDWR | O_NONBLOCK);
766 if (fd == -1)
767 {
768 LOG(VB_MEDIA, LOG_ERR, LOC +
769 " Changing CD/DVD speed needs write access");
770 return;
771 }
772
773 if (fstat(fd, &st) == -1)
774 {
775 close(fd);
776 LOG(VB_MEDIA, LOG_ERR, LOC +
777 QString(":setDeviceSpeed() Failed. device %1 not found")
778 .arg(device));
779 return;
780 }
781
782 if (!S_ISBLK(st.st_mode))
783 {
784 close(fd);
785 LOG(VB_MEDIA, LOG_ERR, LOC +
786 ":setDeviceSpeed() Failed. Not a block device");
787 return;
788 }
789
790 if (speed < 0)
791 speed = -1;
792
793 switch(speed)
794 {
795 case 0: // don't touch speed setting
796 close(fd);
797 return;
798 case -1: // restore default value
799 {
800 rate = 0;
801 buffer[0] = 4;
802 LOG(VB_MEDIA, LOG_INFO, LOC +
803 ":setDeviceSpeed() - Restored CD/DVD Speed");
804 break;
805 }
806 default:
807 {
808 // Speed in Kilobyte/Second. 177KB/s is the maximum data rate
809 // for standard Audio CD's.
810
811 rate = (speed > 0 && speed < 100) ? speed * 177 : speed;
812
813 LOG(VB_MEDIA, LOG_INFO, LOC +
814 QString(":setDeviceSpeed() - Limiting CD/DVD Speed to %1KB/s")
815 .arg(rate));
816 break;
817 }
818 }
819
820 sghdr.interface_id = 'S';
821 sghdr.timeout = 5000;
822 sghdr.dxfer_direction = SG_DXFER_TO_DEV;
823 sghdr.mx_sb_len = sense.size();
824 sghdr.dxfer_len = buffer.size();
825 sghdr.cmd_len = cmd.size();
826 sghdr.sbp = sense.data();
827 sghdr.dxferp = buffer.data();
828 sghdr.cmdp = cmd.data();
829
830 cmd[0] = GPCMD_SET_STREAMING;
831 cmd[10] = buffer.size();
832
833 buffer[8] = 0xff;
834 buffer[9] = 0xff;
835 buffer[10] = 0xff;
836 buffer[11] = 0xff;
837
838 buffer[12] = buffer[20] = (rate >> 24) & 0xff;
839 buffer[13] = buffer[21] = (rate >> 16) & 0xff;
840 buffer[14] = buffer[22] = (rate >> 8) & 0xff;
841 buffer[15] = buffer[23] = rate & 0xff;
842
843 // Note: 0x3e8 == 1000, hence speed = data amount per 1000 milliseconds.
844 buffer[18] = buffer[26] = 0x03;
845 buffer[19] = buffer[27] = 0xe8;
846
847 if (ioctl(fd, SG_IO, &sghdr) < 0)
848 {
849 LOG(VB_MEDIA, LOG_ERR, LOC + " Limit CD/DVD Speed Failed" + ENO);
850 }
851 else
852 {
853 // On my system (2.6.18+ide-cd), SG_IO succeeds without doing anything,
854 // while CDROM_SELECT_SPEED works...
855 if (ioctl(fd, CDROM_SELECT_SPEED, speed) < 0)
856 {
857 LOG(VB_MEDIA, LOG_ERR, LOC +
858 " Limit CD/DVD CDROM_SELECT_SPEED Failed" + ENO);
859 }
860 LOG(VB_MEDIA, LOG_INFO, LOC +
861 ":setDeviceSpeed() - CD/DVD Speed Set Successful");
862 }
863
864 close(fd);
865}
866#endif
Use Linux-specific ioctl()s to detect Audio-CDs, changed media, open trays and blank writable media.
int driveStatus(void)
Exhaustively determine the status.
MythMediaError ejectSCSI()
MythMediaError ejectCDROM(bool open_close)
MythMediaError eject(bool open_close=true) override
void setDeviceSpeed(const char *device, int speed) override
MythMediaStatus checkMedia(void) override
int SCSIstatus(void)
Use a SCSI query packet to see if the drive is really open.
bool checkOK(void) override
MythMediaError lock(void) override
bool hasWritableMedia(void)
Is there blank or eraseable media in the drive?
bool mediaChanged(void) override
MythMediaError testMedia(void) override
MythMediaError unlock(void) override
MythCDROMLinux(QObject *par, const QString &DevicePath, bool SuperMount, bool AllowEject)
bool isSameDevice(const QString &path) override
static ImageType inspectImage(const QString &path)
Definition: mythcdrom.cpp:189
void onDeviceMounted() override
Override this to perform any post mount logic.
Definition: mythcdrom.cpp:64
MythMediaStatus setStatus(MythMediaStatus newStat, bool CloseIt=false)
Definition: mythmedia.cpp:469
QString MediaTypeString()
Definition: mythmedia.cpp:522
bool isUsable() const
Is this device "ready", for a plugin to access?
Definition: mythmedia.h:84
QString m_keyID
KeyID of the media.
Definition: mythmedia.h:151
static const std::array< const QString, 9 > kMediaStatusStrings
Definition: mythmedia.h:117
bool m_allowEject
Allow the user to eject the media?. Read only.
Definition: mythmedia.h:165
virtual bool closeDevice()
Definition: mythmedia.cpp:100
virtual MythMediaError lock()
Definition: mythmedia.cpp:343
MythMediaType m_mediaType
The type of media. Read only.
Definition: mythmedia.h:162
virtual MythMediaError unlock()
Definition: mythmedia.cpp:356
QString m_devicePath
The path to this media's device.
Definition: mythmedia.h:149
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
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
#define O_NONBLOCK
Definition: compat.h:139
#define close
Definition: compat.h:28
unsigned short uint16_t
Definition: iso6937tables.h:3
#define LOC
cdrom_generic_command CDROMgenericCmd
CDROMdiscStatus
@ MEDIA_IS_APPENDABLE
@ MEDIA_IS_EMPTY
@ MEDIA_IS_COMPLETE
@ MEDIA_IS_OTHER
MythCDROM * GetMythCDROMLinux(QObject *par, const QString &devicePath, bool SuperMount, bool AllowEject)
#define ENO
This can be appended to the LOG args with "+".
Definition: mythlogging.h:74
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
@ MEDIATYPE_DVD
Definition: mythmedia.h:29
@ MEDIATYPE_MIXED
Definition: mythmedia.h:27
@ MEDIATYPE_AUDIO
Definition: mythmedia.h:28
@ MEDIATYPE_UNKNOWN
Definition: mythmedia.h:25
@ MEDIATYPE_DATA
Definition: mythmedia.h:26
MythMediaError
Definition: mythmedia.h:39
@ MEDIAERR_OK
Definition: mythmedia.h:40
@ MEDIAERR_FAILED
Definition: mythmedia.h:41
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_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 current_iso_string(bool stripped)
Returns current Date and Time in UTC as a string.
Definition: mythdate.cpp:23
def read(device=None, features=[])
Definition: disc.py:35
uint8_t m_borderStatus
uint8_t m_firstTrackLsb
uint8_t m_nSessionsLsb
uint16_t m_discInformationLength
uint8_t m_discBarCode[8]
uint8_t m_lastTrackLsb
uint16_t m_dataLen[2]
StHandle(const char *dev)
const int m_fd