File indexing completed on 2024-04-28 17:06:14

0001 /*
0002     SPDX-FileCopyrightText: 2000 Shie Erlich <krusader@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2000 Rafi Yanai <krusader@users.sourceforge.net>
0004     SPDX-FileCopyrightText: 2004-2022 Krusader Krew <https://krusader.org>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "kmountman.h"
0010 
0011 // QtCore
0012 #include <QDir>
0013 // QtWidgets
0014 #include <QApplication>
0015 #include <QMenu>
0016 
0017 #include <KConfigCore/KSharedConfig>
0018 #include <KCoreAddons/KJobTrackerInterface>
0019 #include <KCoreAddons/KProcess>
0020 #include <KI18n/KLocalizedString>
0021 #include <KIO/JobUiDelegate>
0022 #include <KWidgetsAddons/KMessageBox>
0023 #include <KWidgetsAddons/KToolBarPopupAction>
0024 
0025 #include <Solid/Block>
0026 #include <Solid/Device>
0027 #include <Solid/DeviceNotifier>
0028 #include <Solid/OpticalDisc>
0029 #include <Solid/OpticalDrive>
0030 #include <Solid/StorageAccess>
0031 #include <Solid/StorageVolume>
0032 #include <utility>
0033 
0034 #include "../Dialogs/krdialogs.h"
0035 #include "../FileSystem/krpermhandler.h"
0036 #include "../defaults.h"
0037 #include "../icon.h"
0038 #include "../kractions.h"
0039 #include "../krglobal.h"
0040 #include "../krservices.h"
0041 #include "kmountmangui.h"
0042 
0043 #ifdef _OS_SOLARIS_
0044 #define FSTAB "/etc/filesystemtab"
0045 #else
0046 #define FSTAB "/etc/fstab"
0047 #endif
0048 
0049 KMountMan::KMountMan(QWidget *parent)
0050     : _operational(false)
0051     , waiting(false)
0052     , mountManGui(nullptr)
0053     , parentWindow(parent)
0054 {
0055     _action = new KToolBarPopupAction(Icon("kr_mountman"), i18n("&MountMan..."), this);
0056     connect(_action, &QAction::triggered, this, &KMountMan::mainWindow);
0057     connect(_action->menu(), &QMenu::aboutToShow, this, &KMountMan::quickList);
0058     _manageAction = _action->menu()->addAction(i18n("Open &MountMan"));
0059     connect(_manageAction, &QAction::triggered, this, &KMountMan::mainWindow);
0060     _action->menu()->addSeparator();
0061 
0062     // added as a precaution, although we use kde services now
0063     _operational = KrServices::cmdExist("mount");
0064 
0065     network_fs << "nfs"
0066                << "smbfs"
0067                << "fuse.fusesmb"
0068                << "fuse.sshfs"; // TODO: is this list complete ?
0069 
0070     // list of FS that we don't manage at all
0071     invalid_fs << "swap"
0072                << "/dev/pts"
0073                << "tmpfs"
0074                << "devpts"
0075                << "sysfs"
0076                << "rpc_pipefs"
0077                << "usbfs"
0078                << "binfmt_misc";
0079 #ifdef BSD
0080     invalid_fs << "procfs";
0081 #else
0082     invalid_fs << "proc";
0083 #endif
0084 
0085     // list of FS that we don't allow to mount/unmount
0086     nonmount_fs << "supermount";
0087     {
0088         KConfigGroup group(krConfig, "Advanced");
0089         QStringList nonmount = group.readEntry("Nonmount Points", _NonMountPoints).split(',');
0090         nonmount_fs_mntpoint += nonmount;
0091         // simplify the white space
0092         for (auto &it : nonmount_fs_mntpoint) {
0093             it = it.simplified();
0094         }
0095     }
0096 
0097     connect(Solid::DeviceNotifier::instance(), &Solid::DeviceNotifier::deviceAdded, this, &KMountMan::deviceAdded);
0098 
0099     for (Solid::Device &device : Solid::Device::allDevices()) {
0100         if (device.isValid()) {
0101             QPointer<Solid::StorageAccess> access = device.as<Solid::StorageAccess>();
0102             if (access) {
0103                 connect(access, &Solid::StorageAccess::teardownRequested, this, &KMountMan::slotTeardownRequested, Qt::UniqueConnection);
0104             }
0105         }
0106     }
0107 }
0108 
0109 KMountMan::~KMountMan() = default;
0110 
0111 bool KMountMan::invalidFilesystem(const QString &type)
0112 {
0113     return (invalid_fs.contains(type));
0114 }
0115 
0116 // this is an ugly hack, but type can actually be a mountpoint. oh well...
0117 bool KMountMan::nonmountFilesystem(const QString &type, const QString &mntPoint)
0118 {
0119     return (nonmount_fs.contains(type) || nonmount_fs_mntpoint.contains(mntPoint));
0120 }
0121 
0122 bool KMountMan::networkFilesystem(const QString &type)
0123 {
0124     return (network_fs.contains(type));
0125 }
0126 
0127 void KMountMan::mainWindow()
0128 {
0129     // left as a precaution, although we use kde's services now
0130     if (!KrServices::cmdExist("mount")) {
0131         KMessageBox::error(nullptr, i18n("Cannot start 'mount'. Check the 'Dependencies' page in konfigurator."));
0132         return;
0133     }
0134 
0135     mountManGui = new KMountManGUI(this);
0136     delete mountManGui; /* as KMountManGUI is modal, we can now delete it */
0137     mountManGui = nullptr; /* for sanity */
0138 }
0139 
0140 QExplicitlySharedDataPointer<KMountPoint> KMountMan::findInListByMntPoint(KMountPoint::List &lst, QString value)
0141 {
0142     if (value.length() > 1 && value.endsWith('/'))
0143         value = value.left(value.length() - 1);
0144 
0145     QExplicitlySharedDataPointer<KMountPoint> m;
0146     for (auto &it : lst) {
0147         m = it.data();
0148         QString mntPnt = m->mountPoint();
0149         if (mntPnt.length() > 1 && mntPnt.endsWith('/'))
0150             mntPnt = mntPnt.left(mntPnt.length() - 1);
0151         if (mntPnt == value)
0152             return m;
0153     }
0154 
0155     return QExplicitlySharedDataPointer<KMountPoint>();
0156 }
0157 
0158 void KMountMan::jobResult(KJob *job)
0159 {
0160     waiting = false;
0161     if (job->error())
0162         job->uiDelegate()->showErrorMessage();
0163 }
0164 
0165 void KMountMan::mount(const QString &mntPoint, bool blocking)
0166 {
0167     QString udi = findUdiForPath(mntPoint, Solid::DeviceInterface::StorageAccess);
0168     if (!udi.isNull()) {
0169         Solid::Device device(udi);
0170         auto *access = device.as<Solid::StorageAccess>();
0171         if (access && !access->isAccessible()) {
0172             connect(access, &Solid::StorageAccess::setupDone, this, &KMountMan::slotSetupDone, Qt::UniqueConnection);
0173             if (blocking)
0174                 waiting = true; // prepare to block
0175             access->setup();
0176         }
0177     } else {
0178         KMountPoint::List possible = KMountPoint::possibleMountPoints(KMountPoint::NeedMountOptions);
0179         QExplicitlySharedDataPointer<KMountPoint> m = findInListByMntPoint(possible, mntPoint);
0180         if (!((bool)m))
0181             return;
0182         if (blocking)
0183             waiting = true; // prepare to block
0184 
0185         // KDE4 doesn't allow mounting devices as user, because they think it's the right behaviour.
0186         // I add this patch, as I don't think so.
0187         if (geteuid()) { // tries to mount as an user?
0188             KProcess proc;
0189             proc << KrServices::fullPathName("mount") << mntPoint;
0190             proc.start();
0191             if (!blocking)
0192                 return;
0193             proc.waitForFinished(-1); // -1 msec blocks without timeout
0194             if (proc.exitStatus() == QProcess::NormalExit && proc.exitCode() == 0)
0195                 return;
0196         }
0197 
0198         KIO::SimpleJob *job = KIO::mount(false, m->mountType().toLocal8Bit(), m->mountedFrom(), m->mountPoint(), KIO::DefaultFlags);
0199         job->setUiDelegate(new KIO::JobUiDelegate());
0200         KIO::getJobTracker()->registerJob(job);
0201         connect(job, &KIO::SimpleJob::result, this, &KMountMan::jobResult);
0202     }
0203     while (blocking && waiting) {
0204         qApp->processEvents();
0205         usleep(1000);
0206     }
0207 }
0208 
0209 void KMountMan::unmount(const QString &mntPoint, bool blocking)
0210 {
0211     // if working dir is below mountpoint cd to ~ first
0212     if (QUrl::fromLocalFile(QDir(mntPoint).canonicalPath()).isParentOf(QUrl::fromLocalFile(QDir::current().canonicalPath())))
0213         QDir::setCurrent(QDir::homePath());
0214 
0215     if (QUrl::fromLocalFile(QDir(mntPoint).canonicalPath()) == QUrl::fromLocalFile(QDir::current().canonicalPath()))
0216         QDir::setCurrent(QDir::homePath());
0217 
0218     QString udi = findUdiForPath(mntPoint, Solid::DeviceInterface::StorageAccess);
0219     if (!udi.isNull()) {
0220         Solid::Device device(udi);
0221         auto *access = device.as<Solid::StorageAccess>();
0222         if (access && access->isAccessible()) {
0223             connect(access, &Solid::StorageAccess::teardownDone, this, &KMountMan::slotTeardownDone, Qt::UniqueConnection);
0224             access->teardown();
0225         }
0226     } else {
0227         if (blocking)
0228             waiting = true; // prepare to block
0229 
0230         // KDE4 doesn't allow unmounting devices as user, because they think it's the right behaviour.
0231         // I add this patch, as I don't think so.
0232         if (geteuid()) { // tries to mount as an user?
0233             KProcess proc;
0234             proc << KrServices::fullPathName("umount") << mntPoint;
0235             proc.start();
0236             if (!blocking)
0237                 return;
0238             proc.waitForFinished(-1); // -1 msec blocks without timeout
0239             if (proc.exitStatus() == QProcess::NormalExit && proc.exitCode() == 0)
0240                 return;
0241         }
0242 
0243         KIO::SimpleJob *job = KIO::unmount(mntPoint, KIO::DefaultFlags);
0244         job->setUiDelegate(new KIO::JobUiDelegate());
0245         KIO::getJobTracker()->registerJob(job);
0246         connect(job, &KIO::SimpleJob::result, this, &KMountMan::jobResult);
0247     }
0248     while (blocking && waiting) {
0249         qApp->processEvents();
0250         usleep(1000);
0251     }
0252 }
0253 
0254 KMountMan::mntStatus KMountMan::getStatus(const QString &mntPoint)
0255 {
0256     QExplicitlySharedDataPointer<KMountPoint> mountPoint;
0257 
0258     // 1: is it already mounted
0259     KMountPoint::List current = KMountPoint::currentMountPoints();
0260     mountPoint = findInListByMntPoint(current, mntPoint);
0261     if ((bool)mountPoint)
0262         return MOUNTED;
0263 
0264     // 2: is it a mount point but not mounted?
0265     KMountPoint::List possible = KMountPoint::possibleMountPoints();
0266     mountPoint = findInListByMntPoint(possible, mntPoint);
0267     if ((bool)mountPoint)
0268         return NOT_MOUNTED;
0269 
0270     // 3: unknown
0271     return DOESNT_EXIST;
0272 }
0273 
0274 void KMountMan::toggleMount(const QString &mntPoint)
0275 {
0276     mntStatus status = getStatus(mntPoint);
0277     switch (status) {
0278     case MOUNTED:
0279         unmount(mntPoint);
0280         break;
0281     case NOT_MOUNTED:
0282         mount(mntPoint);
0283         break;
0284     case DOESNT_EXIST:
0285         // do nothing: no-op to make the compiler quiet ;-)
0286         break;
0287     }
0288 }
0289 
0290 void KMountMan::autoMount(const QString &path)
0291 {
0292     KConfigGroup group(krConfig, "Advanced");
0293     if (!group.readEntry("AutoMount", _AutoMount))
0294         return; // auto mount disabled
0295 
0296     if (getStatus(path) == NOT_MOUNTED)
0297         mount(path);
0298 }
0299 
0300 void KMountMan::eject(const QString &mntPoint)
0301 {
0302     QString udi = findUdiForPath(mntPoint, Solid::DeviceInterface::OpticalDrive);
0303     if (udi.isNull())
0304         return;
0305 
0306     Solid::Device dev(udi);
0307     auto *drive = dev.as<Solid::OpticalDrive>();
0308     if (drive == nullptr)
0309         return;
0310 
0311     // if working dir is below mountpoint cd to ~ first
0312     if (QUrl::fromLocalFile(QDir(mntPoint).canonicalPath()).isParentOf(QUrl::fromLocalFile(QDir::current().canonicalPath())))
0313         QDir::setCurrent(QDir::homePath());
0314 
0315     connect(drive, &Solid::OpticalDrive::ejectDone, this, &KMountMan::slotTeardownDone);
0316 
0317     drive->eject();
0318 }
0319 
0320 // returns true if the path is an ejectable mount point (at the moment CDROM and DVD)
0321 bool KMountMan::ejectable(QString path)
0322 {
0323     QString udi = findUdiForPath(std::move(path), Solid::DeviceInterface::OpticalDisc);
0324     if (udi.isNull())
0325         return false;
0326 
0327     Solid::Device dev(udi);
0328     return dev.as<Solid::OpticalDisc>() != nullptr;
0329 }
0330 
0331 bool KMountMan::removable(QString path)
0332 {
0333     QString udi = findUdiForPath(std::move(path), Solid::DeviceInterface::StorageAccess);
0334     if (udi.isNull())
0335         return false;
0336 
0337     return removable(Solid::Device(udi));
0338 }
0339 
0340 bool KMountMan::removable(Solid::Device d)
0341 {
0342     if (!d.isValid())
0343         return false;
0344     auto *drive = d.as<Solid::StorageDrive>();
0345     if (drive)
0346         return drive->isRemovable();
0347     else
0348         return (removable(d.parent()));
0349 }
0350 
0351 // populate the pop-up menu of the mountman tool-button with actions
0352 void KMountMan::quickList()
0353 {
0354     if (!_operational) {
0355         KMessageBox::error(nullptr, i18n("MountMan is not operational. Sorry"));
0356         return;
0357     }
0358 
0359     // clear mount / unmount actions
0360     for (QAction *action : _action->menu()->actions()) {
0361         if (action == _manageAction || action->isSeparator()) {
0362             continue;
0363         }
0364         _action->menu()->removeAction(action);
0365     }
0366 
0367     // create lists of current and possible mount points
0368     const KMountPoint::List currentMountPoints = KMountPoint::currentMountPoints();
0369 
0370     // create a menu, displaying mountpoints with possible actions
0371     for (QExplicitlySharedDataPointer<KMountPoint> possibleMountPoint : KMountPoint::possibleMountPoints()) {
0372         // skip nonmountable file systems
0373         if (nonmountFilesystem(possibleMountPoint->mountType(), possibleMountPoint->mountPoint()) || invalidFilesystem(possibleMountPoint->mountType())) {
0374             continue;
0375         }
0376         // does the mountpoint exist in current list?
0377         // if so, it can only be umounted, otherwise, it can be mounted
0378         bool needUmount = false;
0379         for (QExplicitlySharedDataPointer<KMountPoint> currentMountPoint : currentMountPoints) {
0380             if (currentMountPoint->mountPoint() == possibleMountPoint->mountPoint()) {
0381                 // found -> needs umount
0382                 needUmount = true;
0383                 break;
0384             }
0385         }
0386         // add the item to the menu
0387         const QString text =
0388             QString("%1 %2 (%3)").arg(needUmount ? i18n("Unmount") : i18n("Mount"), possibleMountPoint->mountPoint(), possibleMountPoint->mountedFrom());
0389 
0390         QAction *act = _action->menu()->addAction(text);
0391         act->setData(QList<QVariant>(
0392             {QVariant(needUmount ? KMountMan::ActionType::Unmount : KMountMan::ActionType::Mount), QVariant(possibleMountPoint->mountPoint())}));
0393     }
0394     connect(_action->menu(), &QMenu::triggered, this, &KMountMan::delayedPerformAction);
0395 }
0396 
0397 void KMountMan::delayedPerformAction(const QAction *action)
0398 {
0399     if (!action || !action->data().canConvert<QList<QVariant>>()) {
0400         return;
0401     }
0402 
0403     disconnect(_action->menu(), &QMenu::triggered, nullptr, nullptr);
0404 
0405     const QList<QVariant> actData = action->data().toList();
0406     const int actionType = actData[0].toInt();
0407     const QString mountPoint = actData[1].toString();
0408 
0409     QTimer::singleShot(0, this, [=] {
0410         if (actionType == KMountMan::ActionType::Mount) {
0411             mount(mountPoint);
0412         } else {
0413             unmount(mountPoint);
0414         }
0415     });
0416 }
0417 
0418 QString KMountMan::findUdiForPath(const QString &path, const Solid::DeviceInterface::Type &expType)
0419 {
0420     KMountPoint::List current = KMountPoint::currentMountPoints();
0421     KMountPoint::List possible = KMountPoint::possibleMountPoints();
0422     QExplicitlySharedDataPointer<KMountPoint> mp = findInListByMntPoint(current, path);
0423     if (!(bool)mp) {
0424         mp = findInListByMntPoint(possible, path);
0425         if (!(bool)mp)
0426             return QString();
0427     }
0428     QString dev = QDir(mp->mountedFrom()).canonicalPath();
0429     QList<Solid::Device> storageDevices = Solid::Device::listFromType(Solid::DeviceInterface::Block);
0430 
0431     for (int p = storageDevices.count() - 1; p >= 0; p--) {
0432         Solid::Device device = storageDevices[p];
0433         QString udi = device.udi();
0434 
0435         auto *sb = device.as<Solid::Block>();
0436         if (sb) {
0437             QString devb = QDir(sb->device()).canonicalPath();
0438             if (expType != Solid::DeviceInterface::Unknown && !device.isDeviceInterface(expType))
0439                 continue;
0440             if (devb == dev)
0441                 return udi;
0442         }
0443     }
0444 
0445     return QString();
0446 }
0447 
0448 QString KMountMan::pathForUdi(const QString &udi)
0449 {
0450     Solid::Device device(udi);
0451     auto *access = device.as<Solid::StorageAccess>();
0452     if (access)
0453         return access->filePath();
0454     else
0455         return QString();
0456 }
0457 
0458 void KMountMan::slotTeardownDone(Solid::ErrorType error, const QVariant &errorData, const QString & /*udi*/)
0459 {
0460     waiting = false;
0461     if (error != Solid::NoError && errorData.isValid()) {
0462         KMessageBox::error(parentWindow, errorData.toString());
0463     }
0464 }
0465 
0466 void KMountMan::slotSetupDone(Solid::ErrorType error, const QVariant &errorData, const QString & /*udi*/)
0467 {
0468     waiting = false;
0469     if (error != Solid::NoError && errorData.isValid()) {
0470         KMessageBox::error(parentWindow, errorData.toString());
0471     }
0472 }
0473 
0474 void KMountMan::slotTeardownRequested(const QString &udi)
0475 {
0476     const QString mntPoint = KMountMan::pathForUdi(udi);
0477     KMountMan::unmount(mntPoint, false);
0478 }
0479 
0480 void KMountMan::deviceAdded(const QString &udi)
0481 {
0482     Solid::Device device(udi);
0483 
0484     if (device.isValid()) {
0485         QPointer<Solid::StorageAccess> access = device.as<Solid::StorageAccess>();
0486         if (access) {
0487             connect(access, &Solid::StorageAccess::teardownRequested, this, &KMountMan::slotTeardownRequested, Qt::UniqueConnection);
0488         }
0489     }
0490 }