File indexing completed on 2024-05-05 17:43:12
0001 /* 0002 * SPDX-FileCopyrightText: 2017, 2018, 2019 Ivan Cukic <ivan.cukic (at) kde.org> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "vault.h" 0008 0009 #include <QDir> 0010 #include <QFutureWatcher> 0011 #include <QDBusMetaType> 0012 #include <QMessageBox> 0013 #include <QUrl> 0014 #include <QPointer> 0015 0016 #include <KConfig> 0017 #include <KConfigGroup> 0018 #include <KSharedConfig> 0019 #include <QRegularExpression> 0020 0021 #include <processcore/process.h> 0022 #include <processcore/processes.h> 0023 0024 #include <KLocalizedString> 0025 #include <kdirnotify.h> 0026 0027 #include "backend_p.h" 0028 0029 #include "asynqt/basic/all.h" 0030 #include "asynqt/wrappers/process.h" 0031 #include "asynqt/operations/listen.h" 0032 #include "asynqt/operations/cast.h" 0033 0034 #include <signal.h> 0035 0036 #define CFG_NAME "name" 0037 #define CFG_LAST_STATUS "lastStatus" 0038 #define CFG_LAST_ERROR "lastError" 0039 #define CFG_MOUNT_POINT "mountPoint" 0040 #define CFG_BACKEND "backend" 0041 #define CFG_ACTIVITIES "activities" 0042 #define CFG_OFFLINEONLY "offlineOnly" 0043 0044 namespace PlasmaVault { 0045 0046 class Vault::Private { 0047 public: 0048 Vault * const q; 0049 0050 KSharedConfigPtr config; 0051 Device device; 0052 0053 FILE* deviceDirectoryLock = nullptr; 0054 0055 void lockDeviceDirectory() 0056 { 0057 if (!deviceDirectoryLock) { 0058 deviceDirectoryLock = fopen(device.data().toLocal8Bit().data(), "r"); 0059 } 0060 } 0061 0062 void unlockDeviceDirectory() 0063 { 0064 if (deviceDirectoryLock) { 0065 fclose(deviceDirectoryLock); 0066 deviceDirectoryLock = nullptr; 0067 } 0068 } 0069 0070 QTimer savingDelay; 0071 0072 0073 enum DeletionState { 0074 Normal, 0075 DeletionBlocked, 0076 DeletionScheduled, 0077 }; 0078 DeletionState deletionState = Normal; 0079 0080 0081 struct Data { 0082 QString name; 0083 MountPoint mountPoint; 0084 0085 VaultInfo::Status status; 0086 QString message; 0087 0088 QStringList activities; 0089 bool isOfflineOnly; 0090 0091 QString backendName; 0092 Backend::Ptr backend; 0093 }; 0094 using ExpectedData = AsynQt::Expected<Data, PlasmaVault::Error>; 0095 ExpectedData data; 0096 0097 0098 0099 void updateMessage(const QString &message) 0100 { 0101 if (!data) return; 0102 0103 data->message = message; 0104 Q_EMIT q->messageChanged(message); 0105 } 0106 0107 0108 void writeConfiguration() 0109 { 0110 if (data) { 0111 0112 const auto deviceStr = device.data(); 0113 const auto mountPointStr = data->mountPoint.data(); 0114 0115 Q_ASSERT(!deviceStr.isEmpty() && !mountPointStr.isEmpty()); 0116 0117 // Saving the data for the current mount 0118 KConfigGroup generalConfig(config, "EncryptedDevices"); 0119 generalConfig.writeEntry(deviceStr, true); 0120 0121 KConfigGroup vaultConfig(config, deviceStr); 0122 vaultConfig.writeEntry(CFG_LAST_STATUS, (int)data->status); 0123 vaultConfig.writeEntry(CFG_MOUNT_POINT, mountPointStr); 0124 vaultConfig.writeEntry(CFG_NAME, data->name); 0125 vaultConfig.writeEntry(CFG_BACKEND, data->backend->name()); 0126 0127 vaultConfig.writeEntry(CFG_ACTIVITIES, data->activities); 0128 vaultConfig.writeEntry(CFG_OFFLINEONLY, data->isOfflineOnly); 0129 0130 } else { 0131 0132 KConfigGroup generalConfig(config, "EncryptedDevices"); 0133 generalConfig.writeEntry(device.data(), false); 0134 0135 KConfigGroup vaultConfig(config, device.data()); 0136 vaultConfig.writeEntry(CFG_LAST_STATUS, (int)VaultInfo::Error); 0137 vaultConfig.writeEntry(CFG_LAST_ERROR, 0138 QString(data.error().message() + QStringLiteral(" (code: ") + 0139 QString::number(data.error().code()) + QStringLiteral(")"))); 0140 // vaultConfig.deleteEntry(CFG_MOUNT_POINT); 0141 // vaultConfig.deleteEntry(CFG_NAME); 0142 // vaultConfig.deleteEntry(CFG_BACKEND); 0143 // vaultConfig.deleteEntry(CFG_ACTIVITIES); 0144 // vaultConfig.deleteEntry(CFG_OFFLINEONLY); 0145 0146 } 0147 0148 config->sync(); 0149 } 0150 0151 0152 void updateStatus() 0153 { 0154 if (data) { 0155 // Checking the status, and whether we should update it 0156 const auto oldStatus = data->status; 0157 0158 if (oldStatus == VaultInfo::Dismantling) { 0159 // This means that the vault should be forgotten 0160 KConfigGroup generalConfig(config, "EncryptedDevices"); 0161 generalConfig.deleteEntry(device.data()); 0162 0163 KConfigGroup vaultConfig(config, device.data()); 0164 vaultConfig.deleteGroup(); 0165 0166 Q_EMIT q->statusChanged(data->status = VaultInfo::Dismantled); 0167 0168 } else { 0169 QDir deviceDir(device.data()); 0170 0171 const auto newStatus = 0172 !deviceDir.exists() ? VaultInfo::DeviceMissing : 0173 isOpened() ? VaultInfo::Opened : 0174 isInitialized() ? VaultInfo::Closed : 0175 VaultInfo::NotInitialized; 0176 0177 if (oldStatus == newStatus) return; 0178 0179 data->status = newStatus; 0180 0181 Q_EMIT q->statusChanged(data->status); 0182 0183 if (newStatus == VaultInfo::Closed // 0184 || newStatus == VaultInfo::Opened) { 0185 Q_EMIT q->isOpenedChanged(newStatus == VaultInfo::Opened); 0186 } 0187 0188 if (oldStatus == VaultInfo::NotInitialized // 0189 || newStatus == VaultInfo::NotInitialized) { 0190 Q_EMIT q->isInitializedChanged(newStatus); 0191 } 0192 0193 if (oldStatus == VaultInfo::Creating // 0194 || oldStatus == VaultInfo::Opening // 0195 || oldStatus == VaultInfo::Closing // 0196 || oldStatus == VaultInfo::Dismantling) { 0197 Q_EMIT q->isBusyChanged(false); 0198 } 0199 0200 writeConfiguration(); 0201 0202 org::kde::KDirNotify::emitFilesAdded( 0203 QUrl::fromLocalFile(data->mountPoint.data())); 0204 } 0205 0206 } else { 0207 Q_EMIT q->isOpenedChanged(false); 0208 Q_EMIT q->isInitializedChanged(false); 0209 Q_EMIT q->isBusyChanged(false); 0210 0211 writeConfiguration(); 0212 0213 Q_EMIT q->statusChanged(VaultInfo::Error); 0214 } 0215 0216 if (data && data->status == VaultInfo::Opened) { 0217 lockDeviceDirectory(); 0218 } else { 0219 unlockDeviceDirectory(); 0220 } 0221 } 0222 0223 0224 0225 ExpectedData errorData(Error::Code error, const QString &message) const 0226 { 0227 qWarning() << "error: " << message; 0228 return ExpectedData::error(error, message); 0229 } 0230 0231 0232 0233 ExpectedData loadVault(const Device &device, 0234 const QString &name = QString(), 0235 const MountPoint &mountPoint = MountPoint(), 0236 const Payload &payload = Payload()) const 0237 { 0238 if (!config->hasGroup(device.data())) { 0239 return errorData(Error::DeviceError, i18n("Unknown device")); 0240 } 0241 0242 Data vaultData; 0243 const QString backendName = payload[KEY_BACKEND].toString(); 0244 const QStringList activities = payload[KEY_ACTIVITIES].toStringList(); 0245 const bool isOfflineOnly = payload[KEY_OFFLINEONLY].toBool(); 0246 0247 // status should never be in this state, if we got an error, 0248 // d->data should not be valid 0249 vaultData.status = VaultInfo::Error; 0250 0251 // Reading the mount data from the config 0252 KConfigGroup vaultConfig(config, device.data()); 0253 vaultData.name = vaultConfig.readEntry(CFG_NAME, name); 0254 vaultData.backendName = vaultConfig.readEntry(CFG_BACKEND, backendName); 0255 vaultData.activities = vaultConfig.readEntry(CFG_ACTIVITIES, activities); 0256 vaultData.isOfflineOnly = vaultConfig.readEntry(CFG_OFFLINEONLY, isOfflineOnly); 0257 0258 const QString configuredMountPoint = vaultConfig.readEntry(CFG_MOUNT_POINT, mountPoint.data()); 0259 vaultData.mountPoint = MountPoint(configuredMountPoint); 0260 const QString actualMountPoint = vaultData.mountPoint.data(); 0261 vaultConfig.writeEntry(CFG_MOUNT_POINT, actualMountPoint); 0262 0263 const QDir mountPointDir(vaultData.mountPoint.data()); 0264 0265 return 0266 // If the backend is not known, we need to fail 0267 !Backend::availableBackends().contains(vaultData.backendName) ? 0268 errorData(Error::BackendError, 0269 i18n("Configured backend does not exist: %1", vaultData.backendName)) : 0270 0271 // If the mount point is empty, we can not do anything 0272 vaultData.mountPoint.isEmpty() ? 0273 errorData(Error::MountPointError, 0274 i18n("Mount point is not specified")) : 0275 0276 // Lets try to create the mount point 0277 !mountPointDir.exists() && !QDir().mkpath(vaultData.mountPoint.data()) ? 0278 errorData(Error::MountPointError, 0279 i18n("Cannot create the mount point")) : 0280 0281 // Instantiate the backend if possible 0282 !(vaultData.backend = Backend::instance(vaultData.backendName)) ? 0283 errorData(Error::BackendError, 0284 i18n("Configured backend cannot be instantiated: %1", vaultData.backendName)) : 0285 0286 // otherwise 0287 ExpectedData::success(vaultData); 0288 } 0289 0290 0291 0292 Private(Vault *parent, const Device &device) 0293 : q(parent) 0294 , config(KSharedConfig::openConfig(PLASMAVAULT_CONFIG_FILE)) 0295 , device(device) 0296 , data(loadVault(device)) 0297 { 0298 updateStatus(); 0299 } 0300 0301 0302 0303 ~Private() 0304 { 0305 unlockDeviceDirectory(); 0306 } 0307 0308 0309 0310 template <typename T> 0311 T followFuture(VaultInfo::Status whileNotFinished, const T &future) 0312 { 0313 using namespace AsynQt::operators; 0314 0315 Q_EMIT q->isBusyChanged(true); 0316 data->status = whileNotFinished; 0317 0318 deletionState = DeletionBlocked; 0319 0320 return future | onSuccess([this] { 0321 updateStatus(); 0322 0323 if (deletionState == DeletionScheduled) { 0324 q->deleteLater(); 0325 } 0326 deletionState = Normal; 0327 }); 0328 } 0329 0330 0331 0332 bool isInitialized() const 0333 { 0334 return data && data->backend->isInitialized(device); 0335 } 0336 0337 0338 0339 bool isOpened() const 0340 { 0341 return data && data->backend->isOpened(data->mountPoint); 0342 } 0343 }; 0344 0345 0346 0347 Vault::Vault(const Device &device, QObject *parent) 0348 : QObject(parent) 0349 , d(new Private(this, device)) 0350 { 0351 d->savingDelay.setInterval(300); 0352 d->savingDelay.setSingleShot(true); 0353 connect(&d->savingDelay, &QTimer::timeout, 0354 this, [&] { 0355 d->writeConfiguration(); 0356 Q_EMIT infoChanged(); 0357 }); 0358 } 0359 0360 0361 0362 Vault::~Vault() 0363 { 0364 if (d->isOpened()) { 0365 close(); 0366 } 0367 } 0368 0369 0370 0371 void Vault::scheduleDeletion() 0372 { 0373 if (d->deletionState == Private::Normal) { 0374 deleteLater(); 0375 } else { 0376 d->deletionState = Private::DeletionScheduled; 0377 } 0378 } 0379 0380 0381 0382 void Vault::saveConfiguration() 0383 { 0384 d->savingDelay.start(); 0385 } 0386 0387 0388 0389 FutureResult<> Vault::create(const QString &name, const MountPoint &mountPoint, 0390 const Payload &payload) 0391 { 0392 using namespace AsynQt::operators; 0393 0394 return 0395 // If the backend is already known, and the device is initialized, 0396 // we do not want to do it again 0397 d->data && d->data->backend->isInitialized(d->device) ? 0398 errorResult(Error::DeviceError, 0399 i18n("This device is already registered. Cannot recreate it.")) : 0400 0401 // Mount not open, check the error messages 0402 !(d->data = d->loadVault(d->device, name, mountPoint, payload)) ? 0403 errorResult(Error::BackendError, 0404 i18n("Unknown error; unable to create the backend.")) : 0405 0406 // otherwise 0407 d->followFuture(VaultInfo::Creating, 0408 d->data->backend->initialize(name, d->device, mountPoint, payload)) 0409 | onSuccess([mountPoint] { 0410 // If we have successfully created the vault, 0411 // lets try to set its icon 0412 QFile dotDir(mountPoint.data() + QStringLiteral("/.directory")); 0413 0414 if (dotDir.open(QIODevice::WriteOnly | QIODevice::Text)) { 0415 QTextStream out(&dotDir); 0416 out << "[Desktop Entry]\nIcon=folder-decrypted\n"; 0417 } 0418 }); 0419 } 0420 0421 0422 0423 FutureResult<> Vault::import(const QString &name, const MountPoint &mountPoint, 0424 const Payload &payload) 0425 { 0426 using namespace AsynQt::operators; 0427 0428 return 0429 // If the backend is already known, and the device is initialized, 0430 // we do not want to do it again 0431 d->data && (!d->data->backend->isInitialized(d->device)) ? 0432 errorResult(Error::DeviceError, 0433 i18n("This device is not initialized. Cannot import it.")) : 0434 0435 // Mount not open, check the error messages 0436 !(d->data = d->loadVault(d->device, name, mountPoint, payload)) ? 0437 errorResult(Error::BackendError, 0438 i18n("Unknown error; unable to create the backend.")) : 0439 0440 // otherwise 0441 d->followFuture(VaultInfo::Creating, 0442 d->data->backend->import(name, d->device, mountPoint, payload)) 0443 | onSuccess([mountPoint] { 0444 // If we have successfully created the vault, 0445 // lets try to set its icon 0446 QFile dotDir(mountPoint.data() + QStringLiteral("/.directory")); 0447 0448 if (dotDir.open(QIODevice::WriteOnly | QIODevice::Text)) { 0449 QTextStream out(&dotDir); 0450 out << "[Desktop Entry]\nIcon=folder-decrypted\n"; 0451 } 0452 }); 0453 } 0454 0455 0456 0457 0458 FutureResult<> Vault::open(const Payload &payload) 0459 { 0460 return 0461 // We can not mount something that has not been registered 0462 // with us before 0463 !d->data ? errorResult(Error::BackendError, 0464 i18n("Cannot open an unknown vault.")) : 0465 0466 // otherwise 0467 d->followFuture(VaultInfo::Opening, 0468 d->data->backend->open(d->device, d->data->mountPoint, payload)); 0469 } 0470 0471 0472 0473 FutureResult<> Vault::close() 0474 { 0475 using namespace AsynQt::operators; 0476 0477 return 0478 // We can not mount something that has not been registered 0479 // with us before 0480 !d->data ? errorResult(Error::BackendError, 0481 i18n("The vault is unknown; cannot close it.")) : 0482 0483 // otherwise 0484 d->followFuture(VaultInfo::Closing, 0485 d->data->backend->close(d->device, d->data->mountPoint)) 0486 | onSuccess([this] (const Result<> &result) { 0487 if (!isOpened() || result) { 0488 d->updateMessage(QString()); 0489 0490 } else { 0491 // We want to check whether there is an application 0492 // that is accessing the vault 0493 AsynQt::Process::getOutput(QStringLiteral("lsof"), { QStringLiteral("-t"), mountPoint().data() }) 0494 | cast<QString>() 0495 | onError([this] { 0496 d->updateMessage(i18n("Unable to close the vault because an application is using it")); 0497 }) 0498 | onSuccess([this] (const QString &result) { 0499 // based on ksolidnotify.cpp 0500 QStringList blockApps; 0501 0502 const auto &pidList = result.split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts); 0503 0504 if (pidList.isEmpty()) { 0505 d->updateMessage(i18n("Unable to close the vault because an application is using it")); 0506 close(); 0507 0508 } else { 0509 KSysGuard::Processes procs; 0510 0511 for (const QString &pidStr: pidList) { 0512 int pid = pidStr.toInt(); 0513 if (!pid) { 0514 continue; 0515 } 0516 0517 procs.updateOrAddProcess(pid); 0518 0519 KSysGuard::Process *proc = procs.getProcess(pid); 0520 0521 if (!blockApps.contains(proc->name())) { 0522 blockApps << proc->name(); 0523 } 0524 } 0525 0526 blockApps.removeDuplicates(); 0527 0528 d->updateMessage(i18n("Unable to close the vault because it is being used by %1", blockApps.join(QStringLiteral(", ")))); 0529 } 0530 }); 0531 } 0532 }); 0533 } 0534 0535 0536 0537 // FutureResult<> Vault::configure() 0538 // { 0539 // return close(); 0540 // } 0541 0542 0543 0544 FutureResult<> Vault::forceClose() 0545 { 0546 using namespace AsynQt::operators; 0547 0548 AsynQt::await( 0549 AsynQt::Process::getOutput(QStringLiteral("lsof"), { QStringLiteral("-t"), mountPoint().data() }) 0550 | cast<QString>() 0551 | onError([this] { 0552 d->updateMessage(i18n("Failed to fetch the list of applications using this vault")); 0553 }) 0554 | onSuccess([] (const QString &result) { 0555 // based on ksolidnotify.cpp 0556 0557 const auto &pidList = result.split(QRegularExpression(QStringLiteral("\\s+")), Qt::SkipEmptyParts); 0558 0559 KSysGuard::Processes procs; 0560 0561 for (const QString &pidStr: pidList) { 0562 int pid = pidStr.toInt(); 0563 if (!pid) { 0564 continue; 0565 } 0566 0567 procs.sendSignal(pid, SIGKILL); 0568 } 0569 })); 0570 0571 return close(); 0572 } 0573 0574 0575 0576 FutureResult<> Vault::dismantle(const Payload &payload) 0577 { 0578 const auto resolvedPath = [] (const QString& path) { 0579 auto result = QDir(path).canonicalPath(); 0580 if (!result.endsWith(QLatin1Char('/'))) { 0581 result += QLatin1Char('/'); 0582 } 0583 return result; 0584 }; 0585 0586 const auto resolvedDevice = resolvedPath(d->device.data()); 0587 const auto resolvedMount = resolvedPath(d->data->mountPoint.data()); 0588 0589 const auto devices = availableDevices(); 0590 const int matches = std::count_if(devices.cbegin(), devices.cend(), 0591 [&] (const Device& device) { 0592 auto thisResolvedDevice = resolvedPath(device.data()); 0593 0594 auto diff = std::mismatch( 0595 resolvedDevice.cbegin(), resolvedDevice.cend(), 0596 thisResolvedDevice.cbegin(), thisResolvedDevice.cend()); 0597 0598 return diff.first == resolvedDevice.cend() || 0599 diff.second == thisResolvedDevice.cend(); 0600 }); 0601 0602 return 0603 matches != 1 ? errorResult(Error::BackendError, 0604 i18n("Cannot delete the vault; there are other vaults with overlapping paths.")): 0605 0606 // We can not mount something that has not been registered 0607 // with us before 0608 !d->data ? errorResult(Error::BackendError, 0609 i18n("The vault is unknown; cannot dismantle it.")) : 0610 0611 // Let's confirm with the user once more 0612 QMessageBox::question(nullptr, i18n("Are you sure you want to delete this vault"), 0613 i18n("This operation will irreversibly delete the following:\n%1\n%2", 0614 d->device.data(), d->data->mountPoint.data()), QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes ? 0615 errorResult(Error::OperationCancelled, i18n("Delete operation cancelled")) : 0616 0617 // otherwise 0618 d->followFuture(VaultInfo::Dismantling, 0619 d->data->backend->dismantle(d->device, d->data->mountPoint, payload)); 0620 } 0621 0622 0623 0624 VaultInfo::Status Vault::status() const 0625 { 0626 return d->data->status; 0627 } 0628 0629 0630 0631 bool Vault::isValid() const 0632 { 0633 return d->data; 0634 } 0635 0636 0637 0638 Device Vault::device() const 0639 { 0640 return d->device; 0641 } 0642 0643 0644 0645 QList<Device> Vault::availableDevices() 0646 { 0647 const auto config = KSharedConfig::openConfig(PLASMAVAULT_CONFIG_FILE); 0648 const KConfigGroup general(config, "EncryptedDevices"); 0649 0650 QList<Device> results; 0651 for (const auto& item: general.keyList()) { 0652 results << Device(item); 0653 } 0654 return results; 0655 } 0656 0657 0658 0659 QStringList Vault::statusMessage() 0660 { 0661 for (const auto& backendName: Backend::availableBackends()) { 0662 auto backend = Backend::instance(backendName); 0663 backend->validateBackend(); 0664 } 0665 0666 return {}; 0667 } 0668 0669 0670 0671 QString Vault::message() const 0672 { 0673 if (!d->data) { 0674 return d->data.error().message(); 0675 } else { 0676 return d->data->message; 0677 } 0678 } 0679 0680 0681 0682 bool Vault::isInitialized() const 0683 { 0684 return d->isInitialized(); 0685 } 0686 0687 0688 0689 bool Vault::isOpened() const 0690 { 0691 return d->isOpened(); 0692 } 0693 0694 0695 0696 MountPoint Vault::mountPoint() const 0697 { 0698 return d->data->mountPoint; 0699 } 0700 0701 void Vault::setMountPoint(const MountPoint &mountPoint) 0702 { 0703 if (d->data->mountPoint.data() != mountPoint.data()) { 0704 QDir().rmpath(d->data->mountPoint.data()); 0705 QDir().mkpath(mountPoint.data()); 0706 0707 d->data->mountPoint = mountPoint; 0708 saveConfiguration(); 0709 } 0710 } 0711 0712 0713 0714 QStringList Vault::activities() const 0715 { 0716 return d->data->activities; 0717 } 0718 0719 void Vault::setActivities(const QStringList &activities) 0720 { 0721 d->data->activities = activities; 0722 Q_EMIT activitiesChanged(activities); 0723 saveConfiguration(); 0724 } 0725 0726 0727 0728 bool Vault::isOfflineOnly() const 0729 { 0730 return d->data->isOfflineOnly; 0731 } 0732 0733 void Vault::setIsOfflineOnly(bool isOfflineOnly) 0734 { 0735 d->data->isOfflineOnly = isOfflineOnly; 0736 Q_EMIT isOfflineOnlyChanged(isOfflineOnly); 0737 saveConfiguration(); 0738 } 0739 0740 0741 0742 QString Vault::name() const 0743 { 0744 return d->data->name; 0745 } 0746 0747 void Vault::setName(const QString &name) 0748 { 0749 d->data->name = name; 0750 Q_EMIT nameChanged(name); 0751 saveConfiguration(); 0752 } 0753 0754 0755 0756 QString Vault::backend() const 0757 { 0758 return d->data->backendName; 0759 } 0760 0761 0762 0763 bool Vault::isBusy() const 0764 { 0765 if (!d->data) { 0766 return false; 0767 } 0768 0769 switch (status()) { 0770 case VaultInfo::Creating: 0771 case VaultInfo::Opening: 0772 case VaultInfo::Closing: 0773 case VaultInfo::Dismantling: 0774 return true; 0775 0776 default: 0777 return false; 0778 } 0779 } 0780 0781 0782 0783 VaultInfo Vault::info() const 0784 { 0785 VaultInfo vaultInfo; 0786 0787 vaultInfo.device = device().data(); 0788 vaultInfo.name = name(); 0789 0790 vaultInfo.status = status(); 0791 vaultInfo.message = message(); 0792 0793 vaultInfo.activities = activities(); 0794 vaultInfo.isOfflineOnly = isOfflineOnly(); 0795 0796 return vaultInfo; 0797 } 0798 0799 0800 0801 void Vault::updateStatus() 0802 { 0803 d->updateStatus(); 0804 } 0805 0806 0807 } // namespace PlasmaVault 0808 0809