1 /*
2 Copyright 2010 Michael Zanetti <mzanetti@kde.org>
3 Copyright 2010 - 2012 Lukáš Tinkl <ltinkl@redhat.com>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) version 3, or any
9 later version accepted by the membership of KDE e.V. (or its
10 successor approved by the membership of KDE e.V.), which shall
11 act as a proxy defined in Section 6 of version 3 of the license.
12
13 This library is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public
19 License along with this library. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include <sys/types.h>
23 #include <unistd.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26
27 #include <QFile>
28 #include <QMap>
29 #include <QMutexLocker>
30 #include <QDBusConnection>
31
32 #include "../shared/udevqt.h"
33
34 #include "udisks2.h"
35 #include "udisksdevice.h"
36 #include "udisksopticaldisc.h"
37 #include "soliddefs_p.h"
38
39 typedef QMap<QByteArray, Solid::OpticalDisc::ContentTypes> ContentTypesCache;
SOLID_GLOBAL_STATIC(ContentTypesCache,cache)40 SOLID_GLOBAL_STATIC(ContentTypesCache, cache)
41 SOLID_GLOBAL_STATIC(QMutex, cacheLock)
42
43 // inspired by http://cgit.freedesktop.org/hal/tree/hald/linux/probing/probe-volume.c
44 static Solid::OpticalDisc::ContentType advancedDiscDetect(const QByteArray & device_file)
45 {
46 /* the discs block size */
47 unsigned short bs;
48 /* the path table size */
49 unsigned short ts;
50 /* the path table location (in blocks) */
51 unsigned int tl;
52 /* length of the directory name in current path table entry */
53 unsigned char len_di = 0;
54 /* the number of the parent directory's path table entry */
55 unsigned int parent = 0;
56 /* filename for the current path table entry */
57 char dirname[256];
58 /* our position into the path table */
59 int pos = 0;
60 /* the path table record we're on */
61 int curr_record = 1;
62
63 Solid::OpticalDisc::ContentType result = Solid::OpticalDisc::NoContent;
64
65 int fd = open(device_file.constData(), O_RDONLY);
66
67 /* read the block size */
68 lseek (fd, 0x8080, SEEK_CUR);
69 if (read (fd, &bs, 2) != 2)
70 {
71 qDebug("Advanced probing on %s failed while reading block size", qPrintable(device_file));
72 goto out;
73 }
74
75 /* read in size of path table */
76 lseek (fd, 2, SEEK_CUR);
77 if (read (fd, &ts, 2) != 2)
78 {
79 qDebug("Advanced probing on %s failed while reading path table size", qPrintable(device_file));
80 goto out;
81 }
82
83 /* read in which block path table is in */
84 lseek (fd, 6, SEEK_CUR);
85 if (read (fd, &tl, 4) != 4)
86 {
87 qDebug("Advanced probing on %s failed while reading path table block", qPrintable(device_file));
88 goto out;
89 }
90
91 /* seek to the path table */
92 lseek (fd, bs * tl, SEEK_SET);
93
94 /* loop through the path table entries */
95 while (pos < ts)
96 {
97 /* get the length of the filename of the current entry */
98 if (read (fd, &len_di, 1) != 1)
99 {
100 qDebug("Advanced probing on %s failed, cannot read more entries", qPrintable(device_file));
101 break;
102 }
103
104 /* get the record number of this entry's parent
105 i'm pretty sure that the 1st entry is always the top directory */
106 lseek (fd, 5, SEEK_CUR);
107 if (read (fd, &parent, 2) != 2)
108 {
109 qDebug("Advanced probing on %s failed, couldn't read parent entry", qPrintable(device_file));
110 break;
111 }
112
113 /* read the name */
114 if (read (fd, dirname, len_di) != len_di)
115 {
116 qDebug("Advanced probing on %s failed, couldn't read the entry name", qPrintable(device_file));
117 break;
118 }
119 dirname[len_di] = 0;
120
121 /* if we found a folder that has the root as a parent, and the directory name matches
122 one of the special directories then set the properties accordingly */
123 if (parent == 1)
124 {
125 if (!strcasecmp (dirname, "VIDEO_TS"))
126 {
127 qDebug("Disc in %s is a Video DVD", qPrintable(device_file));
128 result = Solid::OpticalDisc::VideoDvd;
129 break;
130 }
131 else if (!strcasecmp (dirname, "BDMV"))
132 {
133 qDebug("Disc in %s is a Blu-ray video disc", qPrintable(device_file));
134 result = Solid::OpticalDisc::VideoBluRay;
135 break;
136 }
137 else if (!strcasecmp (dirname, "VCD"))
138 {
139 qDebug("Disc in %s is a Video CD", qPrintable(device_file));
140 result = Solid::OpticalDisc::VideoCd;
141 break;
142 }
143 else if (!strcasecmp (dirname, "SVCD"))
144 {
145 qDebug("Disc in %s is a Super Video CD", qPrintable(device_file));
146 result = Solid::OpticalDisc::SuperVideoCd;
147 break;
148 }
149 }
150
151 /* all path table entries are padded to be even,
152 so if this is an odd-length table, seek a byte to fix it */
153 if (len_di%2 == 1)
154 {
155 lseek (fd, 1, SEEK_CUR);
156 pos++;
157 }
158
159 /* update our position */
160 pos += 8 + len_di;
161 curr_record++;
162 }
163
164 close(fd);
165 return result;
166
167 out:
168 /* go back to the start of the file */
169 lseek (fd, 0, SEEK_SET);
170 close(fd);
171 return result;
172 }
173
174 using namespace Solid::Backends::UDisks2;
175
OpticalDisc(Device * dev)176 OpticalDisc::OpticalDisc(Device *dev)
177 : StorageVolume(dev), m_needsReprobe(true), m_cachedContent(Solid::OpticalDisc::NoContent)
178 {
179 UdevQt::Client client(this);
180 m_udevDevice = client.deviceByDeviceFile(device());
181 //qDebug() << "udev device:" << m_udevDevice.name() << "valid:" << m_udevDevice.isValid();
182 /*qDebug() << "\tProperties:" << */ m_udevDevice.deviceProperties(); // initialize the properties DB so that it doesn't crash further down, #298416
183
184 m_drive = new Device(m_device->drivePath());
185 QDBusConnection::systemBus().connect(UD2_DBUS_SERVICE, m_drive->udi(), DBUS_INTERFACE_PROPS, "PropertiesChanged", this,
186 SLOT(slotDrivePropertiesChanged(QString,QVariantMap,QStringList)));
187 }
188
~OpticalDisc()189 OpticalDisc::~OpticalDisc()
190 {
191 delete m_drive;
192 }
193
capacity() const194 qulonglong OpticalDisc::capacity() const
195 {
196 return m_device->prop("Size").toULongLong();
197 }
198
isRewritable() const199 bool OpticalDisc::isRewritable() const
200 {
201 // the hard way, udisks has no notion of a disc "rewritability"
202 const QString mediaType = media();
203 return mediaType == "optical_cd_rw" || mediaType == "optical_dvd_rw" || mediaType == "optical_dvd_ram" ||
204 mediaType == "optical_dvd_plus_rw" || mediaType == "optical_dvd_plus_rw_dl" ||
205 mediaType == "optical_bd_re" || mediaType == "optical_hddvd_rw";
206 }
207
isBlank() const208 bool OpticalDisc::isBlank() const
209 {
210 return m_drive->prop("OpticalBlank").toBool();
211 }
212
isAppendable() const213 bool OpticalDisc::isAppendable() const
214 {
215 //qDebug() << "appendable prop" << m_udevDevice.deviceProperty("ID_CDROM_MEDIA_STATE");
216 return m_udevDevice.deviceProperty("ID_CDROM_MEDIA_STATE").toString() == QLatin1String("appendable");
217 }
218
discType() const219 Solid::OpticalDisc::DiscType OpticalDisc::discType() const
220 {
221 QMap<Solid::OpticalDisc::DiscType, QString> map;
222 map[Solid::OpticalDisc::CdRom] = "optical_cd";
223 map[Solid::OpticalDisc::CdRecordable] = "optical_cd_r";
224 map[Solid::OpticalDisc::CdRewritable] = "optical_cd_rw";
225 map[Solid::OpticalDisc::DvdRom] = "optical_dvd";
226 map[Solid::OpticalDisc::DvdRecordable] = "optical_dvd_r";
227 map[Solid::OpticalDisc::DvdRewritable] ="optical_dvd_rw";
228 map[Solid::OpticalDisc::DvdRam] ="optical_dvd_ram";
229 map[Solid::OpticalDisc::DvdPlusRecordable] ="optical_dvd_plus_r";
230 map[Solid::OpticalDisc::DvdPlusRewritable] ="optical_dvd_plus_rw";
231 map[Solid::OpticalDisc::DvdPlusRecordableDuallayer] ="optical_dvd_plus_r_dl";
232 map[Solid::OpticalDisc::DvdPlusRewritableDuallayer] ="optical_dvd_plus_rw_dl";
233 map[Solid::OpticalDisc::BluRayRom] ="optical_bd";
234 map[Solid::OpticalDisc::BluRayRecordable] ="optical_bd_r";
235 map[Solid::OpticalDisc::BluRayRewritable] ="optical_bd_re";
236 map[Solid::OpticalDisc::HdDvdRom] ="optical_hddvd";
237 map[Solid::OpticalDisc::HdDvdRecordable] ="optical_hddvd_r";
238 map[Solid::OpticalDisc::HdDvdRewritable] ="optical_hddvd_rw";
239 // TODO add these to Solid
240 //map[Solid::OpticalDisc::MagnetoOptical] ="optical_mo";
241 //map[Solid::OpticalDisc::MountRainer] ="optical_mrw";
242 //map[Solid::OpticalDisc::MountRainerWritable] ="optical_mrw_w";
243
244 return map.key(media(), Solid::OpticalDisc::UnknownDiscType); // FIXME optimize, lookup by value, not key
245 }
246
availableContent() const247 Solid::OpticalDisc::ContentTypes OpticalDisc::availableContent() const
248 {
249 if (isBlank()) {
250 m_needsReprobe = false;
251 return Solid::OpticalDisc::NoContent;
252 }
253
254 if (m_needsReprobe) {
255 QMutexLocker lock(cacheLock);
256
257 const QByteArray deviceFile = m_device->prop("Device").toByteArray();
258
259 if (cache->contains(deviceFile)) {
260 m_cachedContent = cache->value(deviceFile);
261 m_needsReprobe = false;
262 return m_cachedContent;
263 }
264
265 m_cachedContent = Solid::OpticalDisc::NoContent;
266 const bool hasData = m_drive->prop("OpticalNumDataTracks").toUInt() > 0;
267 const bool hasAudio = m_drive->prop("OpticalNumAudioTracks").toUInt() > 0;
268
269 if ( hasData ) {
270 m_cachedContent |= Solid::OpticalDisc::Data;
271 m_cachedContent |= advancedDiscDetect(deviceFile);
272 }
273 if ( hasAudio )
274 m_cachedContent |= Solid::OpticalDisc::Audio;
275
276 m_needsReprobe = false;
277 cache->insert(deviceFile, m_cachedContent);
278 }
279
280 return m_cachedContent;
281 }
282
slotDrivePropertiesChanged(const QString & ifaceName,const QVariantMap & changedProps,const QStringList & invalidatedProps)283 void OpticalDisc::slotDrivePropertiesChanged(const QString &ifaceName, const QVariantMap &changedProps, const QStringList &invalidatedProps)
284 {
285 Q_UNUSED(ifaceName);
286
287 if (changedProps.keys().contains("Media") || invalidatedProps.contains("Media")) {
288 QMutexLocker lock(cacheLock);
289 m_needsReprobe = true;
290 m_cachedContent = Solid::OpticalDisc::NoContent;
291 cache->remove(m_device->prop("Device").toByteArray());
292 }
293 }
294
media() const295 QString OpticalDisc::media() const
296 {
297 return m_drive->prop("Media").toString();
298 }
299