1/*
2 Copyright 2009 Pino Toscano <pino@kde.org>
3 Copyright 2009-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 "udisksstorageaccess.h"
23#include "udisks2.h"
24
25#include <QtDBus/QtDBus>
26#include <QtGui/QApplication>
27#include <QtGui/QWidget>
28#include <QtXml/QDomDocument>
29
30using namespace Solid::Backends::UDisks2;
31
32StorageAccess::StorageAccess(Device *device)
33 : DeviceInterface(device), m_setupInProgress(false), m_teardownInProgress(false), m_passphraseRequested(false)
34{
35 connect(device, SIGNAL(changed()), this, SLOT(checkAccessibility()));
36 updateCache();
37
38 // Delay connecting to DBus signals to avoid the related time penalty
39 // in hot paths such as predicate matching
40 QTimer::singleShot(0, this, SLOT(connectDBusSignals()));
41}
42
43StorageAccess::~StorageAccess()
44{
45}
46
47void StorageAccess::connectDBusSignals()
48{
49 m_device->registerAction("setup", this,
50 SLOT(slotSetupRequested()),
51 SLOT(slotSetupDone(int,QString)));
52
53 m_device->registerAction("teardown", this,
54 SLOT(slotTeardownRequested()),
55 SLOT(slotTeardownDone(int,QString)));
56}
57
58bool StorageAccess::isLuksDevice() const
59{
60 return m_device->isEncryptedContainer(); // encrypted device
61}
62
63bool StorageAccess::isAccessible() const
64{
65 if (isLuksDevice()) { // check if the cleartext slave is mounted
66 const QString path = clearTextPath();
67 //qDebug() << Q_FUNC_INFO << "CLEARTEXT device path: " << path;
68 if (path.isEmpty() || path == "/")
69 return false;
70 Device holderDevice(path);
71 return holderDevice.isMounted();
72 }
73
74 return m_device->isMounted();
75}
76
77QString StorageAccess::filePath() const
78{
79 QByteArrayList mntPoints;
80
81 if (isLuksDevice()) { // encrypted (and unlocked) device
82 const QString path = clearTextPath();
83 if (path.isEmpty() || path == "/")
84 return QString();
85 Device holderDevice(path);
86 mntPoints = qdbus_cast<QByteArrayList>(holderDevice.prop("MountPoints"));
87 if (!mntPoints.isEmpty())
88 return QFile::decodeName(mntPoints.first()); // FIXME Solid doesn't support multiple mount points
89 else
90 return QString();
91 }
92
93 mntPoints = qdbus_cast<QByteArrayList>(m_device->prop("MountPoints"));
94
95 if (!mntPoints.isEmpty())
96 return QFile::decodeName(mntPoints.first()); // FIXME Solid doesn't support multiple mount points
97 else
98 return QString();
99}
100
101bool StorageAccess::isIgnored() const
102{
103 return m_device->prop("HintIgnore").toBool();
104}
105
106bool StorageAccess::setup()
107{
108 if ( m_teardownInProgress || m_setupInProgress )
109 return false;
110 m_setupInProgress = true;
111 m_device->broadcastActionRequested("setup");
112
113 if (m_device->isEncryptedContainer() && clearTextPath().isEmpty())
114 return requestPassphrase();
115 else
116 return mount();
117}
118
119bool StorageAccess::teardown()
120{
121 if ( m_teardownInProgress || m_setupInProgress )
122 return false;
123 m_teardownInProgress = true;
124 m_device->broadcastActionRequested("teardown");
125
126 return unmount();
127}
128
129void StorageAccess::updateCache()
130{
131 m_isAccessible = isAccessible();
132}
133
134void StorageAccess::checkAccessibility()
135{
136 const bool old_isAccessible = m_isAccessible;
137 updateCache();
138
139 if (old_isAccessible != m_isAccessible) {
140 Q_EMIT accessibilityChanged(m_isAccessible, m_device->udi());
141 }
142}
143
144void StorageAccess::slotDBusReply( const QDBusMessage & /*reply*/ )
145{
146 const QString ctPath = clearTextPath();
147 if (m_setupInProgress)
148 {
149 if (isLuksDevice() && !isAccessible()) { // unlocked device, now mount it
150 mount();
151 }
152 else // Don't broadcast setupDone unless the setup is really done. (Fix kde#271156)
153 {
154 m_setupInProgress = false;
155 m_device->broadcastActionDone("setup");
156
157 checkAccessibility();
158 }
159 }
160 else if (m_teardownInProgress) // FIXME
161 {
162 if (isLuksDevice() && !ctPath.isEmpty() && ctPath != "/") // unlocked device, lock it
163 {
164 callCryptoTeardown();
165 }
166 else if (!ctPath.isEmpty() && ctPath != "/") {
167 callCryptoTeardown(true); // Lock crypted parent
168 }
169 else
170 {
171 // try to "eject" (aka safely remove) from the (parent) drive, e.g. SD card from a reader
172 QString drivePath = m_device->drivePath();
173 if (!drivePath.isEmpty() || drivePath != "/")
174 {
175 Device drive(drivePath);
176 if (drive.prop("Ejectable").toBool() &&
177 drive.prop("MediaAvailable").toBool() &&
178 !m_device->isOpticalDisc()) // optical drives have their Eject method
179 {
180 QDBusConnection c = QDBusConnection::systemBus();
181 QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, drivePath, UD2_DBUS_INTERFACE_DRIVE, "Eject");
182 msg << QVariantMap(); // options, unused now
183 c.call(msg, QDBus::NoBlock);
184 }
185 }
186
187 m_teardownInProgress = false;
188 m_device->broadcastActionDone("teardown");
189
190 checkAccessibility();
191 }
192 }
193}
194
195void StorageAccess::slotDBusError( const QDBusError & error )
196{
197 //qDebug() << Q_FUNC_INFO << "DBUS ERROR:" << error.name() << error.message();
198
199 if (m_setupInProgress)
200 {
201 m_setupInProgress = false;
202 m_device->broadcastActionDone("setup", m_device->errorToSolidError(error.name()),
203 m_device->errorToString(error.name()) + ": " +error.message());
204
205 checkAccessibility();
206 }
207 else if (m_teardownInProgress)
208 {
209 m_teardownInProgress = false;
210 m_device->broadcastActionDone("teardown", m_device->errorToSolidError(error.name()),
211 m_device->errorToString(error.name()) + ": " + error.message());
212 checkAccessibility();
213 }
214}
215
216void StorageAccess::slotSetupRequested()
217{
218 m_setupInProgress = true;
219 //qDebug() << "SETUP REQUESTED:" << m_device->udi();
220 Q_EMIT setupRequested(m_device->udi());
221}
222
223void StorageAccess::slotSetupDone(int error, const QString &errorString)
224{
225 m_setupInProgress = false;
226 //qDebug() << "SETUP DONE:" << m_device->udi();
227 Q_EMIT setupDone(static_cast<Solid::ErrorType>(error), errorString, m_device->udi());
228
229 checkAccessibility();
230}
231
232void StorageAccess::slotTeardownRequested()
233{
234 m_teardownInProgress = true;
235 Q_EMIT teardownRequested(m_device->udi());
236}
237
238void StorageAccess::slotTeardownDone(int error, const QString &errorString)
239{
240 m_teardownInProgress = false;
241 Q_EMIT teardownDone(static_cast<Solid::ErrorType>(error), errorString, m_device->udi());
242
243 checkAccessibility();
244}
245
246bool StorageAccess::mount()
247{
248 QString path = m_device->udi();
249 const QString ctPath = clearTextPath();
250
251 if (isLuksDevice() && !ctPath.isEmpty()) { // mount options for the cleartext volume
252 path = ctPath;
253 }
254
255 QDBusConnection c = QDBusConnection::systemBus();
256 QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, path, UD2_DBUS_INTERFACE_FILESYSTEM, "Mount");
257 QVariantMap options;
258
259 if (m_device->prop("IdType").toString() == "vfat")
260 options.insert("options", "flush");
261
262 msg << options;
263
264 return c.callWithCallback(msg, this,
265 SLOT(slotDBusReply(QDBusMessage)),
266 SLOT(slotDBusError(QDBusError)));
267}
268
269bool StorageAccess::unmount()
270{
271 QString path = m_device->udi();
272 const QString ctPath = clearTextPath();
273
274 if (isLuksDevice() && !ctPath.isEmpty()) { // unmount options for the cleartext volume
275 path = ctPath;
276 }
277
278 QDBusConnection c = QDBusConnection::systemBus();
279 QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, path, UD2_DBUS_INTERFACE_FILESYSTEM, "Unmount");
280
281 msg << QVariantMap(); // options, unused now
282
283 return c.callWithCallback(msg, this,
284 SLOT(slotDBusReply(QDBusMessage)),
285 SLOT(slotDBusError(QDBusError)),
286 s_unmountTimeout);
287}
288
289QString StorageAccess::generateReturnObjectPath()
290{
291 static int number = 1;
292
293 return "/org/kde/solid/UDisks2StorageAccess_"+QString::number(number++);
294}
295
296QString StorageAccess::clearTextPath() const
297{
298 const QString prefix = "/org/freedesktop/UDisks2/block_devices";
299 QDBusMessage call = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, prefix,
300 DBUS_INTERFACE_INTROSPECT, "Introspect");
301 QDBusPendingReply<QString> reply = QDBusConnection::systemBus().asyncCall(call);
302 reply.waitForFinished();
303
304 if (reply.isValid()) {
305 QDomDocument dom;
306 dom.setContent(reply.value());
307 QDomNodeList nodeList = dom.documentElement().elementsByTagName("node");
308 for (int i = 0; i < nodeList.count(); i++) {
309 QDomElement nodeElem = nodeList.item(i).toElement();
310 if (!nodeElem.isNull() && nodeElem.hasAttribute("name")) {
311 const QString udi = prefix + "/" + nodeElem.attribute("name");
312 Device holderDevice(udi);
313
314 if (m_device->udi() == holderDevice.prop("CryptoBackingDevice").value<QDBusObjectPath>().path()) {
315 //qDebug() << Q_FUNC_INFO << "CLEARTEXT device path: " << udi;
316 return udi;
317 }
318 }
319 }
320 }
321
322 return QString();
323}
324
325bool StorageAccess::requestPassphrase()
326{
327 QString udi = m_device->udi();
328 QString returnService = QDBusConnection::sessionBus().baseService();
329 m_lastReturnObject = generateReturnObjectPath();
330
331 QDBusConnection::sessionBus().registerObject(m_lastReturnObject, this, QDBusConnection::ExportScriptableSlots);
332
333 QWidget *activeWindow = QApplication::activeWindow();
334 uint wId = 0;
335 if (activeWindow!=0)
336 wId = (uint)activeWindow->winId();
337
338 QString appId = QCoreApplication::applicationName();
339
340 QDBusInterface soliduiserver("org.kde.kded", "/modules/soliduiserver", "org.kde.SolidUiServer");
341 QDBusReply<void> reply = soliduiserver.call("showPassphraseDialog", udi, returnService,
342 m_lastReturnObject, wId, appId);
343 m_passphraseRequested = reply.isValid();
344 if (!m_passphraseRequested)
345 qWarning() << "Failed to call the SolidUiServer, D-Bus said:" << reply.error();
346
347 return m_passphraseRequested;
348}
349
350void StorageAccess::passphraseReply(const QString & passphrase)
351{
352 if (m_passphraseRequested)
353 {
354 QDBusConnection::sessionBus().unregisterObject(m_lastReturnObject);
355 m_passphraseRequested = false;
356 if (!passphrase.isEmpty())
357 callCryptoSetup(passphrase);
358 else
359 {
360 m_setupInProgress = false;
361 m_device->broadcastActionDone("setup");
362 }
363 }
364}
365
366void StorageAccess::callCryptoSetup(const QString & passphrase)
367{
368 QDBusConnection c = QDBusConnection::systemBus();
369 QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, m_device->udi(), UD2_DBUS_INTERFACE_ENCRYPTED, "Unlock");
370
371 msg << passphrase;
372 msg << QVariantMap(); // options, unused now
373
374 c.callWithCallback(msg, this,
375 SLOT(slotDBusReply(QDBusMessage)),
376 SLOT(slotDBusError(QDBusError)));
377}
378
379bool StorageAccess::callCryptoTeardown(bool actOnParent)
380{
381 QDBusConnection c = QDBusConnection::systemBus();
382 QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE,
383 actOnParent ? (m_device->prop("CryptoBackingDevice").value<QDBusObjectPath>().path()) : m_device->udi(),
384 UD2_DBUS_INTERFACE_ENCRYPTED, "Lock");
385 msg << QVariantMap(); // options, unused now
386
387 return c.callWithCallback(msg, this,
388 SLOT(slotDBusReply(QDBusMessage)),
389 SLOT(slotDBusError(QDBusError)));
390}
391