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