The Pedigree Project 0.1
advisory-lock-table.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "advisory-lock-table.h"
3
4namespace PosixAdvisory {
5namespace {
6bool sameOwner(const Grant& a, const Grant& b) {
7 return a.owner == b.owner && a.kind == b.kind;
8}
9
10bool sameSet(const Grant& a, const Grant& b) {
11 return a.inode == b.inode && a.name == b.name;
12}
13
14bool overlaps(const Range& a, const Range& b) {
15 return a.first <= b.last && b.first <= a.last;
16}
17
18bool conflicts(const Grant& a, const Grant& b) {
19 return a.type != Type::Unlock && b.type != Type::Unlock && sameSet(a, b) && !sameOwner(a, b) &&
20 overlaps(a.range, b.range) && (a.type == Type::Write || b.type == Type::Write);
21}
22} // namespace
23
24RangeResult normalise(int whence, int64_t start, int64_t length, uint64_t position,
25 uint64_t fileSize, Range& result) {
26 uint64_t base = 0;
27 if (whence == 1) {
28 base = position;
29 } else if (whence == 2) {
30 base = fileSize;
31 } else if (whence != 0) {
32 return RangeResult::Invalid;
33 }
34 int64_t first = 0;
35 if (base > static_cast<uint64_t>(LastOffset) ||
36 __builtin_add_overflow(static_cast<int64_t>(base), start, &first)) {
37 return RangeResult::Overflow;
38 }
39 if (first < 0) {
40 return RangeResult::Invalid;
41 }
42 int64_t last = LastOffset;
43 if (length > 0) {
44 if (__builtin_add_overflow(first, length - 1, &last)) {
45 return RangeResult::Overflow;
46 }
47 } else if (length < 0) {
48 last = first - 1;
49 first += length;
50 if (first < 0) {
51 return RangeResult::Invalid;
52 }
53 }
54 result = {first, last};
55 return RangeResult::Success;
56}
57
58Table::Table(Grant* current, Grant* scratch, size_t capacity)
59 : m_Current(current), m_Scratch(scratch), m_Capacity(capacity) {}
60
61bool Table::conflict(const Grant& request, Grant& result) const {
62 bool found = false;
63 for (size_t i = 0; i < m_Count; ++i) {
64 const Grant& grant = m_Current[i];
65 if (conflicts(request, grant) && (!found || grant.range.first < result.range.first)) {
66 result = grant;
67 found = true;
68 }
69 }
70 return found;
71}
72
73bool Table::query(const Grant& request, Grant& result) const {
74 if (request.type != Type::Unlock)
75 return conflict(request, result);
76 // F_OFD_GETLK with F_UNLCK inspects this open description's own records.
77 // Unlocking and ordinary conflict checks keep their separate semantics.
78 if (request.kind != OwnerKind::OpenDescription || request.name != Namespace::Record)
79 return false;
80 bool found = false;
81 for (size_t i = 0; i < m_Count; ++i) {
82 const Grant& grant = m_Current[i];
83 if (sameSet(request, grant) && sameOwner(request, grant) &&
84 overlaps(request.range, grant.range) &&
85 (!found || grant.range.first < result.range.first)) {
86 result = grant;
87 found = true;
88 }
89 }
90 return found;
91}
92
93bool Table::append(const Grant& grant, size_t& count) {
94 if (count == m_Capacity)
95 return false;
96 m_Scratch[count++] = grant;
97 return true;
98}
99
100Result Table::apply(const Grant& request) {
101 Grant blocked;
102 if (conflict(request, blocked))
103 return Result::Conflict;
104 Grant replacement = request;
105 if (replacement.type != Type::Unlock) {
106 // Existing owner ranges are canonical; only the new range can connect
107 // them. Expand it before staging so a merge also succeeds at capacity.
108 for (size_t i = 0; i < m_Count;) {
109 const Grant& old = m_Current[i];
110 const bool touching =
111 overlaps(replacement.range, old.range) ||
112 (replacement.range.last != LastOffset && replacement.range.last + 1 == old.range.first) ||
113 (old.range.last != LastOffset && old.range.last + 1 == replacement.range.first);
114 if (sameSet(old, replacement) && sameOwner(old, replacement) &&
115 old.type == replacement.type && touching &&
116 (old.range.first < replacement.range.first || old.range.last > replacement.range.last)) {
117 if (old.range.first < replacement.range.first)
118 replacement.range.first = old.range.first;
119 if (old.range.last > replacement.range.last)
120 replacement.range.last = old.range.last;
121 i = 0;
122 } else {
123 ++i;
124 }
125 }
126 }
127 size_t staged = 0;
128 if (replacement.type != Type::Unlock && !append(replacement, staged))
129 return Result::Full;
130 for (size_t i = 0; i < m_Count; ++i) {
131 const Grant& old = m_Current[i];
132 if (!sameSet(old, replacement) || !sameOwner(old, replacement) ||
133 !overlaps(old.range, replacement.range)) {
134 if (!append(old, staged))
135 return Result::Full;
136 continue;
137 }
138 if (old.range.first < replacement.range.first) {
139 Grant left = old;
140 left.range.last = replacement.range.first - 1;
141 if (!append(left, staged))
142 return Result::Full;
143 }
144 if (old.range.last > replacement.range.last) {
145 Grant right = old;
146 right.range.first = replacement.range.last + 1;
147 if (!append(right, staged))
148 return Result::Full;
149 }
150 }
151 Grant* previous = m_Current;
152 m_Current = m_Scratch;
153 m_Scratch = previous;
154 m_Count = staged;
155 return Result::Success;
156}
157
158bool Table::removeOwner(uint64_t owner, uintptr_t inode) {
159 size_t kept = 0;
160 for (size_t i = 0; i < m_Count; ++i) {
161 const Grant& grant = m_Current[i];
162 if (grant.owner != owner || (inode && grant.inode != inode)) {
163 m_Current[kept++] = grant;
164 }
165 }
166 const bool changed = kept != m_Count;
167 m_Count = kept;
168 return changed;
169}
170
171bool Table::wouldDeadlock(const Grant& request, const Grant* const* waiters,
172 size_t waiterCount) const {
173 if (request.kind != OwnerKind::Process || request.name != Namespace::Record ||
174 request.type == Type::Unlock || waiterCount > MaximumWaiters)
175 return false;
176
177 uint64_t reachable[MaximumWaiters + 1] = {request.owner};
178 size_t reached = 1;
179 for (size_t node = 0; node < reached; ++node) {
180 for (size_t w = 0; w <= waiterCount; ++w) {
181 const Grant* waiting = w == waiterCount ? &request : waiters[w];
182 if (!waiting || waiting->owner != reachable[node] || waiting->kind != OwnerKind::Process ||
183 waiting->name != Namespace::Record)
184 continue;
185 for (size_t g = 0; g < m_Count; ++g) {
186 const Grant& blocker = m_Current[g];
187 if (blocker.kind != OwnerKind::Process || !conflicts(*waiting, blocker))
188 continue;
189 if (blocker.owner == request.owner)
190 return true;
191
192 bool hasWait = false;
193 for (size_t i = 0; i < waiterCount; ++i) {
194 if (waiters[i] && waiters[i]->owner == blocker.owner &&
195 waiters[i]->kind == OwnerKind::Process && waiters[i]->name == Namespace::Record) {
196 hasWait = true;
197 break;
198 }
199 }
200 if (!hasWait)
201 continue;
202 bool seen = false;
203 for (size_t i = 0; i < reached; ++i)
204 seen |= reachable[i] == blocker.owner;
205 if (!seen && reached < MaximumWaiters + 1)
206 reachable[reached++] = blocker.owner;
207 }
208 }
209 }
210 return false;
211}
212} // namespace PosixAdvisory