File indexing completed on 2024-06-09 04:00:41

0001 /*
0002     SPDX-FileCopyrightText: 2010 Michael Zanetti <mzanetti@kde.org>
0003     SPDX-FileCopyrightText: 2010-2012 Lukáš Tinkl <ltinkl@redhat.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006 */
0007 
0008 #include "udisksopticaldisc.h"
0009 #include <fcntl.h>
0010 #include <sys/stat.h>
0011 #include <sys/types.h>
0012 #include <unistd.h>
0013 
0014 #include <QMap>
0015 #include <QSharedMemory>
0016 #include <QSystemSemaphore>
0017 #include <QThreadStorage>
0018 
0019 #include "soliddefs_p.h"
0020 #include "udisks2.h"
0021 #include "udisks_debug.h"
0022 
0023 // inspired by http://cgit.freedesktop.org/hal/tree/hald/linux/probing/probe-volume.c
0024 static Solid::OpticalDisc::ContentType advancedDiscDetect(const QByteArray &device_file)
0025 {
0026     /* the discs block size */
0027     unsigned short bs;
0028     /* the path table size */
0029     unsigned short ts;
0030     /* the path table location (in blocks) */
0031     unsigned int tl;
0032     /* length of the directory name in current path table entry */
0033     unsigned char len_di = 0;
0034     /* the number of the parent directory's path table entry */
0035     unsigned int parent = 0;
0036     /* filename for the current path table entry */
0037     char dirname[256];
0038     /* our position into the path table */
0039     int pos = 0;
0040     /* the path table record we're on */
0041     int curr_record = 1;
0042     /* import debug category */
0043     using Solid::Backends::UDisks2::UDISKS2;
0044 
0045     Solid::OpticalDisc::ContentType result = Solid::OpticalDisc::NoContent;
0046 
0047     int fd = open(device_file.constData(), O_RDONLY);
0048 
0049     /* read the block size */
0050     lseek(fd, 0x8080, SEEK_CUR);
0051     if (read(fd, &bs, 2) != 2) {
0052         qCDebug(UDISKS2, "Advanced probing on %s failed while reading block size", qPrintable(device_file));
0053         goto out;
0054     }
0055 
0056     /* read in size of path table */
0057     lseek(fd, 2, SEEK_CUR);
0058     if (read(fd, &ts, 2) != 2) {
0059         qCDebug(UDISKS2, "Advanced probing on %s failed while reading path table size", qPrintable(device_file));
0060         goto out;
0061     }
0062 
0063     /* read in which block path table is in */
0064     lseek(fd, 6, SEEK_CUR);
0065     if (read(fd, &tl, 4) != 4) {
0066         qCDebug(UDISKS2, "Advanced probing on %s failed while reading path table block", qPrintable(device_file));
0067         goto out;
0068     }
0069 
0070     /* seek to the path table */
0071     lseek(fd, bs * tl, SEEK_SET);
0072 
0073     /* loop through the path table entries */
0074     while (pos < ts) {
0075         /* get the length of the filename of the current entry */
0076         if (read(fd, &len_di, 1) != 1) {
0077             qCDebug(UDISKS2, "Advanced probing on %s failed, cannot read more entries", qPrintable(device_file));
0078             break;
0079         }
0080 
0081         /* get the record number of this entry's parent
0082            i'm pretty sure that the 1st entry is always the top directory */
0083         lseek(fd, 5, SEEK_CUR);
0084         if (read(fd, &parent, 2) != 2) {
0085             qCDebug(UDISKS2, "Advanced probing on %s failed, couldn't read parent entry", qPrintable(device_file));
0086             break;
0087         }
0088 
0089         /* read the name */
0090         if (read(fd, dirname, len_di) != len_di) {
0091             qCDebug(UDISKS2, "Advanced probing on %s failed, couldn't read the entry name", qPrintable(device_file));
0092             break;
0093         }
0094         dirname[len_di] = 0;
0095 
0096         /* if we found a folder that has the root as a parent, and the directory name matches
0097            one of the special directories then set the properties accordingly */
0098         if (parent == 1) {
0099             if (!strcasecmp(dirname, "VIDEO_TS")) {
0100                 qCDebug(UDISKS2, "Disc in %s is a Video DVD", qPrintable(device_file));
0101                 result = Solid::OpticalDisc::VideoDvd;
0102                 break;
0103             } else if (!strcasecmp(dirname, "BDMV")) {
0104                 qCDebug(UDISKS2, "Disc in %s is a Blu-ray video disc", qPrintable(device_file));
0105                 result = Solid::OpticalDisc::VideoBluRay;
0106                 break;
0107             } else if (!strcasecmp(dirname, "VCD")) {
0108                 qCDebug(UDISKS2, "Disc in %s is a Video CD", qPrintable(device_file));
0109                 result = Solid::OpticalDisc::VideoCd;
0110                 break;
0111             } else if (!strcasecmp(dirname, "SVCD")) {
0112                 qCDebug(UDISKS2, "Disc in %s is a Super Video CD", qPrintable(device_file));
0113                 result = Solid::OpticalDisc::SuperVideoCd;
0114                 break;
0115             }
0116         }
0117 
0118         /* all path table entries are padded to be even,
0119            so if this is an odd-length table, seek a byte to fix it */
0120         if (len_di % 2 == 1) {
0121             lseek(fd, 1, SEEK_CUR);
0122             pos++;
0123         }
0124 
0125         /* update our position */
0126         pos += 8 + len_di;
0127         curr_record++;
0128     }
0129 
0130     close(fd);
0131     return result;
0132 
0133 out:
0134     /* go back to the start of the file */
0135     lseek(fd, 0, SEEK_SET);
0136     close(fd);
0137     return result;
0138 }
0139 
0140 using namespace Solid::Backends::UDisks2;
0141 
0142 class ContentTypesCache
0143 {
0144 public:
0145     ContentTypesCache()
0146         : m_n(0)
0147     {
0148     }
0149 
0150     void add(const OpticalDisc::Identity &key, Solid::OpticalDisc::ContentTypes content)
0151     {
0152         if (!find(key)) {
0153             m_n = qMin(m_n + 1, sizeof(m_info) / sizeof(*m_info));
0154             moveToFront(m_n - 1);
0155             front().first = key;
0156         }
0157         front().second = content;
0158     }
0159 
0160     bool find(const OpticalDisc::Identity &key)
0161     {
0162         for (size_t i = 0; i < m_n; i++) {
0163             if (m_info[i].first == key) {
0164                 moveToFront(i);
0165                 return true;
0166             }
0167         }
0168         return false;
0169     }
0170 
0171     QPair<OpticalDisc::Identity, Solid::OpticalDisc::ContentTypes> &front()
0172     {
0173         return *m_info;
0174     }
0175 
0176 private:
0177     void moveToFront(size_t i)
0178     {
0179         while (i) {
0180             qSwap(m_info[i - 1], m_info[i]);
0181             --i;
0182         }
0183     }
0184 
0185     size_t m_n;
0186     QPair<OpticalDisc::Identity, Solid::OpticalDisc::ContentTypes> m_info[100];
0187 };
0188 
0189 class SharedContentTypesCache
0190 {
0191 private:
0192     ContentTypesCache *m_pointer;
0193     QSystemSemaphore m_semaphore;
0194     QSharedMemory m_shmem;
0195 
0196     struct Unlocker {
0197     public:
0198         Unlocker(QSharedMemory *mem)
0199             : m_mem(mem)
0200         {
0201         }
0202         ~Unlocker()
0203         {
0204             m_mem->unlock();
0205         }
0206         Unlocker(const Unlocker &) = delete;
0207         Unlocker &operator=(const Unlocker &) = delete;
0208 
0209     private:
0210         QSharedMemory *m_mem;
0211     };
0212 
0213     struct Releaser {
0214     public:
0215         Releaser(QSystemSemaphore *sem)
0216             : m_sem(sem)
0217         {
0218         }
0219         ~Releaser()
0220         {
0221             m_sem->release();
0222         }
0223         Releaser(const Releaser &) = delete;
0224         Releaser &operator=(const Releaser &) = delete;
0225 
0226     private:
0227         QSystemSemaphore *m_sem;
0228     };
0229 
0230     static QString getKey()
0231     {
0232         static const QString keyTemplate("solid-disk-info-1-%1-%2");
0233         static const QString tableSize(QString::number(sizeof(ContentTypesCache)));
0234 
0235         return keyTemplate.arg(tableSize, QString::number(geteuid()));
0236     }
0237 
0238 public:
0239     SharedContentTypesCache()
0240         : m_pointer(nullptr)
0241         , m_semaphore(getKey() + "sem", 1)
0242         , m_shmem(getKey() + "mem")
0243     {
0244         if (!m_semaphore.acquire()) {
0245             return;
0246         }
0247         Releaser releaser(&m_semaphore);
0248 
0249         if (m_shmem.attach()) {
0250             m_pointer = reinterpret_cast<ContentTypesCache *>(m_shmem.data());
0251             return;
0252         }
0253 
0254         if (!m_shmem.create(sizeof(ContentTypesCache))) {
0255             return;
0256         }
0257 
0258         if (!m_shmem.lock()) {
0259             m_shmem.detach();
0260             return;
0261         }
0262         Unlocker unlocker(&m_shmem);
0263 
0264         m_pointer = new (m_shmem.data()) ContentTypesCache;
0265     }
0266 
0267     Solid::OpticalDisc::ContentTypes getContent(const OpticalDisc::Identity &info, const QByteArray &file)
0268     {
0269         if (!m_pointer) {
0270             return advancedDiscDetect(file);
0271         }
0272 
0273         if (!m_semaphore.acquire()) {
0274             return advancedDiscDetect(file);
0275         }
0276         Releaser releaser(&m_semaphore);
0277 
0278         if (!m_shmem.lock()) {
0279             return advancedDiscDetect(file);
0280         }
0281         Unlocker unlocker(&m_shmem);
0282 
0283         if (!m_pointer->find(info)) {
0284             m_pointer->add(info, advancedDiscDetect(file));
0285         }
0286 
0287         Solid::OpticalDisc::ContentTypes content = m_pointer->front().second;
0288         return content;
0289     }
0290 
0291     ~SharedContentTypesCache()
0292     {
0293         m_semaphore.acquire();
0294         Releaser releaser(&m_semaphore);
0295         m_shmem.detach();
0296     }
0297 };
0298 
0299 Q_GLOBAL_STATIC(QThreadStorage<SharedContentTypesCache>, sharedContentTypesCache)
0300 
0301 OpticalDisc::Identity::Identity()
0302     : m_detectTime(0)
0303     , m_size(0)
0304     , m_labelHash(0)
0305 {
0306 }
0307 
0308 OpticalDisc::Identity::Identity(const Device &device, const Device &drive)
0309     : m_detectTime(drive.prop("TimeMediaDetected").toLongLong())
0310     , m_size(device.prop("Size").toLongLong())
0311     , m_labelHash(qHash(device.prop("IdLabel").toString()))
0312 {
0313 }
0314 
0315 bool OpticalDisc::Identity::operator==(const OpticalDisc::Identity &b) const
0316 {
0317     /* clang-format off */
0318     return m_detectTime == b.m_detectTime
0319         && m_size == b.m_size
0320         && m_labelHash == b.m_labelHash;
0321     /* clang-format on */
0322 }
0323 
0324 OpticalDisc::OpticalDisc(Device *dev)
0325     : StorageVolume(dev)
0326 {
0327 #if UDEV_FOUND
0328     UdevQt::Client client(this);
0329     m_udevDevice = client.deviceByDeviceFile(device());
0330     // qDebug() << "udev device:" << m_udevDevice.name() << "valid:" << m_udevDevice.isValid();
0331     /*qDebug() << "\tProperties:" << */ m_udevDevice.deviceProperties(); // initialize the properties DB so that it doesn't crash further down, #298416
0332 #endif
0333 
0334     m_drive = new Device(m_device->drivePath());
0335 }
0336 
0337 OpticalDisc::~OpticalDisc()
0338 {
0339     delete m_drive;
0340 }
0341 
0342 qulonglong OpticalDisc::capacity() const
0343 {
0344     return m_device->prop("Size").toULongLong();
0345 }
0346 
0347 bool OpticalDisc::isRewritable() const
0348 {
0349     // the hard way, udisks has no notion of a disc "rewritability"
0350     const QString mediaType = media();
0351     /* clang-format off */
0352     return mediaType == "optical_cd_rw"
0353         || mediaType == "optical_dvd_rw"
0354         || mediaType == "optical_dvd_ram"
0355         || mediaType == "optical_dvd_plus_rw"
0356         || mediaType == "optical_dvd_plus_rw_dl"
0357         || mediaType == "optical_bd_re"
0358         || mediaType == "optical_hddvd_rw";
0359     /* clang-format on */
0360 }
0361 
0362 bool OpticalDisc::isBlank() const
0363 {
0364     return m_drive->prop("OpticalBlank").toBool();
0365 }
0366 
0367 bool OpticalDisc::isAppendable() const
0368 {
0369     // qDebug() << "appendable prop" << m_udevDevice.deviceProperty("ID_CDROM_MEDIA_STATE");
0370 #if UDEV_FOUND
0371     return m_udevDevice.deviceProperty("ID_CDROM_MEDIA_STATE").toString() == QLatin1String("appendable");
0372 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
0373     return m_device->prop("bsdisks_IsAppendable").toBool();
0374 #else
0375 #error Implement this or stub this out for your platform
0376 #endif
0377 }
0378 
0379 Solid::OpticalDisc::DiscType OpticalDisc::discType() const
0380 {
0381     QMap<Solid::OpticalDisc::DiscType, QString> map;
0382     map[Solid::OpticalDisc::CdRom] = "optical_cd";
0383     map[Solid::OpticalDisc::CdRecordable] = "optical_cd_r";
0384     map[Solid::OpticalDisc::CdRewritable] = "optical_cd_rw";
0385     map[Solid::OpticalDisc::DvdRom] = "optical_dvd";
0386     map[Solid::OpticalDisc::DvdRecordable] = "optical_dvd_r";
0387     map[Solid::OpticalDisc::DvdRewritable] = "optical_dvd_rw";
0388     map[Solid::OpticalDisc::DvdRam] = "optical_dvd_ram";
0389     map[Solid::OpticalDisc::DvdPlusRecordable] = "optical_dvd_plus_r";
0390     map[Solid::OpticalDisc::DvdPlusRewritable] = "optical_dvd_plus_rw";
0391     map[Solid::OpticalDisc::DvdPlusRecordableDuallayer] = "optical_dvd_plus_r_dl";
0392     map[Solid::OpticalDisc::DvdPlusRewritableDuallayer] = "optical_dvd_plus_rw_dl";
0393     map[Solid::OpticalDisc::BluRayRom] = "optical_bd";
0394     map[Solid::OpticalDisc::BluRayRecordable] = "optical_bd_r";
0395     map[Solid::OpticalDisc::BluRayRewritable] = "optical_bd_re";
0396     map[Solid::OpticalDisc::HdDvdRom] = "optical_hddvd";
0397     map[Solid::OpticalDisc::HdDvdRecordable] = "optical_hddvd_r";
0398     map[Solid::OpticalDisc::HdDvdRewritable] = "optical_hddvd_rw";
0399     // TODO add these to Solid
0400     // map[Solid::OpticalDisc::MagnetoOptical] ="optical_mo";
0401     // map[Solid::OpticalDisc::MountRainer] ="optical_mrw";
0402     // map[Solid::OpticalDisc::MountRainerWritable] ="optical_mrw_w";
0403 
0404     return map.key(media(), Solid::OpticalDisc::UnknownDiscType); // FIXME optimize, lookup by value, not key
0405 }
0406 
0407 Solid::OpticalDisc::ContentTypes OpticalDisc::availableContent() const
0408 {
0409     if (isBlank()) {
0410         return Solid::OpticalDisc::NoContent;
0411     }
0412 
0413     Solid::OpticalDisc::ContentTypes content = Solid::OpticalDisc::NoContent;
0414     const bool hasData = m_drive->prop("OpticalNumDataTracks").toUInt() > 0;
0415     const bool hasAudio = m_drive->prop("OpticalNumAudioTracks").toUInt() > 0;
0416 
0417     if (hasData) {
0418         content |= Solid::OpticalDisc::Data;
0419 
0420         Identity newIdentity(*m_device, *m_drive);
0421         if (!(m_identity == newIdentity)) {
0422             QByteArray deviceFile(m_device->prop("Device").toByteArray());
0423             m_cachedContent = sharedContentTypesCache->localData().getContent(newIdentity, deviceFile);
0424             m_identity = newIdentity;
0425         }
0426 
0427         content |= m_cachedContent;
0428     }
0429     if (hasAudio) {
0430         content |= Solid::OpticalDisc::Audio;
0431     }
0432 
0433     return content;
0434 }
0435 
0436 QString OpticalDisc::media() const
0437 {
0438     return m_drive->prop("Media").toString();
0439 }
0440 
0441 #include "moc_udisksopticaldisc.cpp"