File indexing completed on 2023-09-24 04:08:36
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 }