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

source code of solid/src/solid/devices/backends/udisks2/udisksstorageaccess.cpp