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"