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