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(QStringLiteral("setup"), this, SLOT(slotSetupRequested()), SLOT(slotSetupDone(int, QString)));
72
73 m_device->registerAction(QStringLiteral("teardown"), this, SLOT(slotTeardownRequested()), SLOT(slotTeardownDone(int, QString)));
74
75 m_device->registerAction(QStringLiteral("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 == QLatin1String("/")) {
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(QStringLiteral("IdType")).toString();
109 auto msg = QDBusMessage::createMethodCall(QStringLiteral(UD2_DBUS_SERVICE),
110 QStringLiteral(UD2_DBUS_PATH_MANAGER),
111 QStringLiteral("org.freedesktop.UDisks2.Manager"),
112 QStringLiteral("CanCheck"));
113 msg << idType;
114 QDBusReply<AvailableAnswer> r = c.call(msg);
115 if (!r.isValid()) {
116 qCDebug(UDISKS2) << Q_FUNC_INFO << dbusPath() << idType << "DBus error, code" << r.error().type();
117 return false;
118 }
119
120 const bool ret = r.value().checkResult;
121 qCDebug(UDISKS2) << Q_FUNC_INFO << dbusPath() << idType << ret << r.value().binaryName;
122 return ret;
123}
124
125bool StorageAccess::check()
126{
127 if (m_setupInProgress || m_teardownInProgress) {
128 return false;
129 }
130
131 const auto path = dbusPath();
133 auto msg = QDBusMessage::createMethodCall(QStringLiteral(UD2_DBUS_SERVICE), path, QStringLiteral(UD2_DBUS_INTERFACE_FILESYSTEM), QStringLiteral("Check"));
134 QVariantMap options;
135 msg << options;
136 QDBusReply<bool> r = c.call(msg);
137 bool ret = r.isValid() && r.value();
138 qCDebug(UDISKS2) << Q_FUNC_INFO << path << ret;
139 return ret;
140}
141
142bool StorageAccess::canRepair() const
143{
144 const auto idType = m_device->prop(QStringLiteral("IdType")).toString();
146 auto msg = QDBusMessage::createMethodCall(QStringLiteral(UD2_DBUS_SERVICE),
147 QStringLiteral(UD2_DBUS_PATH_MANAGER),
148 QStringLiteral("org.freedesktop.UDisks2.Manager"),
149 QStringLiteral("CanRepair"));
150 msg << idType;
151 QDBusReply<AvailableAnswer> r = c.call(msg);
152 if (!r.isValid()) {
153 qCDebug(UDISKS2) << Q_FUNC_INFO << dbusPath() << idType << "DBus error, code" << r.error().type();
154 return false;
155 }
156
157 const bool ret = r.value().checkResult;
158 qCDebug(UDISKS2) << Q_FUNC_INFO << dbusPath() << idType << ret << r.value().binaryName;
159 return ret;
160}
161
162bool StorageAccess::repair()
163{
164 if (m_teardownInProgress || m_setupInProgress || m_repairInProgress) {
165 return false;
166 }
167 m_repairInProgress = true;
168 m_device->broadcastActionRequested(QStringLiteral("repair"));
169
170 const auto path = dbusPath();
172 auto msg = QDBusMessage::createMethodCall(QStringLiteral(UD2_DBUS_SERVICE), path, QStringLiteral(UD2_DBUS_INTERFACE_FILESYSTEM), QStringLiteral("Repair"));
173 QVariantMap options;
174 msg << options;
175
176 qCDebug(UDISKS2) << Q_FUNC_INFO << path;
177 return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError)));
178}
179
180static QString baseMountPoint(const QByteArray &dev)
181{
182 QString mountPoint;
183
184#if HAVE_LIBMOUNT
185 // UDisks "MountPoints" property contains multiple paths, this happens with
186 // devices with bind mounts; try finding the "base" mount point
187 if (struct libmnt_table *table = mnt_new_table()) {
188 // This parses "/etc/mtab" if present or "/proc/self/mountinfo" by default
189 if (mnt_table_parse_mtab(table, "/proc/self/mountinfo") == 0) {
190 // BACKWARD because the fs's we're interested in, /dev/sdXY, are typically at the end
191 struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_BACKWARD);
192 struct libmnt_fs *fs;
193
194 const QByteArray devicePath = dev.endsWith('\x00') ? dev.chopped(1) : dev;
195
196 while (mnt_table_next_fs(table, itr, &fs) == 0) {
197 if (mnt_fs_get_srcpath(fs) == devicePath //
198 && (qstrcmp(mnt_fs_get_root(fs), "/") == 0) // Base mount point will have "/" as root fs
199 ) {
200 mountPoint = QFile::decodeName(mnt_fs_get_target(fs));
201 break;
202 }
203 }
204
205 mnt_free_iter(itr);
206 }
207
208 mnt_free_table(table);
209 }
210#else
211 Q_UNUSED(dev);
212#endif
213
214 return mountPoint;
215}
216
217QString StorageAccess::filePath() const
218{
219 if (isLuksDevice()) { // encrypted (and unlocked) device
220 const QString path = clearTextPath();
221 if (path.isEmpty() || path == QLatin1String("/")) {
222 return QString();
223 }
224 Device holderDevice(path);
225 const auto mntPoints = qdbus_cast<QByteArrayList>(holderDevice.prop(QStringLiteral("MountPoints")));
226 if (!mntPoints.isEmpty()) {
227 QByteArray first = mntPoints.first();
228 if (first.endsWith('\x00')) {
229 first.chop(1);
230 }
231 return QFile::decodeName(first); // FIXME Solid doesn't support multiple mount points
232 } else {
233 return QString();
234 }
235 }
236
237 const auto mntPoints = qdbus_cast<QByteArrayList>(m_device->prop(QStringLiteral("MountPoints")));
238 if (mntPoints.isEmpty()) {
239 return {};
240 }
241
242 QByteArray first = mntPoints.first();
243 if (first.endsWith('\x00')) {
244 first.chop(1);
245 }
246 const QString potentialMountPoint = QFile::decodeName(first);
247
248 if (mntPoints.size() == 1) {
249 return potentialMountPoint;
250 }
251
252 // Device has bind mounts?
253 const QString basePoint = baseMountPoint(m_device->prop(QStringLiteral("Device")).toByteArray());
254
255 return !basePoint.isEmpty() ? basePoint : potentialMountPoint;
256}
257
258bool StorageAccess::isIgnored() const
259{
260 if (m_device->prop(QStringLiteral("HintIgnore")).toBool()) {
261 return true;
262 }
263
264 const QStringList mountOptions = m_device->prop(QStringLiteral("UserspaceMountOptions")).toStringList();
265 if (mountOptions.contains(QLatin1String("x-gdu.hide"))) {
266 return true;
267 }
268
269 const QString path = filePath();
270
271 const bool inUserPath = (path.startsWith(QLatin1String("/media/")) //
272 || path.startsWith(QLatin1String("/run/media/")) //
274 return !inUserPath;
275}
276
277bool StorageAccess::setup()
278{
279 if (m_teardownInProgress || m_setupInProgress || m_repairInProgress) {
280 return false;
281 }
282 m_setupInProgress = true;
283 m_device->broadcastActionRequested(QStringLiteral("setup"));
284
285 if (m_device->isEncryptedContainer() && clearTextPath().isEmpty()) {
286 return requestPassphrase();
287 } else {
288 return mount();
289 }
290}
291
292bool StorageAccess::teardown()
293{
294 if (m_teardownInProgress || m_setupInProgress || m_repairInProgress) {
295 return false;
296 }
297 m_teardownInProgress = true;
298 m_device->broadcastActionRequested(QStringLiteral("teardown"));
299
300 return unmount();
301}
302
303void StorageAccess::updateCache()
304{
305 m_isAccessible = isAccessible();
306}
307
308void StorageAccess::checkAccessibility()
309{
310 const bool old_isAccessible = m_isAccessible;
311 updateCache();
312
313 if (old_isAccessible != m_isAccessible) {
314 Q_EMIT accessibilityChanged(m_isAccessible, m_device->udi());
315 }
316}
317
318void StorageAccess::slotDBusReply(const QDBusMessage & /*reply*/)
319{
320 if (m_setupInProgress) {
321 if (isLuksDevice() && !isAccessible()) { // unlocked device, now mount it
322 mount();
323 } else { // Don't broadcast setupDone unless the setup is really done. (Fix kde#271156)
324 m_setupInProgress = false;
325 m_device->invalidateCache();
326 m_device->broadcastActionDone(QStringLiteral("setup"));
327
328 checkAccessibility();
329 }
330 } else if (m_teardownInProgress) { // FIXME
331 const QString ctPath = clearTextPath();
332 qCDebug(UDISKS2) << "Successfully unmounted " << m_device->udi();
333 if (isLuksDevice() && !ctPath.isEmpty() && ctPath != QStringLiteral("/")) { // unlocked device, lock it
334 callCryptoTeardown();
335 } else if (!ctPath.isEmpty() && ctPath != QStringLiteral("/")) {
336 callCryptoTeardown(true); // Lock encrypted parent
337 } else {
338 // try to "eject" (aka safely remove) from the (parent) drive, e.g. SD card from a reader
339 QString drivePath = m_device->drivePath();
340 if (!drivePath.isEmpty() || drivePath != QStringLiteral("/")) {
341 Device drive(drivePath);
343
344 if (drive.prop(QStringLiteral("MediaRemovable")).toBool() //
345 && drive.prop(QStringLiteral("MediaAvailable")).toBool() //
346 && !m_device->isOpticalDisc()) { // optical drives have their Eject method
347 QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral(UD2_DBUS_SERVICE),
348 drivePath,
349 QStringLiteral(UD2_DBUS_INTERFACE_DRIVE),
350 QStringLiteral("Eject"));
351 msg << QVariantMap(); // options, unused now
352 c.call(msg, QDBus::NoBlock);
353 } else if (drive.prop(QStringLiteral("CanPowerOff")).toBool() //
354 && !m_device->isOpticalDisc()) { // avoid disconnecting optical drives from the bus
355 qCDebug(UDISKS2) << "Drive can power off:" << drivePath;
356 QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral(UD2_DBUS_SERVICE),
357 drivePath,
358 QStringLiteral(UD2_DBUS_INTERFACE_DRIVE),
359 QStringLiteral("PowerOff"));
360 msg << QVariantMap(); // options, unused now
361 c.call(msg, QDBus::NoBlock);
362 }
363 }
364
365 m_teardownInProgress = false;
366 m_device->invalidateCache();
367 m_device->broadcastActionDone(QStringLiteral("teardown"));
368
369 checkAccessibility();
370 }
371 } else if (m_repairInProgress) {
372 qCDebug(UDISKS2) << "Successfully repaired " << m_device->udi();
373 m_repairInProgress = false;
374 m_device->broadcastActionDone(QStringLiteral("repair"));
375 }
376}
377
378void StorageAccess::slotDBusError(const QDBusError &error)
379{
380 // qDebug() << Q_FUNC_INFO << "DBUS ERROR:" << error.name() << error.message();
381
382 if (m_setupInProgress) {
383 m_setupInProgress = false;
384 m_device->broadcastActionDone(QStringLiteral("setup"), //
385 m_device->errorToSolidError(error.name()),
386 m_device->errorToString(error.name()) + QStringLiteral(": ") + error.message());
387
388 checkAccessibility();
389 } else if (m_teardownInProgress) {
390 m_teardownInProgress = false;
391 m_device->broadcastActionDone(QStringLiteral("teardown"), //
392 m_device->errorToSolidError(error.name()),
393 m_device->errorToString(error.name()) + QStringLiteral(": ") + error.message());
394 checkAccessibility();
395 } else if (m_repairInProgress) {
396 m_repairInProgress = false;
397 m_device->broadcastActionDone(QStringLiteral("repair"),
398 m_device->errorToSolidError(error.name()),
399 m_device->errorToString(error.name()) + QStringLiteral(": ") + error.message());
400 }
401}
402
403void StorageAccess::slotSetupRequested()
404{
405 m_setupInProgress = true;
406 // qDebug() << "SETUP REQUESTED:" << m_device->udi();
407 Q_EMIT setupRequested(m_device->udi());
408}
409
410void StorageAccess::slotSetupDone(int error, const QString &errorString)
411{
412 m_setupInProgress = false;
413 // qDebug() << "SETUP DONE:" << m_device->udi();
414 checkAccessibility();
415 Q_EMIT setupDone(static_cast<Solid::ErrorType>(error), errorString, m_device->udi());
416}
417
418void StorageAccess::slotTeardownRequested()
419{
420 m_teardownInProgress = true;
421 Q_EMIT teardownRequested(m_device->udi());
422}
423
424void StorageAccess::slotTeardownDone(int error, const QString &errorString)
425{
426 m_teardownInProgress = false;
427 checkAccessibility();
428 Q_EMIT teardownDone(static_cast<Solid::ErrorType>(error), errorString, m_device->udi());
429}
430
431void StorageAccess::slotRepairRequested()
432{
433 m_repairInProgress = true;
434 Q_EMIT repairRequested(m_device->udi());
435}
436
437void StorageAccess::slotRepairDone(int error, const QString &errorString)
438{
439 m_repairInProgress = false;
440 Q_EMIT repairDone(static_cast<Solid::ErrorType>(error), errorString, m_device->udi());
441}
442
443bool StorageAccess::mount()
444{
445 const auto path = dbusPath();
446
448 QDBusMessage msg =
449 QDBusMessage::createMethodCall(QStringLiteral(UD2_DBUS_SERVICE), path, QStringLiteral(UD2_DBUS_INTERFACE_FILESYSTEM), QStringLiteral("Mount"));
450 QVariantMap options;
451
452 if (m_device->prop(QStringLiteral("IdType")).toString() == QLatin1String("vfat")) {
453 options.insert(QStringLiteral("options"), QStringLiteral("flush"));
454 }
455
456 msg << options;
457
458 return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError)));
459}
460
461bool StorageAccess::unmount()
462{
463 const auto path = dbusPath();
464
466 QDBusMessage msg =
467 QDBusMessage::createMethodCall(QStringLiteral(UD2_DBUS_SERVICE), path, QStringLiteral(UD2_DBUS_INTERFACE_FILESYSTEM), QStringLiteral("Unmount"));
468
469 msg << QVariantMap(); // options, unused now
470
471 qCDebug(UDISKS2) << "Initiating unmount of " << path;
472 return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError)), s_unmountTimeout);
473}
474
475QString StorageAccess::generateReturnObjectPath()
476{
477 static QAtomicInt number = 1;
478
479 return QStringLiteral("/org/kde/solid/UDisks2StorageAccess_") + QString::number(number++);
480}
481
482QString StorageAccess::clearTextPath() const
483{
484 const QString path = m_device->prop(QStringLiteral("CleartextDevice")).value<QDBusObjectPath>().path();
485 if (path != QLatin1String("/")) {
486 return path;
487 }
488 return QString();
489}
490
491QString StorageAccess::dbusPath() const
492{
493 QString path = m_device->udi();
494 if (isLuksDevice()) { // mount options for the cleartext volume
495 const QString ctPath = clearTextPath();
496 if (!ctPath.isEmpty()) {
497 path = ctPath;
498 }
499 }
500 return path;
501}
502
503bool StorageAccess::requestPassphrase()
504{
505 QString udi = m_device->udi();
507 m_lastReturnObject = generateReturnObjectPath();
508
510
511 // TODO: this only works on X11, Wayland doesn't have global window ids.
512 // Passing ids to other processes doesn't make any sense
513 auto activeWindow = QGuiApplication::focusWindow();
514 uint wId = 0;
515 if (activeWindow != nullptr) {
516 wId = (uint)activeWindow->winId();
517 }
518
520
521 const auto plasmaVersionMajor = qEnvironmentVariable("KDE_SESSION_VERSION", QStringLiteral("6"));
522
523 // TODO KF6: remove hard dep on Plasma here which provides the SolidUiServer kded plugin
524 QDBusInterface soliduiserver(QStringLiteral("org.kde.kded") + plasmaVersionMajor,
525 QStringLiteral("/modules/soliduiserver"),
526 QStringLiteral("org.kde.SolidUiServer"));
527 QDBusReply<void> reply = soliduiserver.call(QStringLiteral("showPassphraseDialog"), udi, returnService, m_lastReturnObject, wId, appId);
528 m_passphraseRequested = reply.isValid();
529 if (!m_passphraseRequested) {
530 qCWarning(UDISKS2) << "Failed to call the SolidUiServer, D-Bus said:" << reply.error();
531 }
532
533 return m_passphraseRequested;
534}
535
536void StorageAccess::passphraseReply(const QString &passphrase)
537{
538 if (m_passphraseRequested) {
539 QDBusConnection::sessionBus().unregisterObject(m_lastReturnObject);
540 m_passphraseRequested = false;
541 if (!passphrase.isEmpty()) {
542 callCryptoSetup(passphrase);
543 } else {
544 m_setupInProgress = false;
545 m_device->broadcastActionDone(QStringLiteral("setup"), Solid::UserCanceled);
546 }
547 }
548}
549
550void StorageAccess::callCryptoSetup(const QString &passphrase)
551{
553 QDBusMessage msg = QDBusMessage::createMethodCall(QStringLiteral(UD2_DBUS_SERVICE),
554 m_device->udi(),
555 QStringLiteral(UD2_DBUS_INTERFACE_ENCRYPTED),
556 QStringLiteral("Unlock"));
557
558 msg << passphrase;
559 msg << QVariantMap(); // options, unused now
560
561 c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError)));
562}
563
564bool StorageAccess::callCryptoTeardown(bool actOnParent)
565{
567 QDBusMessage msg =
568 QDBusMessage::createMethodCall(QStringLiteral(UD2_DBUS_SERVICE),
569 actOnParent ? (m_device->prop(QStringLiteral("CryptoBackingDevice")).value<QDBusObjectPath>().path()) : m_device->udi(),
570 QStringLiteral(UD2_DBUS_INTERFACE_ENCRYPTED),
571 QStringLiteral("Lock"));
572 msg << QVariantMap(); // options, unused now
573
574 return c.callWithCallback(msg, this, SLOT(slotDBusReply(QDBusMessage)), SLOT(slotDBusError(QDBusError)));
575}
576
577#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.
char * toString(const EngineQuery &query)
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)
KTEXTEDITOR_EXPORT QDebug operator<<(QDebug s, const MovingCursor &cursor)
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)
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)
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 Oct 11 2024 12:08:14 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.