Solid

udisksstorageaccess.cpp
1/*
2 SPDX-FileCopyrightText: 2009 Pino Toscano <pino@kde.org>
3 SPDX-FileCopyrightText: 2009-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 "udisksstorageaccess.h"
9#include "udisks2.h"
10#include "udisks_debug.h"
11
12#include <QDBusConnection>
13#include <QDBusInterface>
14#include <QDBusMetaType>
15#include <QDir>
16#include <QGuiApplication>
17#include <QWindow>
18
19#include <config-solid.h>
20#if HAVE_LIBMOUNT
21#include <libmount/libmount.h>
22#endif
23
24struct AvailableAnswer {
25 bool checkResult;
26 QString binaryName;
27};
28Q_DECLARE_METATYPE(AvailableAnswer)
29
30QDBusArgument &operator<<(QDBusArgument &argument, const AvailableAnswer &answer)
31{
32 argument.beginStructure();
33 argument << answer.checkResult << answer.binaryName;
34 argument.endStructure();
35 return argument;
36}
37
38const QDBusArgument &operator>>(const QDBusArgument &argument, AvailableAnswer &answer)
39{
40 argument.beginStructure();
41 argument >> answer.checkResult >> answer.binaryName;
42 argument.endStructure();
43 return argument;
44}
45
46using namespace Solid::Backends::UDisks2;
47
48StorageAccess::StorageAccess(Device *device)
49 : DeviceInterface(device)
50 , m_setupInProgress(false)
51 , m_teardownInProgress(false)
52 , m_repairInProgress(false)
53 , m_passphraseRequested(false)
54{
55 qDBusRegisterMetaType<AvailableAnswer>();
56
57 connect(device, SIGNAL(changed()), this, SLOT(checkAccessibility()));
58 updateCache();
59
60 // Delay connecting to DBus signals to avoid the related time penalty
61 // in hot paths such as predicate matching
62 QTimer::singleShot(0, this, SLOT(connectDBusSignals()));
63}
64
65StorageAccess::~StorageAccess()
66{
67}
68
69void StorageAccess::connectDBusSignals()
70{
71 m_device->registerAction("setup", this, SLOT(slotSetupRequested()), SLOT(slotSetupDone(int, QString)));
72
73 m_device->registerAction("teardown", this, SLOT(slotTeardownRequested()), SLOT(slotTeardownDone(int, QString)));
74
75 m_device->registerAction("repair", this, SLOT(slotRepairRequested()), SLOT(slotRepairDone(int, QString)));
76}
77
78bool StorageAccess::isLuksDevice() const
79{
80 return m_device->isEncryptedContainer(); // encrypted device
81}
82
83bool StorageAccess::isAccessible() const
84{
85 if (isLuksDevice()) { // check if the cleartext slave is mounted
86 const QString path = clearTextPath();
87 // qDebug() << Q_FUNC_INFO << "CLEARTEXT device path: " << path;
88 if (path.isEmpty() || path == "/") {
89 return false;
90 }
91 Device holderDevice(path);
92 return holderDevice.isMounted();
93 }
94
95 return m_device->isMounted();
96}
97
98bool StorageAccess::isEncrypted() const
99{
100 // FIXME We should also check if physical device is encrypted
101 // FIXME Gocryptfs is not supported
102 return isLuksDevice() || m_device->isEncryptedCleartext();
103}
104
105bool StorageAccess::canCheck() const
106{
107 const auto idType = m_device->prop("IdType").toString();
109 auto msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, UD2_DBUS_PATH_MANAGER, "org.freedesktop.UDisks2.Manager", "CanCheck");
110 msg << idType;
111 QDBusReply<AvailableAnswer> r = c.call(msg);
112 if (!r.isValid()) {
113 qCDebug(UDISKS2) << Q_FUNC_INFO << dbusPath() << idType << "DBus error, code" << r.error().type();
114 return false;
115 }
116
117 const bool ret = r.value().checkResult;
118 qCDebug(UDISKS2) << Q_FUNC_INFO << dbusPath() << idType << ret << r.value().binaryName;
119 return ret;
120}
121
122bool StorageAccess::check()
123{
124 if (m_setupInProgress || m_teardownInProgress) {
125 return false;
126 }
127
128 const auto path = dbusPath();
130 auto msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, path, UD2_DBUS_INTERFACE_FILESYSTEM, "Check");
131 QVariantMap options;
132 msg << options;
133 QDBusReply<bool> r = c.call(msg);
134 bool ret = r.isValid() && r.value();
135 qCDebug(UDISKS2) << Q_FUNC_INFO << path << ret;
136 return ret;
137}
138
139bool StorageAccess::canRepair() const
140{
141 const auto idType = m_device->prop("IdType").toString();
143 auto msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, UD2_DBUS_PATH_MANAGER, "org.freedesktop.UDisks2.Manager", "CanRepair");
144 msg << idType;
145 QDBusReply<AvailableAnswer> r = c.call(msg);
146 if (!r.isValid()) {
147 qCDebug(UDISKS2) << Q_FUNC_INFO << dbusPath() << idType << "DBus error, code" << r.error().type();
148 return false;
149 }
150
151 const bool ret = r.value().checkResult;
152 qCDebug(UDISKS2) << Q_FUNC_INFO << dbusPath() << idType << ret << r.value().binaryName;
153 return ret;
154}
155
156bool StorageAccess::repair()
157{
158 if (m_teardownInProgress || m_setupInProgress || m_repairInProgress) {
159 return false;
160 }
161 m_repairInProgress = true;
162 m_device->broadcastActionRequested("repair");
163
164 const auto path = dbusPath();
166 auto msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, path, UD2_DBUS_INTERFACE_FILESYSTEM, "Repair");
167 QVariantMap options;
168 msg << options;
169
170 qCDebug(UDISKS2) << Q_FUNC_INFO << path;
171 return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError)));
172}
173
174static QString baseMountPoint(const QByteArray &dev)
175{
176 QString mountPoint;
177
178#if HAVE_LIBMOUNT
179 // UDisks "MountPoints" property contains multiple paths, this happens with
180 // devices with bind mounts; try finding the "base" mount point
181 if (struct libmnt_table *table = mnt_new_table()) {
182 // This parses "/etc/mtab" if present or "/proc/self/mountinfo" by default
183 if (mnt_table_parse_mtab(table, "/proc/self/mountinfo") == 0) {
184 // BACKWARD because the fs's we're interested in, /dev/sdXY, are typically at the end
185 struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_BACKWARD);
186 struct libmnt_fs *fs;
187
188 const QByteArray devicePath = dev.endsWith('\x00') ? dev.chopped(1) : dev;
189
190 while (mnt_table_next_fs(table, itr, &fs) == 0) {
191 if (mnt_fs_get_srcpath(fs) == devicePath //
192 && (qstrcmp(mnt_fs_get_root(fs), "/") == 0) // Base mount point will have "/" as root fs
193 ) {
194 mountPoint = QFile::decodeName(mnt_fs_get_target(fs));
195 break;
196 }
197 }
198
199 mnt_free_iter(itr);
200 }
201
202 mnt_free_table(table);
203 }
204#else
205 Q_UNUSED(dev);
206#endif
207
208 return mountPoint;
209}
210
211QString StorageAccess::filePath() const
212{
213 if (isLuksDevice()) { // encrypted (and unlocked) device
214 const QString path = clearTextPath();
215 if (path.isEmpty() || path == "/") {
216 return QString();
217 }
218 Device holderDevice(path);
219 const auto mntPoints = qdbus_cast<QByteArrayList>(holderDevice.prop("MountPoints"));
220 if (!mntPoints.isEmpty()) {
221 QByteArray first = mntPoints.first();
222 if (first.endsWith('\x00')) {
223 first.chop(1);
224 }
225 return QFile::decodeName(first); // FIXME Solid doesn't support multiple mount points
226 } else {
227 return QString();
228 }
229 }
230
231 const auto mntPoints = qdbus_cast<QByteArrayList>(m_device->prop("MountPoints"));
232 if (mntPoints.isEmpty()) {
233 return {};
234 }
235
236 QByteArray first = mntPoints.first();
237 if (first.endsWith('\x00')) {
238 first.chop(1);
239 }
240 const QString potentialMountPoint = QFile::decodeName(first);
241
242 if (mntPoints.size() == 1) {
243 return potentialMountPoint;
244 }
245
246 // Device has bind mounts?
247 const QString basePoint = baseMountPoint(m_device->prop("Device").toByteArray());
248
249 return !basePoint.isEmpty() ? basePoint : potentialMountPoint;
250}
251
252bool StorageAccess::isIgnored() const
253{
254 if (m_device->prop("HintIgnore").toBool()) {
255 return true;
256 }
257
258 const QStringList mountOptions = m_device->prop("UserspaceMountOptions").toStringList();
259 if (mountOptions.contains(QLatin1String("x-gdu.hide"))) {
260 return true;
261 }
262
263 const QString path = filePath();
264
265 const bool inUserPath = (path.startsWith(QLatin1String("/media/")) //
266 || path.startsWith(QLatin1String("/run/media/")) //
268 return !inUserPath;
269}
270
271bool StorageAccess::setup()
272{
273 if (m_teardownInProgress || m_setupInProgress || m_repairInProgress) {
274 return false;
275 }
276 m_setupInProgress = true;
277 m_device->broadcastActionRequested("setup");
278
279 if (m_device->isEncryptedContainer() && clearTextPath().isEmpty()) {
280 return requestPassphrase();
281 } else {
282 return mount();
283 }
284}
285
286bool StorageAccess::teardown()
287{
288 if (m_teardownInProgress || m_setupInProgress || m_repairInProgress) {
289 return false;
290 }
291 m_teardownInProgress = true;
292 m_device->broadcastActionRequested("teardown");
293
294 return unmount();
295}
296
297void StorageAccess::updateCache()
298{
299 m_isAccessible = isAccessible();
300}
301
302void StorageAccess::checkAccessibility()
303{
304 const bool old_isAccessible = m_isAccessible;
305 updateCache();
306
307 if (old_isAccessible != m_isAccessible) {
308 Q_EMIT accessibilityChanged(m_isAccessible, m_device->udi());
309 }
310}
311
312void StorageAccess::slotDBusReply(const QDBusMessage & /*reply*/)
313{
314 if (m_setupInProgress) {
315 if (isLuksDevice() && !isAccessible()) { // unlocked device, now mount it
316 mount();
317 } else { // Don't broadcast setupDone unless the setup is really done. (Fix kde#271156)
318 m_setupInProgress = false;
319 m_device->invalidateCache();
320 m_device->broadcastActionDone("setup");
321
322 checkAccessibility();
323 }
324 } else if (m_teardownInProgress) { // FIXME
325 const QString ctPath = clearTextPath();
326 qCDebug(UDISKS2) << "Successfully unmounted " << m_device->udi();
327 if (isLuksDevice() && !ctPath.isEmpty() && ctPath != "/") { // unlocked device, lock it
328 callCryptoTeardown();
329 } else if (!ctPath.isEmpty() && ctPath != "/") {
330 callCryptoTeardown(true); // Lock encrypted parent
331 } else {
332 // try to "eject" (aka safely remove) from the (parent) drive, e.g. SD card from a reader
333 QString drivePath = m_device->drivePath();
334 if (!drivePath.isEmpty() || drivePath != "/") {
335 Device drive(drivePath);
337
338 if (drive.prop("MediaRemovable").toBool() //
339 && drive.prop("MediaAvailable").toBool() //
340 && !m_device->isOpticalDisc()) { // optical drives have their Eject method
341 QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, drivePath, UD2_DBUS_INTERFACE_DRIVE, "Eject");
342 msg << QVariantMap(); // options, unused now
343 c.call(msg, QDBus::NoBlock);
344 } else if (drive.prop("CanPowerOff").toBool() //
345 && !m_device->isOpticalDisc()) { // avoid disconnecting optical drives from the bus
346 qCDebug(UDISKS2) << "Drive can power off:" << drivePath;
347 QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, drivePath, UD2_DBUS_INTERFACE_DRIVE, "PowerOff");
348 msg << QVariantMap(); // options, unused now
349 c.call(msg, QDBus::NoBlock);
350 }
351 }
352
353 m_teardownInProgress = false;
354 m_device->invalidateCache();
355 m_device->broadcastActionDone("teardown");
356
357 checkAccessibility();
358 }
359 } else if (m_repairInProgress) {
360 qCDebug(UDISKS2) << "Successfully repaired " << m_device->udi();
361 m_repairInProgress = false;
362 m_device->broadcastActionDone("repair");
363 }
364}
365
366void StorageAccess::slotDBusError(const QDBusError &error)
367{
368 // qDebug() << Q_FUNC_INFO << "DBUS ERROR:" << error.name() << error.message();
369
370 if (m_setupInProgress) {
371 m_setupInProgress = false;
372 m_device->broadcastActionDone("setup", //
373 m_device->errorToSolidError(error.name()),
374 m_device->errorToString(error.name()) + ": " + error.message());
375
376 checkAccessibility();
377 } else if (m_teardownInProgress) {
378 m_teardownInProgress = false;
379 m_device->broadcastActionDone("teardown", //
380 m_device->errorToSolidError(error.name()),
381 m_device->errorToString(error.name()) + ": " + error.message());
382 checkAccessibility();
383 } else if (m_repairInProgress) {
384 m_repairInProgress = false;
385 m_device->broadcastActionDone("repair", m_device->errorToSolidError(error.name()), m_device->errorToString(error.name()) + ": " + error.message());
386 }
387}
388
389void StorageAccess::slotSetupRequested()
390{
391 m_setupInProgress = true;
392 // qDebug() << "SETUP REQUESTED:" << m_device->udi();
393 Q_EMIT setupRequested(m_device->udi());
394}
395
396void StorageAccess::slotSetupDone(int error, const QString &errorString)
397{
398 m_setupInProgress = false;
399 // qDebug() << "SETUP DONE:" << m_device->udi();
400 checkAccessibility();
401 Q_EMIT setupDone(static_cast<Solid::ErrorType>(error), errorString, m_device->udi());
402}
403
404void StorageAccess::slotTeardownRequested()
405{
406 m_teardownInProgress = true;
407 Q_EMIT teardownRequested(m_device->udi());
408}
409
410void StorageAccess::slotTeardownDone(int error, const QString &errorString)
411{
412 m_teardownInProgress = false;
413 checkAccessibility();
414 Q_EMIT teardownDone(static_cast<Solid::ErrorType>(error), errorString, m_device->udi());
415}
416
417void StorageAccess::slotRepairRequested()
418{
419 m_repairInProgress = true;
420 Q_EMIT repairRequested(m_device->udi());
421}
422
423void StorageAccess::slotRepairDone(int error, const QString &errorString)
424{
425 m_repairInProgress = false;
426 Q_EMIT repairDone(static_cast<Solid::ErrorType>(error), errorString, m_device->udi());
427}
428
429bool StorageAccess::mount()
430{
431 const auto path = dbusPath();
432
434 QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, path, UD2_DBUS_INTERFACE_FILESYSTEM, "Mount");
435 QVariantMap options;
436
437 if (m_device->prop("IdType").toString() == "vfat") {
438 options.insert("options", "flush");
439 }
440
441 msg << options;
442
443 return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError)));
444}
445
446bool StorageAccess::unmount()
447{
448 const auto path = dbusPath();
449
451 QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, path, UD2_DBUS_INTERFACE_FILESYSTEM, "Unmount");
452
453 msg << QVariantMap(); // options, unused now
454
455 qCDebug(UDISKS2) << "Initiating unmount of " << path;
456 return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError)), s_unmountTimeout);
457}
458
459QString StorageAccess::generateReturnObjectPath()
460{
461 static QAtomicInt number = 1;
462
463 return "/org/kde/solid/UDisks2StorageAccess_" + QString::number(number++);
464}
465
466QString StorageAccess::clearTextPath() const
467{
468 const QString path = m_device->prop("CleartextDevice").value<QDBusObjectPath>().path();
469 if (path != QLatin1String("/")) {
470 return path;
471 }
472 return QString();
473}
474
475QString StorageAccess::dbusPath() const
476{
477 QString path = m_device->udi();
478 if (isLuksDevice()) { // mount options for the cleartext volume
479 const QString ctPath = clearTextPath();
480 if (!ctPath.isEmpty()) {
481 path = ctPath;
482 }
483 }
484 return path;
485}
486
487bool StorageAccess::requestPassphrase()
488{
489 QString udi = m_device->udi();
491 m_lastReturnObject = generateReturnObjectPath();
492
494
495 // TODO: this only works on X11, Wayland doesn't have global window ids.
496 // Passing ids to other processes doesn't make any sense
497 auto activeWindow = QGuiApplication::focusWindow();
498 uint wId = 0;
499 if (activeWindow != nullptr) {
500 wId = (uint)activeWindow->winId();
501 }
502
504
505 const auto plasmaVersionMajor = qEnvironmentVariable("KDE_SESSION_VERSION", "6");
506
507 // TODO KF6: remove hard dep on Plasma here which provides the SolidUiServer kded plugin
508 QDBusInterface soliduiserver(QStringLiteral("org.kde.kded") + plasmaVersionMajor, "/modules/soliduiserver", "org.kde.SolidUiServer");
509 QDBusReply<void> reply = soliduiserver.call("showPassphraseDialog", udi, returnService, m_lastReturnObject, wId, appId);
510 m_passphraseRequested = reply.isValid();
511 if (!m_passphraseRequested) {
512 qCWarning(UDISKS2) << "Failed to call the SolidUiServer, D-Bus said:" << reply.error();
513 }
514
515 return m_passphraseRequested;
516}
517
518void StorageAccess::passphraseReply(const QString &passphrase)
519{
520 if (m_passphraseRequested) {
521 QDBusConnection::sessionBus().unregisterObject(m_lastReturnObject);
522 m_passphraseRequested = false;
523 if (!passphrase.isEmpty()) {
524 callCryptoSetup(passphrase);
525 } else {
526 m_setupInProgress = false;
527 m_device->broadcastActionDone("setup", Solid::UserCanceled);
528 }
529 }
530}
531
532void StorageAccess::callCryptoSetup(const QString &passphrase)
533{
535 QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE, m_device->udi(), UD2_DBUS_INTERFACE_ENCRYPTED, "Unlock");
536
537 msg << passphrase;
538 msg << QVariantMap(); // options, unused now
539
540 c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError)));
541}
542
543bool StorageAccess::callCryptoTeardown(bool actOnParent)
544{
546 QDBusMessage msg = QDBusMessage::createMethodCall(UD2_DBUS_SERVICE,
547 actOnParent ? (m_device->prop("CryptoBackingDevice").value<QDBusObjectPath>().path()) : m_device->udi(),
548 UD2_DBUS_INTERFACE_ENCRYPTED,
549 "Lock");
550 msg << QVariantMap(); // options, unused now
551
552 return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError)));
553}
554
555#include "moc_udisksstorageaccess.cpp"
void broadcastActionRequested(const QString &actionName) const
Allows to broadcast that an action just got requested on a device to all the corresponding devices in...
void broadcastActionDone(const QString &actionName, int error=Solid::NoError, const QString &errorString=QString()) const
Allows to broadcast that an action just completed in a device to all the corresponding devices in oth...
void registerAction(const QString &actionName, QObject *dest, const char *requestSlot, const char *doneSlot) const
Register an action for the given device.
KCALENDARCORE_EXPORT QDataStream & operator>>(QDataStream &in, const KCalendarCore::Alarm::Ptr &)
KIOCORE_EXPORT QString number(KIO::filesize_t size)
QString path(const QString &relativePath)
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QDebug operator<<(QDebug dbg, const PerceptualColor::LchaDouble &value)
void chop(qsizetype n)
QByteArray chopped(qsizetype len) const const
bool endsWith(QByteArrayView bv) const const
QByteArray first(qsizetype n) const const
void beginStructure()
void endStructure()
QString baseService() const const
QDBusMessage call(const QDBusMessage &message, QDBus::CallMode mode, int timeout) const const
bool callWithCallback(const QDBusMessage &message, QObject *receiver, const char *returnMethod, const char *errorMethod, int timeout) const const
bool registerObject(const QString &path, QObject *object, RegisterOptions options)
QDBusConnection sessionBus()
QDBusConnection systemBus()
void unregisterObject(const QString &path, UnregisterMode mode)
ErrorType type() const const
QDBusMessage createMethodCall(const QString &service, const QString &path, const QString &interface, const QString &method)
QString path() const const
const QDBusError & error()
bool isValid() const const
QString homePath()
QString decodeName(const QByteArray &localFileName)
QWindow * focusWindow()
Q_EMITQ_EMIT
bool isEmpty() const const
QString number(double n, char format, int precision)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
bool toBool() const const
QByteArray toByteArray() const const
QString toString() const const
QStringList toStringList() const const
T value() 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.