The Pedigree Project 0.1
positional-vector-io-regressions.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/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/errors.h"
11#include "pedigree/kernel/process/Process.h"
12#include "pedigree/kernel/process/Thread.h"
13#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
14#include "pedigree/kernel/processor/Processor.h"
15#include "pedigree/kernel/utilities/utility.h"
16
17#include <fcntl.h>
18#include <stdint.h>
19
20#include "modules/subsys/posix/FileDescriptor.h"
21#include "modules/subsys/posix/PosixSubsystem.h"
22#include "modules/subsys/posix/file-syscalls.h"
23#include "modules/system/vfs/File.h"
25#include "modules/system/vfs/Pipe.h"
26
27namespace {
28constexpr size_t BounceCapacity = PIPE_BUF_MAX + 1;
29constexpr int PreservedErrno = 173;
30constexpr int LinuxRwfNoAppend = 0x20;
31
32enum class FaultMode { None, WriteAfterFirst, ReadBeforeSecondCopy };
33
34bool closeDescriptor(PosixSubsystem* subsystem, size_t fd) {
35 DescriptorLease descriptor;
36 return subsystem->acquireFileDescriptor(fd, descriptor) &&
37 subsystem->closeFileDescriptor(fd, descriptor);
38}
39
40bool allocateUserMapping(Process* process, size_t length, uintptr_t& address) {
41 address = 0;
42 if (!process->allocateUserRange(Process::UserRegion::Normal, length, address)) {
43 return false;
44 }
45
46 uintptr_t mappedAddress = address;
48 mappedAddress, length, MemoryMappedObject::Read | MemoryMappedObject::Write);
49 if (!mapping || mappedAddress != address) {
50 MemoryMapManager::instance().remove(address, length);
51 process->freeUserRange(Process::UserRegion::Normal, address, length);
52 address = 0;
53 return false;
54 }
55 return true;
56}
57
58class PositionalVectorProbeFile final : public File {
59 public:
60 explicit PositionalVectorProbeFile(FaultMode faultMode, size_t initialSize = 0)
61 : File(String("positional-vector-probe"), 0, 0, 0, 1, nullptr, initialSize, nullptr),
62 m_FaultMode(faultMode),
63 m_UserBase(0),
64 m_MappingLength(0),
65 m_PageSize(0),
66 m_ReadCalls(0),
67 m_WriteCalls(0),
68 m_SawRawPointer(false),
69 m_ReadOffsets{0, 0, 0, 0},
70 m_WriteOffsets{0, 0, 0, 0} {}
71
72 void configure(uintptr_t userBase, size_t mappingLength, size_t pageSize) {
73 m_UserBase = userBase;
74 m_MappingLength = mappingLength;
75 m_PageSize = pageSize;
76 }
77
78 size_t readCalls() const {
79 return m_ReadCalls;
80 }
81
82 size_t writeCalls() const {
83 return m_WriteCalls;
84 }
85
86 uint64_t readOffset(size_t index) const {
87 return m_ReadOffsets[index];
88 }
89
90 uint64_t writeOffset(size_t index) const {
91 return m_WriteOffsets[index];
92 }
93
94 bool sawRawPointer() const {
95 return m_SawRawPointer;
96 }
97
98 protected:
99 bool isBytewise() const override {
100 return true;
101 }
102
103 uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
104 const size_t slot = m_ReadCalls;
105 m_ReadCalls += 1;
106 recordPointer(buffer);
107 if (slot < 4) {
108 m_ReadOffsets[slot] = location;
109 }
110 ByteSet(reinterpret_cast<void*>(buffer), static_cast<char>('a' + slot), size);
111 if (m_FaultMode == FaultMode::ReadBeforeSecondCopy && slot == 1) {
112 MemoryMapManager::instance().setPermissions(
113 m_UserBase + m_PageSize, m_MappingLength - m_PageSize, MemoryMappedObject::Read);
114 }
115 return size;
116 }
117
118 uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
119 const size_t slot = m_WriteCalls;
120 m_WriteCalls += 1;
121 recordPointer(buffer);
122 if (slot < 4) {
123 m_WriteOffsets[slot] = location;
124 }
125 if (m_FaultMode == FaultMode::WriteAfterFirst && slot == 0) {
126 MemoryMapManager::instance().setPermissions(
127 m_UserBase + m_PageSize, m_MappingLength - m_PageSize, MemoryMappedObject::None);
128 }
129 if (location + size > getSize()) {
130 setSize(location + size);
131 }
132 return size;
133 }
134
135 private:
136 void recordPointer(uintptr_t buffer) {
137 if (buffer >= m_UserBase && buffer < m_UserBase + m_MappingLength) {
138 m_SawRawPointer = true;
139 }
140 }
141
142 FaultMode m_FaultMode;
143 uintptr_t m_UserBase;
144 size_t m_MappingLength;
145 size_t m_PageSize;
146 Atomic<size_t> m_ReadCalls;
147 Atomic<size_t> m_WriteCalls;
148 bool m_SawRawPointer;
149 uint64_t m_ReadOffsets[4];
150 uint64_t m_WriteOffsets[4];
151};
152
153struct PositionalVectorContext {
154 PositionalVectorContext(Process* process, PositionalVectorProbeFile* policyWrite,
155 PositionalVectorProbeFile* policyRead,
156 PositionalVectorProbeFile* faultWrite,
157 PositionalVectorProbeFile* faultRead)
158 : process(process),
159 policyWrite(policyWrite),
160 policyRead(policyRead),
161 faultWrite(faultWrite),
162 faultRead(faultRead),
163 results{},
164 errors{},
165 readValues{},
166 setup(false),
167 returned(0) {}
168
169 Process* process;
170 PositionalVectorProbeFile* policyWrite;
171 PositionalVectorProbeFile* policyRead;
172 PositionalVectorProbeFile* faultWrite;
173 PositionalVectorProbeFile* faultRead;
174 ssize_t results[9];
175 int errors[9];
176 char readValues[4];
177 bool setup;
178 Atomic<size_t> returned;
179};
180
181int positionalVectorWorker(void* parameter) {
182 constexpr int PolicyWriteDescriptor = 96;
183 constexpr int PolicyReadDescriptor = 97;
184 constexpr int FaultWriteDescriptor = 98;
185 constexpr int FaultReadDescriptor = 99;
186
187 PositionalVectorContext* context = reinterpret_cast<PositionalVectorContext*>(parameter);
188 Thread* thread = Processor::information().getCurrentThread();
189 const size_t pageSize = PhysicalMemoryManager::getPageSize();
190 const size_t mappingLength = pageSize * 4;
191 uintptr_t address = 0;
192 if (!allocateUserMapping(context->process, mappingLength, address)) {
193 context->returned += 1;
194 return 1;
195 }
196
197 context->policyWrite->configure(address, mappingLength, pageSize);
198 context->policyRead->configure(address, mappingLength, pageSize);
199 context->faultWrite->configure(address, mappingLength, pageSize);
200 context->faultRead->configure(address, mappingLength, pageSize);
201
202 char* userBuffer = reinterpret_cast<char*>(address);
203 struct iovec smallVectors[2] = {
204 {userBuffer, 3},
205 {userBuffer + 32, 2},
206 };
207
208 ByteSet(userBuffer, 'w', mappingLength);
209 thread->setErrno(PreservedErrno);
210 context->results[0] =
211 posix_pwritev(PolicyWriteDescriptor, smallVectors, 2, static_cast<off_t>(7));
212 context->errors[0] = thread->getErrno();
213
214 thread->setErrno(PreservedErrno);
215 context->results[1] =
216 posix_pwritev2(PolicyWriteDescriptor, smallVectors, 2, static_cast<off_t>(9), 0);
217 context->errors[1] = thread->getErrno();
218
219 thread->setErrno(PreservedErrno);
220 context->results[2] = posix_pwritev2(PolicyWriteDescriptor, smallVectors, 2,
221 static_cast<off_t>(11), LinuxRwfNoAppend);
222 context->errors[2] = thread->getErrno();
223
224 thread->setErrno(PreservedErrno);
225 context->results[3] =
226 posix_pwritev2(PolicyWriteDescriptor, smallVectors, 2, -1, LinuxRwfNoAppend);
227 context->errors[3] = thread->getErrno();
228
229 ByteSet(userBuffer, 0, mappingLength);
230 thread->setErrno(PreservedErrno);
231 context->results[4] = posix_preadv(PolicyReadDescriptor, smallVectors, 2, static_cast<off_t>(19));
232 context->errors[4] = thread->getErrno();
233 context->readValues[0] = userBuffer[0];
234 context->readValues[1] = userBuffer[32];
235
236 thread->setErrno(PreservedErrno);
237 context->results[5] = posix_preadv2(PolicyReadDescriptor, smallVectors, 2, -1, 0);
238 context->errors[5] = thread->getErrno();
239 context->readValues[2] = userBuffer[0];
240 context->readValues[3] = userBuffer[32];
241
242 struct iovec faultWriteVectors[2] = {
243 {userBuffer, BounceCapacity},
244 {userBuffer + pageSize * 2, BounceCapacity},
245 };
246 ByteSet(userBuffer, 'f', mappingLength);
247 thread->setErrno(PreservedErrno);
248 context->results[6] =
249 posix_pwritev(FaultWriteDescriptor, faultWriteVectors, 2, static_cast<off_t>(31));
250 context->errors[6] = thread->getErrno();
251 MemoryMapManager::instance().setPermissions(address, mappingLength,
252 MemoryMappedObject::Read | MemoryMappedObject::Write);
253
254 struct iovec faultReadVector = {userBuffer, BounceCapacity * 2};
255 ByteSet(userBuffer, 0, mappingLength);
256 thread->setErrno(PreservedErrno);
257 context->results[7] =
258 posix_preadv(FaultReadDescriptor, &faultReadVector, 1, static_cast<off_t>(41));
259 context->errors[7] = thread->getErrno();
260 MemoryMapManager::instance().setPermissions(address, mappingLength,
261 MemoryMappedObject::Read | MemoryMappedObject::Write);
262
263 thread->setErrno(0);
264 context->results[8] =
265 posix_pwritev2(PolicyWriteDescriptor, smallVectors, 2, 0, LinuxRwfNoAppend << 1);
266 context->errors[8] = thread->getErrno();
267
268 MemoryMapManager::instance().remove(address, mappingLength);
269 context->process->freeUserRange(Process::UserRegion::Normal, address, mappingLength);
270 context->setup = true;
271 context->returned += 1;
272 return 0;
273}
274
275bool positionalVectorSemantics(Process* kernelProcess) {
276 constexpr size_t PolicyWriteDescriptor = 96;
277 constexpr size_t PolicyReadDescriptor = 97;
278 constexpr size_t FaultWriteDescriptor = 98;
279 constexpr size_t FaultReadDescriptor = 99;
280
281 Process* process = new Process(kernelProcess);
282 PosixSubsystem* subsystem = new PosixSubsystem;
283 process->setSubsystem(subsystem);
284
285 PositionalVectorProbeFile policyWrite(FaultMode::None, 100);
286 PositionalVectorProbeFile policyRead(FaultMode::None);
287 PositionalVectorProbeFile faultWrite(FaultMode::WriteAfterFirst);
288 PositionalVectorProbeFile faultRead(FaultMode::ReadBeforeSecondCopy);
289 subsystem->addFileDescriptor(
290 PolicyWriteDescriptor,
291 new FileDescriptor(&policyWrite, 123, PolicyWriteDescriptor, 0, O_WRONLY | O_APPEND));
292 subsystem->addFileDescriptor(
293 PolicyReadDescriptor,
294 new FileDescriptor(&policyRead, 147, PolicyReadDescriptor, 0, O_RDONLY | O_APPEND));
295 subsystem->addFileDescriptor(
296 FaultWriteDescriptor,
297 new FileDescriptor(&faultWrite, 173, FaultWriteDescriptor, 0, O_WRONLY | O_APPEND));
298 subsystem->addFileDescriptor(
299 FaultReadDescriptor, new FileDescriptor(&faultRead, 197, FaultReadDescriptor, 0, O_RDONLY));
300
301 PositionalVectorContext context(process, &policyWrite, &policyRead, &faultWrite, &faultRead);
302 Thread* worker =
303 new Thread(process, positionalVectorWorker, &context, nullptr, false, true, true);
304 worker->setName("hosted positional vector I/O semantics");
305 const bool started = worker->start();
306 const bool joined = started && worker->joinForCompletion();
307 if (!started) {
308 delete worker;
309 }
310
311 DescriptorLease policyWriter;
312 DescriptorLease policyReader;
313 DescriptorLease faultWriter;
314 DescriptorLease faultReader;
315 const bool acquired = subsystem->acquireFileDescriptor(PolicyWriteDescriptor, policyWriter) &&
316 subsystem->acquireFileDescriptor(PolicyReadDescriptor, policyReader) &&
317 subsystem->acquireFileDescriptor(FaultWriteDescriptor, faultWriter) &&
318 subsystem->acquireFileDescriptor(FaultReadDescriptor, faultReader);
319
320 bool passed = started && joined && context.returned == 1 && context.setup && acquired;
321 for (size_t i = 0; i < 8; ++i) {
322 passed = context.results[i] == (i == 6 || i == 7 ? static_cast<ssize_t>(BounceCapacity)
323 : static_cast<ssize_t>(5)) &&
324 context.errors[i] == PreservedErrno && passed;
325 }
326 passed = context.results[8] == -1 && context.errors[8] == Error::OperationNotSupported &&
327 policyWriter->getOffset() == 128 && policyReader->getOffset() == 152 &&
328 faultWriter->getOffset() == 173 && faultReader->getOffset() == 197 &&
329 policyWrite.writeCalls() == 4 && policyWrite.writeOffset(0) == 7 &&
330 policyWrite.writeOffset(1) == 100 && policyWrite.writeOffset(2) == 11 &&
331 policyWrite.writeOffset(3) == 123 && policyRead.readCalls() == 4 &&
332 policyRead.readOffset(0) == 19 && policyRead.readOffset(1) == 22 &&
333 policyRead.readOffset(2) == 147 && policyRead.readOffset(3) == 150 &&
334 context.readValues[0] == 'a' && context.readValues[1] == 'b' &&
335 context.readValues[2] == 'c' && context.readValues[3] == 'd' &&
336 faultWrite.writeCalls() == 1 && faultWrite.writeOffset(0) == 31 &&
337 faultRead.readCalls() == 2 && faultRead.readOffset(0) == 41 &&
338 faultRead.readOffset(1) == 41 + BounceCapacity && !policyWrite.sawRawPointer() &&
339 !policyRead.sawRawPointer() && !faultWrite.sawRawPointer() &&
340 !faultRead.sawRawPointer() && passed;
341
342 policyWriter.reset();
343 policyReader.reset();
344 faultWriter.reset();
345 faultReader.reset();
346 for (size_t fd = PolicyWriteDescriptor; fd <= FaultReadDescriptor; ++fd) {
347 passed = closeDescriptor(subsystem, fd) && passed;
348 }
349 delete process;
350
351 if (!passed) {
352 ERROR(
353 "HOSTED-SYSCALL-TEST: FAIL positional-vector-io-semantics: offset isolation, append "
354 "policy, partial progress, or usercopy regressed");
355 return false;
356 }
357
358 NOTICE("HOSTED-SYSCALL-TEST: PASS positional-vector-io-semantics");
359 return true;
360}
361} // namespace
362
363bool runHostedPositionalVectorIoRegressions(Process* process) {
364 return positionalVectorSemantics(process);
365}
Memory-mapped file interface.
uint64_t getOffset() const
Definition File.h:74
virtual uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition File.cc:1233
virtual bool isBytewise() const
Definition File.cc:1229
virtual uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition File.cc:1240
MemoryMappedObject * mapAnon(uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms)
size_t remove(uintptr_t base, size_t length)
static MemoryMapManager & instance()
bool acquireFileDescriptor(size_t fd, DescriptorLease &descriptor)
bool closeFileDescriptor(size_t fd, const DescriptorLease &descriptor)
void addFileDescriptor(size_t fd, FileDescriptor *pFd)
static ProcessorInformation & information()
void setErrno(size_t err)
Definition Thread.h:478
size_t getErrno()
Definition Thread.h:473