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 mp->d->m_deviceId = mnt_fs_get_devno(fs);
363
364 if (infoNeeded & NeedMountOptions) {
365 mp->d->m_mountOptions = QFile::decodeName(mnt_fs_get_options(fs)).split(QLatin1Char(','));
366 }
367
368 if (infoNeeded & NeedRealDeviceName) {
369 if (mp->d->m_mountedFrom.startsWith(QLatin1Char('/'))) {
370 mp->d->m_device = mp->d->m_mountedFrom;
371 }
372 }
373
374 mp->d->resolveGvfsMountPoints(result);
375
376 mp->d->finalizeCurrentMountPoint(infoNeeded);
377 result.push_back(mp);
378 }
379
380 mnt_free_iter(itr);
381 }
382
383 mnt_free_table(table);
384 }
385#endif
386
387 return result;
388}
389
391{
392 return d->m_mountedFrom;
393}
394
396{
397 return d->m_deviceId;
398}
399
401{
402 return d->m_isNetFs || isNetfs(d->m_mountType);
403}
404
406{
407 return d->m_device;
408}
409
411{
412 return d->m_mountPoint;
413}
414
416{
417 return d->m_mountType;
418}
419
421{
422 return d->m_mountOptions;
423}
424
425KMountPoint::List::List()
426 : QList<Ptr>()
427{
428}
429
431{
432#ifdef Q_OS_WIN
433 const QString realPath = QDir::fromNativeSeparators(QDir(path).absolutePath());
434#else
435 /* If the path contains symlinks, get the real name */
436 QFileInfo fileinfo(path);
437 // canonicalFilePath won't work unless file exists
438 const QString realPath = fileinfo.exists() ? fileinfo.canonicalFilePath() : fileinfo.absolutePath();
439#endif
440
441 KMountPoint::Ptr result;
442
443 if (QT_STATBUF buff; QT_LSTAT(QFile::encodeName(realPath).constData(), &buff) == 0) {
444 auto it = std::find_if(this->cbegin(), this->cend(), [&buff, &realPath](const KMountPoint::Ptr &mountPtr) {
445 // For a bind mount, the deviceId() is that of the base mount point, e.g. /mnt/foo,
446 // however the path we're looking for, e.g. /home/user/bar, doesn't start with the
447 // mount point of the base device, so we go on searching
448 return mountPtr->deviceId() == buff.st_dev && realPath.startsWith(mountPtr->mountPoint());
449 });
450
451 if (it != this->cend()) {
452 result = *it;
453 }
454 }
455
456 return result;
457}
458
460{
461 const QString realDevice = QFileInfo(device).canonicalFilePath();
462 if (realDevice.isEmpty()) { // d->m_device can be empty in the loop below, don't match empty with it
463 return Ptr();
464 }
465 for (const KMountPoint::Ptr &mountPoint : *this) {
466 if (realDevice.compare(mountPoint->d->m_device, cs) == 0 || realDevice.compare(mountPoint->d->m_mountedFrom, cs) == 0) {
467 return mountPoint;
468 }
469 }
470 return Ptr();
471}
472
474{
475 /* clang-format off */
476 return isOnNetwork()
477 || d->m_mountType == QLatin1String("autofs")
478 || d->m_mountType == QLatin1String("subfs")
479 // Technically KIOFUSe mounts local workers as well,
480 // such as recents:/, but better safe than sorry...
481 || d->m_mountType == QLatin1String("fuse.kio-fuse");
482 /* clang-format on */
483}
484
485bool KMountPoint::testFileSystemFlag(FileSystemFlag flag) const
486{
487 /* clang-format off */
488 const bool isMsDos = d->m_mountType == QLatin1String("msdos")
489 || d->m_mountType == QLatin1String("fat")
490 || d->m_mountType == QLatin1String("vfat");
491
492 const bool isNtfs = d->m_mountType.contains(QLatin1String("fuse.ntfs"))
493 || d->m_mountType.contains(QLatin1String("fuseblk.ntfs"))
494 // fuseblk could really be anything. But its most common use is for NTFS mounts, these days.
495 || d->m_mountType == QLatin1String("fuseblk");
496
497 const bool isSmb = d->m_mountType == QLatin1String("cifs")
498 || d->m_mountType == QLatin1String("smb3")
499 || d->m_mountType == QLatin1String("smbfs")
500 // gvfs-fuse mounted SMB share
501 || d->m_mountType == QLatin1String("smb-share");
502 /* clang-format on */
503
504 switch (flag) {
505 case SupportsChmod:
506 case SupportsChown:
507 case SupportsUTime:
508 case SupportsSymlinks:
509 return !isMsDos && !isNtfs && !isSmb; // it's amazing the number of things Microsoft filesystems don't support :)
510 case CaseInsensitive:
511 return isMsDos;
512 }
513 return false;
514}
515
516KIOCORE_EXPORT QDebug operator<<(QDebug debug, const KMountPoint::Ptr &mp)
517{
518 QDebugStateSaver saver(debug);
519 if (!mp) {
520 debug << "QDebug operator<< called on a null KMountPoint::Ptr";
521 return debug;
522 }
523
524 // clang-format off
525 debug.nospace() << "KMountPoint ["
526 << "Mounted from: " << mp->d->m_mountedFrom
527 << ", device name: " << mp->d->m_device
528 << ", mount point: " << mp->d->m_mountPoint
529 << ", mount type: " << mp->d->m_mountType
530 <<']';
531
532 // clang-format on
533 return debug;
534}
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.
The KMountPoint class provides information about mounted and unmounted disks.
Definition kmountpoint.h:31
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.
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-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:16:27 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.