Solid

udisksopticaldisc.cpp
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
24static 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
133out:
134 /* go back to the start of the file */
135 lseek(fd, 0, SEEK_SET);
136 close(fd);
137 return result;
138}
139
140using namespace Solid::Backends::UDisks2;
141
142class ContentTypesCache
143{
144public:
145 ContentTypesCache()
146 : m_n(0)
147 {
148 }
149
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
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
171 QPair<OpticalDisc::Identity, Solid::OpticalDisc::ContentTypes> &front()
172 {
173 return *m_info;
174 }
175
176private:
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
189class SharedContentTypesCache
190{
191private:
192 ContentTypesCache *m_pointer;
193 QSystemSemaphore m_semaphore;
194 QSharedMemory m_shmem;
195
196 struct Unlocker {
197 public:
198 Unlocker(QSharedMemory *mem)
199 : m_mem(mem)
200 {
201 }
202 ~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:
215 Releaser(QSystemSemaphore *sem)
216 : m_sem(sem)
217 {
218 }
219 ~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
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
238public:
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
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
291 ~SharedContentTypesCache()
292 {
293 m_semaphore.acquire();
294 Releaser releaser(&m_semaphore);
295 m_shmem.detach();
296 }
297};
298
299Q_GLOBAL_STATIC(QThreadStorage<SharedContentTypesCache>, sharedContentTypesCache)
300
301OpticalDisc::Identity::Identity()
302 : m_detectTime(0)
303 , m_size(0)
304 , m_labelHash(0)
305{
306}
307
308OpticalDisc::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
315bool 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
324OpticalDisc::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
337OpticalDisc::~OpticalDisc()
338{
339 delete m_drive;
340}
341
342qulonglong OpticalDisc::capacity() const
343{
344 return m_device->prop("Size").toULongLong();
345}
346
347bool 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
362bool OpticalDisc::isBlank() const
363{
364 return m_drive->prop("OpticalBlank").toBool();
365}
366
367bool 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
379Solid::OpticalDisc::DiscType OpticalDisc::discType() const
380{
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
407Solid::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
436QString OpticalDisc::media() const
437{
438 return m_drive->prop("Media").toString();
439}
440
441#include "moc_udisksopticaldisc.cpp"
DiscType
This enum type defines the type of optical disc it can be.
ContentType
This enum type defines the type of content available in an optical disc.
char * toString(const EngineQuery &query)
QVariant read(const QByteArray &data, int versionOverride=0)
const QList< QKeySequence > & close()
const QList< QKeySequence > & open()
KTEXTEDITOR_EXPORT size_t qHash(KTextEditor::Cursor cursor, size_t seed=0) noexcept
const char * constData() const const
bool attach(AccessMode mode)
bool create(qsizetype size, AccessMode mode)
void * data()
QString number(double n, char format, int precision)
bool release(int n)
QFuture< void > map(Iterator begin, Iterator end, MapFunctor &&function)
bool toBool() const const
QByteArray toByteArray() const const
QString toString() const const
uint toUInt(bool *ok) const const
qulonglong toULongLong(bool *ok) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 3 2024 11:47:59 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.