1/*
2 * This file is part of the KDE libraries
3 * Copyright (c) 2003 Waldo Bastian <bastian@kde.org>
4 * 2007 David Faure <faure@kde.org>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License version 2 as published by the Free Software Foundation.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 */
20
21#include "kmountpoint.h"
22
23#include <stdlib.h>
24
25#include <config-kmountpoint.h>
26
27#include <QDir>
28#include <QFile>
29#include <QTextStream>
30#include <QFileInfo>
31
32#ifdef Q_OS_WIN
33#include <qt_windows.h>
34#endif
35
36#ifdef Q_OS_WIN
37static const Qt::CaseSensitivity cs = Qt::CaseInsensitive;
38#else
39static const Qt::CaseSensitivity cs = Qt::CaseSensitive;
40#endif
41
42#if HAVE_VOLMGT
43#include <volmgt.h>
44#endif
45#if HAVE_SYS_MNTTAB_H
46#include <sys/mnttab.h>
47#endif
48#if HAVE_MNTENT_H
49#include <mntent.h>
50#elif HAVE_SYS_MNTENT_H
51#include <sys/mntent.h>
52#endif
53
54// This is the *BSD branch
55#if HAVE_SYS_MOUNT_H
56#if HAVE_SYS_TYPES_H
57#include <sys/types.h>
58#endif
59#if HAVE_SYS_PARAM_H
60#include <sys/param.h>
61#endif
62#include <sys/mount.h>
63#endif
64
65#if HAVE_FSTAB_H
66#include <fstab.h>
67#endif
68
69#if ! HAVE_GETMNTINFO
70# ifdef _PATH_MOUNTED
71// On some Linux, MNTTAB points to /etc/fstab !
72# undef MNTTAB
73# define MNTTAB _PATH_MOUNTED
74# else
75# ifndef MNTTAB
76# ifdef MTAB_FILE
77# define MNTTAB MTAB_FILE
78# else
79# define MNTTAB "/etc/mnttab"
80# endif
81# endif
82# endif
83#endif
84
85#ifdef _OS_SOLARIS_
86#define FSTAB "/etc/vfstab"
87#else
88#define FSTAB "/etc/fstab"
89#endif
90
91class Q_DECL_HIDDEN KMountPoint::Private
92{
93public:
94 void finalizePossibleMountPoint(DetailsNeededFlags infoNeeded);
95 void finalizeCurrentMountPoint(DetailsNeededFlags infoNeeded);
96
97 QString mountedFrom;
98 QString device; // Only available when the NeedRealDeviceName flag was set.
99 QString mountPoint;
100 QString mountType;
101 QStringList mountOptions;
102};
103
104KMountPoint::KMountPoint()
105 : d(new Private)
106{
107}
108
109KMountPoint::~KMountPoint()
110{
111 delete d;
112}
113
114// There are (at least) four kind of APIs:
115// setmntent + getmntent + struct mntent (linux...)
116// getmntent + struct mnttab
117// getmntinfo + struct statfs&flags (BSD 4.4 and friends)
118// getfsent + char* (BSD 4.3 and friends)
119
120#if HAVE_SETMNTENT
121#define SETMNTENT setmntent
122#define ENDMNTENT endmntent
123#define STRUCT_MNTENT struct mntent *
124#define STRUCT_SETMNTENT FILE *
125#define GETMNTENT(file, var) ((var = getmntent(file)) != nullptr)
126#define MOUNTPOINT(var) var->mnt_dir
127#define MOUNTTYPE(var) var->mnt_type
128#define MOUNTOPTIONS(var) var->mnt_opts
129#define FSNAME(var) var->mnt_fsname
130#else
131#define SETMNTENT fopen
132#define ENDMNTENT fclose
133#define STRUCT_MNTENT struct mnttab
134#define STRUCT_SETMNTENT FILE *
135#define GETMNTENT(file, var) (getmntent(file, &var) == nullptr)
136#define MOUNTPOINT(var) var.mnt_mountp
137#define MOUNTTYPE(var) var.mnt_fstype
138#define MOUNTOPTIONS(var) var.mnt_mntopts
139#define FSNAME(var) var.mnt_special
140#endif
141
142void KMountPoint::Private::finalizePossibleMountPoint(DetailsNeededFlags infoNeeded)
143{
144 if (mountedFrom.startsWith(QLatin1String("UUID="))) {
145 const QStringRef uuid = mountedFrom.midRef(5);
146 const QString potentialDevice = QFile::symLinkTarget(QLatin1String("/dev/disk/by-uuid/") + uuid);
147 if (QFile::exists(potentialDevice)) {
148 mountedFrom = potentialDevice;
149 }
150 }
151 if (mountedFrom.startsWith(QLatin1String("LABEL="))) {
152 const QStringRef label = mountedFrom.midRef(6);
153 const QString potentialDevice = QFile::symLinkTarget(QLatin1String("/dev/disk/by-label/") + label);
154 if (QFile::exists(potentialDevice)) {
155 mountedFrom = potentialDevice;
156 }
157 }
158
159 if (infoNeeded & NeedRealDeviceName) {
160 if (mountedFrom.startsWith(QLatin1Char('/'))) {
161 device = QFileInfo(mountedFrom).canonicalFilePath();
162 }
163 }
164
165 // Chop trailing slash
166 if (mountedFrom.endsWith(QLatin1Char('/'))) {
167 mountedFrom.chop(1);
168 }
169}
170
171void KMountPoint::Private::finalizeCurrentMountPoint(DetailsNeededFlags infoNeeded)
172{
173 if (infoNeeded & NeedRealDeviceName) {
174 if (mountedFrom.startsWith(QLatin1Char('/'))) {
175 device = QFileInfo(mountedFrom).canonicalFilePath();
176 }
177 }
178}
179
180KMountPoint::List KMountPoint::possibleMountPoints(DetailsNeededFlags infoNeeded)
181{
182#ifdef Q_OS_WIN
183 return KMountPoint::currentMountPoints(infoNeeded);
184#endif
185
186 KMountPoint::List result;
187
188#if HAVE_SETMNTENT
189 STRUCT_SETMNTENT fstab;
190 if ((fstab = SETMNTENT(FSTAB, "r")) == nullptr) {
191 return result;
192 }
193
194 STRUCT_MNTENT fe;
195 while (GETMNTENT(fstab, fe)) {
196 Ptr mp(new KMountPoint);
197 mp->d->mountedFrom = QFile::decodeName(FSNAME(fe));
198
199 mp->d->mountPoint = QFile::decodeName(MOUNTPOINT(fe));
200 mp->d->mountType = QFile::decodeName(MOUNTTYPE(fe));
201
202 if (infoNeeded & NeedMountOptions) {
203 QString options = QFile::decodeName(MOUNTOPTIONS(fe));
204 mp->d->mountOptions = options.split(QLatin1Char(','));
205 }
206
207 mp->d->finalizePossibleMountPoint(infoNeeded);
208
209 result.append(mp);
210 }
211 ENDMNTENT(fstab);
212#else
213 QFile f(QLatin1String(FSTAB));
214 if (!f.open(QIODevice::ReadOnly)) {
215 return result;
216 }
217
218 QTextStream t(&f);
219 QString s;
220
221 while (! t.atEnd()) {
222 s = t.readLine().simplified();
223 if (s.isEmpty() || (s[0] == QLatin1Char('#'))) {
224 continue;
225 }
226
227 // not empty or commented out by '#'
228 const QStringList item = s.split(QLatin1Char(' '));
229
230#ifdef _OS_SOLARIS_
231 if (item.count() < 5) {
232 continue;
233 }
234#else
235 if (item.count() < 4) {
236 continue;
237 }
238#endif
239
240 Ptr mp(new KMountPoint);
241
242 int i = 0;
243 mp->d->mountedFrom = item[i++];
244#ifdef _OS_SOLARIS_
245 //device to fsck
246 i++;
247#endif
248 mp->d->mountPoint = item[i++];
249 mp->d->mountType = item[i++];
250 QString options = item[i++];
251
252 if (infoNeeded & NeedMountOptions) {
253 mp->d->mountOptions = options.split(QLatin1Char(','));
254 }
255
256 mp->d->finalizePossibleMountPoint(infoNeeded);
257
258 result.append(mp);
259 } //while
260
261 f.close();
262#endif
263 return result;
264}
265
266KMountPoint::List KMountPoint::currentMountPoints(DetailsNeededFlags infoNeeded)
267{
268 KMountPoint::List result;
269
270#if HAVE_GETMNTINFO
271
272#if GETMNTINFO_USES_STATVFS
273 struct statvfs *mounted;
274#else
275 struct statfs *mounted;
276#endif
277
278 int num_fs = getmntinfo(&mounted, MNT_NOWAIT);
279
280 for (int i = 0; i < num_fs; i++) {
281 Ptr mp(new KMountPoint);
282 mp->d->mountedFrom = QFile::decodeName(mounted[i].f_mntfromname);
283 mp->d->mountPoint = QFile::decodeName(mounted[i].f_mntonname);
284
285#ifdef __osf__
286 mp->d->mountType = QFile::decodeName(mnt_names[mounted[i].f_type]);
287#else
288 mp->d->mountType = QFile::decodeName(mounted[i].f_fstypename);
289#endif
290
291 if (infoNeeded & NeedMountOptions) {
292 struct fstab *ft = getfsfile(mounted[i].f_mntonname);
293 if (ft != 0) {
294 QString options = QFile::decodeName(ft->fs_mntops);
295 mp->d->mountOptions = options.split(QLatin1Char(','));
296 } else {
297 // TODO: get mount options if not mounted via fstab, see mounted[i].f_flags
298 }
299 }
300
301 mp->d->finalizeCurrentMountPoint(infoNeeded);
302 // TODO: Strip trailing '/' ?
303 result.append(mp);
304 }
305
306#elif defined(Q_OS_WIN)
307 //nothing fancy with infoNeeded but it gets the job done
308 DWORD bits = GetLogicalDrives();
309 if (!bits) {
310 return result;
311 }
312
313 for (int i = 0; i < 26; i++) {
314 if (bits & (1 << i)) {
315 Ptr mp(new KMountPoint);
316 mp->d->mountPoint = QString(QLatin1Char('A' + i) + QLatin1String(":/"));
317 result.append(mp);
318 }
319 }
320
321#elif !defined(Q_OS_ANDROID)
322 STRUCT_SETMNTENT mnttab;
323 if ((mnttab = SETMNTENT(MNTTAB, "r")) == nullptr) {
324 return result;
325 }
326
327 STRUCT_MNTENT fe;
328 while (GETMNTENT(mnttab, fe)) {
329 Ptr mp(new KMountPoint);
330 mp->d->mountedFrom = QFile::decodeName(FSNAME(fe));
331
332 mp->d->mountPoint = QFile::decodeName(MOUNTPOINT(fe));
333 mp->d->mountType = QFile::decodeName(MOUNTTYPE(fe));
334
335 if (infoNeeded & NeedMountOptions) {
336 QString options = QFile::decodeName(MOUNTOPTIONS(fe));
337 mp->d->mountOptions = options.split(QLatin1Char(','));
338 }
339
340 // Resolve gvfs mountpoints
341 if (mp->d->mountedFrom == QLatin1String("gvfsd-fuse")) {
342 const QDir gvfsDir(mp->d->mountPoint);
343 const QStringList mountDirs = gvfsDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
344 for (const auto &mountDir : mountDirs) {
345 const QString type = mountDir.section(QLatin1Char(':'), 0, 0);
346 if (type.isEmpty()) {
347 continue;
348 }
349
350 Ptr gvfsmp(new KMountPoint);
351 gvfsmp->d->mountedFrom = mp->d->mountedFrom;
352 gvfsmp->d->mountPoint = mp->d->mountPoint + QLatin1Char('/') + mountDir;
353 gvfsmp->d->mountType = type;
354 result.append(gvfsmp);
355 }
356 }
357
358 mp->d->finalizeCurrentMountPoint(infoNeeded);
359
360 result.append(mp);
361 }
362 ENDMNTENT(mnttab);
363#endif
364 return result;
365}
366
367QString KMountPoint::mountedFrom() const
368{
369 return d->mountedFrom;
370}
371
372QString KMountPoint::realDeviceName() const
373{
374 return d->device;
375}
376
377QString KMountPoint::mountPoint() const
378{
379 return d->mountPoint;
380}
381
382QString KMountPoint::mountType() const
383{
384 return d->mountType;
385}
386
387QStringList KMountPoint::mountOptions() const
388{
389 return d->mountOptions;
390}
391
392KMountPoint::List::List()
393 : QList<Ptr>()
394{
395}
396
397static bool pathsAreParentAndChildOrEqual(const QString &parent, const QString &child)
398{
399 const QLatin1Char slash('/');
400 if (child.startsWith(parent, cs)) {
401 // Check if either
402 // (a) both paths are equal, or
403 // (b) parent ends with '/', or
404 // (c) the first character of child that is not shared with parent is '/'.
405 // Note that child is guaranteed to be longer than parent if (a) is false.
406 //
407 // This prevents that we incorrectly consider "/books" a child of "/book".
408 return parent.compare(child, cs) == 0 || parent.endsWith(slash) || child.at(parent.length()) == slash;
409 } else {
410 // Note that "/books" is a child of "/books/".
411 return parent.endsWith(slash) && (parent.length() == child.length() + 1) && parent.startsWith(child, cs);
412 }
413}
414
415KMountPoint::Ptr KMountPoint::List::findByPath(const QString &path) const
416{
417#ifndef Q_OS_WIN
418 /* If the path contains symlinks, get the real name */
419 QFileInfo fileinfo(path);
420 const QString realname = fileinfo.exists()
421 ? fileinfo.canonicalFilePath()
422 : fileinfo.absolutePath(); //canonicalFilePath won't work unless file exists
423#else
424 const QString realname = QDir::fromNativeSeparators(QDir(path).absolutePath());
425#endif
426
427 int max = 0;
428 KMountPoint::Ptr result;
429 for (const_iterator it = begin(); it != end(); ++it) {
430 const QString mountpoint = (*it)->d->mountPoint;
431 const int length = mountpoint.length();
432 if (length > max && pathsAreParentAndChildOrEqual(mountpoint, realname)) {
433 max = length;
434 result = *it;
435 // keep iterating to check for a better match (bigger max)
436 }
437 }
438 return result;
439}
440
441KMountPoint::Ptr KMountPoint::List::findByDevice(const QString &device) const
442{
443 const QString realDevice = QFileInfo(device).canonicalFilePath();
444 if (realDevice.isEmpty()) { // d->device can be empty in the loop below, don't match empty with it
445 return Ptr();
446 }
447 for (const_iterator it = begin(); it != end(); ++it) {
448 if (realDevice.compare((*it)->d->device, cs) == 0 ||
449 realDevice.compare((*it)->d->mountedFrom, cs) == 0) {
450 return *it;
451 }
452 }
453 return Ptr();
454}
455
456bool KMountPoint::probablySlow() const
457{
458 return d->mountType == QLatin1String("nfs")
459 || d->mountType == QLatin1String("nfs4")
460 || d->mountType == QLatin1String("cifs")
461 || d->mountType == QLatin1String("autofs")
462 || d->mountType == QLatin1String("subfs");
463}
464
465bool KMountPoint::testFileSystemFlag(FileSystemFlag flag) const
466{
467 const bool isMsDos = (d->mountType == QLatin1String("msdos") || d->mountType == QLatin1String("fat") || d->mountType == QLatin1String("vfat"));
468 const bool isNtfs = d->mountType.contains(QLatin1String("fuse.ntfs")) || d->mountType.contains(QLatin1String("fuseblk.ntfs"))
469 // fuseblk could really be anything. But its most common use is for NTFS mounts, these days.
470 || d->mountType == QLatin1String("fuseblk");
471 const bool isSmb = d->mountType == QLatin1String("cifs")
472 || d->mountType == QLatin1String("smbfs")
473 // gvfs-fuse mounted SMB share
474 || d->mountType == QLatin1String("smb-share");
475
476 switch (flag) {
477 case SupportsChmod:
478 case SupportsChown:
479 case SupportsUTime:
480 case SupportsSymlinks:
481 return !isMsDos && !isNtfs && !isSmb; // it's amazing the number of things Microsoft filesystems don't support :)
482 case CaseInsensitive:
483 return isMsDos;
484 }
485 return false;
486}
487
488