Solid

fstabhandling.cpp
1/*
2 SPDX-FileCopyrightText: 2006-2010 Kevin Ottens <ervin@kde.org>
3 SPDX-FileCopyrightText: 2010 Mario Bensi <mbensi@ipsquad.net>
4
5 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6*/
7
8#include "fstabhandling.h"
9#include "fstab_debug.h"
10
11#include <QFile>
12#include <QObject>
13#include <QProcess>
14#include <QRegularExpression>
15#include <QStandardPaths>
16#include <QTextStream>
17#include <QThreadStorage>
18
19#include <solid/devices/soliddefs_p.h>
20
21#include <solid/config-solid.h>
22#include <stdlib.h>
23
24#if HAVE_SYS_MNTTAB_H
25#include <sys/mnttab.h>
26#endif
27#if HAVE_MNTENT_H
28#include <mntent.h>
29#elif defined(HAVE_SYS_MNTENT_H)
30#include <sys/mntent.h>
31#endif
32
33// This is the *BSD branch
34#if HAVE_SYS_MOUNT_H
35#if HAVE_SYS_TYPES_H
36#include <sys/types.h>
37#endif
38#if HAVE_SYS_PARAM_H
39#include <sys/param.h>
40#endif
41#include <sys/mount.h>
42#endif
43
44#ifdef Q_OS_SOLARIS
45#define FSTAB "/etc/vfstab"
46#else
47#define FSTAB "/etc/fstab"
48#endif
49
50#if !HAVE_GETMNTINFO
51#ifdef _PATH_MOUNTED
52// On some Linux, MNTTAB points to /etc/fstab !
53#undef MNTTAB
54#define MNTTAB _PATH_MOUNTED
55#else
56#ifndef MNTTAB
57#ifdef MTAB_FILE
58#define MNTTAB MTAB_FILE
59#else
60#define MNTTAB "/etc/mnttab"
61#endif
62#endif
63#endif
64#endif
65
66// There are (at least) four kind of APIs:
67// setmntent + getmntent + struct mntent (linux...)
68// getmntent + struct mnttab
69// getmntinfo + struct statfs&flags (BSD 4.4 and friends)
70// getfsent + char* (BSD 4.3 and friends)
71
72#if HAVE_SETMNTENT
73#define SETMNTENT setmntent
74#define ENDMNTENT endmntent
75#define STRUCT_MNTENT struct mntent *
76#define STRUCT_SETMNTENT FILE *
77#define GETMNTENT(file, var) ((var = getmntent(file)) != nullptr)
78#define MOUNTPOINT(var) var->mnt_dir
79#define MOUNTTYPE(var) var->mnt_type
80#define MOUNTOPTIONS(var) var->mnt_opts
81#define FSNAME(var) var->mnt_fsname
82#else
83#define SETMNTENT fopen
84#define ENDMNTENT fclose
85#define STRUCT_MNTENT struct mnttab
86#define STRUCT_SETMNTENT FILE *
87#define GETMNTENT(file, var) (getmntent(file, &var) == nullptr)
88#define MOUNTPOINT(var) var.mnt_mountp
89#define MOUNTTYPE(var) var.mnt_fstype
90#define MOUNTOPTIONS(var) var.mnt_mntopts
91#define FSNAME(var) var.mnt_special
92#endif
93
94Q_GLOBAL_STATIC(QThreadStorage<Solid::Backends::Fstab::FstabHandling>, globalFstabCache)
95
96Solid::Backends::Fstab::FstabHandling::FstabHandling()
97 : m_fstabCacheValid(false)
98 , m_mtabCacheValid(false)
99{
100}
101
102bool _k_isFstabNetworkFileSystem(const QString &fstype, const QString &devName)
103{
104 if (fstype == "nfs" //
105 || fstype == "nfs4" //
106 || fstype == "smbfs" //
107 || fstype == "cifs" //
108 || fstype == "smb3" //
109 || fstype == "fuse.sshfs" //
110 || devName.startsWith(QLatin1String("//"))) {
111 return true;
112 }
113 return false;
114}
115
116bool _k_isFstabSupportedLocalFileSystem(const QString &fstype)
117{
118 if (fstype == "fuse.encfs" //
119 || fstype == "fuse.cryfs" //
120 || fstype == "fuse.gocryptfs" //
121 || fstype == "overlay") {
122 return true;
123 }
124 return false;
125}
126
127QString _k_deviceNameForMountpoint(const QString &source, const QString &fstype, const QString &mountpoint)
128{
129 if (fstype.startsWith("fuse.") || fstype == QLatin1String("overlay")) {
130 return fstype + mountpoint;
131 }
132 // A source may be mounted several times, e.g. with different
133 // options, often a network share with different credentials
134 // for different users. Make sure it is unique by appending the
135 // mountpoint (which is unique).
136
137 auto _mountpoint = mountpoint;
138 if (fstype == "nfs" || fstype == "nfs4") {
139 if (!mountpoint.startsWith(QLatin1Char('/'))) {
140 // making sure mount point starts with /
141 _mountpoint.prepend(QLatin1Char('/'));
142 }
143 }
144 return source + QLatin1Char(':') + _mountpoint;
145}
146
147void Solid::Backends::Fstab::FstabHandling::_k_updateFstabMountPointsCache()
148{
149 if (globalFstabCache->localData().m_fstabCacheValid) {
150 return;
151 }
152
153 globalFstabCache->localData().m_fstabCache.clear();
154 globalFstabCache->localData().m_fstabOptionsCache.clear();
155
156#if HAVE_SETMNTENT
157
158 FILE *fstab;
159 if ((fstab = setmntent(FSTAB, "r")) == nullptr) {
160 return;
161 }
162
163 struct mntent *fe;
164 while ((fe = getmntent(fstab)) != nullptr) {
165 const QString fsname = QFile::decodeName(fe->mnt_fsname);
166 const QString fstype = QFile::decodeName(fe->mnt_type);
167 if (_k_isFstabNetworkFileSystem(fstype, fsname) || _k_isFstabSupportedLocalFileSystem(fstype)) {
168 const QString mountpoint = QFile::decodeName(fe->mnt_dir);
169 const QString device = _k_deviceNameForMountpoint(fsname, fstype, mountpoint);
170 QStringList options = QFile::decodeName(fe->mnt_opts).split(QLatin1Char(','));
171
172 globalFstabCache->localData().m_fstabCache.insert(device, mountpoint);
173 globalFstabCache->localData().m_fstabFstypeCache.insert(device, fstype);
174 while (!options.isEmpty()) {
175 globalFstabCache->localData().m_fstabOptionsCache.insert(device, options.takeFirst());
176 }
177 }
178 }
179
180 endmntent(fstab);
181
182#else
183
184 QFile fstab(FSTAB);
185 if (!fstab.open(QIODevice::ReadOnly)) {
186 return;
187 }
188
189 QTextStream stream(&fstab);
190 QString line;
191
192 while (!stream.atEnd()) {
193 line = stream.readLine().simplified();
194 if (line.isEmpty() || line.startsWith('#')) {
195 continue;
196 }
197
198 // not empty or commented out by '#'
199 const QStringList items = line.split(' ');
200
201#ifdef Q_OS_SOLARIS
202 if (items.count() < 5) {
203 continue;
204 }
205#else
206 if (items.count() < 4) {
207 continue;
208 }
209#endif
210 // prevent accessing a blocking directory
211 if (_k_isFstabNetworkFileSystem(items.at(2), items.at(0)) || _k_isFstabSupportedLocalFileSystem(items.at(2))) {
212 const QString device = items.at(0);
213 const QString fsType = items.at(2);
214 QString mountpoint = items.at(1);
215
216 if (fsType == "nfs" || fsType == "nfs4") {
217 if (!mountpoint.startsWith(QLatin1Char('/'))) {
218 // making sure mount point starts with /
219 mountpoint.prepend(QLatin1Char('/'));
220 }
221 }
222
223 globalFstabCache->localData().m_fstabCache.insert(device, mountpoint);
224 }
225 }
226
227 fstab.close();
228#endif
229 globalFstabCache->localData().m_fstabCacheValid = true;
230}
231
232QStringList Solid::Backends::Fstab::FstabHandling::deviceList()
233{
234 _k_updateFstabMountPointsCache();
235 _k_updateMtabMountPointsCache();
236
237 QStringList devices = globalFstabCache->localData().m_mtabCache.keys();
238
239 // Ensure that regardless an fstab device ends with a slash
240 // it will match its eventual mounted device regardless whether or not its path
241 // ends with a slash
242 for (auto it = globalFstabCache->localData().m_fstabCache.constBegin(), end = globalFstabCache->localData().m_fstabCache.constEnd(); it != end; ++it) {
243 auto device = it.key();
244 // the device is already known
245 if (devices.contains(device)) {
246 continue;
247 }
248
249 // deviceName will or won't end with / depending if device ended with one
250 QString deviceName = device;
251 if (deviceName.endsWith(QLatin1Char('/'))) {
252 deviceName.chop(1);
253 } else {
254 deviceName.append(QLatin1Char('/'));
255 }
256 if (!devices.contains(deviceName)) {
257 devices.append(device);
258 }
259 }
260 return devices;
261}
262
263QStringList Solid::Backends::Fstab::FstabHandling::mountPoints(const QString &device)
264{
265 _k_updateFstabMountPointsCache();
266 _k_updateMtabMountPointsCache();
267
268 QStringList mountpoints = globalFstabCache->localData().m_fstabCache.values(device);
269 mountpoints += globalFstabCache->localData().m_mtabCache.values(device);
270 mountpoints.removeDuplicates();
271 return mountpoints;
272}
273
274QStringList Solid::Backends::Fstab::FstabHandling::options(const QString &device)
275{
276 _k_updateFstabMountPointsCache();
277
278 QStringList options = globalFstabCache->localData().m_fstabOptionsCache.values(device);
279 return options;
280}
281
282QString Solid::Backends::Fstab::FstabHandling::fstype(const QString &device)
283{
284 _k_updateFstabMountPointsCache();
285
286 return globalFstabCache->localData().m_fstabFstypeCache.value(device);
287}
288
289bool Solid::Backends::Fstab::FstabHandling::callSystemCommand(const QString &commandName,
290 const QStringList &args,
291 const QObject *receiver,
292 std::function<void(QProcess *)> callback)
293{
294 static const QStringList searchPaths{QStringLiteral("/sbin"), QStringLiteral("/bin"), QStringLiteral("/usr/sbin"), QStringLiteral("/usr/bin")};
295 static const QString joinedPaths = searchPaths.join(QLatin1Char(':'));
296 const QString exec = QStandardPaths::findExecutable(commandName, searchPaths);
297 if (exec.isEmpty()) {
298 qCWarning(FSTAB_LOG) << "Couldn't find executable " + commandName + " in " + joinedPaths;
299 return false;
300 }
301
302 QProcess *process = new QProcess();
303
304 QObject::connect(process,
305 static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
306 receiver,
307 [process, callback](int exitCode, QProcess::ExitStatus exitStatus) {
308 Q_UNUSED(exitCode);
309 Q_UNUSED(exitStatus);
310 callback(process);
311 process->deleteLater();
312 });
313
314 static const QRegularExpression re(QStringLiteral("^PATH=.*"), QRegularExpression::CaseInsensitiveOption);
316 env.replaceInStrings(re, QLatin1String("PATH=") + joinedPaths);
317 process->setEnvironment(env);
318 process->start(exec, args);
319
320 if (process->waitForStarted()) {
321 return true;
322 }
323
324 delete process;
325 return false;
326}
327
328void Solid::Backends::Fstab::FstabHandling::_k_updateMtabMountPointsCache()
329{
330 if (globalFstabCache->localData().m_mtabCacheValid) {
331 return;
332 }
333
334 globalFstabCache->localData().m_mtabCache.clear();
335
336#if HAVE_GETMNTINFO
337
338#if GETMNTINFO_USES_STATVFS
339 struct statvfs *mounted;
340#else
341 struct statfs *mounted;
342#endif
343
344 int num_fs = getmntinfo(&mounted, MNT_NOWAIT);
345
346 for (int i = 0; i < num_fs; i++) {
347 QString type = QFile::decodeName(mounted[i].f_fstypename);
348 if (_k_isFstabNetworkFileSystem(type, QString()) || _k_isFstabSupportedLocalFileSystem(type)) {
349 const QString fsname = QFile::decodeName(mounted[i].f_mntfromname);
350 const QString mountpoint = QFile::decodeName(mounted[i].f_mntonname);
351 const QString device = _k_deviceNameForMountpoint(fsname, type, mountpoint);
352 globalFstabCache->localData().m_mtabCache.insert(device, mountpoint);
353 globalFstabCache->localData().m_fstabFstypeCache.insert(device, type);
354 }
355 }
356
357#else
358 STRUCT_SETMNTENT mnttab;
359 if ((mnttab = SETMNTENT(MNTTAB, "r")) == nullptr) {
360 return;
361 }
362
363 STRUCT_MNTENT fe;
364 while (GETMNTENT(mnttab, fe)) {
365 QString type = QFile::decodeName(MOUNTTYPE(fe));
366 if (_k_isFstabNetworkFileSystem(type, QString()) || _k_isFstabSupportedLocalFileSystem(type)) {
367 const QString fsname = QFile::decodeName(FSNAME(fe));
368 const QString mountpoint = QFile::decodeName(MOUNTPOINT(fe));
369 const QString device = _k_deviceNameForMountpoint(fsname, type, mountpoint);
370 globalFstabCache->localData().m_mtabCache.insert(device, mountpoint);
371 globalFstabCache->localData().m_fstabFstypeCache.insert(device, type);
372 }
373 }
374 ENDMNTENT(mnttab);
375#endif
376
377 globalFstabCache->localData().m_mtabCacheValid = true;
378}
379
380QStringList Solid::Backends::Fstab::FstabHandling::currentMountPoints(const QString &device)
381{
382 _k_updateMtabMountPointsCache();
383 return globalFstabCache->localData().m_mtabCache.values(device);
384}
385
386void Solid::Backends::Fstab::FstabHandling::flushMtabCache()
387{
388 globalFstabCache->localData().m_mtabCacheValid = false;
389}
390
391void Solid::Backends::Fstab::FstabHandling::flushFstabCache()
392{
393 globalFstabCache->localData().m_fstabCacheValid = false;
394}
Type type(const QSqlDatabase &db)
const QList< QKeySequence > & end()
QString decodeName(const QByteArray &localFileName)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
qsizetype count() const const
iterator insert(const_iterator before, parameter_type value)
bool isEmpty() const const
value_type takeFirst()
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void deleteLater()
void setEnvironment(const QStringList &environment)
void finished(int exitCode, QProcess::ExitStatus exitStatus)
void start(OpenMode mode)
QStringList systemEnvironment()
bool waitForStarted(int msecs)
QString findExecutable(const QString &executableName, const QStringList &paths)
QString & append(QChar ch)
void chop(qsizetype n)
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
QString & insert(qsizetype position, QChar ch)
bool isEmpty() const const
QString & prepend(QChar ch)
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
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
qsizetype removeDuplicates()
QStringList & replaceInStrings(QStringView before, QStringView after, Qt::CaseSensitivity cs)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:17:12 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.