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

KDE's Doxygen guidelines are available online.