The Pedigree Project 0.1
fanotify-state.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/LockGuard.h"
3#include "pedigree/kernel/process/TerminationDeferral.h"
4#include "pedigree/kernel/process/Thread.h"
5#include "pedigree/kernel/processor/Processor.h"
6#include "pedigree/kernel/processor/ProcessorInformation.h"
7#include "pedigree/kernel/syscallError.h"
8#include "pedigree/kernel/utilities/utility.h"
9
10#include "fanotify-queue.h"
11#include "fanotify-syscalls.h"
12#include "file-handle-syscalls.h"
13#include "modules/subsys/posix/PosixSubsystem.h"
14#include "modules/system/vfs/File.h"
15
16namespace {
17constexpr size_t MaximumGroups = 64, MaximumMarks = 256, MaximumGlobalMarks = 4096;
18size_t groups = 0, marks = 0;
19constexpr FileEventMask Events = FileEvents::Modify | FileEvents::Attributes | FileEvents::Open |
20 FileEvents::CloseWrite | FileEvents::CloseNoWrite;
21
22bool charge(size_t& counter, size_t limit) {
23 size_t value = __atomic_load_n(&counter, __ATOMIC_RELAXED);
24 do {
25 if (value >= limit)
26 return false;
27 } while (!__atomic_compare_exchange_n(&counter, &value, value + 1, false, __ATOMIC_ACQ_REL,
28 __ATOMIC_RELAXED));
29 return true;
30}
31
32class FanotifyObserver final : public FileEventObserver {
33 public:
34 FanotifyObserver(const SharedPointer<FanotifyQueue>& queue, const FanotifyRecord& identity)
35 : m_Queue(queue), m_Identity(identity) {}
36 ~FanotifyObserver() override {
37 LockGuard<Mutex> guard(m_Lock);
38 releaseCharge();
39 }
40 bool arm(uint64_t mask) {
41 LockGuard<Mutex> guard(m_Lock);
42 if (m_Retired)
43 return false;
44 m_Mask = mask;
45 m_Active = true;
46 return true;
47 }
48 bool active() {
49 LockGuard<Mutex> guard(m_Lock);
50 return m_Active;
51 }
52 bool change(uint64_t mask, bool remove) {
53 LockGuard<Mutex> guard(m_Lock);
54 if (remove)
55 m_Mask &= ~mask;
56 else
57 m_Mask |= mask;
58 if (!m_Mask) {
59 m_Active = false;
60 releaseCharge();
61 }
62 return m_Active;
63 }
64 void deactivate() {
65 LockGuard<Mutex> guard(m_Lock);
66 m_Active = false;
67 m_Retired = true;
68 releaseCharge();
69 }
70 void fileEvent(const FileEvent& event) override {
71 LockGuard<Mutex> guard(m_Lock);
72 if (event.mask & FileEvents::SourceRetired) {
73 m_Active = false;
74 m_Retired = true;
75 releaseCharge();
76 return;
77 }
78 uint64_t selected = 0;
79 if (event.mask & FileEvents::Modify)
80 selected |= 0x02;
81 if (event.mask & FileEvents::Attributes)
82 selected |= 0x04;
83 if (event.mask & FileEvents::CloseWrite)
84 selected |= 0x08;
85 if (event.mask & FileEvents::CloseNoWrite)
86 selected |= 0x10;
87 if (event.mask & FileEvents::Open)
88 selected |= 0x20;
89 selected &= m_Mask;
90 if (!m_Active || !selected)
91 return;
92 FanotifyRecord record = m_Identity;
93 record.mask = selected;
94 record.producer = event.producerPid;
95 m_Queue->enqueue(record);
96 }
97
98 private:
99 void releaseCharge() {
100 if (m_Charged) {
101 m_Charged = false;
102 __atomic_sub_fetch(&marks, 1, __ATOMIC_ACQ_REL);
103 }
104 }
105 Mutex m_Lock;
107 FanotifyRecord m_Identity;
108 uint64_t m_Mask = 0;
109 bool m_Active = false, m_Retired = false;
110 bool m_Charged = true;
111};
112
113struct FanotifyMark {
114 ~FanotifyMark() {
115 inode.reset();
116 mount.reset();
117 if (charged)
118 __atomic_sub_fetch(&marks, 1, __ATOMIC_ACQ_REL);
119 }
121 VFS::MountIdentity mountIdentity;
123 FanotifyObserver* concrete = nullptr;
124 FileEventSubscription inode, mount;
125 bool charged = false;
126};
127} // namespace
128
130 public:
131 Mutex lock;
133 UniquePointer<FanotifyMark> marks[MaximumMarks];
134 size_t count = 0;
135 bool closed = false;
136};
137
138FanotifyInstance::FanotifyInstance() : m_State(new FanotifyState) {}
139FanotifyInstance::~FanotifyInstance() {
140 lastDescriptorClosed();
141 delete m_State;
142}
143SharedPointer<FanotifyInstance> FanotifyInstance::create() {
144 if (!charge(groups, MaximumGroups)) {
145 SYSCALL_ERROR(ProcessFileLimit);
146 return {};
147 }
148 auto* instance = new FanotifyInstance;
149 if (!instance || !instance->m_State || !instance->m_State->queue ||
150 !instance->m_State->queue->valid()) {
151 delete instance;
152 __atomic_sub_fetch(&groups, 1, __ATOMIC_ACQ_REL);
153 SYSCALL_ERROR(OutOfMemory);
154 return {};
155 }
156 instance->m_Quota = true;
157 return SharedPointer<FanotifyInstance>(instance);
158}
159
160void FanotifyInstance::reapMarks() {
161 UniquePointer<FanotifyMark> retiring[MaximumMarks];
162 size_t count = 0;
163 LockGuard<Mutex> guard(m_State->lock);
164 for (size_t index = 0; index < m_State->count;) {
165 if (m_State->marks[index].get()->concrete->active()) {
166 ++index;
167 continue;
168 }
169 retiring[count++] = pedigree_std::move(m_State->marks[index]);
170 --m_State->count;
171 if (index != m_State->count)
172 m_State->marks[index] = pedigree_std::move(m_State->marks[m_State->count]);
173 }
174}
175
176int FanotifyInstance::changeMark(File& target, const VFS::MountOperation& mount,
177 const FileHandle& handle, const FileSystemId& fsid, uint64_t mask,
178 bool remove) {
179 TerminationDeferral lifetime;
180 reapMarks();
182 identity.mountId = mount.id();
183 identity.handle = handle;
184 identity.fsid = fsid;
185 UniquePointer<FanotifyMark> retiring, candidate;
186 LockGuard<Mutex> guard(m_State->lock);
187 if (m_State->closed) {
188 SYSCALL_ERROR(BadFileDescriptor);
189 return -1;
190 }
191 for (size_t index = 0; index < m_State->count; ++index) {
192 auto* mark = m_State->marks[index].get();
193 if (!mark->identity.sameTarget(identity) || !mark->concrete->active())
194 continue;
195 if (!mark->concrete->change(mask, remove)) {
196 retiring = pedigree_std::move(m_State->marks[index]);
197 --m_State->count;
198 if (index != m_State->count)
199 m_State->marks[index] = pedigree_std::move(m_State->marks[m_State->count]);
200 }
201 return 0;
202 }
203 if (remove) {
204 SYSCALL_ERROR(DoesNotExist);
205 return -1;
206 }
207 if (m_State->count == MaximumMarks || !charge(marks, MaximumGlobalMarks)) {
208 SYSCALL_ERROR(NoSpaceLeftOnDevice);
209 return -1;
210 }
212 if (!candidate) {
213 __atomic_sub_fetch(&marks, 1, __ATOMIC_ACQ_REL);
214 SYSCALL_ERROR(OutOfMemory);
215 return -1;
216 }
217 auto* mark = candidate.get();
218 mark->charged = true;
219 mark->identity = identity;
220 mark->mountIdentity = mount.identity();
221 mark->concrete = new FanotifyObserver(m_State->queue, identity);
222 if (!mark->concrete) {
223 SYSCALL_ERROR(OutOfMemory);
224 return -1;
225 }
226 mark->observer.reset(mark->concrete);
227 // Retirement releases quota immediately; only subscription drain and
228 // storage reclamation need to wait for an ordinary group operation.
229 mark->charged = false;
230 const auto status =
231 target.subscribeInodeEvents(Events | FileEvents::SourceRetired, mark->observer, mark->inode);
232 if (status != FileHandleStatus::Success)
233 return posix_handle_error(status);
234 if (!mark->mountIdentity.subscribeRetirement(mark->observer, mark->mount)) {
235 SYSCALL_ERROR(OutOfMemory);
236 return -1;
237 }
238 if (!mark->concrete->arm(mask)) {
239 SYSCALL_ERROR(DoesNotExist);
240 return -1;
241 }
242 m_State->marks[m_State->count++] = pedigree_std::move(candidate);
243 return 0;
244}
245
246int FanotifyInstance::flushMarks() {
247 TerminationDeferral lifetime;
248 UniquePointer<FanotifyMark> retiring[MaximumMarks];
249 LockGuard<Mutex> guard(m_State->lock);
250 if (m_State->closed) {
251 SYSCALL_ERROR(BadFileDescriptor);
252 return -1;
253 }
254 for (size_t index = 0; index < m_State->count; ++index) {
255 m_State->marks[index].get()->concrete->deactivate();
256 retiring[index] = pedigree_std::move(m_State->marks[index]);
257 }
258 m_State->count = 0;
259 return 0;
260}
261
262void FanotifyInstance::lastDescriptorClosed() {
263 TerminationDeferral lifetime;
264 if (!m_State)
265 return;
266 UniquePointer<FanotifyMark> retiring[MaximumMarks];
267 {
268 LockGuard<Mutex> guard(m_State->lock);
269 if (m_State->closed)
270 return;
271 m_State->closed = true;
272 for (size_t index = 0; index < m_State->count; ++index) {
273 m_State->marks[index].get()->concrete->deactivate();
274 retiring[index] = pedigree_std::move(m_State->marks[index]);
275 }
276 m_State->count = 0;
277 if (m_Quota) {
278 m_Quota = false;
279 __atomic_sub_fetch(&groups, 1, __ATOMIC_ACQ_REL);
280 }
281 }
282 if (m_State->queue)
283 m_State->queue->close();
284}
285
286ssize_t FanotifyInstance::readToUser(void* buffer, size_t count, bool canBlock) {
287 struct Cursor {
288 uintptr_t address;
289 } cursor{reinterpret_cast<uintptr_t>(buffer)};
290 auto copy = [](void* opaque, const void* bytes, size_t length) -> bool {
291 auto* cursor = static_cast<Cursor*>(opaque);
292 if (length > ~uintptr_t(0) - cursor->address ||
293 !PosixSubsystem::copyToUser(reinterpret_cast<void*>(cursor->address), bytes, length))
294 return false;
295 cursor->address += length;
296 return true;
297 };
298 return readWithCopy(count, canBlock, copy, &cursor);
299}
300
301ssize_t FanotifyInstance::readWithCopy(size_t count, bool canBlock, PosixDescriptorReadCopy copy,
302 void* opaque) {
303 TerminationDeferral lifetime;
304 reapMarks();
305 auto queue = m_State->queue;
306 size_t copied = 0;
307 for (;;) {
308 FanotifyRecord record;
309 const auto result = queue->take(record, count - copied, canBlock && !copied);
310 if (result != FanotifyQueue::Take::Ready) {
311 if (copied) {
312 Processor::information().getCurrentThread()->setErrno(0);
313 return copied;
314 }
315 syscallError(result == FanotifyQueue::Take::Empty ? Error::NoMoreProcesses
316 : result == FanotifyQueue::Take::TooSmall ? Error::InvalidArgument
317 : result == FanotifyQueue::Take::Interrupted ? Error::Interrupted
318 : Error::BadFileDescriptor);
319 return -1;
320 }
321 uint8_t bytes[FanotifyQueue::MaximumRecordSize];
322 record.encode(bytes);
323 const size_t size = record.encodedSize();
324 // Fanotify drops a dequeued notification after failed copyout. EFAULT
325 // overrides even a previously copied prefix of this read/readv.
326 if (!copy(opaque, bytes, size)) {
327 SYSCALL_ERROR(BadAddress);
328 return -1;
329 }
330 copied += size;
331 }
332}
333
334ReadyMask FanotifyInstance::queryReady() {
335 return m_State->queue->queryReady();
336}
337ReadinessSource* FanotifyInstance::readinessSource() {
338 return m_State->queue.get();
339}
340int FanotifyInstance::queuedMetadataBytes() {
341 return m_State->queue->queuedMetadataBytes();
342}
virtual void fileEvent(const FileEvent &event)=0
Definition File.h:74
Definition Mutex.h:56
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static ProcessorInformation & information()
T * get() const