1 /*
2 SPDX-FileCopyrightText: 2010 Michael Zanetti <mzanetti@kde.org>
3 SPDX-FileCopyrightText: 2010-2012 Lukáš Tinkl <ltinkl@redhat.com>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7
8 #include "udisksopticaldisc.h"
9 #include <fcntl.h>
10 #include <sys/stat.h>
11 #include <sys/types.h>
12 #include <unistd.h>
13
14 #include <QMap>
15 #include <QSharedMemory>
16 #include <QSystemSemaphore>
17 #include <QThreadStorage>
18
19 #include "soliddefs_p.h"
20 #include "udisks2.h"
21 #include "udisks_debug.h"
22
23 // inspired by http://cgit.freedesktop.org/hal/tree/hald/linux/probing/probe-volume.c
advancedDiscDetect(const QByteArray & device_file)24 static Solid::OpticalDisc::ContentType advancedDiscDetect(const QByteArray &device_file)
25 {
26 /* the discs block size */
27 unsigned short bs;
28 /* the path table size */
29 unsigned short ts;
30 /* the path table location (in blocks) */
31 unsigned int tl;
32 /* length of the directory name in current path table entry */
33 unsigned char len_di = 0;
34 /* the number of the parent directory's path table entry */
35 unsigned int parent = 0;
36 /* filename for the current path table entry */
37 char dirname[256];
38 /* our position into the path table */
39 int pos = 0;
40 /* the path table record we're on */
41 int curr_record = 1;
42 /* import debug category */
43 using Solid::Backends::UDisks2::UDISKS2;
44
45 Solid::OpticalDisc::ContentType result = Solid::OpticalDisc::NoContent;
46
47 int fd = open(device_file.constData(), O_RDONLY);
48
49 /* read the block size */
50 lseek(fd, 0x8080, SEEK_CUR);
51 if (read(fd, &bs, 2) != 2) {
52 qCDebug(UDISKS2, "Advanced probing on %s failed while reading block size", qPrintable(device_file));
53 goto out;
54 }
55
56 /* read in size of path table */
57 lseek(fd, 2, SEEK_CUR);
58 if (read(fd, &ts, 2) != 2) {
59 qCDebug(UDISKS2, "Advanced probing on %s failed while reading path table size", qPrintable(device_file));
60 goto out;
61 }
62
63 /* read in which block path table is in */
64 lseek(fd, 6, SEEK_CUR);
65 if (read(fd, &tl, 4) != 4) {
66 qCDebug(UDISKS2, "Advanced probing on %s failed while reading path table block", qPrintable(device_file));
67 goto out;
68 }
69
70 /* seek to the path table */
71 lseek(fd, bs * tl, SEEK_SET);
72
73 /* loop through the path table entries */
74 while (pos < ts) {
75 /* get the length of the filename of the current entry */
76 if (read(fd, &len_di, 1) != 1) {
77 qCDebug(UDISKS2, "Advanced probing on %s failed, cannot read more entries", qPrintable(device_file));
78 break;
79 }
80
81 /* get the record number of this entry's parent
82 i'm pretty sure that the 1st entry is always the top directory */
83 lseek(fd, 5, SEEK_CUR);
84 if (read(fd, &parent, 2) != 2) {
85 qCDebug(UDISKS2, "Advanced probing on %s failed, couldn't read parent entry", qPrintable(device_file));
86 break;
87 }
88
89 /* read the name */
90 if (read(fd, dirname, len_di) != len_di) {
91 qCDebug(UDISKS2, "Advanced probing on %s failed, couldn't read the entry name", qPrintable(device_file));
92 break;
93 }
94 dirname[len_di] = 0;
95
96 /* if we found a folder that has the root as a parent, and the directory name matches
97 one of the special directories then set the properties accordingly */
98 if (parent == 1) {
99 if (!strcasecmp(dirname, "VIDEO_TS")) {
100 qCDebug(UDISKS2, "Disc in %s is a Video DVD", qPrintable(device_file));
101 result = Solid::OpticalDisc::VideoDvd;
102 break;
103 } else if (!strcasecmp(dirname, "BDMV")) {
104 qCDebug(UDISKS2, "Disc in %s is a Blu-ray video disc", qPrintable(device_file));
105 result = Solid::OpticalDisc::VideoBluRay;
106 break;
107 } else if (!strcasecmp(dirname, "VCD")) {
108 qCDebug(UDISKS2, "Disc in %s is a Video CD", qPrintable(device_file));
109 result = Solid::OpticalDisc::VideoCd;
110 break;
111 } else if (!strcasecmp(dirname, "SVCD")) {
112 qCDebug(UDISKS2, "Disc in %s is a Super Video CD", qPrintable(device_file));
113 result = Solid::OpticalDisc::SuperVideoCd;
114 break;
115 }
116 }
117
118 /* all path table entries are padded to be even,
119 so if this is an odd-length table, seek a byte to fix it */
120 if (len_di % 2 == 1) {
121 lseek(fd, 1, SEEK_CUR);
122 pos++;
123 }
124
125 /* update our position */
126 pos += 8 + len_di;
127 curr_record++;
128 }
129
130 close(fd);
131 return result;
132
133 out:
134 /* go back to the start of the file */
135 lseek(fd, 0, SEEK_SET);
136 close(fd);
137 return result;
138 }
139
140 using namespace Solid::Backends::UDisks2;
141
142 class ContentTypesCache
143 {
144 public:
ContentTypesCache()145 ContentTypesCache()
146 : m_n(0)
147 {
148 }
149
add(const OpticalDisc::Identity & key,Solid::OpticalDisc::ContentTypes content)150 void add(const OpticalDisc::Identity &key, Solid::OpticalDisc::ContentTypes content)
151 {
152 if (!find(key)) {
153 m_n = qMin(m_n + 1, sizeof(m_info) / sizeof(*m_info));
154 moveToFront(m_n - 1);
155 front().first = key;
156 }
157 front().second = content;
158 }
159
find(const OpticalDisc::Identity & key)160 bool find(const OpticalDisc::Identity &key)
161 {
162 for (size_t i = 0; i < m_n; i++) {
163 if (m_info[i].first == key) {
164 moveToFront(i);
165 return true;
166 }
167 }
168 return false;
169 }
170
front()171 QPair<OpticalDisc::Identity, Solid::OpticalDisc::ContentTypes> &front()
172 {
173 return *m_info;
174 }
175
176 private:
moveToFront(size_t i)177 void moveToFront(size_t i)
178 {
179 while (i) {
180 qSwap(m_info[i - 1], m_info[i]);
181 --i;
182 }
183 }
184
185 size_t m_n;
186 QPair<OpticalDisc::Identity, Solid::OpticalDisc::ContentTypes> m_info[100];
187 };
188
189 class SharedContentTypesCache
190 {
191 private:
192 ContentTypesCache *m_pointer;
193 QSystemSemaphore m_semaphore;
194 QSharedMemory m_shmem;
195
196 struct Unlocker {
197 public:
UnlockerSharedContentTypesCache::Unlocker198 Unlocker(QSharedMemory *mem)
199 : m_mem(mem)
200 {
201 }
~UnlockerSharedContentTypesCache::Unlocker202 ~Unlocker()
203 {
204 m_mem->unlock();
205 }
206 Unlocker(const Unlocker &) = delete;
207 Unlocker &operator=(const Unlocker &) = delete;
208
209 private:
210 QSharedMemory *m_mem;
211 };
212
213 struct Releaser {
214 public:
ReleaserSharedContentTypesCache::Releaser215 Releaser(QSystemSemaphore *sem)
216 : m_sem(sem)
217 {
218 }
~ReleaserSharedContentTypesCache::Releaser219 ~Releaser()
220 {
221 m_sem->release();
222 }
223 Releaser(const Releaser &) = delete;
224 Releaser &operator=(const Releaser &) = delete;
225
226 private:
227 QSystemSemaphore *m_sem;
228 };
229
getKey()230 static QString getKey()
231 {
232 static const QString keyTemplate("solid-disk-info-1-%1-%2");
233 static const QString tableSize(QString::number(sizeof(ContentTypesCache)));
234
235 return keyTemplate.arg(tableSize, QString::number(geteuid()));
236 }
237
238 public:
SharedContentTypesCache()239 SharedContentTypesCache()
240 : m_pointer(nullptr)
241 , m_semaphore(getKey() + "sem", 1)
242 , m_shmem(getKey() + "mem")
243 {
244 if (!m_semaphore.acquire()) {
245 return;
246 }
247 Releaser releaser(&m_semaphore);
248
249 if (m_shmem.attach()) {
250 m_pointer = reinterpret_cast<ContentTypesCache *>(m_shmem.data());
251 return;
252 }
253
254 if (!m_shmem.create(sizeof(ContentTypesCache))) {
255 return;
256 }
257
258 if (!m_shmem.lock()) {
259 m_shmem.detach();
260 return;
261 }
262 Unlocker unlocker(&m_shmem);
263
264 m_pointer = new (m_shmem.data()) ContentTypesCache;
265 }
266
getContent(const OpticalDisc::Identity & info,const QByteArray & file)267 Solid::OpticalDisc::ContentTypes getContent(const OpticalDisc::Identity &info, const QByteArray &file)
268 {
269 if (!m_pointer) {
270 return advancedDiscDetect(file);
271 }
272
273 if (!m_semaphore.acquire()) {
274 return advancedDiscDetect(file);
275 }
276 Releaser releaser(&m_semaphore);
277
278 if (!m_shmem.lock()) {
279 return advancedDiscDetect(file);
280 }
281 Unlocker unlocker(&m_shmem);
282
283 if (!m_pointer->find(info)) {
284 m_pointer->add(info, advancedDiscDetect(file));
285 }
286
287 Solid::OpticalDisc::ContentTypes content = m_pointer->front().second;
288 return content;
289 }
290
~SharedContentTypesCache()291 ~SharedContentTypesCache()
292 {
293 m_semaphore.acquire();
294 Releaser releaser(&m_semaphore);
295 m_shmem.detach();
296 }
297 };
298
Q_GLOBAL_STATIC(QThreadStorage<SharedContentTypesCache>,sharedContentTypesCache)299 Q_GLOBAL_STATIC(QThreadStorage<SharedContentTypesCache>, sharedContentTypesCache)
300
301 OpticalDisc::Identity::Identity()
302 : m_detectTime(0)
303 , m_size(0)
304 , m_labelHash(0)
305 {
306 }
307
Identity(const Device & device,const Device & drive)308 OpticalDisc::Identity::Identity(const Device &device, const Device &drive)
309 : m_detectTime(drive.prop("TimeMediaDetected").toLongLong())
310 , m_size(device.prop("Size").toLongLong())
311 , m_labelHash(qHash(device.prop("IdLabel").toString()))
312 {
313 }
314
operator ==(const OpticalDisc::Identity & b) const315 bool OpticalDisc::Identity::operator==(const OpticalDisc::Identity &b) const
316 {
317 /* clang-format off */
318 return m_detectTime == b.m_detectTime
319 && m_size == b.m_size
320 && m_labelHash == b.m_labelHash;
321 /* clang-format on */
322 }
323
OpticalDisc(Device * dev)324 OpticalDisc::OpticalDisc(Device *dev)
325 : StorageVolume(dev)
326 {
327 #if UDEV_FOUND
328 UdevQt::Client client(this);
329 m_udevDevice = client.deviceByDeviceFile(device());
330 // qDebug() << "udev device:" << m_udevDevice.name() << "valid:" << m_udevDevice.isValid();
331 /*qDebug() << "\tProperties:" << */ m_udevDevice.deviceProperties(); // initialize the properties DB so that it doesn't crash further down, #298416
332 #endif
333
334 m_drive = new Device(m_device->drivePath());
335 }
336
~OpticalDisc()337 OpticalDisc::~OpticalDisc()
338 {
339 delete m_drive;
340 }
341
capacity() const342 qulonglong OpticalDisc::capacity() const
343 {
344 return m_device->prop("Size").toULongLong();
345 }
346
isRewritable() const347 bool OpticalDisc::isRewritable() const
348 {
349 // the hard way, udisks has no notion of a disc "rewritability"
350 const QString mediaType = media();
351 /* clang-format off */
352 return mediaType == "optical_cd_rw"
353 || mediaType == "optical_dvd_rw"
354 || mediaType == "optical_dvd_ram"
355 || mediaType == "optical_dvd_plus_rw"
356 || mediaType == "optical_dvd_plus_rw_dl"
357 || mediaType == "optical_bd_re"
358 || mediaType == "optical_hddvd_rw";
359 /* clang-format on */
360 }
361
isBlank() const362 bool OpticalDisc::isBlank() const
363 {
364 return m_drive->prop("OpticalBlank").toBool();
365 }
366
isAppendable() const367 bool OpticalDisc::isAppendable() const
368 {
369 // qDebug() << "appendable prop" << m_udevDevice.deviceProperty("ID_CDROM_MEDIA_STATE");
370 #if UDEV_FOUND
371 return m_udevDevice.deviceProperty("ID_CDROM_MEDIA_STATE").toString() == QLatin1String("appendable");
372 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD)
373 return m_device->prop("bsdisks_IsAppendable").toBool();
374 #else
375 #error Implement this or stub this out for your platform
376 #endif
377 }
378
discType() const379 Solid::OpticalDisc::DiscType OpticalDisc::discType() const
380 {
381 QMap<Solid::OpticalDisc::DiscType, QString> map;
382 map[Solid::OpticalDisc::CdRom] = "optical_cd";
383 map[Solid::OpticalDisc::CdRecordable] = "optical_cd_r";
384 map[Solid::OpticalDisc::CdRewritable] = "optical_cd_rw";
385 map[Solid::OpticalDisc::DvdRom] = "optical_dvd";
386 map[Solid::OpticalDisc::DvdRecordable] = "optical_dvd_r";
387 map[Solid::OpticalDisc::DvdRewritable] = "optical_dvd_rw";
388 map[Solid::OpticalDisc::DvdRam] = "optical_dvd_ram";
389 map[Solid::OpticalDisc::DvdPlusRecordable] = "optical_dvd_plus_r";
390 map[Solid::OpticalDisc::DvdPlusRewritable] = "optical_dvd_plus_rw";
391 map[Solid::OpticalDisc::DvdPlusRecordableDuallayer] = "optical_dvd_plus_r_dl";
392 map[Solid::OpticalDisc::DvdPlusRewritableDuallayer] = "optical_dvd_plus_rw_dl";
393 map[Solid::OpticalDisc::BluRayRom] = "optical_bd";
394 map[Solid::OpticalDisc::BluRayRecordable] = "optical_bd_r";
395 map[Solid::OpticalDisc::BluRayRewritable] = "optical_bd_re";
396 map[Solid::OpticalDisc::HdDvdRom] = "optical_hddvd";
397 map[Solid::OpticalDisc::HdDvdRecordable] = "optical_hddvd_r";
398 map[Solid::OpticalDisc::HdDvdRewritable] = "optical_hddvd_rw";
399 // TODO add these to Solid
400 // map[Solid::OpticalDisc::MagnetoOptical] ="optical_mo";
401 // map[Solid::OpticalDisc::MountRainer] ="optical_mrw";
402 // map[Solid::OpticalDisc::MountRainerWritable] ="optical_mrw_w";
403
404 return map.key(media(), Solid::OpticalDisc::UnknownDiscType); // FIXME optimize, lookup by value, not key
405 }
406
availableContent() const407 Solid::OpticalDisc::ContentTypes OpticalDisc::availableContent() const
408 {
409 if (isBlank()) {
410 return Solid::OpticalDisc::NoContent;
411 }
412
413 Solid::OpticalDisc::ContentTypes content = Solid::OpticalDisc::NoContent;
414 const bool hasData = m_drive->prop("OpticalNumDataTracks").toUInt() > 0;
415 const bool hasAudio = m_drive->prop("OpticalNumAudioTracks").toUInt() > 0;
416
417 if (hasData) {
418 content |= Solid::OpticalDisc::Data;
419
420 Identity newIdentity(*m_device, *m_drive);
421 if (!(m_identity == newIdentity)) {
422 QByteArray deviceFile(m_device->prop("Device").toByteArray());
423 m_cachedContent = sharedContentTypesCache->localData().getContent(newIdentity, deviceFile);
424 m_identity = newIdentity;
425 }
426
427 content |= m_cachedContent;
428 }
429 if (hasAudio) {
430 content |= Solid::OpticalDisc::Audio;
431 }
432
433 return content;
434 }
435
media() const436 QString OpticalDisc::media() const
437 {
438 return m_drive->prop("Media").toString();
439 }
440