File indexing completed on 2024-04-21 03:55:07

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
0004     SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-only
0007 */
0008 
0009 #include "kmountpoint.h"
0010 
0011 #include <stdlib.h>
0012 
0013 #include "../utils_p.h"
0014 #include <config-kmountpoint.h>
0015 #include <kioglobal_p.h> // Defines QT_LSTAT on windows to kio_windows_lstat
0016 
0017 #include <QDebug>
0018 #include <QDir>
0019 #include <QFile>
0020 #include <QFileInfo>
0021 #include <QTextStream>
0022 
0023 #include <qplatformdefs.h>
0024 
0025 #ifdef Q_OS_WIN
0026 #include <qt_windows.h>
0027 static const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
0028 #else
0029 static const Qt::CaseSensitivity cs = Qt::CaseSensitive;
0030 #endif
0031 
0032 // This is the *BSD branch
0033 #if HAVE_SYS_MOUNT_H
0034 #if HAVE_SYS_PARAM_H
0035 #include <sys/param.h>
0036 #endif
0037 // FreeBSD has a table of names of mount-options in mount.h, which is only
0038 // defined (as MNTOPT_NAMES) if _WANT_MNTOPTNAMES is defined.
0039 #define _WANT_MNTOPTNAMES
0040 #include <sys/mount.h>
0041 #undef _WANT_MNTOPTNAMES
0042 #endif
0043 
0044 #if HAVE_FSTAB_H
0045 #include <fstab.h>
0046 #endif
0047 
0048 // Linux
0049 #if HAVE_LIB_MOUNT
0050 #include <libmount/libmount.h>
0051 #endif
0052 
0053 static bool isNetfs(const QString &mountType)
0054 {
0055     // List copied from util-linux/libmount/src/utils.c
0056     static const std::vector<QLatin1String> netfsList{
0057         QLatin1String("cifs"),
0058         QLatin1String("smb3"),
0059         QLatin1String("smbfs"),
0060         QLatin1String("nfs"),
0061         QLatin1String("nfs3"),
0062         QLatin1String("nfs4"),
0063         QLatin1String("afs"),
0064         QLatin1String("ncpfs"),
0065         QLatin1String("fuse.curlftpfs"),
0066         QLatin1String("fuse.sshfs"),
0067         QLatin1String("9p"),
0068     };
0069 
0070     return std::any_of(netfsList.cbegin(), netfsList.cend(), [mountType](const QLatin1String netfs) {
0071         return mountType == netfs;
0072     });
0073 }
0074 
0075 class KMountPointPrivate
0076 {
0077 public:
0078     void resolveGvfsMountPoints(KMountPoint::List &result);
0079     void finalizePossibleMountPoint(KMountPoint::DetailsNeededFlags infoNeeded);
0080     void finalizeCurrentMountPoint(KMountPoint::DetailsNeededFlags infoNeeded);
0081 
0082     QString m_mountedFrom;
0083     QString m_device; // Only available when the NeedRealDeviceName flag was set.
0084     QString m_mountPoint;
0085     QString m_mountType;
0086     QStringList m_mountOptions;
0087     dev_t m_deviceId = 0;
0088     bool m_isNetFs = false;
0089 };
0090 
0091 KMountPoint::KMountPoint()
0092     : d(new KMountPointPrivate)
0093 {
0094 }
0095 
0096 KMountPoint::~KMountPoint() = default;
0097 
0098 #if HAVE_GETMNTINFO
0099 
0100 #ifdef MNTOPT_NAMES
0101 static struct mntoptnames bsdOptionNames[] = {MNTOPT_NAMES};
0102 
0103 /** @brief Get mount options from @p flags and puts human-readable version in @p list
0104  *
0105  * Appends all positive options found in @p flags to the @p list
0106  * This is roughly paraphrased from FreeBSD's mount.c, prmount().
0107  */
0108 static void translateMountOptions(QStringList &list, uint64_t flags)
0109 {
0110     const struct mntoptnames *optionInfo = bsdOptionNames;
0111 
0112     // Not all 64 bits are useful option names
0113     flags = flags & MNT_VISFLAGMASK;
0114     // Chew up options as long as we're in the table and there
0115     // are any flags left.
0116     for (; flags != 0 && optionInfo->o_opt != 0; ++optionInfo) {
0117         if (flags & optionInfo->o_opt) {
0118             list.append(QString::fromLatin1(optionInfo->o_name));
0119             flags &= ~optionInfo->o_opt;
0120         }
0121     }
0122 }
0123 #else
0124 /** @brief Get mount options from @p flags and puts human-readable version in @p list
0125  *
0126  * This default version just puts the hex representation of @p flags
0127  * in the list, because there is no human-readable version.
0128  */
0129 static void translateMountOptions(QStringList &list, uint64_t flags)
0130 {
0131     list.append(QStringLiteral("0x%1").arg(QString::number(flags, 16)));
0132 }
0133 #endif
0134 
0135 #endif // HAVE_GETMNTINFO
0136 
0137 void KMountPointPrivate::finalizePossibleMountPoint(KMountPoint::DetailsNeededFlags infoNeeded)
0138 {
0139     QString potentialDevice;
0140     if (const auto tag = QLatin1String("UUID="); m_mountedFrom.startsWith(tag)) {
0141         potentialDevice = QFile::symLinkTarget(QLatin1String("/dev/disk/by-uuid/") + QStringView(m_mountedFrom).mid(tag.size()));
0142     } else if (const auto tag = QLatin1String("LABEL="); m_mountedFrom.startsWith(tag)) {
0143         potentialDevice = QFile::symLinkTarget(QLatin1String("/dev/disk/by-label/") + QStringView(m_mountedFrom).mid(tag.size()));
0144     }
0145 
0146     if (QFile::exists(potentialDevice)) {
0147         m_mountedFrom = potentialDevice;
0148     }
0149 
0150     if (infoNeeded & KMountPoint::NeedRealDeviceName) {
0151         if (m_mountedFrom.startsWith(QLatin1Char('/'))) {
0152             m_device = QFileInfo(m_mountedFrom).canonicalFilePath();
0153         }
0154     }
0155 
0156     // Chop trailing slash
0157     Utils::removeTrailingSlash(m_mountedFrom);
0158 }
0159 
0160 void KMountPointPrivate::finalizeCurrentMountPoint(KMountPoint::DetailsNeededFlags infoNeeded)
0161 {
0162     if (infoNeeded & KMountPoint::NeedRealDeviceName) {
0163         if (m_mountedFrom.startsWith(QLatin1Char('/'))) {
0164             m_device = QFileInfo(m_mountedFrom).canonicalFilePath();
0165         }
0166     }
0167 }
0168 
0169 KMountPoint::List KMountPoint::possibleMountPoints(DetailsNeededFlags infoNeeded)
0170 {
0171     KMountPoint::List result;
0172 
0173 #ifdef Q_OS_WIN
0174     result = KMountPoint::currentMountPoints(infoNeeded);
0175 
0176 #elif HAVE_LIB_MOUNT
0177     if (struct libmnt_table *table = mnt_new_table()) {
0178         // By default parses "/etc/fstab"
0179         if (mnt_table_parse_fstab(table, nullptr) == 0) {
0180             struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD);
0181             struct libmnt_fs *fs;
0182 
0183             while (mnt_table_next_fs(table, itr, &fs) == 0) {
0184                 const char *fsType = mnt_fs_get_fstype(fs);
0185                 if (qstrcmp(fsType, "swap") == 0) {
0186                     continue;
0187                 }
0188 
0189                 Ptr mp(new KMountPoint);
0190                 mp->d->m_mountType = QFile::decodeName(fsType);
0191                 const char *target = mnt_fs_get_target(fs);
0192                 mp->d->m_mountPoint = QFile::decodeName(target);
0193 
0194                 if (QT_STATBUF buff; QT_LSTAT(target, &buff) == 0) {
0195                     mp->d->m_deviceId = buff.st_dev;
0196                 }
0197 
0198                 // First field in /etc/fstab, e.g. /dev/sdXY, LABEL=, UUID=, /some/bind/mount/dir
0199                 // or some network mount
0200                 if (const char *source = mnt_fs_get_source(fs)) {
0201                     mp->d->m_mountedFrom = QFile::decodeName(source);
0202                 }
0203 
0204                 if (infoNeeded & NeedMountOptions) {
0205                     mp->d->m_mountOptions = QFile::decodeName(mnt_fs_get_options(fs)).split(QLatin1Char(','));
0206                 }
0207 
0208                 mp->d->finalizePossibleMountPoint(infoNeeded);
0209                 result.append(mp);
0210             }
0211             mnt_free_iter(itr);
0212         }
0213 
0214         mnt_free_table(table);
0215     }
0216 #elif HAVE_FSTAB_H
0217 
0218     QFile f{QLatin1String(FSTAB)};
0219     if (!f.open(QIODevice::ReadOnly)) {
0220         return result;
0221     }
0222 
0223     QTextStream t(&f);
0224     QString s;
0225 
0226     while (!t.atEnd()) {
0227         s = t.readLine().simplified();
0228         if (s.isEmpty() || (s[0] == QLatin1Char('#'))) {
0229             continue;
0230         }
0231 
0232         // not empty or commented out by '#'
0233         const QStringList item = s.split(QLatin1Char(' '));
0234 
0235         if (item.count() < 4) {
0236             continue;
0237         }
0238 
0239         Ptr mp(new KMountPoint);
0240 
0241         int i = 0;
0242         mp->d->m_mountedFrom = item[i++];
0243         mp->d->m_mountPoint = item[i++];
0244         mp->d->m_mountType = item[i++];
0245         if (mp->d->m_mountType == QLatin1String("swap")) {
0246             continue;
0247         }
0248         QString options = item[i++];
0249 
0250         if (infoNeeded & NeedMountOptions) {
0251             mp->d->m_mountOptions = options.split(QLatin1Char(','));
0252         }
0253 
0254         mp->d->finalizePossibleMountPoint(infoNeeded);
0255 
0256         result.append(mp);
0257     } // while
0258 
0259     f.close();
0260 #endif
0261 
0262     return result;
0263 }
0264 
0265 void KMountPointPrivate::resolveGvfsMountPoints(KMountPoint::List &result)
0266 {
0267     if (m_mountedFrom == QLatin1String("gvfsd-fuse")) {
0268         const QDir gvfsDir(m_mountPoint);
0269         const QStringList mountDirs = gvfsDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
0270         for (const QString &mountDir : mountDirs) {
0271             const QString type = mountDir.section(QLatin1Char(':'), 0, 0);
0272             if (type.isEmpty()) {
0273                 continue;
0274             }
0275 
0276             KMountPoint::Ptr gvfsmp(new KMountPoint);
0277             gvfsmp->d->m_mountedFrom = m_mountedFrom;
0278             gvfsmp->d->m_mountPoint = m_mountPoint + QLatin1Char('/') + mountDir;
0279             gvfsmp->d->m_mountType = type;
0280             result.append(gvfsmp);
0281         }
0282     }
0283 }
0284 
0285 KMountPoint::List KMountPoint::currentMountPoints(DetailsNeededFlags infoNeeded)
0286 {
0287     KMountPoint::List result;
0288 
0289 #if HAVE_GETMNTINFO
0290 
0291 #if GETMNTINFO_USES_STATVFS
0292     struct statvfs *mounted;
0293 #else
0294     struct statfs *mounted;
0295 #endif
0296 
0297     int num_fs = getmntinfo(&mounted, MNT_NOWAIT);
0298 
0299     result.reserve(num_fs);
0300 
0301     for (int i = 0; i < num_fs; i++) {
0302         Ptr mp(new KMountPoint);
0303         mp->d->m_mountedFrom = QFile::decodeName(mounted[i].f_mntfromname);
0304         mp->d->m_mountPoint = QFile::decodeName(mounted[i].f_mntonname);
0305         mp->d->m_mountType = QFile::decodeName(mounted[i].f_fstypename);
0306 
0307         if (QT_STATBUF buff; QT_LSTAT(mounted[i].f_mntonname, &buff) == 0) {
0308             mp->d->m_deviceId = buff.st_dev;
0309         }
0310 
0311         if (infoNeeded & NeedMountOptions) {
0312             struct fstab *ft = getfsfile(mounted[i].f_mntonname);
0313             if (ft != nullptr) {
0314                 QString options = QFile::decodeName(ft->fs_mntops);
0315                 mp->d->m_mountOptions = options.split(QLatin1Char(','));
0316             } else {
0317                 translateMountOptions(mp->d->m_mountOptions, mounted[i].f_flags);
0318             }
0319         }
0320 
0321         mp->d->finalizeCurrentMountPoint(infoNeeded);
0322         // TODO: Strip trailing '/' ?
0323         result.append(mp);
0324     }
0325 
0326 #elif defined(Q_OS_WIN)
0327     // nothing fancy with infoNeeded but it gets the job done
0328     DWORD bits = GetLogicalDrives();
0329     if (!bits) {
0330         return result;
0331     }
0332 
0333     for (int i = 0; i < 26; i++) {
0334         if (bits & (1 << i)) {
0335             Ptr mp(new KMountPoint);
0336             mp->d->m_mountPoint = QString(QLatin1Char('A' + i) + QLatin1String(":/"));
0337             result.append(mp);
0338         }
0339     }
0340 
0341 #elif HAVE_LIB_MOUNT
0342     if (struct libmnt_table *table = mnt_new_table()) {
0343         // if "/etc/mtab" is a regular file,
0344         // "/etc/mtab" is used by default instead of "/proc/self/mountinfo" file.
0345         // This leads to NTFS mountpoints being hidden.
0346         if (mnt_table_parse_mtab(table, "/proc/self/mountinfo") == 0) {
0347             struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD);
0348             struct libmnt_fs *fs;
0349 
0350             while (mnt_table_next_fs(table, itr, &fs) == 0) {
0351                 Ptr mp(new KMountPoint);
0352                 mp->d->m_mountedFrom = QFile::decodeName(mnt_fs_get_source(fs));
0353                 mp->d->m_mountPoint = QFile::decodeName(mnt_fs_get_target(fs));
0354                 mp->d->m_mountType = QFile::decodeName(mnt_fs_get_fstype(fs));
0355                 mp->d->m_isNetFs = mnt_fs_is_netfs(fs) == 1;
0356                 mp->d->m_deviceId = mnt_fs_get_devno(fs);
0357 
0358                 if (infoNeeded & NeedMountOptions) {
0359                     mp->d->m_mountOptions = QFile::decodeName(mnt_fs_get_options(fs)).split(QLatin1Char(','));
0360                 }
0361 
0362                 if (infoNeeded & NeedRealDeviceName) {
0363                     if (mp->d->m_mountedFrom.startsWith(QLatin1Char('/'))) {
0364                         mp->d->m_device = mp->d->m_mountedFrom;
0365                     }
0366                 }
0367 
0368                 mp->d->resolveGvfsMountPoints(result);
0369 
0370                 mp->d->finalizeCurrentMountPoint(infoNeeded);
0371                 result.push_back(mp);
0372             }
0373 
0374             mnt_free_iter(itr);
0375         }
0376 
0377         mnt_free_table(table);
0378     }
0379 #endif
0380 
0381     return result;
0382 }
0383 
0384 QString KMountPoint::mountedFrom() const
0385 {
0386     return d->m_mountedFrom;
0387 }
0388 
0389 dev_t KMountPoint::deviceId() const
0390 {
0391     return d->m_deviceId;
0392 }
0393 
0394 bool KMountPoint::isOnNetwork() const
0395 {
0396     return d->m_isNetFs || isNetfs(d->m_mountType);
0397 }
0398 
0399 QString KMountPoint::realDeviceName() const
0400 {
0401     return d->m_device;
0402 }
0403 
0404 QString KMountPoint::mountPoint() const
0405 {
0406     return d->m_mountPoint;
0407 }
0408 
0409 QString KMountPoint::mountType() const
0410 {
0411     return d->m_mountType;
0412 }
0413 
0414 QStringList KMountPoint::mountOptions() const
0415 {
0416     return d->m_mountOptions;
0417 }
0418 
0419 KMountPoint::List::List()
0420     : QList<Ptr>()
0421 {
0422 }
0423 
0424 KMountPoint::Ptr KMountPoint::List::findByPath(const QString &path) const
0425 {
0426 #ifdef Q_OS_WIN
0427     const QString realPath = QDir::fromNativeSeparators(QDir(path).absolutePath());
0428 #else
0429     /* If the path contains symlinks, get the real name */
0430     QFileInfo fileinfo(path);
0431     // canonicalFilePath won't work unless file exists
0432     const QString realPath = fileinfo.exists() ? fileinfo.canonicalFilePath() : fileinfo.absolutePath();
0433 #endif
0434 
0435     KMountPoint::Ptr result;
0436 
0437     if (QT_STATBUF buff; QT_LSTAT(QFile::encodeName(realPath).constData(), &buff) == 0) {
0438         auto it = std::find_if(this->cbegin(), this->cend(), [&buff, &realPath](const KMountPoint::Ptr &mountPtr) {
0439             // For a bind mount, the deviceId() is that of the base mount point, e.g. /mnt/foo,
0440             // however the path we're looking for, e.g. /home/user/bar, doesn't start with the
0441             // mount point of the base device, so we go on searching
0442             return mountPtr->deviceId() == buff.st_dev && realPath.startsWith(mountPtr->mountPoint());
0443         });
0444 
0445         if (it != this->cend()) {
0446             result = *it;
0447         }
0448     }
0449 
0450     return result;
0451 }
0452 
0453 KMountPoint::Ptr KMountPoint::List::findByDevice(const QString &device) const
0454 {
0455     const QString realDevice = QFileInfo(device).canonicalFilePath();
0456     if (realDevice.isEmpty()) { // d->m_device can be empty in the loop below, don't match empty with it
0457         return Ptr();
0458     }
0459     for (const KMountPoint::Ptr &mountPoint : *this) {
0460         if (realDevice.compare(mountPoint->d->m_device, cs) == 0 || realDevice.compare(mountPoint->d->m_mountedFrom, cs) == 0) {
0461             return mountPoint;
0462         }
0463     }
0464     return Ptr();
0465 }
0466 
0467 bool KMountPoint::probablySlow() const
0468 {
0469     /* clang-format off */
0470     return isOnNetwork()
0471         || d->m_mountType == QLatin1String("autofs")
0472         || d->m_mountType == QLatin1String("subfs")
0473         // Technically KIOFUSe mounts local workers as well,
0474         // such as recents:/, but better safe than sorry...
0475         || d->m_mountType == QLatin1String("fuse.kio-fuse");
0476     /* clang-format on */
0477 }
0478 
0479 bool KMountPoint::testFileSystemFlag(FileSystemFlag flag) const
0480 {
0481     /* clang-format off */
0482     const bool isMsDos = d->m_mountType == QLatin1String("msdos")
0483                          || d->m_mountType == QLatin1String("fat")
0484                          || d->m_mountType == QLatin1String("vfat");
0485 
0486     const bool isNtfs = d->m_mountType.contains(QLatin1String("fuse.ntfs"))
0487                         || d->m_mountType.contains(QLatin1String("fuseblk.ntfs"))
0488                         // fuseblk could really be anything. But its most common use is for NTFS mounts, these days.
0489                         || d->m_mountType == QLatin1String("fuseblk");
0490 
0491     const bool isSmb = d->m_mountType == QLatin1String("cifs")
0492                        || d->m_mountType == QLatin1String("smb3")
0493                        || d->m_mountType == QLatin1String("smbfs")
0494                        // gvfs-fuse mounted SMB share
0495                        || d->m_mountType == QLatin1String("smb-share");
0496     /* clang-format on */
0497 
0498     switch (flag) {
0499     case SupportsChmod:
0500     case SupportsChown:
0501     case SupportsUTime:
0502     case SupportsSymlinks:
0503         return !isMsDos && !isNtfs && !isSmb; // it's amazing the number of things Microsoft filesystems don't support :)
0504     case CaseInsensitive:
0505         return isMsDos;
0506     }
0507     return false;
0508 }
0509 
0510 KIOCORE_EXPORT QDebug operator<<(QDebug debug, const KMountPoint::Ptr &mp)
0511 {
0512     QDebugStateSaver saver(debug);
0513     if (!mp) {
0514         debug << "QDebug operator<< called on a null KMountPoint::Ptr";
0515         return debug;
0516     }
0517 
0518     // clang-format off
0519     debug.nospace() << "KMountPoint ["
0520                     << "Mounted from: "  << mp->d->m_mountedFrom
0521                     << ", device name: " << mp->d->m_device
0522                     << ", mount point: " << mp->d->m_mountPoint
0523                     << ", mount type: "  << mp->d->m_mountType
0524                     <<']';
0525 
0526     // clang-format on
0527     return debug;
0528 }