1 /*
2 Copyright 2009 Pino Toscano <pino@kde.org>
3 Copyright 2009, 2011 Lukas 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 "udisks.h"
24
25 #include <QProcess>
26 #include <QtDBus>
27 #include <QApplication>
28 #include <QWidget>
29
30 using namespace Solid::Backends::UDisks;
31
UDisksStorageAccess(UDisksDevice * device)32 UDisksStorageAccess::UDisksStorageAccess(UDisksDevice *device)
33 : DeviceInterface(device), m_setupInProgress(false), m_teardownInProgress(false), m_passphraseRequested(false)
34 {
35 connect(device, SIGNAL(changed()), this, SLOT(slotChanged()));
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
~UDisksStorageAccess()43 UDisksStorageAccess::~UDisksStorageAccess()
44 {
45 }
46
connectDBusSignals()47 void UDisksStorageAccess::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
isLuksDevice() const58 bool UDisksStorageAccess::isLuksDevice() const
59 {
60 return m_device->prop("DeviceIsLuks").toBool();
61 }
62
isAccessible() const63 bool UDisksStorageAccess::isAccessible() const
64 {
65 if (isLuksDevice()) { // check if the cleartext slave is mounted
66 UDisksDevice holderDevice(m_device->prop("LuksHolder").value<QDBusObjectPath>().path());
67 return holderDevice.prop("DeviceIsMounted").toBool();
68 }
69
70 return m_device->prop("DeviceIsMounted").toBool();
71 }
72
filePath() const73 QString UDisksStorageAccess::filePath() const
74 {
75 if (!isAccessible())
76 return QString();
77
78 QStringList mntPoints;
79
80 if (isLuksDevice()) { // encrypted (and unlocked) device
81 QString path = m_device->prop("LuksHolder").value<QDBusObjectPath>().path();
82 if (path.isEmpty() || path == "/")
83 return QString();
84 UDisksDevice holderDevice(path);
85 mntPoints = holderDevice.prop("DeviceMountPaths").toStringList();
86 if (!mntPoints.isEmpty())
87 return mntPoints.first(); // FIXME Solid doesn't support multiple mount points
88 else
89 return QString();
90 }
91
92 mntPoints = m_device->prop("DeviceMountPaths").toStringList();
93
94 if (!mntPoints.isEmpty())
95 return mntPoints.first(); // FIXME Solid doesn't support multiple mount points
96 else
97 return QString();
98 }
99
isIgnored() const100 bool UDisksStorageAccess::isIgnored() const
101 {
102 return m_device->isDeviceBlacklisted();
103 }
104
setup()105 bool UDisksStorageAccess::setup()
106 {
107 if ( m_teardownInProgress || m_setupInProgress )
108 return false;
109 m_setupInProgress = true;
110 m_device->broadcastActionRequested("setup");
111
112 if (m_device->prop("IdUsage").toString() == "crypto")
113 return requestPassphrase();
114 else
115 return mount();
116 }
117
teardown()118 bool UDisksStorageAccess::teardown()
119 {
120 if ( m_teardownInProgress || m_setupInProgress )
121 return false;
122 m_teardownInProgress = true;
123 m_device->broadcastActionRequested("teardown");
124
125 return unmount();
126 }
127
slotChanged()128 void UDisksStorageAccess::slotChanged()
129 {
130 const bool old_isAccessible = m_isAccessible;
131 updateCache();
132
133 if (old_isAccessible != m_isAccessible)
134 {
135 emit accessibilityChanged(m_isAccessible, m_device->udi());
136 }
137 }
138
updateCache()139 void UDisksStorageAccess::updateCache()
140 {
141 m_isAccessible = isAccessible();
142 }
143
slotDBusReply(const QDBusMessage & reply)144 void UDisksStorageAccess::slotDBusReply( const QDBusMessage & reply )
145 {
146 Q_UNUSED(reply);
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 }
158 else if (m_teardownInProgress)
159 {
160 QString clearTextPath = m_device->prop("LuksHolder").value<QDBusObjectPath>().path();
161 if (isLuksDevice() && clearTextPath != "/") // unlocked device, lock it
162 {
163 callCryptoTeardown();
164 }
165 else if (m_device->prop("DeviceIsLuksCleartext").toBool()) {
166 callCryptoTeardown(true); // Lock crypted parent
167 }
168 else
169 {
170 if (m_device->prop("DriveIsMediaEjectable").toBool() &&
171 m_device->prop("DeviceIsMediaAvailable").toBool() &&
172 !m_device->prop("DeviceIsOpticalDisc").toBool()) // optical drives have their Eject method
173 {
174 QString devnode = m_device->prop("DeviceFile").toString();
175
176 #if defined(Q_OS_OPENBSD)
177 QString program = "cdio";
178 QStringList args;
179 args << "-f" << devnode << "eject";
180 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD)
181 devnode.remove("/dev/").replace("([0-9]).", "\\1");
182 QString program = "cdcontrol";
183 QStringList args;
184 args << "-f" << devnode << "eject";
185 #else
186 QString program = "eject";
187 QStringList args;
188 args << devnode;
189 #endif
190
191 QProcess::startDetached( program, args );
192 }
193
194 // try to eject the (parent) drive, e.g. SD card from a reader
195 QString drivePath = m_device->prop("PartitionSlave").value<QDBusObjectPath>().path();
196 if (!drivePath.isEmpty() || drivePath != "/")
197 {
198 QDBusConnection c = QDBusConnection::systemBus();
199 QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, drivePath, UD_DBUS_INTERFACE_DISKS_DEVICE, "DriveEject");
200 msg << QStringList(); // options, unused now
201 c.call(msg, QDBus::NoBlock);
202
203 // power down removable USB hard drives, rhbz#852196
204 UDisksDevice drive(drivePath);
205 if (drive.prop("DriveCanDetach").toBool()) {
206 QDBusMessage msg2 = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, drivePath, UD_DBUS_INTERFACE_DISKS_DEVICE, "DriveDetach");
207 msg2 << QStringList(); // options, unused now
208 c.call(msg2, QDBus::NoBlock);
209 }
210 }
211
212 m_teardownInProgress = false;
213 m_device->broadcastActionDone("teardown");
214 }
215 }
216 }
217
slotDBusError(const QDBusError & error)218 void UDisksStorageAccess::slotDBusError( const QDBusError & error )
219 {
220 if (m_setupInProgress)
221 {
222 m_setupInProgress = false;
223 m_device->broadcastActionDone("setup", m_device->errorToSolidError(error.name()),
224 m_device->errorToString(error.name()) + ": " +error.message());
225
226 }
227 else if (m_teardownInProgress)
228 {
229 m_teardownInProgress = false;
230 m_device->broadcastActionDone("teardown", m_device->errorToSolidError(error.name()),
231 m_device->errorToString(error.name()) + ": " + error.message());
232 }
233 }
234
slotSetupRequested()235 void UDisksStorageAccess::slotSetupRequested()
236 {
237 m_setupInProgress = true;
238 emit setupRequested(m_device->udi());
239 }
240
slotSetupDone(int error,const QString & errorString)241 void UDisksStorageAccess::slotSetupDone(int error, const QString &errorString)
242 {
243 m_setupInProgress = false;
244 emit setupDone(static_cast<Solid::ErrorType>(error), errorString, m_device->udi());
245 slotChanged();
246 }
247
slotTeardownRequested()248 void UDisksStorageAccess::slotTeardownRequested()
249 {
250 m_teardownInProgress = true;
251 emit teardownRequested(m_device->udi());
252 }
253
slotTeardownDone(int error,const QString & errorString)254 void UDisksStorageAccess::slotTeardownDone(int error, const QString &errorString)
255 {
256 m_teardownInProgress = false;
257 emit teardownDone(static_cast<Solid::ErrorType>(error), errorString, m_device->udi());
258 slotChanged();
259 }
260
mount()261 bool UDisksStorageAccess::mount()
262 {
263 QString path = m_device->udi();
264 if (path.endsWith(":media")) {
265 path.chop(6);
266 }
267 QString fstype;
268 QStringList options;
269
270 if (isLuksDevice()) { // mount options for the cleartext volume
271 path = m_device->prop("LuksHolder").value<QDBusObjectPath>().path();
272 UDisksDevice holderDevice(path);
273 fstype = holderDevice.prop("IdType").toString();
274 }
275
276 QDBusConnection c = QDBusConnection::systemBus();
277 QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, path, UD_DBUS_INTERFACE_DISKS_DEVICE, "FilesystemMount");
278
279 if (m_device->prop("IdUsage").toString() == "filesystem")
280 fstype = m_device->prop("IdType").toString();
281
282 if (fstype == "vfat") {
283 options << "flush";
284 }
285
286 msg << fstype;
287 msg << options;
288
289 return c.callWithCallback(msg, this,
290 SLOT(slotDBusReply(QDBusMessage)),
291 SLOT(slotDBusError(QDBusError)));
292 }
293
unmount()294 bool UDisksStorageAccess::unmount()
295 {
296 QString path = m_device->udi();
297 if (path.endsWith(":media")) {
298 path.chop(6);
299 }
300
301 if (isLuksDevice()) { // unmount options for the cleartext volume
302 path = m_device->prop("LuksHolder").value<QDBusObjectPath>().path();
303 }
304
305 QDBusConnection c = QDBusConnection::systemBus();
306 QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, path, UD_DBUS_INTERFACE_DISKS_DEVICE, "FilesystemUnmount");
307
308 msg << QStringList(); // options, unused now
309
310 return c.callWithCallback(msg, this,
311 SLOT(slotDBusReply(QDBusMessage)),
312 SLOT(slotDBusError(QDBusError)),
313 s_unmountTimeout);
314 }
315
generateReturnObjectPath()316 QString UDisksStorageAccess::generateReturnObjectPath()
317 {
318 static int number = 1;
319
320 return "/org/kde/solid/UDisksStorageAccess_"+QString::number(number++);
321 }
322
requestPassphrase()323 bool UDisksStorageAccess::requestPassphrase()
324 {
325 QString udi = m_device->udi();
326 QString returnService = QDBusConnection::sessionBus().baseService();
327 m_lastReturnObject = generateReturnObjectPath();
328
329 QDBusConnection::sessionBus().registerObject(m_lastReturnObject, this, QDBusConnection::ExportScriptableSlots);
330
331 QWidget *activeWindow = QApplication::activeWindow();
332 uint wId = 0;
333 if (activeWindow!=0)
334 wId = (uint)activeWindow->winId();
335
336 QString appId = QCoreApplication::applicationName();
337
338 QDBusInterface soliduiserver("org.kde.kded", "/modules/soliduiserver", "org.kde.SolidUiServer");
339 QDBusReply<void> reply = soliduiserver.call("showPassphraseDialog", udi, returnService,
340 m_lastReturnObject, wId, appId);
341 m_passphraseRequested = reply.isValid();
342 if (!m_passphraseRequested)
343 qWarning() << "Failed to call the SolidUiServer, D-Bus said:" << reply.error();
344
345 return m_passphraseRequested;
346 }
347
passphraseReply(const QString & passphrase)348 void UDisksStorageAccess::passphraseReply( const QString & passphrase )
349 {
350 if (m_passphraseRequested)
351 {
352 QDBusConnection::sessionBus().unregisterObject(m_lastReturnObject);
353 m_passphraseRequested = false;
354 if (!passphrase.isEmpty())
355 callCryptoSetup(passphrase);
356 else
357 {
358 m_setupInProgress = false;
359 m_device->broadcastActionDone("setup");
360 }
361 }
362 }
363
callCryptoSetup(const QString & passphrase)364 void UDisksStorageAccess::callCryptoSetup( const QString & passphrase )
365 {
366 QDBusConnection c = QDBusConnection::systemBus();
367 QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE, m_device->udi(), UD_DBUS_INTERFACE_DISKS_DEVICE, "LuksUnlock");
368
369 msg << passphrase;
370 msg << QStringList(); // options, unused now
371
372 c.callWithCallback(msg, this,
373 SLOT(slotDBusReply(QDBusMessage)),
374 SLOT(slotDBusError(QDBusError)));
375 }
376
callCryptoTeardown(bool actOnParent)377 bool UDisksStorageAccess::callCryptoTeardown(bool actOnParent)
378 {
379 QDBusConnection c = QDBusConnection::systemBus();
380 QDBusMessage msg = QDBusMessage::createMethodCall(UD_DBUS_SERVICE,
381 actOnParent?(m_device->prop("LuksCleartextSlave").value<QDBusObjectPath>().path()):m_device->udi(),
382 UD_DBUS_INTERFACE_DISKS_DEVICE, "LuksLock");
383 msg << QStringList(); // options, unused now
384
385 return c.callWithCallback(msg, this,
386 SLOT(slotDBusReply(QDBusMessage)),
387 SLOT(slotDBusError(QDBusError)));
388 }
389
390 #include "backends/udisks/moc_udisksstorageaccess.cpp"
391