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_LIBMOUNT
25#include <libmount.h>
26#endif
27
28// This is the *BSD branch
29#if HAVE_SYS_MOUNT_H
30#if HAVE_SYS_TYPES_H
31#include <sys/types.h>
32#endif
33#if HAVE_SYS_PARAM_H
34#include <sys/param.h>
35#endif
36#include <sys/mount.h>
37#endif
38
39#define FSTAB "/etc/fstab"
40
41// There are currently two APIs implemented:
42// libmount for linux
43// getmntinfo + struct statfs&flags (BSD 4.4 and friends)
44
45Q_GLOBAL_STATIC(QThreadStorage<Solid::Backends::Fstab::FstabHandling>, globalFstabCache)
46
47Solid::Backends::Fstab::FstabHandling::FstabHandling()
48 : m_fstabCacheValid(false)
49 , m_mtabCacheValid(false)
50{
51}
52
53bool _k_isFstabNetworkFileSystem(const QString &fstype, const QString &devName)
54{
55 if (fstype == QLatin1String("nfs") //
56 || fstype == QLatin1String("nfs4") //
57 || fstype == QLatin1String("smbfs") //
58 || fstype == QLatin1String("cifs") //
59 || fstype == QLatin1String("smb3") //
60 || fstype == QLatin1String("fuse.sshfs") //
61 || fstype == QLatin1String("fuse.rclone") //
62 || devName.startsWith(QLatin1String("//"))) {
63 return true;
64 }
65 return false;
66}
67
68bool _k_isFstabSupportedLocalFileSystem(const QString &fstype)
69{
70 if (fstype == QLatin1String("fuse.encfs") //
71 || fstype == QLatin1String("fuse.cryfs") //
72 || fstype == QLatin1String("fuse.gocryptfs") //
73 || fstype == QLatin1String("overlay")) {
74 return true;
75 }
76 return false;
77}
78
79QString _k_mntFstype(const QString &orig)
80{
81 if (orig == QLatin1String("sshfs") || orig == QLatin1String("rclone")) {
82 return QStringLiteral("fuse.%1").arg(orig);
83 }
84 return orig;
85}
86
87QString _k_deviceNameForMountpoint(const QString &source, const QString &fstype, const QString &mountpoint)
88{
89 if (fstype.startsWith(QLatin1String("fuse.")) || fstype == QLatin1String("overlay")) {
90 return fstype + mountpoint;
91 }
92 // A source may be mounted several times, e.g. with different
93 // options, often a network share with different credentials
94 // for different users. Make sure it is unique by appending the
95 // mountpoint (which is unique).
96
97 auto _mountpoint = mountpoint;
98 if (fstype == QLatin1String("nfs") || fstype == QLatin1String("nfs4")) {
99 if (!mountpoint.startsWith(QLatin1Char('/'))) {
100 // making sure mount point starts with /
101 _mountpoint.prepend(QLatin1Char('/'));
102 }
103 }
104 return source + QLatin1Char(':') + _mountpoint;
105}
106
107void Solid::Backends::Fstab::FstabHandling::_k_updateFstabMountPointsCache()
108{
109 if (globalFstabCache->localData().m_fstabCacheValid) {
110 return;
111 }
112
113 globalFstabCache->localData().m_fstabCache.clear();
114 globalFstabCache->localData().m_fstabOptionsCache.clear();
115
116#if HAVE_LIBMOUNT
117
118 struct libmnt_table *table = mnt_new_table();
119 if (!table) {
120 return;
121 }
122
123 if (mnt_table_parse_fstab(table, NULL) != 0) {
124 mnt_free_table(table);
125 return;
126 }
127
128 struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD);
129 struct libmnt_fs *fs;
130
131 while (mnt_table_next_fs(table, itr, &fs) == 0) {
132 const QString fstype = _k_mntFstype(QFile::decodeName(mnt_fs_get_fstype(fs)));
133 const QString fsname = QFile::decodeName(mnt_fs_get_srcpath(fs));
134 if (_k_isFstabNetworkFileSystem(fstype, fsname) || _k_isFstabSupportedLocalFileSystem(fstype)) {
135 const QString mountpoint = QFile::decodeName(mnt_fs_get_target(fs));
136 const QString device = _k_deviceNameForMountpoint(fsname, fstype, mountpoint);
137 char *name = mnt_fs_strdup_options(fs);
138 const QStringList options = QFile::decodeName(name).split(QLatin1Char(','));
139 free(name);
140
141 globalFstabCache->localData().m_fstabCache.insert(device, mountpoint);
142 globalFstabCache->localData().m_fstabFstypeCache.insert(device, fstype);
143 for (const auto &optionLine : options) {
144 const auto split = optionLine.split(QLatin1Char('='));
145 const auto optionName = split[0];
146 const auto optionValue = split.size() > 1 ? split[1] : QString{};
147
148 globalFstabCache->localData().m_fstabOptionsCache[device].insert(optionName, optionValue);
149 }
150 }
151 }
152
153 mnt_free_iter(itr);
154
155 mnt_free_table(table);
156
157#else
158
159 QFile fstab(QStringLiteral(FSTAB));
160 if (!fstab.open(QIODevice::ReadOnly)) {
161 return;
162 }
163
164 QTextStream stream(&fstab);
165 QString line;
166
167 while (!stream.atEnd()) {
168 line = stream.readLine().simplified();
169 if (line.isEmpty() || line.startsWith(QLatin1Char('#'))) {
170 continue;
171 }
172
173 // not empty or commented out by '#'
174 const QStringList items = line.split(QLatin1Char(' '));
175 if (items.count() < 4) {
176 continue;
177 }
178
179 const QString device = items.at(0);
180 const QString fstype = _k_mntFstype(items.at(2));
181
182 // prevent accessing a blocking directory
183 if (_k_isFstabNetworkFileSystem(fstype, device) || _k_isFstabSupportedLocalFileSystem(fstype)) {
184 QString mountpoint = items.at(1);
185
186 if (fstype == QLatin1String("nfs") || fstype == QLatin1String("nfs4")) {
187 if (!mountpoint.startsWith(QLatin1Char('/'))) {
188 // making sure mount point starts with /
189 mountpoint.prepend(QLatin1Char('/'));
190 }
191 }
192
193 globalFstabCache->localData().m_fstabCache.insert(device, mountpoint);
194 }
195 }
196
197 fstab.close();
198#endif
199 globalFstabCache->localData().m_fstabCacheValid = true;
200}
201
202QStringList Solid::Backends::Fstab::FstabHandling::deviceList()
203{
204 _k_updateFstabMountPointsCache();
205 _k_updateMtabMountPointsCache();
206
207 QStringList devices = globalFstabCache->localData().m_mtabCache.keys();
208
209 // Ensure that regardless an fstab device ends with a slash
210 // it will match its eventual mounted device regardless whether or not its path
211 // ends with a slash
212 for (auto it = globalFstabCache->localData().m_fstabCache.constBegin(), end = globalFstabCache->localData().m_fstabCache.constEnd(); it != end; ++it) {
213 auto device = it.key();
214 // the device is already known
215 if (devices.contains(device)) {
216 continue;
217 }
218
219 // deviceName will or won't end with / depending if device ended with one
220 QString deviceName = device;
221 if (deviceName.endsWith(QLatin1Char('/'))) {
222 deviceName.chop(1);
223 } else {
224 deviceName.append(QLatin1Char('/'));
225 }
226 if (!devices.contains(deviceName)) {
227 devices.append(device);
228 }
229 }
230 return devices;
231}
232
233QStringList Solid::Backends::Fstab::FstabHandling::mountPoints(const QString &device)
234{
235 _k_updateFstabMountPointsCache();
236 _k_updateMtabMountPointsCache();
237
238 QStringList mountpoints = globalFstabCache->localData().m_fstabCache.values(device);
239 mountpoints += globalFstabCache->localData().m_mtabCache.values(device);
240 mountpoints.removeDuplicates();
241 return mountpoints;
242}
243
244QHash<QString, QString> Solid::Backends::Fstab::FstabHandling::options(const QString &device)
245{
246 _k_updateFstabMountPointsCache();
247 _k_updateMtabMountPointsCache();
248
249 auto options = globalFstabCache->localData().m_mtabOptionsCache.value(device);
250
251 const auto optionsFstab = globalFstabCache->localData().m_fstabOptionsCache.value(device);
252 for (const auto &it : optionsFstab.asKeyValueRange()) {
253 if (!options.contains(it.first)) {
254 options.insert(it.first, it.second);
255 }
256 }
257
258 return options;
259}
260
261QString Solid::Backends::Fstab::FstabHandling::fstype(const QString &device)
262{
263 _k_updateFstabMountPointsCache();
264
265 return globalFstabCache->localData().m_fstabFstypeCache.value(device);
266}
267
268bool Solid::Backends::Fstab::FstabHandling::callSystemCommand(const QString &commandName,
269 const QStringList &args,
270 const QObject *receiver,
271 std::function<void(QProcess *)> callback)
272{
273 static const QStringList searchPaths{QStringLiteral("/sbin"), QStringLiteral("/bin"), QStringLiteral("/usr/sbin"), QStringLiteral("/usr/bin")};
274 static const QString joinedPaths = searchPaths.join(QLatin1Char(':'));
275 const QString exec = QStandardPaths::findExecutable(commandName, searchPaths);
276 if (exec.isEmpty()) {
277 qCWarning(FSTAB_LOG) << "Couldn't find executable" << commandName << "in" << joinedPaths;
278 return false;
279 }
280
281 QProcess *process = new QProcess();
282
283 QObject::connect(process,
284 static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
285 receiver,
286 [process, callback](int exitCode, QProcess::ExitStatus exitStatus) {
287 Q_UNUSED(exitCode);
288 Q_UNUSED(exitStatus);
289 callback(process);
290 process->deleteLater();
291 });
292
293 static const QRegularExpression re(QStringLiteral("^PATH=.*"), QRegularExpression::CaseInsensitiveOption);
295 env.replaceInStrings(re, QLatin1String("PATH=") + joinedPaths);
296 process->setEnvironment(env);
297 process->start(exec, args);
298
299 if (process->waitForStarted()) {
300 return true;
301 }
302
303 delete process;
304 return false;
305}
306
307void Solid::Backends::Fstab::FstabHandling::_k_updateMtabMountPointsCache()
308{
309 if (globalFstabCache->localData().m_mtabCacheValid) {
310 return;
311 }
312
313 globalFstabCache->localData().m_mtabCache.clear();
314 globalFstabCache->localData().m_mtabOptionsCache.clear();
315
316#if HAVE_GETMNTINFO
317
318#if GETMNTINFO_USES_STATVFS
319 struct statvfs *mounted;
320#else
321 struct statfs *mounted;
322#endif
323
324 int num_fs = getmntinfo(&mounted, MNT_NOWAIT);
325
326 for (int i = 0; i < num_fs; i++) {
327 QString type = _k_mntFstype(QFile::decodeName(mounted[i].f_fstypename));
328 if (_k_isFstabNetworkFileSystem(type, QString()) || _k_isFstabSupportedLocalFileSystem(type)) {
329 const QString fsname = QFile::decodeName(mounted[i].f_mntfromname);
330 const QString mountpoint = QFile::decodeName(mounted[i].f_mntonname);
331 const QString device = _k_deviceNameForMountpoint(fsname, type, mountpoint);
332 globalFstabCache->localData().m_mtabCache.insert(device, mountpoint);
333 globalFstabCache->localData().m_fstabFstypeCache.insert(device, type);
334 }
335 }
336
337#elif HAVE_LIBMOUNT
338
339 struct libmnt_table *table = mnt_new_table();
340 if (!table) {
341 return;
342 }
343
344 if (mnt_table_parse_mtab(table, NULL) != 0) {
345 mnt_free_table(table);
346 return;
347 }
348
349 struct libmnt_iter *itr = mnt_new_iter(MNT_ITER_FORWARD);
350 struct libmnt_fs *fs;
351
352 while (mnt_table_next_fs(table, itr, &fs) == 0) {
353 const QString fstype = _k_mntFstype(QFile::decodeName(mnt_fs_get_fstype(fs)));
354 if (_k_isFstabNetworkFileSystem(fstype, QString{}) || _k_isFstabSupportedLocalFileSystem(fstype)) {
355 const QString mountpoint = QFile::decodeName(mnt_fs_get_target(fs));
356 const QString fsname = QFile::decodeName(mnt_fs_get_srcpath(fs));
357 const QString device = _k_deviceNameForMountpoint(fsname, fstype, mountpoint);
358 char *name = mnt_fs_strdup_options(fs);
359 const QStringList options = QFile::decodeName(name).split(QLatin1Char(','));
360 free(name);
361
362 globalFstabCache->localData().m_mtabCache.insert(device, mountpoint);
363 globalFstabCache->localData().m_fstabFstypeCache.insert(device, fstype);
364 for (const auto &optionLine : options) {
365 const auto split = optionLine.split(QLatin1Char('='));
366 const auto optionName = split[0];
367 const auto optionValue = split.size() > 1 ? split[1] : QString{};
368
369 globalFstabCache->localData().m_mtabOptionsCache[device].insert(optionName, optionValue);
370 }
371 }
372 }
373
374 mnt_free_iter(itr);
375
376 mnt_free_table(table);
377
378#endif
379
380 globalFstabCache->localData().m_mtabCacheValid = true;
381}
382
383QStringList Solid::Backends::Fstab::FstabHandling::currentMountPoints(const QString &device)
384{
385 _k_updateMtabMountPointsCache();
386 return globalFstabCache->localData().m_mtabCache.values(device);
387}
388
389void Solid::Backends::Fstab::FstabHandling::flushMtabCache()
390{
391 globalFstabCache->localData().m_mtabCacheValid = false;
392}
393
394void Solid::Backends::Fstab::FstabHandling::flushFstabCache()
395{
396 globalFstabCache->localData().m_fstabCacheValid = false;
397}
Type type(const QSqlDatabase &db)
QString name(StandardAction id)
QString decodeName(const QByteArray &localFileName)
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
qsizetype count() const const
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 Sat Dec 21 2024 17:03:23 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.