File indexing completed on 2024-05-05 17:43:11

0001 /*
0002  *   SPDX-FileCopyrightText: 2017 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 "fusebackend_p.h"
0008 
0009 #include <QDir>
0010 #include <QRegularExpression>
0011 #include <QUrl>
0012 
0013 #include <KIO/DeleteJob>
0014 #include <KLocalizedString>
0015 #include <KMountPoint>
0016 
0017 #include <algorithm>
0018 
0019 #include <asynqt/basic/all.h>
0020 #include <asynqt/operations/collect.h>
0021 #include <asynqt/operations/transform.h>
0022 #include <asynqt/wrappers/kjob.h>
0023 #include <asynqt/wrappers/process.h>
0024 
0025 #include "singleton_p.h"
0026 
0027 using namespace AsynQt;
0028 
0029 namespace PlasmaVault
0030 {
0031 Result<> FuseBackend::hasProcessFinishedSuccessfully(QProcess *process)
0032 {
0033     const auto out = process->readAllStandardOutput();
0034     const auto err = process->readAllStandardError();
0035 
0036     return
0037         // If all went well, just return success
0038         (process->exitStatus() == QProcess::NormalExit && process->exitCode() == 0) ? Result<>::success() :
0039 
0040                                                                                     // If we tried to mount into a non-empty location, report
0041         (err.contains("'nonempty'") || err.contains("non empty"))
0042         ? Result<>::error(Error::CommandError, i18n("The mount point directory is not empty, refusing to open the vault"))
0043         :
0044 
0045         // If we have a message for the user, report it
0046         // !out.isEmpty() ?
0047         //     Result<>::error(Error::CommandError,
0048         //                     out) :
0049 
0050         // otherwise just report that we failed
0051         Result<>::error(Error::CommandError, i18n("Unable to perform the operation"), out, err);
0052 }
0053 
0054 void FuseBackend::removeDotDirectory(const MountPoint &mountPoint)
0055 {
0056     QDir dir(mountPoint.data());
0057     const auto dirContents = dir.entryList(QDir::NoDotAndDotDot | QDir::Files | QDir::Hidden | QDir::Dirs);
0058     if (dirContents.length() == 1 && dirContents.first() == QStringLiteral(".directory"))
0059         dir.remove(QStringLiteral(".directory"));
0060 }
0061 
0062 FuseBackend::FuseBackend()
0063 {
0064 }
0065 
0066 FuseBackend::~FuseBackend()
0067 {
0068 }
0069 
0070 QProcess *FuseBackend::process(const QString &executable, const QStringList &arguments, const QHash<QString, QString> &environment) const
0071 {
0072     auto result = new QProcess();
0073     result->setProgram(executable);
0074     result->setArguments(arguments);
0075 
0076     if (environment.count() > 0) {
0077         auto env = result->processEnvironment();
0078         for (const auto &key : environment.keys()) {
0079             env.insert(key, environment[key]);
0080         }
0081         result->setProcessEnvironment(env);
0082     }
0083 
0084     return result;
0085 }
0086 
0087 QProcess *FuseBackend::fusermount(const QStringList &arguments) const
0088 {
0089     return process("fusermount", arguments, {});
0090 }
0091 
0092 FutureResult<> FuseBackend::initialize(const QString &name, const Device &device, const MountPoint &mountPoint, const Vault::Payload &payload)
0093 {
0094     Q_UNUSED(name);
0095 
0096     return isInitialized(device) ? errorResult(Error::BackendError, i18n("This directory already contains encrypted data")) :
0097 
0098         directoryExists(device.data()) || directoryExists(mountPoint.data())
0099         ? errorResult(Error::BackendError, i18n("You need to select empty directories for the encrypted storage and for the mount point"))
0100         :
0101 
0102         // otherwise
0103         mount(device, mountPoint, payload);
0104 }
0105 
0106 FutureResult<> FuseBackend::import(const QString &name, const Device &device, const MountPoint &mountPoint, const Vault::Payload &payload)
0107 {
0108     Q_UNUSED(name);
0109 
0110     // clang-format off
0111     return
0112         !isInitialized(device) ?
0113             errorResult(Error::BackendError,
0114                         i18n("This directory doesn't contain encrypted data")) :
0115 
0116         !directoryExists(device.data()) || directoryExists(mountPoint.data()) ?
0117             errorResult(Error::BackendError,
0118                         i18n("You need to select an empty directory for the mount point")) :
0119 
0120         // otherwise
0121             mount(device, mountPoint, payload);
0122     // clang-format on
0123 }
0124 
0125 FutureResult<> FuseBackend::open(const Device &device, const MountPoint &mountPoint, const Vault::Payload &payload)
0126 {
0127     return isOpened(mountPoint) //
0128         ? errorResult(Error::BackendError, i18n("Device is already open"))
0129         : mount(device, mountPoint, payload);
0130 }
0131 
0132 FutureResult<> FuseBackend::close(const Device &device, const MountPoint &mountPoint)
0133 {
0134     Q_UNUSED(device);
0135 
0136     return !isOpened(mountPoint) ? errorResult(Error::BackendError, i18n("Device is not open")) :
0137 
0138                                  // otherwise
0139         makeFuture(fusermount({"-u", mountPoint.data()}), hasProcessFinishedSuccessfully);
0140 }
0141 
0142 FutureResult<> FuseBackend::dismantle(const Device &device, const MountPoint &mountPoint, const Vault::Payload &payload)
0143 {
0144     // TODO:
0145     // mount
0146     // unmount
0147     // remove the directories
0148     // return Fuse::dismantle(device, mountPoint, password);
0149 
0150     Q_UNUSED(payload)
0151 
0152     // Removing the data and the mount point
0153     return transform(makeFuture<KJob *>(KIO::del({QUrl::fromLocalFile(device.data()), QUrl::fromLocalFile(mountPoint.data())})), [](KJob *job) {
0154         job->deleteLater();
0155         return job->error() == 0 ? Result<>::success() : Result<>::error(Error::DeletionError, job->errorString());
0156     });
0157 }
0158 
0159 QFuture<QPair<bool, QString>> FuseBackend::checkVersion(QProcess *process, const std::tuple<int, int, int> &requiredVersion) const
0160 {
0161     using namespace AsynQt::operators;
0162 
0163     return makeFuture(process, [=](QProcess *process) {
0164         if (process->exitStatus() != QProcess::NormalExit) {
0165             return qMakePair(false, i18n("Failed to execute"));
0166         }
0167 
0168         QRegularExpression versionMatcher("([0-9]+)[.]([0-9]+)[.]([0-9]+)");
0169 
0170         const auto out = process->readAllStandardOutput();
0171         const auto err = process->readAllStandardError();
0172         const auto all = out + err;
0173 
0174         const auto matches = versionMatcher.match(all);
0175 
0176         if (!matches.hasMatch()) {
0177             return qMakePair(false, i18n("Unable to detect the version"));
0178         }
0179 
0180         const auto matchedVersion = std::make_tuple(matches.captured(1).toInt(), matches.captured(2).toInt(), matches.captured(3).toInt());
0181 
0182         if (matchedVersion < requiredVersion) {
0183             // Bad version, we need to notify the world
0184             return qMakePair(false,
0185                              i18n("Wrong version installed. The required version is %1.%2.%3",
0186                                   std::get<0>(requiredVersion),
0187                                   std::get<1>(requiredVersion),
0188                                   std::get<2>(requiredVersion)));
0189         }
0190 
0191         return qMakePair(true, i18n("Correct version found"));
0192     });
0193 }
0194 
0195 bool FuseBackend::isOpened(const MountPoint &mountPoint) const
0196 {
0197     // warning: KMountPoint depends on /etc/mtab according to the documentation.
0198     KMountPoint::Ptr ptr = KMountPoint::currentMountPoints().findByPath(mountPoint.data());
0199 
0200     // we can not rely on ptr->realDeviceName() since it is empty,
0201     // KMountPoint can not get the source
0202 
0203     return ptr && ptr->mountPoint() == mountPoint.data();
0204 }
0205 
0206 } // namespace PlasmaVault