The Pedigree Project 0.1
sysv-semaphore-syscalls.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "pedigree/kernel/LockGuard.h"
9#include "pedigree/kernel/process/ConditionVariable.h"
10#include "pedigree/kernel/process/Mutex.h"
11#include "pedigree/kernel/process/Process.h"
12#include "pedigree/kernel/process/Thread.h"
13#include "pedigree/kernel/processor/Processor.h"
14#include "pedigree/kernel/processor/ProcessorInformation.h"
15#include "pedigree/kernel/syscallError.h"
16#include "pedigree/kernel/time/Time.h"
17#include "pedigree/kernel/utilities/SharedPointer.h"
18
19#include "PosixSubsystem.h"
20#include "ipc-common.h"
21#include "sysv-semaphore-syscalls.h"
22
23namespace {
24constexpr size_t MaximumSets = 128;
25constexpr size_t MaximumSemaphores = 256;
26constexpr size_t MaximumOperations = 128;
27constexpr size_t MaximumUndoRecords = 4096;
28constexpr size_t MaximumUndoOwners = 4096;
29constexpr int MaximumValue = 32767;
30constexpr int Create = 01000, Exclusive = 02000, NoWait = 04000, Undo = 0x1000;
31constexpr int Remove = 0, SetMetadata = 1, Stat = 2, Info = 3;
32constexpr int GetPid = 11, GetValue = 12, GetAll = 13, GetNegativeCount = 14;
33constexpr int GetZeroCount = 15, SetValue = 16, SetAll = 17;
34constexpr int SemStat = 18, SemInfo = 19, SemStatAny = 20;
35
36struct Operation {
37 uint16_t number;
38 int16_t value;
39 int16_t flags;
40};
41
42struct Metadata {
43 PosixIpc::Permission permission;
44 int64_t operationTime;
45 uint64_t unused1;
46 int64_t changeTime;
47 uint64_t unused2;
48 uint64_t count;
49 uint64_t unused3;
50 uint64_t unused4;
51};
52
53struct Information {
54 int map, identifiers, semaphores, undoStructures, perSet, operations;
55 int undoEntries, undoSize, maximumValue, maximumAdjustment;
56};
57
58static_assert(sizeof(Operation) == 6, "Linux sembuf ABI");
59static_assert(sizeof(PosixIpc::Permission) == 48, "Linux ipc_perm ABI");
60static_assert(sizeof(Metadata) == 104, "Linux amd64 semid_ds ABI");
61
62struct SemaphoreValue {
63 unsigned short value = 0;
64 int pid = 0;
65 unsigned negativeWaiters = 0;
66 unsigned zeroWaiters = 0;
67};
68
69struct Set {
70 uint64_t namespaceId = posix_ipc_namespace_id();
71 Metadata metadata = {};
72 SemaphoreValue semaphores[MaximumSemaphores];
73 ConditionVariable changed;
74 int id = 0;
75 bool removed = false;
76};
77
78struct UndoGroup {
79 size_t owners = 0;
80};
81
82struct UndoOwner {
83 Thread* thread;
84 UndoGroup* group;
85 UndoOwner* next;
86};
87
88struct UndoRecord {
89 UndoGroup* group;
90 int id;
91 unsigned number;
92 int adjustment;
93 UndoRecord* next;
94};
95
96// One lock also orders undo retirement against SETVAL, SETALL and IPC_RMID.
97Mutex registryLock;
98SharedPointer<Set> registry[MaximumSets];
99unsigned sequences[MaximumSets] = {};
100UndoRecord* undoRecords = nullptr;
101size_t undoRecordCount = 0;
102UndoOwner* undoOwners = nullptr;
103size_t undoOwnerCount = 0;
104
105SharedPointer<Set> findSet(int id, bool enforceNamespace = true) {
106 if (id < 0)
107 return {};
108 const SharedPointer<Set>& set = registry[static_cast<unsigned>(id) % MaximumSets];
109 return set && set->id == id && (!enforceNamespace || set->namespaceId == posix_ipc_namespace_id())
110 ? set
111 : SharedPointer<Set>();
112}
113
114UndoOwner* findOwner(Thread* thread) {
115 for (UndoOwner* owner = undoOwners; owner; owner = owner->next)
116 if (owner->thread == thread)
117 return owner;
118 return nullptr;
119}
120
121UndoGroup* createUndoGroup(Thread* thread) {
122 UndoOwner* owner = findOwner(thread);
123 if (owner)
124 return owner->group;
125 if (undoOwnerCount == MaximumUndoOwners) {
126 SYSCALL_ERROR(OutOfMemory);
127 return nullptr;
128 }
129 UndoGroup* group = new UndoGroup;
130 group->owners = 1;
131 undoOwners = new UndoOwner{thread, group, undoOwners};
132 ++undoOwnerCount;
133 return group;
134}
135
136UndoRecord* findUndo(UndoGroup* group, int id, unsigned number) {
137 for (UndoRecord* record = undoRecords; record; record = record->next)
138 if (record->group == group && record->id == id && record->number == number)
139 return record;
140 return nullptr;
141}
142
143void clearUndo(int id, int number = -1) {
144 UndoRecord** link = &undoRecords;
145 while (*link) {
146 UndoRecord* record = *link;
147 if (record->id == id && (number < 0 || record->number == static_cast<unsigned>(number))) {
148 *link = record->next;
149 delete record;
150 --undoRecordCount;
151 } else {
152 link = &record->next;
153 }
154 }
155}
156
157// No values or undo entries are published until the entire vector can commit.
158int perform(Set& set, Process* process, UndoGroup* group, const Operation* operations, size_t count,
159 size_t& blocked) {
160 unsigned short values[MaximumSemaphores];
161 int adjustments[MaximumSemaphores] = {};
162 bool adjusted[MaximumSemaphores] = {};
163 for (size_t i = 0; i < set.metadata.count; ++i)
164 values[i] = set.semaphores[i].value;
165 for (size_t i = 0; i < count; ++i) {
166 const Operation& operation = operations[i];
167 int next = values[operation.number] + operation.value;
168 if ((!operation.value && next) || next < 0) {
169 blocked = i;
170 return 1;
171 }
172 if (next > MaximumValue) {
173 SYSCALL_ERROR(BadRange);
174 return -1;
175 }
176 values[operation.number] = next;
177 if ((operation.flags & Undo) && operation.value) {
178 if (!adjusted[operation.number]) {
179 UndoRecord* record = findUndo(group, set.id, operation.number);
180 adjustments[operation.number] = record ? record->adjustment : 0;
181 adjusted[operation.number] = true;
182 }
183 const int adjustment = adjustments[operation.number] - operation.value;
184 if (adjustment < -MaximumValue - 1 || adjustment > MaximumValue) {
185 SYSCALL_ERROR(BadRange);
186 return -1;
187 }
188 adjustments[operation.number] = adjustment;
189 }
190 }
191 size_t needed = 0;
192 for (size_t i = 0; i < set.metadata.count; ++i)
193 if (adjusted[i] && adjustments[i] && !findUndo(group, set.id, i))
194 ++needed;
195 if (needed > MaximumUndoRecords - undoRecordCount) {
196 SYSCALL_ERROR(OutOfMemory);
197 return -1;
198 }
199 for (size_t i = 0; i < set.metadata.count; ++i) {
200 if (adjusted[i]) {
201 UndoRecord* record = findUndo(group, set.id, i);
202 if (record) {
203 record->adjustment = adjustments[i];
204 } else if (adjustments[i]) {
205 undoRecords =
206 new UndoRecord{group, set.id, static_cast<unsigned>(i), adjustments[i], undoRecords};
207 ++undoRecordCount;
208 }
209 }
210 set.semaphores[i].value = values[i];
211 }
212 for (size_t i = 0; i < count; ++i)
213 set.semaphores[operations[i].number].pid = process->getUserspaceId();
214 UndoRecord** link = &undoRecords;
215 while (*link) {
216 UndoRecord* record = *link;
217 if (!record->adjustment) {
218 *link = record->next;
219 delete record;
220 --undoRecordCount;
221 } else {
222 link = &record->next;
223 }
224 }
225 set.metadata.operationTime = Time::getTime();
226 set.changed.broadcast();
227 return 0;
228}
229} // namespace
230
231int posix_semget(int key, int count, int flags) {
232 if (count < 0 || count > static_cast<int>(MaximumSemaphores)) {
233 SYSCALL_ERROR(InvalidArgument);
234 return -1;
235 }
236 LockGuard<Mutex> guard(registryLock);
237 size_t freeSlot = MaximumSets;
238 for (size_t i = 0; i < MaximumSets; ++i) {
239 if (!registry[i]) {
240 if (freeSlot == MaximumSets)
241 freeSlot = i;
242 continue;
243 }
244 Set& set = *registry[i];
245 if (!key || set.namespaceId != posix_ipc_namespace_id() || set.metadata.permission.key != key) {
246 continue;
247 }
248 if ((flags & (Create | Exclusive)) == (Create | Exclusive)) {
249 SYSCALL_ERROR(FileExists);
250 return -1;
251 }
252 if (static_cast<unsigned>(count) > set.metadata.count) {
253 SYSCALL_ERROR(InvalidArgument);
254 return -1;
255 }
256 const unsigned requested = ((flags >> 6) | (flags >> 3) | flags) & 7;
257 if (!PosixIpc::allowed(set.metadata.permission, requested)) {
258 SYSCALL_ERROR(PermissionDenied);
259 return -1;
260 }
261 return set.id;
262 }
263 if (key && !(flags & Create)) {
264 SYSCALL_ERROR(DoesNotExist);
265 return -1;
266 }
267 if (!count) {
268 SYSCALL_ERROR(InvalidArgument);
269 return -1;
270 }
271 if (freeSlot == MaximumSets) {
272 SYSCALL_ERROR(NoSpaceLeftOnDevice);
273 return -1;
274 }
275 SharedPointer<Set> set(new Set);
276 auto& permission = set->metadata.permission;
277 PosixIpc::initialize(permission, key, flags & 0777, sequences[freeSlot]++ & 0xffff);
278 set->id = permission.sequence * MaximumSets + freeSlot;
279 set->metadata.count = count;
280 set->metadata.changeTime = Time::getTime();
281 registry[freeSlot] = set;
282 return set->id;
283}
284
285int posix_semop(int id, const void* operations, size_t count) {
286 return posix_semtimedop(id, operations, count, nullptr);
287}
288
289int posix_semtimedop(int id, const void* operations, size_t count, const void* timeout) {
290 if (id < 0 || !count) {
291 SYSCALL_ERROR(InvalidArgument);
292 return -1;
293 }
294 if (count > MaximumOperations) {
295 SYSCALL_ERROR(TooBig);
296 return -1;
297 }
298 Operation requested[MaximumOperations];
299 if (!PosixSubsystem::copyFromUser(requested, operations, count * sizeof(Operation))) {
300 SYSCALL_ERROR(BadAddress);
301 return -1;
302 }
303 Time::Timestamp remaining = Time::Infinity;
304 Time::Timestamp deadline = Time::Infinity;
305 if (timeout) {
306 struct {
307 int64_t seconds, nanoseconds;
308 } duration;
309 if (!PosixSubsystem::copyFromUser(&duration, timeout, sizeof(duration))) {
310 SYSCALL_ERROR(BadAddress);
311 return -1;
312 }
313 if (duration.seconds < 0 || duration.nanoseconds < 0 ||
314 duration.nanoseconds >= static_cast<int64_t>(Time::Multiplier::Second)) {
315 SYSCALL_ERROR(InvalidArgument);
316 return -1;
317 }
318 const Time::Timestamp seconds = duration.seconds;
319 remaining = seconds > (Time::Infinity - 1 - duration.nanoseconds) / Time::Multiplier::Second
320 ? Time::Infinity - 1
321 : seconds * Time::Multiplier::Second + duration.nanoseconds;
322 const Time::Timestamp now = Time::getTicks();
323 deadline = remaining >= Time::Infinity - now ? Time::Infinity - 1 : now + remaining;
324 }
325 LockGuard<Mutex> guard(registryLock);
326 SharedPointer<Set> set = findSet(id);
327 if (!set || set->namespaceId != posix_ipc_namespace_id()) {
328 SYSCALL_ERROR(InvalidArgument);
329 return -1;
330 }
331 unsigned access = 4;
332 bool needsUndo = false;
333 for (size_t i = 0; i < count; ++i) {
334 if (requested[i].number >= set->metadata.count) {
335 SYSCALL_ERROR(FileTooLarge);
336 return -1;
337 }
338 if (requested[i].value)
339 access = 2;
340 needsUndo |= (requested[i].flags & Undo) && requested[i].value;
341 }
342 Process* process = PosixIpc::process();
343 if (!PosixIpc::allowed(set->metadata.permission, access)) {
344 SYSCALL_ERROR(PermissionDenied);
345 return -1;
346 }
347 UndoGroup* group = nullptr;
348 if (needsUndo) {
349 group = createUndoGroup(Processor::information().getCurrentThread());
350 if (!group)
351 return -1;
352 }
353 for (;;) {
354 if (set->removed) {
355 SYSCALL_ERROR(IdentifierRemoved);
356 return -1;
357 }
358 size_t blocked = 0;
359 const int result = perform(*set, process, group, requested, count, blocked);
360 if (result <= 0)
361 return result;
362 if (deadline != Time::Infinity) {
363 const Time::Timestamp now = Time::getTicks();
364 remaining = now >= deadline ? 0 : deadline - now;
365 }
366 if ((requested[blocked].flags & NoWait) || !remaining) {
367 SYSCALL_ERROR(NoMoreProcesses);
368 return -1;
369 }
370 SemaphoreValue& semaphore = set->semaphores[requested[blocked].number];
371 unsigned& waiters =
372 requested[blocked].value ? semaphore.negativeWaiters : semaphore.zeroWaiters;
373 ++waiters;
374 ConditionVariable::Error error = ConditionVariable::NoError;
375 const bool awakened = set->changed.wait(registryLock, remaining, error);
376 --waiters;
377 if (set->removed) {
378 SYSCALL_ERROR(IdentifierRemoved);
379 return -1;
380 }
381 if (!awakened) {
382 if (error == ConditionVariable::TimedOut)
383 SYSCALL_ERROR(NoMoreProcesses);
384 else
385 SYSCALL_ERROR(Interrupted);
386 return -1;
387 }
388 }
389}
390
391int posix_semctl(int id, int number, int command, uintptr_t argument) {
392 if (id < 0 || command < Remove || (command > Info && command < GetPid) || command > SemStatAny) {
393 SYSCALL_ERROR(InvalidArgument);
394 return -1;
395 }
396 if (command == SetValue &&
397 (static_cast<int>(argument) < 0 || static_cast<int>(argument) > MaximumValue)) {
398 SYSCALL_ERROR(BadRange);
399 return -1;
400 }
401 LockGuard<Mutex> guard(registryLock);
402 if (command == Info || command == SemInfo) {
403 int highest = 0, usedSets = 0, usedSemaphores = 0;
404 for (size_t i = 0; i < MaximumSets; ++i) {
405 if (registry[i] && registry[i]->namespaceId == posix_ipc_namespace_id()) {
406 highest = i;
407 ++usedSets;
408 usedSemaphores += registry[i]->metadata.count;
409 }
410 }
411 const Information information = {0,
412 MaximumSets,
413 MaximumSets * MaximumSemaphores,
414 MaximumUndoRecords,
415 MaximumSemaphores,
416 MaximumOperations,
417 MaximumUndoRecords,
418 command == SemInfo ? usedSets : 20,
419 MaximumValue,
420 command == SemInfo ? usedSemaphores : MaximumValue};
421 if (!PosixSubsystem::copyToUser(reinterpret_cast<void*>(argument), &information,
422 sizeof(information))) {
423 SYSCALL_ERROR(BadAddress);
424 return -1;
425 }
426 return highest;
427 }
428 const bool byIndex = command == SemStat || command == SemStatAny;
429 SharedPointer<Set> set = byIndex && id >= 0 && id < static_cast<int>(MaximumSets) ? registry[id]
430 : byIndex ? SharedPointer<Set>()
431 : findSet(id);
432 if (!set || set->namespaceId != posix_ipc_namespace_id()) {
433 SYSCALL_ERROR(InvalidArgument);
434 return -1;
435 }
436 Process* process = PosixIpc::process();
437 auto& permission = set->metadata.permission;
438 if (command == Remove || command == SetMetadata) {
439 if (!PosixIpc::owner(permission)) {
440 SYSCALL_ERROR(NotEnoughPermissions);
441 return -1;
442 }
443 } else if (command != SemStatAny &&
444 !PosixIpc::allowed(permission, command == SetValue || command == SetAll ? 2 : 4)) {
445 SYSCALL_ERROR(PermissionDenied);
446 return -1;
447 }
448 switch (command) {
449 case Remove:
450 set->removed = true;
451 clearUndo(set->id);
452 registry[static_cast<unsigned>(set->id) % MaximumSets].reset();
453 set->changed.broadcast();
454 return 0;
455 case SetMetadata: {
456 Metadata requested;
457 if (!PosixSubsystem::copyFromUser(&requested, reinterpret_cast<void*>(argument),
458 sizeof(requested))) {
459 SYSCALL_ERROR(BadAddress);
460 return -1;
461 }
462 if (requested.permission.uid == 0xffffffffU || requested.permission.gid == 0xffffffffU) {
463 SYSCALL_ERROR(InvalidArgument);
464 return -1;
465 }
466 permission.uid = requested.permission.uid;
467 permission.gid = requested.permission.gid;
468 permission.mode = requested.permission.mode & 0777;
469 set->metadata.changeTime = Time::getTime();
470 return 0;
471 }
472 case Stat:
473 case SemStat:
474 case SemStatAny:
475 if (!PosixSubsystem::copyToUser(reinterpret_cast<void*>(argument), &set->metadata,
476 sizeof(set->metadata))) {
477 SYSCALL_ERROR(BadAddress);
478 return -1;
479 }
480 return byIndex ? set->id : 0;
481 case GetAll:
482 case SetAll: {
483 unsigned short values[MaximumSemaphores];
484 const size_t bytes = set->metadata.count * sizeof(values[0]);
485 if (command == GetAll) {
486 for (size_t i = 0; i < set->metadata.count; ++i)
487 values[i] = set->semaphores[i].value;
488 if (!PosixSubsystem::copyToUser(reinterpret_cast<void*>(argument), values, bytes)) {
489 SYSCALL_ERROR(BadAddress);
490 return -1;
491 }
492 } else {
493 if (!PosixSubsystem::copyFromUser(values, reinterpret_cast<void*>(argument), bytes)) {
494 SYSCALL_ERROR(BadAddress);
495 return -1;
496 }
497 for (size_t i = 0; i < set->metadata.count; ++i) {
498 if (values[i] > MaximumValue) {
499 SYSCALL_ERROR(BadRange);
500 return -1;
501 }
502 }
503 clearUndo(set->id);
504 for (size_t i = 0; i < set->metadata.count; ++i) {
505 set->semaphores[i].value = values[i];
506 set->semaphores[i].pid = process->getUserspaceId();
507 }
508 set->metadata.changeTime = Time::getTime();
509 set->changed.broadcast();
510 }
511 return 0;
512 }
513 case GetPid:
514 case GetValue:
515 case GetNegativeCount:
516 case GetZeroCount:
517 case SetValue:
518 break;
519 default:
520 SYSCALL_ERROR(InvalidArgument);
521 return -1;
522 }
523 if (number < 0 || static_cast<unsigned>(number) >= set->metadata.count) {
524 SYSCALL_ERROR(InvalidArgument);
525 return -1;
526 }
527 SemaphoreValue& semaphore = set->semaphores[number];
528 switch (command) {
529 case GetPid:
530 return semaphore.pid;
531 case GetValue:
532 return semaphore.value;
533 case GetNegativeCount:
534 return semaphore.negativeWaiters;
535 case GetZeroCount:
536 return semaphore.zeroWaiters;
537 default:
538 const int value = static_cast<int>(argument);
539 clearUndo(set->id, number);
540 semaphore.value = value;
541 semaphore.pid = process->getUserspaceId();
542 set->metadata.changeTime = Time::getTime();
543 set->changed.broadcast();
544 return 0;
545 }
546}
547
548bool posix_sem_clone(Thread* parent, Thread* child, bool shareUndo) {
549 if (!shareUndo)
550 return true;
551 LockGuard<Mutex> guard(registryLock);
552 if (findOwner(child))
553 return true;
554 const size_t needed = findOwner(parent) ? 1 : 2;
555 if (needed > MaximumUndoOwners - undoOwnerCount) {
556 SYSCALL_ERROR(OutOfMemory);
557 return false;
558 }
559 UndoGroup* group = createUndoGroup(parent);
560 undoOwners = new UndoOwner{child, group, undoOwners};
561 ++group->owners;
562 ++undoOwnerCount;
563 return true;
564}
565
566void posix_sem_thread_exit(Thread* thread) {
567 LockGuard<Mutex> guard(registryLock);
568 UndoOwner** ownerLink = &undoOwners;
569 while (*ownerLink && (*ownerLink)->thread != thread)
570 ownerLink = &(*ownerLink)->next;
571 if (!*ownerLink)
572 return;
573 UndoOwner* owner = *ownerLink;
574 UndoGroup* group = owner->group;
575 *ownerLink = owner->next;
576 delete owner;
577 --undoOwnerCount;
578 if (--group->owners)
579 return;
580 UndoRecord** link = &undoRecords;
581 while (*link) {
582 UndoRecord* record = *link;
583 if (record->group != group) {
584 link = &record->next;
585 continue;
586 }
587 SharedPointer<Set> set = findSet(record->id, false);
588 if (set && record->adjustment) {
589 SemaphoreValue& semaphore = set->semaphores[record->number];
590 const int value = semaphore.value + record->adjustment;
591 // Exit must not block; Linux clips an adjustment which can no longer fit.
592 semaphore.value = value < 0 ? 0 : value > MaximumValue ? MaximumValue : value;
593 semaphore.pid = thread->getParent()->getUserspaceId();
594 set->metadata.operationTime = Time::getTime();
595 set->changed.broadcast();
596 }
597 *link = record->next;
598 delete record;
599 --undoRecordCount;
600 }
601 delete group;
602}
603
604void posix_sem_namespace_exit(uint64_t identity) {
605 LockGuard<Mutex> guard(registryLock);
606 for (auto& set : registry) {
607 if (set && set->namespaceId == identity) {
608 set->removed = true;
609 clearUndo(set->id);
610 set->changed.broadcast();
611 set.reset();
612 }
613 }
614}
Definition Mutex.h:56
static bool copyFromUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
size_t getUserspaceId() const
Definition Process.h:504
static ProcessorInformation & information()
Process * getParent() const
Definition Thread.h:340