1/*
2 SPDX-FileCopyrightText: 2010 Mario Bensi <mbensi@ipsquad.net>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6
7#include "fstabwatcher.h"
8#include "fstab_debug.h"
9#include "soliddefs_p.h"
10
11#include <QCoreApplication>
12#include <QFile>
13#include <QFileSystemWatcher>
14#include <QSocketNotifier>
15
16namespace Solid
17{
18namespace Backends
19{
20namespace Fstab
21{
22Q_GLOBAL_STATIC(FstabWatcher, globalFstabWatcher)
23
24static const QString s_mtabFile = QStringLiteral("/etc/mtab");
25static const QString s_fstabFile = QStringLiteral("/etc/fstab");
26static const QString s_fstabPath = QStringLiteral("/etc");
27
28FstabWatcher::FstabWatcher()
29{
30#ifdef Q_OS_LINUX
31 auto mountMonitor = mnt_new_monitor();
32
33 if (!mountMonitor) {
34 qCritical(catFunc: FSTAB_LOG) << "could not start mount monitor";
35 return;
36 }
37 m_mountMonitor = mountMonitor;
38
39 auto r = mnt_monitor_enable_kernel(mn: m_mountMonitor, enable: true);
40 if (r < 0) {
41 mnt_unref_monitor(mn: m_mountMonitor);
42 qCritical(catFunc: FSTAB_LOG) << "Failed to enable watching of kernel mount events:" << strerror(errno);
43 }
44
45 r = mnt_monitor_enable_userspace(mn: m_mountMonitor, enable: true, NULL);
46 if (r < 0) {
47 mnt_unref_monitor(mn: m_mountMonitor);
48 qCritical(catFunc: FSTAB_LOG) << "Failed to enable watching of userspace mount events:" << strerror(errno);
49 }
50
51 auto fd = mnt_monitor_get_fd(mn: m_mountMonitor);
52 if (fd < 0) {
53 mnt_unref_monitor(mn: m_mountMonitor);
54 qCritical(catFunc: FSTAB_LOG) << "Failed to acquire watch file descriptor" << strerror(errno);
55 m_mountMonitor = nullptr;
56 return;
57 }
58
59 m_socketNotifier = new QSocketNotifier(fd, QSocketNotifier::Read, this);
60 connect(sender: m_socketNotifier, signal: &QSocketNotifier::activated, context: this, slot: &FstabWatcher::onMountChanged);
61
62 if (qApp) {
63 connect(qApp, signal: &QCoreApplication::aboutToQuit, context: this, slot: &FstabWatcher::onQuit);
64 }
65#else
66 m_isRoutineInstalled = false;
67 m_fileSystemWatcher = new QFileSystemWatcher(this);
68
69#ifndef Q_OS_OPENBSD
70 m_mtabFile = new QFile(s_mtabFile, this);
71 if (m_mtabFile && m_mtabFile->symLinkTarget().startsWith(QLatin1String("/proc/")) && m_mtabFile->open(QIODevice::ReadOnly)) {
72 m_socketNotifier = new QSocketNotifier(m_mtabFile->handle(), QSocketNotifier::Exception, this);
73 connect(m_socketNotifier, &QSocketNotifier::activated, this, &FstabWatcher::mtabChanged);
74 } else {
75 m_fileSystemWatcher->addPath(s_mtabFile);
76 }
77#endif
78
79 m_fileSystemWatcher->addPath(s_fstabPath);
80 connect(m_fileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, [this](const QString &) {
81 if (!m_isFstabWatched) {
82 m_isFstabWatched = m_fileSystemWatcher->addPath(s_fstabFile);
83 if (m_isFstabWatched) {
84 qCDebug(FSTAB_LOG) << "Re-added" << s_fstabFile;
85 Q_EMIT onFileChanged(s_fstabFile);
86 }
87 }
88 });
89
90 m_isFstabWatched = m_fileSystemWatcher->addPath(s_fstabFile);
91 connect(m_fileSystemWatcher, &QFileSystemWatcher::fileChanged, this, &FstabWatcher::onFileChanged);
92#endif
93}
94
95FstabWatcher::~FstabWatcher()
96{
97#ifdef Q_OS_LINUX
98 if (m_mountMonitor) {
99 mnt_unref_monitor(mn: m_mountMonitor);
100 }
101#else
102 // The QFileSystemWatcher doesn't work correctly in a singleton
103 // The solution so far was to destroy the QFileSystemWatcher when the application quits
104 // But we have some crash with this solution.
105 // For the moment to workaround the problem, we detach the QFileSystemWatcher from the parent
106 // effectively leaking it on purpose.
107 m_fileSystemWatcher->setParent(nullptr);
108#endif
109}
110
111void FstabWatcher::onQuit()
112{
113#ifndef Q_OS_LINUX
114 m_fileSystemWatcher->setParent(nullptr);
115#endif
116}
117
118FstabWatcher *FstabWatcher::instance()
119{
120#if 0
121 FstabWatcher *fstabWatcher = globalFstabWatcher;
122
123 if (fstabWatcher && !fstabWatcher->m_isRoutineInstalled) {
124 qAddPostRoutine(globalFstabWatcher.destroy);
125 fstabWatcher->m_isRoutineInstalled = true;
126 }
127 return fstabWatcher;
128#else
129 return globalFstabWatcher;
130#endif
131}
132
133#ifdef Q_OS_LINUX
134void FstabWatcher::onMountChanged()
135{
136 auto mtab = false;
137 auto fstab = false;
138 const char *filename;
139 while (mnt_monitor_next_change(mn: m_mountMonitor, filename: &filename, NULL) == 0) {
140 if (!mtab && ((strcmp(s1: filename, s2: "/proc/self/mountinfo") == 0) || (strcmp(s1: filename, s2: "/run/mount/utab") == 0))) {
141 mtab = true;
142 }
143 if (!fstab && (strcmp(s1: filename, s2: "/etc/fstab") == 0)) {
144 fstab = true;
145 }
146 }
147
148 if (mtab) {
149 Q_EMIT mtabChanged();
150 }
151 if (fstab) {
152 Q_EMIT fstabChanged();
153 }
154}
155#else
156void FstabWatcher::onFileChanged(const QString &path)
157{
158 if (path == s_mtabFile) {
159 Q_EMIT mtabChanged();
160 if (!m_fileSystemWatcher->files().contains(s_mtabFile)) {
161 m_fileSystemWatcher->addPath(s_mtabFile);
162 }
163 }
164 if (path == s_fstabFile) {
165 Q_EMIT fstabChanged();
166 if (!m_fileSystemWatcher->files().contains(s_fstabFile)) {
167 m_isFstabWatched = m_fileSystemWatcher->addPath(s_fstabFile);
168 qCDebug(FSTAB_LOG) << "Fstab removed, re-added:" << m_isFstabWatched;
169 }
170 }
171}
172#endif
173}
174}
175} // namespace Solid:Backends::Fstab
176
177#include "moc_fstabwatcher.cpp"
178

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