The Pedigree Project 0.1
sysv-shm-syscalls.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
3#include "pedigree/kernel/processor/VirtualAddressSpace.h"
4#include "pedigree/kernel/syscallError.h"
5#include "pedigree/kernel/time/Time.h"
6#include "pedigree/kernel/utilities/SharedPointer.h"
7#include "pedigree/kernel/utilities/assert.h"
8#include "pedigree/kernel/utilities/utility.h"
9
10#include <errno.h>
11
12#include "PosixSubsystem.h"
13#include "ipc-common.h"
16#include "modules/system/vfs/VFS.h"
17#include "sysv-shm-syscalls.h"
18
19namespace {
20constexpr size_t MaximumSegments = 128;
21constexpr size_t MaximumSegmentBytes = 16 * 1024 * 1024;
22constexpr size_t MaximumTotalBytes = 64 * 1024 * 1024;
23constexpr size_t MaximumAttachments = 128;
24constexpr int Create = 01000, Exclusive = 02000, HugePages = 04000, NoReserve = 010000;
25constexpr int ReadOnly = 010000, Round = 020000, Remap = 040000, Executable = 0100000;
26constexpr int Remove = 0, Set = 1, Stat = 2, Info = 3;
27constexpr int Lock = 11, Unlock = 12, SegmentStat = 13, SegmentInfo = 14, SegmentStatAny = 15;
28constexpr unsigned Destroyed = 01000, Locked = 02000;
29
30struct SegmentStatus {
31 PosixIpc::Permission permission;
32 uint64_t size;
33 int64_t attachTime, detachTime, changeTime;
34 int32_t creator, lastPid;
35 uint64_t attachments, unused[2];
36};
37static_assert(sizeof(SegmentStatus) == 112, "Linux amd64 shmid_ds layout");
38static_assert(__builtin_offsetof(SegmentStatus, attachments) == 88, "Linux shm_nattch offset");
39struct SegmentLimits {
40 uint64_t maximumSize, minimumSize, segmentCount, attachments, totalPages, unused[4];
41};
42struct SegmentUsage {
43 int32_t usedIds, padding;
44 uint64_t totalPages, residentPages, swappedPages, swapAttempts, swapSuccesses;
45};
46static_assert(sizeof(SegmentLimits) == 72 && sizeof(SegmentUsage) == 48, "Linux shm info layout");
47
48struct Segment {
49 uint64_t namespaceId = posix_ipc_namespace_id();
50 Segment(int identifier, size_t slot, int32_t key, size_t size, size_t roundedSize, unsigned mode)
51 : id(identifier),
52 slot(slot),
53 roundedSize(roundedSize),
54 lockedPages(0),
55 pendingAttachments(0),
56 status(),
57 filesystem(),
58 file(new RamFile(String("shm"), identifier, &filesystem, nullptr)) {
59 PosixIpc::initialize(status.permission, key, mode, identifier / MaximumSegments);
60 status.size = size;
61 status.creator = PosixIpc::process()->getUserspaceId();
62 status.changeTime = Time::getTime();
64 }
65 ~Segment() {
66 unlock();
68 }
69 void unlock() {
70 const size_t pageSize = PhysicalMemoryManager::getPageSize();
71 while (lockedPages) {
72 file->returnPhysicalPage(--lockedPages * pageSize);
73 }
74 status.permission.mode &= ~Locked;
75 }
76 bool lock() {
77 if (status.permission.mode & Locked) {
78 return true;
79 }
80 const size_t pageSize = PhysicalMemoryManager::getPageSize();
81 for (size_t offset = 0; offset < roundedSize; offset += pageSize) {
82 if (file->read(offset, pageSize, 0) != pageSize || file->getPhysicalPage(offset) == ~0UL) {
83 unlock();
84 SYSCALL_ERROR(OutOfMemory);
85 return false;
86 }
87 ++lockedPages;
88 }
89 status.permission.mode |= Locked;
90 return true;
91 }
92 int id;
93 size_t slot, roundedSize, lockedPages, pendingAttachments;
94 SegmentStatus status;
95 RamFs filesystem;
96 RamFile* file;
97};
98
99class ShmAttachment;
100// Mapping teardown invokes attachment callbacks synchronously. The recursive
101// operation gate protects this registry as well as mapping publication/removal.
102SharedPointer<Segment> segments[MaximumSegments];
103List<ShmAttachment*> attachments;
104size_t totalBytes = 0;
105uint32_t nextSequence = 0;
106
107void retire(const SharedPointer<Segment>& segment) {
108 if ((segment->status.permission.mode & Destroyed) && !segment->status.attachments &&
109 !segment->pendingAttachments && segments[segment->slot] == segment) {
110 totalBytes -= segment->roundedSize;
111 segments[segment->slot].reset();
112 }
113}
114
115class ShmAttachment final : public MappingAttachment {
116 public:
117 explicit ShmAttachment(const SharedPointer<Segment>& segment)
118 : m_Segment(segment), m_Base(0), m_Pid(0), m_Active(false) {}
119 ~ShmAttachment() override {
121 if (!m_Active) {
122 return;
123 }
124 for (auto it = attachments.begin(); it != attachments.end(); ++it) {
125 if (*it == this) {
126 attachments.erase(it);
127 break;
128 }
129 }
130 assert(m_Segment->status.attachments);
131 --m_Segment->status.attachments;
132 m_Segment->status.detachTime = Time::getTime();
133 m_Segment->status.lastPid = m_UserPid;
134 retire(m_Segment);
135 }
136 void activate(uintptr_t base, size_t pid, size_t userPid) {
137 m_Base = base;
138 m_Pid = pid;
139 m_UserPid = userPid;
140 m_Active = true;
141 ++m_Segment->status.attachments;
142 m_Segment->status.attachTime = Time::getTime();
143 m_Segment->status.lastPid = userPid;
144 attachments.pushBack(this);
145 }
146 uintptr_t baseAddress() const override {
147 return m_Base;
148 }
149 void relocate(uintptr_t newBase) override {
150 m_Base = newBase;
151 }
152 size_t pid() const {
153 return m_Pid;
154 }
155 SharedPointer<MappingAttachment> clone(Process* target) override {
156 auto* copy = new ShmAttachment(m_Segment);
157 copy->activate(m_Base, target->getId(), target->getUserspaceId());
159 }
160
161 private:
162 SharedPointer<Segment> m_Segment;
163 uintptr_t m_Base;
164 size_t m_Pid;
165 size_t m_UserPid = 0;
166 bool m_Active;
167};
168
169SharedPointer<Segment> findSegment(int id, bool index = false) {
170 if (id >= 0) {
171 const size_t slot = index ? static_cast<size_t>(id) : id % MaximumSegments;
172 if (slot < MaximumSegments && segments[slot] &&
173 segments[slot]->namespaceId == posix_ipc_namespace_id() &&
174 (index || segments[slot]->id == id)) {
175 return segments[slot];
176 }
177 }
178 SYSCALL_ERROR(InvalidArgument);
179 return SharedPointer<Segment>();
180}
181
182int information(int command, void* buffer) {
183 int highest = 0;
184 SegmentUsage usage = {};
185 const size_t pageSize = PhysicalMemoryManager::getPageSize();
186 for (size_t i = 0; i < MaximumSegments; ++i) {
187 if (segments[i] && segments[i]->namespaceId == posix_ipc_namespace_id()) {
188 highest = i;
189 ++usage.usedIds;
190 usage.totalPages += segments[i]->roundedSize / pageSize;
191 usage.residentPages += segments[i]->file->getAttributes().blocks * 512 / pageSize;
192 }
193 }
194 if (command == Info) {
195 const SegmentLimits limits = {
196 MaximumSegmentBytes, 1, MaximumSegments, MaximumAttachments,
197 MaximumTotalBytes / pageSize, {}};
198 if (PosixSubsystem::copyToUser(buffer, &limits, sizeof(limits))) {
199 return highest;
200 }
201 } else if (PosixSubsystem::copyToUser(buffer, &usage, sizeof(usage))) {
202 return highest;
203 }
204 SYSCALL_ERROR(BadAddress);
205 return -1;
206}
207} // namespace
208
209int posix_shmget(int32_t key, size_t size, int flags) {
211 if (flags & HugePages) {
212 SYSCALL_ERROR(OperationNotSupported);
213 return -1;
214 }
215 if (flags & ~(0777 | Create | Exclusive | NoReserve)) {
216 SYSCALL_ERROR(InvalidArgument);
217 return -1;
218 }
219 size_t freeSlot = MaximumSegments;
220 for (size_t i = 0; i < MaximumSegments; ++i) {
221 if (!segments[i]) {
222 if (freeSlot == MaximumSegments) {
223 freeSlot = i;
224 }
225 continue;
226 }
227 if (key && segments[i]->namespaceId == posix_ipc_namespace_id() &&
228 segments[i]->status.permission.key == key) {
229 if ((flags & (Create | Exclusive)) == (Create | Exclusive)) {
230 SYSCALL_ERROR(FileExists);
231 return -1;
232 }
233 if (size > segments[i]->status.size) {
234 SYSCALL_ERROR(InvalidArgument);
235 return -1;
236 }
237 const unsigned access = ((flags >> 6) | (flags >> 3) | flags) & 7;
238 if (!PosixIpc::allowed(segments[i]->status.permission, access)) {
239 SYSCALL_ERROR(PermissionDenied);
240 return -1;
241 }
242 return segments[i]->id;
243 }
244 }
245 if (key && !(flags & Create)) {
246 SYSCALL_ERROR(DoesNotExist);
247 return -1;
248 }
249 if (!size || size > MaximumSegmentBytes) {
250 SYSCALL_ERROR(InvalidArgument);
251 return -1;
252 }
253 const size_t pageMask = PhysicalMemoryManager::getPageSize() - 1;
254 const size_t roundedSize = (size + pageMask) & ~pageMask;
255 if (freeSlot == MaximumSegments || roundedSize > MaximumTotalBytes - totalBytes ||
256 nextSequence > (0x7fffffffU - freeSlot) / MaximumSegments) {
257 SYSCALL_ERROR(NoSpaceLeftOnDevice);
258 return -1;
259 }
260 const int id = nextSequence++ * MaximumSegments + freeSlot;
261 SharedPointer<Segment> segment(new Segment(id, freeSlot, key, size, roundedSize, flags & 0777));
262 if (!segment->file->resize(roundedSize)) {
263 return -1;
264 }
265 totalBytes += roundedSize;
266 segments[freeSlot] = segment;
267 return id;
268}
269
270void* posix_shmat(int id, const void* requestedAddress, int flags) {
272 MemoryMapManager::OperationGuard guard(mappings);
273 void* const failed = reinterpret_cast<void*>(~static_cast<uintptr_t>(0));
274 if ((flags & ~(ReadOnly | Round | Remap | Executable)) ||
275 ((flags & Remap) && !requestedAddress)) {
276 SYSCALL_ERROR(InvalidArgument);
277 return failed;
278 }
279 auto segment = findSegment(id);
280 if (!segment) {
281 return failed;
282 }
283 unsigned access = 4 | (flags & ReadOnly ? 0 : 2) | (flags & Executable ? 1 : 0);
284 if (!PosixIpc::allowed(segment->status.permission, access)) {
285 SYSCALL_ERROR(PermissionDenied);
286 return failed;
287 }
288 size_t count = 0;
289 const size_t pid = PosixIpc::process()->getId();
290 for (auto it = attachments.begin(); it != attachments.end(); ++it) {
291 count += (*it)->pid() == pid;
292 }
293 if (count >= MaximumAttachments) {
294 syscallError(EMFILE);
295 return failed;
296 }
297 uintptr_t address = reinterpret_cast<uintptr_t>(requestedAddress);
298 const size_t pageMask = PhysicalMemoryManager::getPageSize() - 1;
299 if (flags & Round) {
300 address &= ~pageMask;
301 }
302 VirtualAddressSpace& space = Processor::information().getVirtualAddressSpace();
303 if ((requestedAddress && !address) || (address & pageMask) ||
304 (address && (address < space.getUserStart() || address >= space.getKernelStart() ||
305 segment->roundedSize > space.getKernelStart() - address))) {
306 SYSCALL_ERROR(InvalidArgument);
307 return failed;
308 }
309 const auto placement = !requestedAddress ? MemoryMapManager::Placement::Hint
310 : flags & Remap ? MemoryMapManager::Placement::FixedReplace
311 : MemoryMapManager::Placement::FixedNoReplace;
312 MemoryMappedObject::Permissions permissions = MemoryMappedObject::Read;
313 if (!(flags & ReadOnly)) {
314 permissions |= MemoryMappedObject::Write;
315 }
316 if (flags & Executable) {
317 permissions |= MemoryMappedObject::Exec;
318 }
319 auto* owner = new ShmAttachment(segment);
320 SharedPointer<MappingAttachment> attachment(owner);
321 MemoryMapManager::MapStatus status;
322 // A replacement may remove this segment's last older attachment. Keep its
323 // identifier published until this new attachment either commits or fails.
324 ++segment->pendingAttachments;
325 if (!mappings.mapFile(segment->file, address, segment->roundedSize, permissions, 0, false,
326 placement, &status, permissions, attachment)) {
327 --segment->pendingAttachments;
328 retire(segment);
329 syscallError(status == MemoryMapManager::MapStatus::AddressInUse ? Error::InvalidArgument
330 : Error::OutOfMemory);
331 return failed;
332 }
333 owner->activate(address, pid, PosixIpc::process()->getUserspaceId());
334 --segment->pendingAttachments;
335 return reinterpret_cast<void*>(address);
336}
337
338int posix_shmdt(const void* address) {
340 MemoryMapManager::OperationGuard guard(mappings);
341 auto attachment = mappings.findAttachment(reinterpret_cast<uintptr_t>(address));
342 if (!attachment || !mappings.removeAttachment(attachment)) {
343 SYSCALL_ERROR(InvalidArgument);
344 return -1;
345 }
346 return 0;
347}
348
349int posix_shmctl(int id, int command, void* buffer) {
351 command &= ~0x100;
352 if (command == Info || command == SegmentInfo) {
353 return information(command, buffer);
354 }
355 const bool index = command == SegmentStat || command == SegmentStatAny;
356 auto segment = findSegment(id, index);
357 if (!segment) {
358 return -1;
359 }
360 if (command == Stat || index) {
361 if (command != SegmentStatAny && !PosixIpc::allowed(segment->status.permission, 4)) {
362 SYSCALL_ERROR(PermissionDenied);
363 return -1;
364 }
365 if (!PosixSubsystem::copyToUser(buffer, &segment->status, sizeof(segment->status))) {
366 SYSCALL_ERROR(BadAddress);
367 return -1;
368 }
369 return index ? segment->id : 0;
370 }
371 if (command != Remove && command != Set && command != Lock && command != Unlock) {
372 SYSCALL_ERROR(InvalidArgument);
373 return -1;
374 }
375 if (!PosixIpc::owner(segment->status.permission)) {
376 SYSCALL_ERROR(NotEnoughPermissions);
377 return -1;
378 }
379 if (command == Remove) {
380 segment->status.permission.mode |= Destroyed;
381 segment->status.permission.key = 0;
382 retire(segment);
383 } else if (command == Lock) {
384 if (!segment->lock()) {
385 return -1;
386 }
387 } else if (command == Unlock) {
388 segment->unlock();
389 } else {
390 SegmentStatus input = {};
391 if (!PosixSubsystem::copyFromUser(&input, buffer, sizeof(input))) {
392 SYSCALL_ERROR(BadAddress);
393 return -1;
394 }
395 if (input.permission.uid == 0xffffffffU || input.permission.gid == 0xffffffffU) {
396 SYSCALL_ERROR(InvalidArgument);
397 return -1;
398 }
399 segment->status.permission.uid = input.permission.uid;
400 segment->status.permission.gid = input.permission.gid;
401 segment->status.permission.mode =
402 (segment->status.permission.mode & ~0777U) | (input.permission.mode & 0777);
403 segment->status.changeTime = Time::getTime();
404 }
405 return 0;
406}
407
408void posix_shm_namespace_exit(uint64_t identity) {
410 for (auto& entry : segments) {
411 if (entry && entry->namespaceId == identity) {
412 auto segment = entry;
413 segment->status.permission.mode |= Destroyed;
414 segment->status.permission.key = 0;
415 retire(segment);
416 }
417 }
418}
Memory-mapped file interface.
An in-RAM filesystem.
Definition List.h:61
Iterator begin()
Definition List.h:122
Iterator end()
Definition List.h:132
SharedPointer< MappingAttachment > findAttachment(uintptr_t base)
static MemoryMapManager & instance()
MemoryMappedObject * mapFile(File *pFile, uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms, size_t offset=0, bool bCopyOnWrite=true)
size_t removeAttachment(const SharedPointer< MappingAttachment > &attachment)
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
size_t getId()
Definition Process.h:499
static ProcessorInformation & information()
Definition RamFs.h:118
bool untrackFile(File *pFile, bool destroy=true)
Definition VFS.cc:1670
static VFS & instance()
Definition VFS.cc:311
void trackFile(File *pFile)
Track a File object that exists. It is necessary to keep track of File objects, or at least those tha...
Definition VFS.cc:1625
virtual uintptr_t getUserStart() const =0
virtual uintptr_t getKernelStart() const =0
Iterator erase(Iterator &Iter)
Definition List.h:352
void pushBack(const T &value)
Definition List.h:216