KIO

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

KDE's Doxygen guidelines are available online.