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