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

source code of solid/src/solid/devices/backends/fstab/fstabhandling.cpp