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 }