1 /*
2     This file is part of the KDE libraries
3 
4     SPDX-FileCopyrightText: 1998 Sven Radej <sven@lisa.exp.univie.ac.at>
5     SPDX-FileCopyrightText: 2006 Dirk Mueller <mueller@kde.org>
6     SPDX-FileCopyrightText: 2007 Flavio Castelli <flavio.castelli@gmail.com>
7     SPDX-FileCopyrightText: 2008 Jarosław Staniek <staniek@kde.org>
8     SPDX-FileCopyrightText: 2020 Harald Sitter <sitter@kde.org>
9 
10     SPDX-License-Identifier: LGPL-2.0-only
11 
12     Private Header for class of KDirWatchPrivate
13     this separate header file is needed for MOC processing
14     because KDirWatchPrivate has signals and slots
15 */
16 
17 #ifndef KDIRWATCH_P_H
18 #define KDIRWATCH_P_H
19 
20 #include "kdirwatch.h"
21 #include <io/config-kdirwatch.h>
22 
23 #ifndef QT_NO_FILESYSTEMWATCHER
24 #define HAVE_QFILESYSTEMWATCHER 1
25 #else
26 #define HAVE_QFILESYSTEMWATCHER 0
27 #endif
28 
29 #include <QList>
30 #include <QMap>
31 #include <QObject>
32 #include <QSet>
33 #include <QString>
34 #include <QTimer>
35 class QSocketNotifier;
36 
37 #if HAVE_FAM
38 #include <fam.h>
39 #include <limits.h>
40 #endif
41 
42 #include <ctime>
43 #include <sys/types.h> // time_t, ino_t
44 
45 #define invalid_ctime (static_cast<time_t>(-1))
46 
47 #if HAVE_QFILESYSTEMWATCHER
48 #include <QFileSystemWatcher>
49 #endif // HAVE_QFILESYSTEMWATCHER
50 
51 /* KDirWatchPrivate is a singleton and does the watching
52  * for every KDirWatch instance in the application.
53  */
54 class KDirWatchPrivate : public QObject
55 {
56     Q_OBJECT
57 public:
58     enum entryStatus {
59         Normal = 0,
60         NonExistent,
61     };
62     enum entryMode {
63         UnknownMode = 0,
64         StatMode,
65         INotifyMode,
66         FAMMode,
67         QFSWatchMode,
68     };
69     enum {
70         NoChange = 0,
71         Changed = 1,
72         Created = 2,
73         Deleted = 4,
74     };
75 
76     struct Client {
ClientClient77         Client(KDirWatch *inst, KDirWatch::WatchModes watchModes)
78             : instance(inst)
79             , count(1)
80             , watchingStopped(inst->isStopped())
81             , pending(NoChange)
82             , m_watchModes(watchModes)
83         {
84         }
85 
86         // The compiler needs a copy ctor for Client when Entry is inserted into m_mapEntries
87         // (even though the vector of clients is empty at that point, so no performance penalty there)
88         // Client(const Client &) = delete;
89         // Client &operator=(const Client &) = delete;
90         // Client(Client &&) = default;
91         // Client &operator=(Client &&) = default;
92 
93         KDirWatch *instance;
94         int count;
95         // did the instance stop watching
96         bool watchingStopped;
97         // events blocked when stopped
98         int pending;
99         KDirWatch::WatchModes m_watchModes;
100     };
101 
102     class Entry
103     {
104     public:
105         ~Entry();
106         // instances interested in events
107         std::vector<Client> m_clients;
108         // nonexistent entries of this directory
109         QList<Entry *> m_entries;
110         QString path;
111 
112         // the last observed modification time
113         time_t m_ctime;
114         // last observed inode
115         ino_t m_ino;
116         // the last observed link count
117         int m_nlink;
118         entryStatus m_status;
119         entryMode m_mode;
120         int msecLeft, freq;
121         bool isDir;
122 
123         QString parentDirectory() const;
124         void addClient(KDirWatch *, KDirWatch::WatchModes);
125         void removeClient(KDirWatch *);
126         int clientCount() const;
isValid()127         bool isValid()
128         {
129             return !m_clients.empty() || !m_entries.empty();
130         }
131 
findSubEntry(const QString & path)132         Entry *findSubEntry(const QString &path) const
133         {
134             for (Entry *sub_entry : std::as_const(m_entries)) {
135                 if (sub_entry->path == path) {
136                     return sub_entry;
137                 }
138             }
139             return nullptr;
140         }
141 
142         bool dirty;
143         void propagate_dirty();
144 
145         QList<const Client *> clientsForFileOrDir(const QString &tpath, bool *isDir) const;
146         QList<const Client *> inotifyClientsForFileOrDir(bool isDir) const;
147 
148 #if HAVE_FAM
149         FAMRequest fr;
150         bool m_famReportedSeen;
151 #endif
152 
153 #if HAVE_SYS_INOTIFY_H
154         int wd;
155         // Creation and Deletion of files happens infrequently, so
156         // can safely be reported as they occur.  File changes i.e. those that emit "dirty()" can
157         // happen many times per second, though, so maintain a list of files in this directory
158         // that can be emitted and flushed at the next slotRescan(...).
159         // This will be unused if the Entry is not a directory.
160         QList<QString> m_pendingFileChanges;
161 #endif
162     };
163 
164     typedef QMap<QString, Entry> EntryMap;
165 
166     KDirWatchPrivate();
167     ~KDirWatchPrivate() override;
168 
169     void resetList(KDirWatch *instance, bool skippedToo);
170     void useFreq(Entry *e, int newFreq);
171     void addEntry(KDirWatch *instance, const QString &_path, Entry *sub_entry, bool isDir, KDirWatch::WatchModes watchModes = KDirWatch::WatchDirOnly);
172     void removeEntry(KDirWatch *instance, const QString &path, Entry *sub_entry);
173     void removeEntry(KDirWatch *instance, Entry *e, Entry *sub_entry);
174     bool stopEntryScan(KDirWatch *instance, Entry *e);
175     bool restartEntryScan(KDirWatch *instance, Entry *e, bool notify);
176     void stopScan(KDirWatch *instance);
177     void startScan(KDirWatch *instance, bool notify, bool skippedToo);
178 
179     void removeEntries(KDirWatch *instance);
180     void statistics();
181 
182     void addWatch(Entry *entry);
183     void removeWatch(Entry *entry);
184     Entry *entry(const QString &_path);
185     int scanEntry(Entry *e);
186     void emitEvent(Entry *e, int event, const QString &fileName = QString());
187 
188     static bool isNoisyFile(const char *filename);
189 
190     void ref();
191     void unref();
192 
193 public Q_SLOTS:
194     void slotRescan();
195     void famEventReceived(); // for FAM
196     void inotifyEventReceived(); // for inotify
197     void slotRemoveDelayed();
198     void fswEventReceived(const QString &path); // for QFileSystemWatcher
199 
200 public:
201     QTimer timer;
202     EntryMap m_mapEntries;
203 
204     KDirWatch::Method m_preferredMethod, m_nfsPreferredMethod;
205     int freq;
206     int statEntries;
207     int m_nfsPollInterval, m_PollInterval;
208     bool useStat(Entry *e);
209 
210     // removeList is allowed to contain any entry at most once
211     QSet<Entry *> removeList;
212     bool delayRemove;
213 
214     bool rescan_all;
215     QTimer rescan_timer;
216 
217 #if HAVE_FAM
218     QSocketNotifier *sn;
219     FAMConnection fc;
220     bool use_fam;
221 
222     void checkFAMEvent(FAMEvent *fe);
223     bool useFAM(Entry *e);
224     void disableFAM();
225 #endif
226 
227 #if HAVE_SYS_INOTIFY_H
228     QSocketNotifier *mSn;
229     bool supports_inotify;
230     int m_inotify_fd;
231     QHash<int, Entry *> m_inotify_wd_to_entry;
232 
233     bool useINotify(Entry *e);
234 #endif
235 #if HAVE_QFILESYSTEMWATCHER
236     QFileSystemWatcher *fsWatcher;
237     bool useQFSWatch(Entry *e);
238 #endif
239 
240     bool _isStopped;
241 
242 private:
243     // Public objects that reference this thread-local private instance.
244     uint m_references;
245 };
246 
247 QDebug operator<<(QDebug debug, const KDirWatchPrivate::Entry &entry);
248 
249 #endif // KDIRWATCH_P_H
250