udisks2monitor.cpp 32.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
/*
 * Copyright (C) 2018 Jolla Ltd. <raine.makelainen@jolla.com>
 *
 * You may use this file under the terms of the BSD license as follows:
 *
 * "Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *   * Neither the name of Nemo Mobile nor the names of its contributors
 *     may be used to endorse or promote products derived from this
 *     software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
 */

#include "udisks2monitor_p.h"
33
#include "udisks2block_p.h"
34
#include "udisks2blockdevices_p.h"
35 36
#include "udisks2job_p.h"
#include "udisks2defines.h"
37
#include "nemo-dbus/dbus.h"
38 39

#include "partitionmanager_p.h"
40
#include "logging_p.h"
41 42 43 44

#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <QDBusError>
45
#include <QDBusInterface>
46 47
#include <QDBusMetaType>

48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
struct ErrorEntry {
    Partition::Error errorCode;
    const char *dbusErrorName;
};

// These are "copied" error from udiskserror.c so that we do not link against it.
static const ErrorEntry dbus_error_entries[] =
{
    { Partition::ErrorFailed,                 "org.freedesktop.UDisks2.Error.Failed" },
    { Partition::ErrorCancelled,              "org.freedesktop.UDisks2.Error.Cancelled" },
    { Partition::ErrorAlreadyCancelled,       "org.freedesktop.UDisks2.Error.AlreadyCancelled" },
    { Partition::ErrorNotAuthorized,          "org.freedesktop.UDisks2.Error.NotAuthorized" },
    { Partition::ErrorNotAuthorizedCanObtain, "org.freedesktop.UDisks2.Error.NotAuthorizedCanObtain" },
    { Partition::ErrorNotAuthorizedDismissed, "org.freedesktop.UDisks2.Error.NotAuthorizedDismissed" },
    { Partition::ErrorAlreadyMounted,         UDISKS2_ERROR_ALREADY_MOUNTED },
    { Partition::ErrorNotMounted,             "org.freedesktop.UDisks2.Error.NotMounted" },
    { Partition::ErrorOptionNotPermitted,     "org.freedesktop.UDisks2.Error.OptionNotPermitted" },
    { Partition::ErrorMountedByOtherUser,     "org.freedesktop.UDisks2.Error.MountedByOtherUser" },
    { Partition::ErrorAlreadyUnmounting,      UDISKS2_ERROR_ALREADY_UNMOUNTING },
    { Partition::ErrorNotSupported,           "org.freedesktop.UDisks2.Error.NotSupported" },
    { Partition::ErrorTimedout,               "org.freedesktop.UDisks2.Error.Timedout" },
    { Partition::ErrorWouldWakeup,            "org.freedesktop.UDisks2.Error.WouldWakeup" },
    { Partition::ErrorDeviceBusy,             "org.freedesktop.UDisks2.Error.DeviceBusy" }
};

73 74 75 76
UDisks2::Monitor *UDisks2::Monitor::sharedInstance = nullptr;

UDisks2::Monitor *UDisks2::Monitor::instance()
{
77
    Q_ASSERT(sharedInstance);
78 79 80 81 82 83 84

    return sharedInstance;
}

UDisks2::Monitor::Monitor(PartitionManagerPrivate *manager, QObject *parent)
    : QObject(parent)
    , m_manager(manager)
85
    , m_blockDevices(BlockDevices::instance())
86 87 88 89
{
    Q_ASSERT(!sharedInstance);
    sharedInstance = this;

90
    qDBusRegisterMetaType<UDisks2::InterfacePropertyMap>();
91 92 93
    QDBusConnection systemBus = QDBusConnection::systemBus();

    connect(systemBus.interface(), &QDBusConnectionInterface::callWithCallbackFailed, this, [this](const QDBusError &error, const QDBusMessage &call) {
94 95 96 97 98 99 100 101 102
        qCInfo(lcMemoryCardLog) << "====================================================";
        qCInfo(lcMemoryCardLog) << "DBus call with callback failed:" << error.message();
        qCInfo(lcMemoryCardLog) << "Name:" << error.name();
        qCInfo(lcMemoryCardLog) << "Error name" << call.errorName();
        qCInfo(lcMemoryCardLog) << "Error message:" << call.errorMessage();
        qCInfo(lcMemoryCardLog) << "Call interface:" << call.interface();
        qCInfo(lcMemoryCardLog) << "Call path:" << call.path();
        qCInfo(lcMemoryCardLog) << "====================================================";
        emit errorMessage(call.path(), error.name());
103 104 105 106 107 108
    });

    if (!systemBus.connect(
                UDISKS2_SERVICE,
                UDISKS2_PATH,
                DBUS_OBJECT_MANAGER_INTERFACE,
109
                interfacesAddedSignal,
110
                this,
111
                SLOT(interfacesAdded(QDBusObjectPath, UDisks2::InterfacePropertyMap)))) {
112
        qCWarning(lcMemoryCardLog) << "Failed to connect to interfaces added signal:" << qPrintable(systemBus.lastError().message());
113 114 115 116 117 118
    }

    if (!systemBus.connect(
                UDISKS2_SERVICE,
                UDISKS2_PATH,
                DBUS_OBJECT_MANAGER_INTERFACE,
119
                interfacesRemovedSignal,
120 121
                this,
                SLOT(interfacesRemoved(QDBusObjectPath, QStringList)))) {
122
        qCWarning(lcMemoryCardLog) << "Failed to connect to interfaces removed signal:" << qPrintable(systemBus.lastError().message());
123
    }
124 125

    getBlockDevices();
126 127

    connect(m_blockDevices, &BlockDevices::newBlock, this, &Monitor::handleNewBlock);
128 129 130 131 132 133 134
}

UDisks2::Monitor::~Monitor()
{
    sharedInstance = nullptr;
    qDeleteAll(m_jobsToWait);
    m_jobsToWait.clear();
135 136 137

    delete m_blockDevices;
    m_blockDevices = nullptr;
138 139
}

140 141
// TODO : Move lock, unlock, mount, unmount, format inside udisks2block.cpp
// unlock, mount, format should be considered completed only after file system interface re-appears for the block.
142
void UDisks2::Monitor::lock(const QString &devicePath)
143 144 145 146
{
    QVariantList arguments;
    QVariantMap options;
    arguments << options;
147

148
    if (Block *block = m_blockDevices->find(devicePath)) {
149 150 151 152 153 154 155 156
        block->dumpInfo();
        block->setLocking();

        // Unmount if mounted.
        if (!block->mountPath().isEmpty()) {
            m_operationQueue.enqueue(Operation(UDISKS2_ENCRYPTED_LOCK, devicePath));
            unmount(block->device());
        } else {
157
            startLuksOperation(devicePath, UDISKS2_ENCRYPTED_LOCK, m_blockDevices->objectPath(devicePath), arguments);
158
        }
159 160
    } else {
        qCWarning(lcMemoryCardLog) << "Block device" << devicePath << "not found";
161
    }
162 163
}

164
void UDisks2::Monitor::unlock(const QString &devicePath, const QString &passphrase)
165 166 167 168 169
{
    QVariantList arguments;
    arguments << passphrase;
    QVariantMap options;
    arguments << options;
170
    startLuksOperation(devicePath, UDISKS2_ENCRYPTED_UNLOCK, m_blockDevices->objectPath(devicePath), arguments);
171 172
}

173
void UDisks2::Monitor::mount(const QString &devicePath)
174
{
175 176
    QVariantList arguments;
    QVariantMap options;
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195

    if (Block *block = m_blockDevices->find(devicePath)) {
        QString objectPath;
        if (block->device() == devicePath) {
            objectPath = block->path();
        } else if (block->cryptoBackingDevicePath() == devicePath) {
            objectPath = block->cryptoBackingDeviceObjectPath();
        }

        // Find has the same condition.
        Q_ASSERT(!objectPath.isEmpty());

        options.insert(QStringLiteral("fstype"), block->idType());
        arguments << options;
        startMountOperation(devicePath, UDISKS2_FILESYSTEM_MOUNT, objectPath, arguments);
    } else {
        emit mountError(Partition::ErrorOptionNotPermitted);
        emit status(devicePath, Partition::Unmounted);
    }
196 197
}

198
void UDisks2::Monitor::unmount(const QString &devicePath)
199
{
200 201 202
    QVariantList arguments;
    QVariantMap options;
    arguments << options;
203
    startMountOperation(devicePath, UDISKS2_FILESYSTEM_UNMOUNT, m_blockDevices->objectPath(devicePath), arguments);
204 205
}

206
void UDisks2::Monitor::format(const QString &devicePath, const QString &filesystemType, const QVariantMap &arguments)
207
{
208
    if (devicePath.isEmpty()) {
209 210 211 212 213
        qCCritical(lcMemoryCardLog) << "Cannot format without device name";
        return;
    }

    QStringList fsList = m_manager->supportedFileSystems();
214
    if (!fsList.contains(filesystemType)) {
215 216 217 218
        qCWarning(lcMemoryCardLog) << "Can only format" << fsList.join(", ") << "filesystems.";
        return;
    }

219
    const QString objectPath = m_blockDevices->objectPath(devicePath);
220 221
    PartitionManagerPrivate::Partitions affectedPartitions;
    lookupPartitions(affectedPartitions, QStringList() << objectPath);
222

223
    for (auto partition : affectedPartitions) {
224
        // Mark block to formatting state.
225
        if (Block *block = m_blockDevices->find(devicePath)) {
226 227 228 229 230 231 232 233 234 235
            block->setFormatting(true);
        }

        // Lock unlocked block device before formatting.
        if (!partition->cryptoBackingDevicePath.isEmpty()) {
            lock(partition->cryptoBackingDevicePath);
            m_operationQueue.enqueue(Operation(UDISKS2_BLOCK_FORMAT, partition->cryptoBackingDevicePath, objectPath, filesystemType, arguments));
            return;
        } else if (partition->status == Partition::Mounted) {
            m_operationQueue.enqueue(Operation(UDISKS2_BLOCK_FORMAT, devicePath, objectPath, filesystemType, arguments));
236
            unmount(devicePath);
237 238 239 240
            return;
        }
    }

241
    doFormat(devicePath, objectPath, filesystemType, arguments);
242 243
}

244
void UDisks2::Monitor::interfacesAdded(const QDBusObjectPath &objectPath, const UDisks2::InterfacePropertyMap &interfaces)
245
{
246
    QString path = objectPath.path();
247
    qCDebug(lcMemoryCardLog) << "UDisks interface added:" << path << BlockDevices::isExternal(path);
248
    qCInfo(lcMemoryCardLog) << "UDisks dump interface:" << interfaces;
249 250
    // External device must have file system or partition so that it can added to the model.
    // Devices without partition table have filesystem interface.
251 252
    if (path.startsWith(QStringLiteral("/org/freedesktop/UDisks2/block_devices/")) && BlockDevices::isExternal(path)) {
        m_blockDevices->createBlockDevice(path, interfaces);
253 254 255
    } else if (path.startsWith(QStringLiteral("/org/freedesktop/UDisks2/jobs"))) {
        QVariantMap dict = interfaces.value(UDISKS2_JOB_INTERFACE);
        QString operation = dict.value(UDISKS2_JOB_KEY_OPERATION, QString()).toString();
256 257 258
        if (operation == UDISKS2_JOB_OP_ENC_LOCK ||
                operation == UDISKS2_JOB_OP_ENC_UNLOCK ||
                operation == UDISKS2_JOB_OP_FS_MOUNT ||
259
                operation == UDISKS2_JOB_OP_FS_UNMOUNT ||
260 261
                operation == UDISKS2_JOB_OP_CLEANUP ||
                operation == UDISKS2_JOB_OF_FS_FORMAT) {
262
            UDisks2::Job *job = new UDisks2::Job(path, dict);
263
            updatePartitionStatus(job, true);
264 265 266 267 268
            if (job->operation() == Job::Lock) {
                for (const QString &dbusObjectPath : job->objects()) {
                    m_blockDevices->lock(dbusObjectPath);
                }
            }
269 270 271

            connect(job, &UDisks2::Job::completed, this, [this](bool success) {
                UDisks2::Job *job = qobject_cast<UDisks2::Job *>(sender());
272
                job->dumpInfo();
273
                if (job->operation() != Job::Lock) {
274 275
                    updatePartitionStatus(job, success);
                }
276
            });
277 278 279

            if (job->operation() == Job::Format) {
                for (const QString &objectPath : job->objects()) {
280
                    if (UDisks2::Block *block = m_blockDevices->device(objectPath)) {
281 282 283 284 285 286 287
                        block->blockSignals(true);
                        block->setFormatting(true);
                        block->blockSignals(false);
                    }
                }
            }

288
            m_jobsToWait.insert(path, job);
289
            job->dumpInfo();
290 291 292 293 294 295 296
        }
    }
}

void UDisks2::Monitor::interfacesRemoved(const QDBusObjectPath &objectPath, const QStringList &interfaces)
{
    QString path = objectPath.path();
297 298
    qCDebug(lcMemoryCardLog) << "UDisks interface removed:" << path;
    qCInfo(lcMemoryCardLog) << "UDisks dump interface:" << interfaces;
299

300 301 302
    if (m_jobsToWait.contains(path)) {
        UDisks2::Job *job = m_jobsToWait.take(path);
        job->deleteLater();
303
    } else if (m_blockDevices->contains(path) && interfaces.contains(UDISKS2_BLOCK_INTERFACE)) {
304
        // Cleanup partitions first.
305
        if (BlockDevices::isExternal(path)) {
306 307 308 309
            PartitionManagerPrivate::Partitions removedPartitions;
            QStringList blockDevPaths = { path };
            lookupPartitions(removedPartitions, blockDevPaths);
            m_manager->remove(removedPartitions);
310
        }
311

312 313 314
        m_blockDevices->remove(path);
    } else {
        m_blockDevices->removeInterfaces(path, interfaces);
315
    }
316
}
317

318 319 320 321 322 323 324
void UDisks2::Monitor::setPartitionProperties(QExplicitlySharedDataPointer<PartitionPrivate> &partition, const UDisks2::Block *blockDevice)
{
    QString label = blockDevice->idLabel();
    if (label.isEmpty()) {
        label = blockDevice->idUUID();
    }

325
    qCDebug(lcMemoryCardLog) << "Set partition properties";
326
    blockDevice->dumpInfo();
327 328

    partition->devicePath = blockDevice->device();
329 330 331 332
    QString deviceName = partition->devicePath.section(QChar('/'), 2);
    partition->deviceName = deviceName;
    partition->deviceRoot = deviceRoot.match(deviceName).hasMatch();

333 334 335
    partition->mountPath = blockDevice->mountPath();
    partition->deviceLabel = label;
    partition->filesystemType = blockDevice->idType();
336
    partition->isSupportedFileSystemType = m_manager->supportedFileSystems().contains(partition->filesystemType);
337
    partition->readOnly = blockDevice->isReadOnly();
338
    partition->canMount = blockDevice->isMountable() && m_manager->supportedFileSystems().contains(partition->filesystemType);
339 340 341 342 343 344 345 346 347 348

    if (blockDevice->isFormatting()) {
        partition->status = Partition::Formatting;
    } else if (blockDevice->isEncrypted()) {
        partition->status = Partition::Locked;
    } else if (blockDevice->mountPath().isEmpty()) {
        partition->status = Partition::Unmounted;
    } else {
        partition->status = Partition::Mounted;
    }
349
    partition->isCryptoDevice = blockDevice->isCryptoBlock();
350
    partition->isEncrypted = blockDevice->isEncrypted();
351
    partition->cryptoBackingDevicePath = blockDevice->cryptoBackingDevicePath();
352

353 354
    QVariantMap drive;

355 356
    QString connectionBus = blockDevice->connectionBus();
    if (connectionBus == QLatin1String("sdio")) {
357
        drive.insert(QLatin1String("connectionBus"), Partition::SDIO);
358
    } else if (connectionBus == QLatin1String("usb")) {
359
        drive.insert(QLatin1String("connectionBus"), Partition::USB);
360
    } else if (connectionBus == QLatin1String("ieee1394")) {
361
        drive.insert(QLatin1String("connectionBus"), Partition::IEEE1394);
362
    } else {
363
        drive.insert(QLatin1String("connectionBus"), Partition::UnknownBus);
364
    }
365 366 367
    drive.insert(QLatin1String("model"), blockDevice->driveModel());
    drive.insert(QLatin1String("vendor"), blockDevice->driveVendor());
    partition->drive = drive;
368 369
}

370 371
void UDisks2::Monitor::updatePartitionProperties(const UDisks2::Block *blockDevice)
{
372
    bool hasCryptoBackingDevice = blockDevice->hasCryptoBackingDevice();
373 374
    const QString cryptoBackingDevicePath = blockDevice->cryptoBackingDevicePath();

375
    for (auto partition : m_manager->m_partitions) {
376
        if ((partition->devicePath == blockDevice->device()) || (hasCryptoBackingDevice && (partition->devicePath == cryptoBackingDevicePath))) {
377
            setPartitionProperties(partition, blockDevice);
378 379 380
            partition->valid = true;
            m_manager->refresh(partition.data());
        }
381 382 383
    }
}

384
void UDisks2::Monitor::updatePartitionStatus(const UDisks2::Job *job, bool success)
385 386
{
    UDisks2::Job::Operation operation = job->operation();
387
    PartitionManagerPrivate::Partitions affectedPartitions;
388
    lookupPartitions(affectedPartitions, job->objects());
389
    if (operation == UDisks2::Job::Lock || operation == UDisks2::Job::Unlock) {
390
        for (auto partition : affectedPartitions) {
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
            Partition::Status oldStatus = partition->status;
            if (success) {
                if (job->status() == UDisks2::Job::Added) {
                    partition->activeState = QStringLiteral("inactive");
                    partition->status = operation == UDisks2::Job::Unlock ? Partition::Unlocking : Partition::Locking;
                } else {
                    partition->activeState = QStringLiteral("inactive");
                    partition->status = operation == UDisks2::Job::Unlock ? Partition::Unmounted : Partition::Locked;
                }
            } else {
                partition->activeState = QStringLiteral("failed");
                partition->status = operation == UDisks2::Job::Unlock ? Partition::Locked : Partition::Unmounted;
            }
            partition->valid = true;
            if (oldStatus != partition->status) {
                m_manager->refresh(partition.data());
            }
        }
    } else if (operation == UDisks2::Job::Mount || operation == UDisks2::Job::Unmount) {
410
        for (auto partition : affectedPartitions) {
411
            Partition::Status oldStatus = partition->status;
412

413 414 415 416 417 418 419 420
            if (success) {
                if (job->status() == UDisks2::Job::Added) {
                    partition->activeState = operation == UDisks2::Job::Mount ? QStringLiteral("activating") : QStringLiteral("deactivating");
                    partition->status = operation == UDisks2::Job::Mount ? Partition::Mounting : Partition::Unmounting;
                } else {
                    // Completed busy unmount job shall stay in mounted state.
                    if (job->deviceBusy() && operation == UDisks2::Job::Unmount)
                        operation = UDisks2::Job::Mount;
421

422 423
                    partition->activeState = operation == UDisks2::Job::Mount ? QStringLiteral("active") : QStringLiteral("inactive");
                    partition->status = operation == UDisks2::Job::Mount ? Partition::Mounted : Partition::Unmounted;
424
                }
425 426 427 428 429 430 431 432 433 434 435 436
            } else {
                partition->activeState = QStringLiteral("failed");
                partition->status = operation == UDisks2::Job::Mount ? Partition::Mounted : Partition::Unmounted;
            }

            partition->valid = true;
            partition->mountFailed = job->deviceBusy() ? false : !success;
            if (oldStatus != partition->status) {
                m_manager->refresh(partition.data());
            }
        }
    } else if (operation == UDisks2::Job::Format) {
437
        for (auto partition : affectedPartitions) {
438 439 440 441 442
            Partition::Status oldStatus = partition->status;
            if (success) {
                if (job->status() == UDisks2::Job::Added) {
                    partition->activeState = QStringLiteral("inactive");
                    partition->status = Partition::Formatting;
443 444 445 446 447 448
                    partition->bytesAvailable = 0;
                    partition->bytesTotal = 0;
                    partition->bytesFree = 0;
                    partition->filesystemType.clear();
                    partition->canMount = false;
                    partition->valid = false;
449 450 451 452
                }
            } else {
                partition->activeState = QStringLiteral("failed");
                partition->status = Partition::Unmounted;
453
                partition->valid = false;
454
            }
455

456 457
            if (oldStatus != partition->status) {
                m_manager->refresh(partition.data());
458 459 460 461 462
            }
        }
    }
}

463
void UDisks2::Monitor::startLuksOperation(const QString &devicePath, const QString &dbusMethod, const QString &dbusObjectPath, const QVariantList &arguments)
464
{
465 466
    Q_ASSERT(dbusMethod == UDISKS2_ENCRYPTED_LOCK || dbusMethod == UDISKS2_ENCRYPTED_UNLOCK);

467
    if (devicePath.isEmpty()) {
468 469 470 471 472 473 474 475 476 477 478 479
        qCCritical(lcMemoryCardLog) << "Cannot" << dbusMethod.toLower() << "without device name";
        return;
    }

    QDBusInterface udisks2Interface(UDISKS2_SERVICE,
                                    dbusObjectPath,
                                    UDISKS2_ENCRYPTED_INTERFACE,
                                    QDBusConnection::systemBus());

    QDBusPendingCall pendingCall = udisks2Interface.asyncCallWithArgumentList(dbusMethod, arguments);
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
    connect(watcher, &QDBusPendingCallWatcher::finished,
480
            this, [this, devicePath, dbusMethod](QDBusPendingCallWatcher *watcher) {
481 482
        if (watcher->isValid() && watcher->isFinished()) {
            if (dbusMethod == UDISKS2_ENCRYPTED_LOCK) {
483
                emit status(devicePath, Partition::Locked);
484
            } else {
485
                emit status(devicePath, Partition::Unmounted);
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
            }
        } else if (watcher->isError()) {
            QDBusError error = watcher->error();
            QByteArray errorData = error.name().toLocal8Bit();
            const char *errorCStr = errorData.constData();

            qCWarning(lcMemoryCardLog) << dbusMethod << "error:" << errorCStr << error.message();

            for (uint i = 0; i < sizeof(dbus_error_entries) / sizeof(ErrorEntry); i++) {
                if (strcmp(dbus_error_entries[i].dbusErrorName, errorCStr) == 0) {
                    if (dbusMethod == UDISKS2_ENCRYPTED_LOCK) {
                        emit lockError(dbus_error_entries[i].errorCode);
                        break;
                    } else {
                        emit unlockError(dbus_error_entries[i].errorCode);
                        break;
                    }
                }
            }

            if (dbusMethod == UDISKS2_ENCRYPTED_LOCK) {
                // All other errors will revert back the previous state.
508
                emit status(devicePath, Partition::Unmounted); // TODO: could it have been in Mounted state?
509 510
            } else if (dbusMethod == UDISKS2_ENCRYPTED_UNLOCK) {
                // All other errors will revert back the previous state.
511
                emit status(devicePath, Partition::Locked);
512 513
            }
        }
514

515 516 517 518
        watcher->deleteLater();
    });

    if (dbusMethod == UDISKS2_ENCRYPTED_LOCK) {
519
        emit status(devicePath, Partition::Locking);
520
    } else {
521
        emit status(devicePath, Partition::Unlocking);
522 523 524
    }
}

525
void UDisks2::Monitor::startMountOperation(const QString &devicePath, const QString &dbusMethod, const QString &dbusObjectPath, const QVariantList &arguments)
526
{
527 528
    Q_ASSERT(dbusMethod == UDISKS2_FILESYSTEM_MOUNT || dbusMethod == UDISKS2_FILESYSTEM_UNMOUNT);

529
    if (devicePath.isEmpty()) {
530 531 532 533 534
        qCCritical(lcMemoryCardLog) << "Cannot" << dbusMethod.toLower() << "without device name";
        return;
    }

    QDBusInterface udisks2Interface(UDISKS2_SERVICE,
535
                                    dbusObjectPath,
536 537 538
                                    UDISKS2_FILESYSTEM_INTERFACE,
                                    QDBusConnection::systemBus());

539
    QDBusPendingCall pendingCall = udisks2Interface.asyncCallWithArgumentList(dbusMethod, arguments);
540 541
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
    connect(watcher, &QDBusPendingCallWatcher::finished,
542
            this, [this, devicePath, dbusMethod](QDBusPendingCallWatcher *watcher) {
543
        if (watcher->isValid() && watcher->isFinished()) {
544
            Block *block = m_blockDevices->find(devicePath);
545 546 547
            if (block && block->isFormatting()) {
                // Do nothing
            } else if (dbusMethod == UDISKS2_FILESYSTEM_MOUNT) {
548
                emit status(devicePath, Partition::Mounted);
549
            } else {
550
                emit status(devicePath, Partition::Unmounted);
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573
            }
        } else if (watcher->isError()) {
            QDBusError error = watcher->error();
            QByteArray errorData = error.name().toLocal8Bit();
            const char *errorCStr = errorData.constData();

            qCWarning(lcMemoryCardLog) << dbusMethod << "error:" << errorCStr;

            for (uint i = 0; i < sizeof(dbus_error_entries) / sizeof(ErrorEntry); i++) {
                if (strcmp(dbus_error_entries[i].dbusErrorName, errorCStr) == 0) {
                    if (dbusMethod == UDISKS2_FILESYSTEM_MOUNT) {
                        emit mountError(dbus_error_entries[i].errorCode);
                        break;
                    } else {
                        emit unmountError(dbus_error_entries[i].errorCode);
                        break;
                    }
                }
            }

            if (strcmp(UDISKS2_ERROR_ALREADY_UNMOUNTING, errorCStr) == 0) {
                // Do nothing
            } else if (strcmp(UDISKS2_ERROR_ALREADY_MOUNTED, errorCStr) == 0) {
574
                emit status(devicePath, Partition::Mounted);
575 576
            } else if (dbusMethod == UDISKS2_FILESYSTEM_UNMOUNT) {
                // All other errors will revert back the previous state.
577
                emit status(devicePath, Partition::Mounted);
578 579
            } else if (dbusMethod == UDISKS2_FILESYSTEM_MOUNT) {
                // All other errors will revert back the previous state.
580
                emit status(devicePath, Partition::Unmounted);
581 582 583 584 585 586
            }
        }

        watcher->deleteLater();
    });

587
    Block *block = m_blockDevices->find(devicePath);
588 589 590
    if (block && block->isFormatting()) {
        emit status(devicePath, Partition::Formatting);
    } else if (dbusMethod == UDISKS2_FILESYSTEM_MOUNT) {
591
        emit status(devicePath, Partition::Mounting);
592
    } else {
593
        emit status(devicePath, Partition::Unmounting);
594 595 596
    }
}

597
void UDisks2::Monitor::lookupPartitions(PartitionManagerPrivate::Partitions &affectedPartitions, const QStringList &objects)
598
{
599
    QStringList blockDevs = m_blockDevices->devicePaths(objects);
600
    for (const QString &dev : blockDevs) {
601
        for (auto partition : m_manager->m_partitions) {
602
            if (partition->devicePath == dev) {
603
                affectedPartitions << partition;
604 605 606 607 608
            }
        }
    }
}

609 610 611 612 613 614 615 616
void UDisks2::Monitor::createPartition(const UDisks2::Block *block)
{
    QExplicitlySharedDataPointer<PartitionPrivate> partition(new PartitionPrivate(m_manager.data()));
    partition->storageType = Partition::External;
    partition->devicePath = block->device();
    partition->bytesTotal = block->size();
    setPartitionProperties(partition, block);
    partition->valid = true;
617
    m_manager->add(partition);
618 619
}

620
void UDisks2::Monitor::doFormat(const QString &devicePath, const QString &dbusObjectPath, const QString &filesystemType, const QVariantMap &arguments)
621 622
{
    QDBusInterface blockDeviceInterface(UDISKS2_SERVICE,
623
                                    dbusObjectPath,
624 625 626
                                    UDISKS2_BLOCK_INTERFACE,
                                    QDBusConnection::systemBus());

627
    QDBusPendingCall pendingCall = blockDeviceInterface.asyncCall(UDISKS2_BLOCK_FORMAT, filesystemType, arguments);
628 629
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
    connect(watcher, &QDBusPendingCallWatcher::finished,
630
            this, [this, devicePath, dbusObjectPath, arguments](QDBusPendingCallWatcher *watcher) {
631
        if (watcher->isValid() && watcher->isFinished()) {
632
            emit status(devicePath, Partition::Formatted);
633
        } else if (watcher->isError()) {
634
            Block *block = m_blockDevices->find(devicePath);
635 636 637
            if (block) {
                block->setFormatting(false);
            }
638 639 640
            QDBusError error = watcher->error();
            QByteArray errorData = error.name().toLocal8Bit();
            const char *errorCStr = errorData.constData();
641
            qCWarning(lcMemoryCardLog) << "Format error:" << errorCStr << dbusObjectPath;
642 643 644 645 646 647 648 649 650 651 652 653 654 655

            for (uint i = 0; i < sizeof(dbus_error_entries) / sizeof(ErrorEntry); i++) {
                if (strcmp(dbus_error_entries[i].dbusErrorName, errorCStr) == 0) {
                    emit formatError(dbus_error_entries[i].errorCode);
                    break;
                }
            }
        }
        watcher->deleteLater();
    });
}

void UDisks2::Monitor::getBlockDevices()
{
656 657 658 659 660 661 662 663 664 665 666 667
    QDBusInterface managerInterface(UDISKS2_SERVICE,
                                    UDISKS2_MANAGER_PATH,
                                    UDISKS2_MANAGER_INTERFACE,
                                    QDBusConnection::systemBus());
    QDBusPendingCall pendingCall = managerInterface.asyncCallWithArgumentList(
                QStringLiteral("GetBlockDevices"),
                QVariantList() << QVariantMap());
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall, this);
    connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher *watcher) {
        if (watcher->isValid() && watcher->isFinished()) {
            QDBusPendingReply<QList<QDBusObjectPath> > reply = *watcher;
            const QList<QDBusObjectPath> blockDevicePaths = reply.argumentAt<0>();
668
            m_blockDevices->createBlockDevices(blockDevicePaths);
669 670 671 672 673
        } else if (watcher->isError()) {
            QDBusError error = watcher->error();
            qCWarning(lcMemoryCardLog) << "Unable to enumerate block devices:" << error.name() << error.message();
        }
    });
674
}
675

676
void UDisks2::Monitor::connectSignals(UDisks2::Block *block)
677
{
678 679 680 681 682 683 684 685 686 687 688
    connect(block, &UDisks2::Block::formatted, this, [this]() {
        UDisks2::Block *block = qobject_cast<UDisks2::Block *>(sender());
        if (m_blockDevices->contains(block->path())) {
            for (auto partition : m_manager->m_partitions) {
                if (partition->devicePath == block->device()) {
                    partition->status = Partition::Formatted;
                    partition->activeState = QStringLiteral("inactive");
                    partition->valid = true;
                    m_manager->refresh(partition.data());
                }
            }
689
        }
690
    }, Qt::UniqueConnection);
691

692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727
    // When block info updated
    connect(block, &UDisks2::Block::updated, this, [this]() {
        UDisks2::Block *block = qobject_cast<UDisks2::Block *>(sender());
        if (m_blockDevices->contains(block->path())) {
            updatePartitionProperties(block);
        }
    }, Qt::UniqueConnection);

    connect(block, &UDisks2::Block::mountPathChanged, this, [this]() {
        UDisks2::Block *block = qobject_cast<UDisks2::Block *>(sender());
        // Both updatePartitionStatus and updatePartitionProperties
        // emits partition refresh => latter one is enough.

        m_manager->blockSignals(true);
        QVariantMap data;
        data.insert(UDISKS2_JOB_KEY_OPERATION, block->mountPath().isEmpty() ? UDISKS2_JOB_OP_FS_UNMOUNT : UDISKS2_JOB_OP_FS_MOUNT);
        data.insert(UDISKS2_JOB_KEY_OBJECTS, QStringList() << block->path());
        qCDebug(lcMemoryCardLog) << "New partition status:" << data;
        UDisks2::Job tmpJob(QString(), data);
        tmpJob.complete(true);
        updatePartitionStatus(&tmpJob, true);
        m_manager->blockSignals(false);

        updatePartitionProperties(block);

        if (!m_operationQueue.isEmpty()) {
            Operation op = m_operationQueue.head();
            if (op.command == UDISKS2_BLOCK_FORMAT && block->mountPath().isEmpty()) {
                m_operationQueue.dequeue();
                doFormat(op.devicePath, op.dbusObjectPath, op.filesystemType, op.arguments);
            } else if (op.command == UDISKS2_ENCRYPTED_LOCK && block->mountPath().isEmpty()) {
                m_operationQueue.dequeue();
                lock(op.devicePath);
            }
        }
    }, Qt::UniqueConnection);
728 729
}

730
void UDisks2::Monitor::handleNewBlock(UDisks2::Block *block)
731
{
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752
    const QString cryptoBackingDeviceObjectPath = block->cryptoBackingDeviceObjectPath();
    if (block->hasCryptoBackingDevice() && m_blockDevices->contains(cryptoBackingDeviceObjectPath)) {
        // Update crypto backing device to file system device.
        UDisks2::Block *fsBlock = m_blockDevices->replace(cryptoBackingDeviceObjectPath, block);
        updatePartitionProperties(fsBlock);
    } else if (!m_blockDevices->contains(block->path())) {
        m_blockDevices->insert(block->path(), block);
        createPartition(block);

        if (block->isFormatting()) {
            if (!m_operationQueue.isEmpty()) {
                Operation op = m_operationQueue.head();
                if (op.command == UDISKS2_BLOCK_FORMAT) {
                    m_operationQueue.dequeue();
                    QMetaObject::invokeMethod(this, "doFormat", Qt::QueuedConnection,
                                              Q_ARG(QString, op.devicePath), Q_ARG(QString, op.dbusObjectPath),
                                              Q_ARG(QString, op.filesystemType), Q_ARG(QVariantMap, op.arguments));
                }
            } else {
                qCDebug(lcMemoryCardLog) << "Formatting cannot be executed. Is block mounted:" << !block->mountPath().isEmpty();
            }
753 754 755
        }
    }

756 757
    connectSignals(block);

758
}