KIO

kmountpoint.cpp
1/*
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2003 Waldo Bastian <bastian@kde.org>
4 SPDX-FileCopyrightText: 2007 David Faure <faure@kde.org>
5
6 SPDX-License-Identifier: LGPL-2.0-only
7*/
8
9#include "kmountpoint.h"
10
11#include <stdlib.h>
12
13#include "../utils_p.h"
14#include <config-kmountpoint.h>
15#include <kioglobal_p.h> // Defines QT_LSTAT on windows to kio_windows_lstat
16
17#include <QDebug>
18#include <QDir>
19#include <QFile>
20#include <QFileInfo>
21#include <QTextStream>
22
23#include <qplatformdefs.h>
24
25#ifdef Q_OS_WIN
26#include <qt_windows.h>
28#else
30#endif
31
32// This is the *BSD branch
33#if HAVE_SYS_MOUNT_H
34#if HAVE_SYS_PARAM_H
35#include <sys/param.h>
36#endif
37// FreeBSD has a table of names of mount-options in mount.h, which is only
38// defined (as MNTOPT_NAMES) if _WANT_MNTOPTNAMES is defined.
39#define _WANT_MNTOPTNAMES
40#include <sys/mount.h>
41#undef _WANT_MNTOPTNAMES
42#endif
43
44#if HAVE_FSTAB_H
45#include <fstab.h>
46#endif
47
48// Linux
49#if HAVE_LIB_MOUNT
50#include <libmount/libmount.h>
51#endif
52
53static bool isNetfs(const QString &mountType)
54{
55 // List copied from util-linux/libmount/src/utils.c
56 static const std::vector<QLatin1String> netfsList{
57 QLatin1String("cifs"),
58 QLatin1String("smb3"),
59 QLatin1String("smbfs"),
60 QLatin1String("nfs"),
61 QLatin1String("nfs3"),
62 QLatin1String("nfs4"),
63 QLatin1String("afs"),
64 QLatin1String("ncpfs"),
65 QLatin1String("fuse.curlftpfs"),
66 QLatin1String("fuse.sshfs"),
67 QLatin1String("9p"),
68 };
69
70 return std::any_of(netfsList.cbegin(), netfsList.cend(), [mountType](const QLatin1String netfs) {
71 return mountType == netfs;
72 });
73}
74
75class KMountPointPrivate
76{
77public:
78 void resolveGvfsMountPoints(KMountPoint::List &result);
79 void finalizePossibleMountPoint(KMountPoint::DetailsNeededFlags infoNeeded);
80 void finalizeCurrentMountPoint(KMountPoint::DetailsNeededFlags infoNeeded);
81
82 QString m_mountedFrom;
83 QString m_device; // Only available when the NeedRealDeviceName flag was set.
84 QString m_mountPoint;
85 QString m_mountType;
86 QStringList m_mountOptions;
87 dev_t m_deviceId = 0;
88 bool m_isNetFs = false;
89};
90
91KMountPoint::KMountPoint()
92 : d(new KMountPointPrivate)
93{
94}
95
97
98#if HAVE_GETMNTINFO
99
100#ifdef MNTOPT_NAMES
101static struct mntoptnames bsdOptionNames[] = {MNTOPT_NAMES};
102
103/** @brief Get mount options from @p flags and puts human-readable version in @p list
104 *
105 * Appends all positive options found in @p flags to the @p list
106 * This is roughly paraphrased from FreeBSD's mount.c, prmount().
107 */
108static void translateMountOptions(QStringList &list, uint64_t flags)
109{
110 const struct mntoptnames *optionInfo = bsdOptionNames;
111
112 // Not all 64 bits are useful option names
113 flags = flags & MNT_VISFLAGMASK;
114 // Chew up options as long as we're in the table and there
115 // are any flags left.
116 for (; flags != 0 && optionInfo->o_opt != 0; ++optionInfo) {
117 if (flags & optionInfo->o_opt) {
118 list.append(QString::fromLatin1(optionInfo->o_name));
119 flags &= ~optionInfo->o_opt;
120 }
121 }
122}
123#else
124/** @brief Get mount options from @p flags and puts human-readable version in @p list
125 *
126 * This default version just puts the hex representation of @p flags
127 * in the list, because there is no human-readable version.
128 */
129static void translateMountOptions(QStringList &list, uint64_t flags)
130{
131 list.append(QStringLiteral("0x%1").arg(QString::number(flags, 16)));
132}
133#endif
134
135#endif // HAVE_GETMNTINFO
136
137void KMountPointPrivate::finalizePossibleMountPoint(KMountPoint::DetailsNeededFlags infoNeeded)
138{
139 QString potentialDevice;
140 if (const auto tag = QLatin1String("UUID="); m_mountedFrom.startsWith(tag)) {
141 potentialDevice = QFile::symLinkTarget(QLatin1String("/dev/disk/by-uuid/") + QStringView(m_mountedFrom).mid(tag.size()));
142 } else if (const auto tag = QLatin1String("LABEL="); m_mountedFrom.startsWith(tag)) {
143 potentialDevice = QFile::symLinkTarget(QLatin1String("/dev/disk/by-label/") + QStringView(m_mountedFrom).mid(tag.size()));
144 }
145
146 if (QFile::exists(potentialDevice)) {
147 m_mountedFrom = potentialDevice;
148 }
149
150 if (infoNeeded & KMountPoint::NeedRealDeviceName) {
151 if (m_mountedFrom.startsWith(QLatin1Char('/'))) {
152 m_device = QFileInfo(m_mountedFrom).canonicalFilePath();
153 }
154 }
155
156 // Chop trailing slash
157 Utils::removeTrailingSlash(m_mountedFrom);
158}
159
160void KMountPointPrivate::finalizeCurrentMountPoint(KMountPoint::DetailsNeededFlags infoNeeded)
161{
162 if (infoNeeded & KMountPoint::NeedRealDeviceName) {
163 if (m_mountedFrom.startsWith(QLatin1Char('/'))) {
164 m_device = QFileInfo(m_mountedFrom).canonicalFilePath();
165 }
166 }
167}
168
170{
171 KMountPoint::List result;
172
173#ifdef Q_OS_WIN
174 result = KMountPoint::currentMountPoints(infoNeeded);
175
176#elif HAVE_LIB_MOUNT
177 if (struct libmnt_table *table = mnt_new_table()) {
178 // By default parses "/etc/fstab"
179 if (mnt_table_parse_fstab(table, nullptr) == 0) {
180 struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD);
181 struct libmnt_fs *fs;
182
183 while (mnt_table_next_fs(table, itr, &fs) == 0) {
184 const char *fsType = mnt_fs_get_fstype(fs);
185 if (qstrcmp(fsType, "swap") == 0) {
186 continue;
187 }
188
189 Ptr mp(new KMountPoint);
190 mp->d->m_mountType = QFile::decodeName(fsType);
191 const char *target = mnt_fs_get_target(fs);
192 mp->d->m_mountPoint = QFile::decodeName(target);
193
194 if (QT_STATBUF buff; QT_LSTAT(target, &buff) == 0) {
195 mp->d->m_deviceId = buff.st_dev;
196 }
197
198 // First field in /etc/fstab, e.g. /dev/sdXY, LABEL=, UUID=, /some/bind/mount/dir
199 // or some network mount
200 if (const char *source = mnt_fs_get_source(fs)) {
201 mp->d->m_mountedFrom = QFile::decodeName(source);
202 }
203
204 if (infoNeeded & NeedMountOptions) {
205 mp->d->m_mountOptions = QFile::decodeName(mnt_fs_get_options(fs)).split(QLatin1Char(','));
206 }
207
208 mp->d->finalizePossibleMountPoint(infoNeeded);
209 result.append(mp);
210 }
211 mnt_free_iter(itr);
212 }
213
214 mnt_free_table(table);
215 }
216#elif HAVE_FSTAB_H
217
218 QFile f{QLatin1String(FSTAB)};
219 if (!f.open(QIODevice::ReadOnly)) {
220 return result;
221 }
222
223 QTextStream t(&f);
224 QString s;
225
226 while (!t.atEnd()) {
227 s = t.readLine().simplified();
228 if (s.isEmpty() || (s[0] == QLatin1Char('#'))) {
229 continue;
230 }
231
232 // not empty or commented out by '#'
233 const QStringList item = s.split(QLatin1Char(' '));
234
235 if (item.count() < 4) {
236 continue;
237 }
238
239 Ptr mp(new KMountPoint);
240
241 int i = 0;
242 mp->d->m_mountedFrom = item[i++];
243 mp->d->m_mountPoint = item[i++];
244 mp->d->m_mountType = item[i++];
245 if (mp->d->m_mountType == QLatin1String("swap")) {
246 continue;
247 }
248 QString options = item[i++];
249
250 if (infoNeeded & NeedMountOptions) {
251 mp->d->m_mountOptions = options.split(QLatin1Char(','));
252 }
253
254 mp->d->finalizePossibleMountPoint(infoNeeded);
255
256 result.append(mp);
257 } // while
258
259 f.close();
260#endif
261
262 return result;
263}
264
265void KMountPointPrivate::resolveGvfsMountPoints(KMountPoint::List &result)
266{
267 if (m_mountedFrom == QLatin1String("gvfsd-fuse")) {
268 const QDir gvfsDir(m_mountPoint);
269 const QStringList mountDirs = gvfsDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
270 for (const QString &mountDir : mountDirs) {
271 const QString type = mountDir.section(QLatin1Char(':'), 0, 0);
272 if (type.isEmpty()) {
273 continue;
274 }
275
276 KMountPoint::Ptr gvfsmp(new KMountPoint);
277 gvfsmp->d->m_mountedFrom = m_mountedFrom;
278 gvfsmp->d->m_mountPoint = m_mountPoint + QLatin1Char('/') + mountDir;
279 gvfsmp->d->m_mountType = type;
280 result.append(gvfsmp);
281 }
282 }
283}
284
286{
287 KMountPoint::List result;
288
289#if HAVE_GETMNTINFO
290
291#if GETMNTINFO_USES_STATVFS
292 struct statvfs *mounted;
293#else
294 struct statfs *mounted;
295#endif
296
297 int num_fs = getmntinfo(&mounted, MNT_NOWAIT);
298
299 result.reserve(num_fs);
300
301 for (int i = 0; i < num_fs; i++) {
302 Ptr mp(new KMountPoint);
303 mp->d->m_mountedFrom = QFile::decodeName(mounted[i].f_mntfromname);
304 mp->d->m_mountPoint = QFile::decodeName(mounted[i].f_mntonname);
305 mp->d->m_mountType = QFile::decodeName(mounted[i].f_fstypename);
306
307 if (QT_STATBUF buff; QT_LSTAT(mounted[i].f_mntonname, &buff) == 0) {
308 mp->d->m_deviceId = buff.st_dev;
309 }
310
311 if (infoNeeded & NeedMountOptions) {
312 struct fstab *ft = getfsfile(mounted[i].f_mntonname);
313 if (ft != nullptr) {
314 QString options = QFile::decodeName(ft->fs_mntops);
315 mp->d->m_mountOptions = options.split(QLatin1Char(','));
316 } else {
317 translateMountOptions(mp->d->m_mountOptions, mounted[i].f_flags);
318 }
319 }
320
321 mp->d->finalizeCurrentMountPoint(infoNeeded);
322 // TODO: Strip trailing '/' ?
323 result.append(mp);
324 }
325
326#elif defined(Q_OS_WIN)
327 // nothing fancy with infoNeeded but it gets the job done
328 DWORD bits = GetLogicalDrives();
329 if (!bits) {
330 return result;
331 }
332
333 for (int i = 0; i < 26; i++) {
334 if (bits & (1 << i)) {
335 Ptr mp(new KMountPoint);
336 mp->d->m_mountPoint = QString(QLatin1Char('A' + i) + QLatin1String(":/"));
337 result.append(mp);
338 }
339 }
340
341#elif HAVE_LIB_MOUNT
342 if (struct libmnt_table *table = mnt_new_table()) {
343 // if "/etc/mtab" is a regular file,
344 // "/etc/mtab" is used by default instead of "/proc/self/mountinfo" file.
345 // This leads to NTFS mountpoints being hidden.
346 if (
347#if QT_VERSION_CHECK(LIBMOUNT_MAJOR_VERSION, LIBMOUNT_MINOR_VERSION, LIBMOUNT_PATCH_VERSION) >= QT_VERSION_CHECK(2, 39, 0)
348 mnt_table_parse_mtab(table, nullptr)
349#else // backwards compat, the documentation advises to use nullptr so lets do that whenever possible
350 mnt_table_parse_mtab(table, "/proc/self/mountinfo")
351#endif
352 == 0) {
353 struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD);
354 struct libmnt_fs *fs;
355
356 while (mnt_table_next_fs(table, itr, &fs) == 0) {
357 Ptr mp(new KMountPoint);
358 mp->d->m_mountedFrom = QFile::decodeName(mnt_fs_get_source(fs));
359 mp->d->m_mountPoint = QFile::decodeName(mnt_fs_get_target(fs));
360 mp->d->m_mountType = QFile::decodeName(mnt_fs_get_fstype(fs));
361 mp->d->m_isNetFs = mnt_fs_is_netfs(fs) == 1;
362
363 // handle bind mounts
364 if (mp->d->m_mountedFrom != mp->d->m_mountPoint) {
365 if (QT_STATBUF buff; QT_LSTAT(mp->d->m_mountPoint.toLatin1().constData(), &buff) == 0) {
366 mp->d->m_deviceId = buff.st_dev;
367 }
368 } else {
369 mp->d->m_deviceId = mnt_fs_get_devno(fs);
370 }
371
372 if (infoNeeded & NeedMountOptions) {
373 mp->d->m_mountOptions = QFile::decodeName(mnt_fs_get_options(fs)).split(QLatin1Char(','));
374 }
375
376 if (infoNeeded & NeedRealDeviceName) {
377 if (mp->d->m_mountedFrom.startsWith(QLatin1Char('/'))) {
378 mp->d->m_device = mp->d->m_mountedFrom;
379 }
380 }
381
382 mp->d->resolveGvfsMountPoints(result);
383
384 mp->d->finalizeCurrentMountPoint(infoNeeded);
385 result.push_back(mp);
386 }
387
388 mnt_free_iter(itr);
389 }
390
391 mnt_free_table(table);
392 }
393#endif
394
395 return result;
396}
397
399{
400 return d->m_mountedFrom;
401}
402
404{
405 return d->m_deviceId;
406}
407
409{
410 return d->m_isNetFs || isNetfs(d->m_mountType);
411}
412
414{
415 return d->m_device;
416}
417
419{
420 return d->m_mountPoint;
421}
422
424{
425 return d->m_mountType;
426}
427
429{
430 return d->m_mountOptions;
431}
432
433KMountPoint::List::List()
434 : QList<Ptr>()
435{
436}
437
438KMountPoint::Ptr KMountPoint::List::findByPath(const QString &path) const
439{
440#ifdef Q_OS_WIN
441 const QString realPath = QDir::fromNativeSeparators(QDir(path).absolutePath());
442#else
443 /* If the path contains symlinks, get the real name */
444 QFileInfo fileinfo(path);
445 // canonicalFilePath won't work unless file exists
446 const QString realPath = fileinfo.exists() ? fileinfo.canonicalFilePath() : fileinfo.absolutePath();
447#endif
448
449 KMountPoint::Ptr result;
450
451 if (QT_STATBUF buff; QT_LSTAT(QFile::encodeName(realPath).constData(), &buff) == 0) {
452 auto it = std::find_if(this->cbegin(), this->cend(), [&buff, &realPath](const KMountPoint::Ptr &mountPtr) {
453 // For a bind mount, the deviceId() is that of the base mount point, e.g. /mnt/foo,
454 // however the path we're looking for, e.g. /home/user/bar, doesn't start with the
455 // mount point of the base device, so we go on searching
456 return mountPtr->deviceId() == buff.st_dev && realPath.startsWith(mountPtr->mountPoint());
457 });
458
459 if (it != this->cend()) {
460 result = *it;
461 }
462 }
463
464 return result;
465}
466
467KMountPoint::Ptr KMountPoint::List::findByDevice(const QString &device) const
468{
469 const QString realDevice = QFileInfo(device).canonicalFilePath();
470 if (realDevice.isEmpty()) { // d->m_device can be empty in the loop below, don't match empty with it
471 return Ptr();
472 }
473 for (const KMountPoint::Ptr &mountPoint : *this) {
474 if (realDevice.compare(mountPoint->d->m_device, cs) == 0 || realDevice.compare(mountPoint->d->m_mountedFrom, cs) == 0) {
475 return mountPoint;
476 }
477 }
478 return Ptr();
479}
480
482{
483 /* clang-format off */
484 return isOnNetwork()
485 || d->m_mountType == QLatin1String("autofs")
486 || d->m_mountType == QLatin1String("subfs")
487 // Technically KIOFUSe mounts local workers as well,
488 // such as recents:/, but better safe than sorry...
489 || d->m_mountType == QLatin1String("fuse.kio-fuse");
490 /* clang-format on */
491}
492
493bool KMountPoint::testFileSystemFlag(FileSystemFlag flag) const
494{
495 /* clang-format off */
496 const bool isMsDos = d->m_mountType == QLatin1String("msdos")
497 || d->m_mountType == QLatin1String("fat")
498 || d->m_mountType == QLatin1String("vfat");
499
500 const bool isNtfs = d->m_mountType.contains(QLatin1String("fuse.ntfs"))
501 || d->m_mountType.contains(QLatin1String("fuseblk.ntfs"))
502 // fuseblk could really be anything. But its most common use is for NTFS mounts, these days.
503 || d->m_mountType == QLatin1String("fuseblk");
504
505 const bool isSmb = d->m_mountType == QLatin1String("cifs")
506 || d->m_mountType == QLatin1String("smb3")
507 || d->m_mountType == QLatin1String("smbfs")
508 // gvfs-fuse mounted SMB share
509 || d->m_mountType == QLatin1String("smb-share");
510 /* clang-format on */
511
512 switch (flag) {
513 case SupportsChmod:
514 case SupportsChown:
515 case SupportsUTime:
516 case SupportsSymlinks:
517 return !isMsDos && !isNtfs && !isSmb; // it's amazing the number of things Microsoft filesystems don't support :)
518 case CaseInsensitive:
519 return isMsDos;
520 }
521 return false;
522}
523
524KIOCORE_EXPORT QDebug operator<<(QDebug debug, const KMountPoint::Ptr &mp)
525{
526 QDebugStateSaver saver(debug);
527 if (!mp) {
528 debug << "QDebug operator<< called on a null KMountPoint::Ptr";
529 return debug;
530 }
531
532 // clang-format off
533 debug.nospace() << "KMountPoint ["
534 << "Mounted from: " << mp->d->m_mountedFrom
535 << ", device name: " << mp->d->m_device
536 << ", mount point: " << mp->d->m_mountPoint
537 << ", mount type: " << mp->d->m_mountType
538 <<']';
539
540 // clang-format on
541 return debug;
542}
List of mount points.
Definition kmountpoint.h:39
Ptr findByPath(const QString &path) const
Find the mountpoint on which resides path For instance if /home is a separate partition,...
Ptr findByDevice(const QString &device) const
Returns the mount point associated with device, i.e.
bool testFileSystemFlag(FileSystemFlag flag) const
Checks the capabilities of the filesystem.
@ NeedRealDeviceName
Also fetch the device name (with symlinks resolved), see KMountPoint::realDeviceName().
Definition kmountpoint.h:77
@ NeedMountOptions
Also fetch the options used when mounting, see KMountPoint::mountOptions().
Definition kmountpoint.h:73
QString realDeviceName() const
Canonical name of the device where the filesystem got mounted from.
QString mountType() const
Type of filesystem.
QString mountPoint() const
Path where the filesystem is mounted (if you used currentMountPoints()), or can be mounted (if you us...
~KMountPoint()
Destructor.
bool probablySlow() const
Returns true if the filesystem is "probably" slow, e.g. a network mount, false otherwise.
static List currentMountPoints(DetailsNeededFlags infoNeeded=BasicInfoNeeded)
Returns a list of all current mountpoints.
QStringList mountOptions() const
Options used to mount the filesystem.
QString mountedFrom() const
Where this filesystem gets mounted from.
QFlags< DetailsNeededFlag > DetailsNeededFlags
Stores a combination of DetailsNeededFlag values.
Definition kmountpoint.h:82
static List possibleMountPoints(DetailsNeededFlags infoNeeded=BasicInfoNeeded)
This function gives a list of all possible mountpoints.
dev_t deviceId() const
Returns the device ID (dev_t, major, minor) of this mount point.
bool isOnNetwork() const
Returns true if this mount point represents a network filesystem (e.g. NFS, CIFS, etc....
Type type(const QSqlDatabase &db)
KIOCORE_EXPORT QStringList list(const QString &fileClass)
Returns a list of directories associated with this file-class.
QDebug & nospace()
QString fromNativeSeparators(const QString &pathName)
QString decodeName(const QByteArray &localFileName)
QByteArray encodeName(const QString &fileName)
bool exists() const const
QString symLinkTarget() const const
QString absolutePath() const const
QString canonicalFilePath() const const
bool exists(const QString &path)
void append(QList< T > &&value)
const_iterator cbegin() const const
const_iterator cend() const const
const_pointer constData() const const
qsizetype count() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
int compare(QLatin1StringView s1, const QString &s2, Qt::CaseSensitivity cs)
QString fromLatin1(QByteArrayView str)
bool isEmpty() const const
QString number(double n, char format, int precision)
QString simplified() const const
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
CaseSensitivity
bool atEnd() const const
QString readLine(qint64 maxlen)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Feb 21 2025 11:50:09 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.