The Pedigree Project 0.1
fanotify-queue.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "fanotify-queue.h"
3#include "pedigree/kernel/LockGuard.h"
4#include "pedigree/kernel/utilities/utility.h"
5
6namespace {
7constexpr uint64_t Overflow = 0x4000;
8struct Metadata {
9 uint32_t eventLength;
10 uint8_t version;
11 uint8_t reserved;
12 uint16_t metadataLength;
13 uint64_t mask;
14 int32_t fd;
15 int32_t pid;
16};
17struct FidInfo {
18 uint8_t type;
19 uint8_t pad;
20 uint16_t length;
21 FileSystemId fsid;
22 uint32_t handleLength;
23 int32_t handleType;
24};
25static_assert(sizeof(Metadata) == 24 && sizeof(FidInfo) == 20, "Linux fanotify record layout");
26} // namespace
27
28size_t FanotifyRecord::encodedSize() const {
29 return sizeof(Metadata) + (mask == Overflow ? 0 : ((sizeof(FidInfo) + handle.length + 3) & ~3U));
30}
31
32void FanotifyRecord::encode(void* buffer) const {
33 const size_t length = encodedSize();
34 Metadata metadata = {static_cast<uint32_t>(length), 3, 0, sizeof(Metadata), mask, -1,
35 static_cast<int32_t>(producer)};
36 ByteSet(buffer, 0, length);
37 MemoryCopy(buffer, &metadata, sizeof(metadata));
38 if (mask == Overflow)
39 return;
40 FidInfo info = {
41 1, 0, static_cast<uint16_t>(length - sizeof(Metadata)), fsid, handle.length, handle.type};
42 auto* bytes = static_cast<uint8_t*>(buffer);
43 MemoryCopy(bytes + sizeof(metadata), &info, sizeof(info));
44 MemoryCopy(bytes + sizeof(metadata) + sizeof(info), handle.bytes, handle.length);
45}
46
47bool FanotifyRecord::sameTarget(const FanotifyRecord& other) const {
48 if (mask == Overflow || other.mask == Overflow || mountId != other.mountId ||
49 producer != other.producer || handle.type != other.handle.type ||
50 handle.length != other.handle.length || handle.length > sizeof(handle.bytes))
51 return false;
52 for (size_t index = 0; index < handle.length; ++index) {
53 if (handle.bytes[index] != other.handle.bytes[index])
54 return false;
55 }
56 return true;
57}
58
59FanotifyQueue::FanotifyQueue()
60 : m_Records(UniqueArray<FanotifyRecord>::allocate(MaximumEvents + 1)) {}
61FanotifyQueue::~FanotifyQueue() {
62 close();
63}
64bool FanotifyQueue::valid() const {
65 return static_cast<bool>(m_Records);
66}
67
68void FanotifyQueue::enqueue(const FanotifyRecord& record) {
69 bool readable = false;
70 {
71 LockGuard<Mutex> guard(m_Lock);
72 if (m_Closed || !m_Records || record.handle.length > sizeof(record.handle.bytes))
73 return;
74 if (m_Count) {
75 auto& last = m_Records.get()[(m_Head + m_Count - 1) % (MaximumEvents + 1)];
76 if (last.sameTarget(record)) {
77 last.mask |= record.mask;
78 return;
79 }
80 }
81 if (m_Count >= MaximumEvents && m_OverflowQueued)
82 return;
83 auto& destination = m_Records.get()[(m_Head + m_Count) % (MaximumEvents + 1)];
84 if (m_Count >= MaximumEvents) {
85 destination = FanotifyRecord();
86 destination.mask = Overflow;
87 m_OverflowQueued = true;
88 } else {
89 destination = record;
90 }
91 readable = m_Count++ == 0;
92 if (readable)
93 ++m_Generations.read;
94 }
95 if (readable) {
96 m_Readers.broadcast();
97 notifyReadiness(ReadyRead);
98 }
99}
100
101FanotifyQueue::Take FanotifyQueue::take(FanotifyRecord& record, size_t capacity, bool canBlock) {
102 m_Lock.acquire();
103 while (!m_Closed && !m_Count) {
104 if (!canBlock) {
105 m_Lock.release();
106 return Take::Empty;
107 }
108 ConditionVariable::Error error = ConditionVariable::NoError;
109 if (!m_Readers.wait(m_Lock, error)) {
111 m_Lock.release();
112 return error == ConditionVariable::Interrupted ||
113 error == ConditionVariable::TerminationDeferred
114 ? Take::Interrupted
115 : Take::Closed;
116 }
117 }
118 if (m_Closed) {
119 m_Lock.release();
120 return Take::Closed;
121 }
122 if (m_Records.get()[m_Head].encodedSize() > capacity) {
123 m_Lock.release();
124 return Take::TooSmall;
125 }
126 record = m_Records.get()[m_Head];
127 m_Head = (m_Head + 1) % (MaximumEvents + 1);
128 --m_Count;
129 if (record.mask == Overflow)
130 m_OverflowQueued = false;
131 m_Lock.release();
132 return Take::Ready;
133}
134
135void FanotifyQueue::close() {
136 {
137 LockGuard<Mutex> guard(m_Lock);
138 if (m_Closed)
139 return;
140 m_Closed = true;
141 m_Count = 0;
142 m_OverflowQueued = false;
143 }
144 m_Readers.broadcast();
145 closeReadiness(ReadyInvalid | ReadyHangup);
146}
147
148ReadyMask FanotifyQueue::queryReady() {
149 LockGuard<Mutex> guard(m_Lock);
150 return m_Closed ? ReadyInvalid | ReadyHangup : (m_Count ? ReadyRead : ReadyNone);
151}
153 LockGuard<Mutex> guard(m_Lock);
154 return m_Generations;
155}
156int FanotifyQueue::queuedMetadataBytes() {
157 LockGuard<Mutex> guard(m_Lock);
158 // Linux reports the fixed metadata contribution, excluding FID information.
159 return static_cast<int>(m_Count * sizeof(Metadata));
160}
MUST_USE_RESULT bool wait(Mutex &mutex, Time::Timestamp &timeout, Error &error, WaitQueue::StackDiscardCleanup onStackDiscard=nullptr, void *stackDiscardContext=nullptr)
static bool mutexAcquired(Error error)
ReadinessGenerations readinessGenerations() override
void notifyReadiness(ReadyMask mask)
Definition Readiness.cc:201
void closeReadiness(ReadyMask mask=ReadyInvalid|ReadyHangup)
Definition Readiness.cc:208
void release(size_t n=1)
Definition Semaphore.cc:546
bool acquire(size_t n=1, size_t timeoutSecs=0, size_t timeoutUsecs=0)
Definition Semaphore.cc:352