The Pedigree Project 0.1
scalar-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/Scheduler.h"
13#include "pedigree/kernel/process/Semaphore.h"
14#include "pedigree/kernel/process/Thread.h"
15#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
16#include "pedigree/kernel/processor/Processor.h"
17#include "pedigree/kernel/processor/VirtualAddressSpace.h"
18#include "pedigree/kernel/utilities/utility.h"
19
20#include <fcntl.h>
21#include <signal.h>
22
23#include "modules/subsys/posix/FileDescriptor.h"
24#include "modules/subsys/posix/PosixSubsystem.h"
25#include "modules/subsys/posix/file-syscalls.h"
26#include "modules/system/vfs/File.h"
28#include "modules/system/vfs/Pipe.h"
29
30namespace {
31constexpr size_t BounceCapacity = PIPE_BUF_MAX + 1;
32constexpr size_t ChunkedWriteLength = BounceCapacity * 2 + 17;
33constexpr int PreservedErrno = 123;
34
35bool closeDescriptor(PosixSubsystem* subsystem, size_t fd) {
36 DescriptorLease descriptor;
37 return subsystem->acquireFileDescriptor(fd, descriptor) &&
38 subsystem->closeFileDescriptor(fd, descriptor);
39}
40
41class ScalarWriteProbeFile final : public File {
42 public:
43 ScalarWriteProbeFile()
44 : File(String("scalar-write-probe"), 0, 0, 0, 1, nullptr, 0, nullptr),
45 m_FirstWriteEntered(0, false),
46 m_ReleaseFirstWrite(0, false),
47 m_UserSource(nullptr),
48 m_WriteCount(0),
49 m_RawPointer(false),
50 m_Offsets{0, 0, 0, 0},
51 m_Sizes{0, 0, 0, 0},
52 m_FirstValues{0, 0, 0, 0} {}
53
54 void setUserSource(char* source) {
55 m_UserSource = source;
56 }
57
58 bool waitForFirstWrite() {
59 return m_FirstWriteEntered.acquireForCompletion();
60 }
61
62 void releaseFirstWrite() {
63 m_ReleaseFirstWrite.release();
64 }
65
66 size_t writeCount() const {
67 return m_WriteCount;
68 }
69
70 uint64_t offset(size_t index) const {
71 return m_Offsets[index];
72 }
73
74 size_t size(size_t index) const {
75 return m_Sizes[index];
76 }
77
78 char firstValue(size_t index) const {
79 return m_FirstValues[index];
80 }
81
82 bool sawRawPointer() const {
83 return m_RawPointer;
84 }
85
86 protected:
87 bool isBytewise() const override {
88 return true;
89 }
90
91 uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
92 const size_t slot = (m_WriteCount += 1) - 1;
93 if (slot < 4) {
94 m_Offsets[slot] = location;
95 m_Sizes[slot] = size;
96 }
97
98 if (!slot) {
99 m_RawPointer = buffer == reinterpret_cast<uintptr_t>(m_UserSource);
100 ByteSet(m_UserSource, 'z', ChunkedWriteLength);
101 }
102
103 if (slot < 4 && size) {
104 m_FirstValues[slot] = *reinterpret_cast<const char*>(buffer);
105 }
106
107 if (!slot) {
108 m_FirstWriteEntered.release();
109 if (!m_ReleaseFirstWrite.acquireForCompletion()) {
110 return 0;
111 }
112 }
113
114 if (location + size > getSize()) {
115 setSize(location + size);
116 }
117 return size;
118 }
119
120 private:
121 Semaphore m_FirstWriteEntered;
122 Semaphore m_ReleaseFirstWrite;
123 char* m_UserSource;
124 Atomic<size_t> m_WriteCount;
125 bool m_RawPointer;
126 uint64_t m_Offsets[4];
127 size_t m_Sizes[4];
128 char m_FirstValues[4];
129};
130
131class ScalarReplacementFile final : public File {
132 public:
133 ScalarReplacementFile()
134 : File(String("scalar-replacement"), 0, 0, 0, 2, nullptr, 0, nullptr), m_Writes(0) {}
135
136 size_t writes() const {
137 return m_Writes;
138 }
139
140 protected:
141 bool isBytewise() const override {
142 return true;
143 }
144
145 uint64_t writeBytewise(uint64_t, uint64_t size, uintptr_t, bool) override {
146 m_Writes += 1;
147 return size;
148 }
149
150 private:
151 Atomic<size_t> m_Writes;
152};
153
154struct ScalarWriteContext {
155 ScalarWriteContext(size_t descriptor, ScalarWriteProbeFile* file)
156 : descriptor(descriptor), file(file), entered(0), result(-2), error(0), returned(0) {}
157
158 size_t descriptor;
159 ScalarWriteProbeFile* file;
160 Atomic<size_t> entered;
161 int result;
162 int error;
163 Atomic<size_t> returned;
164};
165
166int chunkedScalarWrite(void* parameter) {
167 ScalarWriteContext* context = reinterpret_cast<ScalarWriteContext*>(parameter);
168 char payload[ChunkedWriteLength];
169 ByteSet(payload, 'a', sizeof(payload));
170 context->file->setUserSource(payload);
171 context->entered += 1;
172
173 Thread* thread = Processor::information().getCurrentThread();
174 thread->setErrno(PreservedErrno);
175 context->result =
176 posix_write(static_cast<int>(context->descriptor), payload, sizeof(payload), false);
177 context->error = thread->getErrno();
178 context->returned += 1;
179 return 0;
180}
181
182struct AliasWriteContext {
183 explicit AliasWriteContext(size_t descriptor)
184 : descriptor(descriptor), entered(0), result(-2), error(0), returned(0) {}
185
186 size_t descriptor;
187 Atomic<size_t> entered;
188 int result;
189 int error;
190 Atomic<size_t> returned;
191};
192
193int scalarAliasWrite(void* parameter) {
194 AliasWriteContext* context = reinterpret_cast<AliasWriteContext*>(parameter);
195 char value = 'q';
196 context->entered += 1;
197 Thread* thread = Processor::information().getCurrentThread();
198 thread->setErrno(PreservedErrno);
199 context->result = posix_write(static_cast<int>(context->descriptor), &value, 1, false);
200 context->error = thread->getErrno();
201 context->returned += 1;
202 return 0;
203}
204
205bool scalarWriteBounceAndLifetime(Process* kernelProcess) {
206 constexpr size_t SourceDescriptor = 82;
207 constexpr size_t AliasDescriptor = 83;
208
209 Process* process = new Process(kernelProcess);
210 PosixSubsystem* subsystem = new PosixSubsystem;
211 process->setSubsystem(subsystem);
212
213 ScalarWriteProbeFile sourceFile;
214 ScalarReplacementFile replacementFile;
215 FileDescriptor* source = new FileDescriptor(&sourceFile, 0, SourceDescriptor, 0, O_WRONLY);
216 FileDescriptor* alias = new FileDescriptor(*source);
217 alias->fd = AliasDescriptor;
218 subsystem->addFileDescriptor(SourceDescriptor, source);
219 subsystem->addFileDescriptor(AliasDescriptor, alias);
221
222 ScalarWriteContext sourceContext(SourceDescriptor, &sourceFile);
223 AliasWriteContext aliasContext(AliasDescriptor);
224 Thread* sourceWorker =
225 new Thread(process, chunkedScalarWrite, &sourceContext, nullptr, false, true, true);
226 Thread* aliasWorker =
227 new Thread(process, scalarAliasWrite, &aliasContext, nullptr, false, true, true);
228 sourceWorker->setName("hosted scalar bounce writer");
229 aliasWorker->setName("hosted scalar bounce alias");
230
231 const bool sourceStarted = sourceWorker->start();
232 const bool firstEntered = sourceStarted && sourceFile.waitForFirstWrite();
233 const bool aliasStarted = firstEntered && aliasWorker->start();
234 while (aliasStarted && !aliasContext.entered) {
236 }
237 for (size_t attempt = 0; attempt < 32 && aliasStarted && !aliasContext.returned; ++attempt) {
239 }
240 const bool aliasWasSerialized = aliasStarted && !aliasContext.returned;
241
242 const bool sourceClosed = firstEntered && closeDescriptor(subsystem, SourceDescriptor);
243 subsystem->addFileDescriptor(
244 SourceDescriptor, new FileDescriptor(&replacementFile, 0, SourceDescriptor, 0, O_WRONLY));
245
246 sourceFile.releaseFirstWrite();
247 const bool sourceJoined = sourceStarted && sourceWorker->joinForCompletion();
248 const bool aliasJoined = aliasStarted && aliasWorker->joinForCompletion();
249 if (!sourceStarted) {
250 delete sourceWorker;
251 }
252 if (!aliasStarted) {
253 delete aliasWorker;
254 }
255
256 bool passed = sourceStarted && firstEntered && aliasStarted && aliasWasSerialized &&
257 sourceClosed && sourceJoined && aliasJoined && sourceContext.returned == 1 &&
258 sourceContext.result == static_cast<int>(ChunkedWriteLength) &&
259 sourceContext.error == PreservedErrno && aliasContext.returned == 1 &&
260 aliasContext.result == 1 && aliasContext.error == PreservedErrno &&
261 !sourceFile.sawRawPointer() && sourceFile.writeCount() == 4 &&
262 sourceFile.offset(0) == 0 && sourceFile.size(0) == BounceCapacity &&
263 sourceFile.firstValue(0) == 'a' && sourceFile.offset(1) == BounceCapacity &&
264 sourceFile.size(1) == BounceCapacity && sourceFile.firstValue(1) == 'z' &&
265 sourceFile.offset(2) == BounceCapacity * 2 && sourceFile.size(2) == 17 &&
266 sourceFile.firstValue(2) == 'z' && sourceFile.offset(3) == ChunkedWriteLength &&
267 sourceFile.size(3) == 1 && sourceFile.firstValue(3) == 'q' &&
268 replacementFile.writes() == 0 && alias->getOffset() == ChunkedWriteLength + 1;
269
270 const bool aliasClosed = closeDescriptor(subsystem, AliasDescriptor);
271 const bool replacementClosed = closeDescriptor(subsystem, SourceDescriptor);
272 passed = passed && aliasClosed && replacementClosed;
273 description.reset();
274 delete process;
275
276 if (!passed) {
277 ERROR(
278 "HOSTED-SYSCALL-TEST: FAIL scalar-write-bounce-lifetime: "
279 "the scalar write exposed a user pointer, lost its descriptor/OFD, or interleaved chunks");
280 return false;
281 }
282
283 NOTICE("HOSTED-SYSCALL-TEST: PASS scalar-write-bounce-lifetime");
284 return true;
285}
286
287class FaultingScalarFile final : public File {
288 public:
289 explicit FaultingScalarFile(bool reading)
290 : File(String(reading ? "scalar-read-fault" : "scalar-write-fault"), 0, 0, 0, reading ? 3 : 4,
291 nullptr, 0, nullptr),
292 m_UserBase(0),
293 m_MappingLength(0),
294 m_PageSize(0),
295 m_Calls(0),
296 m_RawPointer(false),
297 m_FirstValue(0) {}
298
299 void configure(uintptr_t userBase, size_t mappingLength, size_t pageSize) {
300 m_UserBase = userBase;
301 m_MappingLength = mappingLength;
302 m_PageSize = pageSize;
303 }
304
305 size_t calls() const {
306 return m_Calls;
307 }
308
309 bool sawRawPointer() const {
310 return m_RawPointer;
311 }
312
313 char firstValue() const {
314 return m_FirstValue;
315 }
316
317 protected:
318 bool isBytewise() const override {
319 return true;
320 }
321
322 uint64_t readBytewise(uint64_t, uint64_t size, uintptr_t buffer, bool) override {
323 const size_t slot = (m_Calls += 1) - 1;
324 const bool raw = buffer >= m_UserBase && buffer < m_UserBase + m_MappingLength;
325 m_RawPointer = m_RawPointer || raw;
326
327 if (slot == 1) {
328 MemoryMapManager::instance().setPermissions(
329 m_UserBase + m_PageSize, m_MappingLength - m_PageSize, MemoryMappedObject::Read);
330 }
331 if (!raw) {
332 ByteSet(reinterpret_cast<void*>(buffer), 'r', size);
333 }
334 return size;
335 }
336
337 uint64_t writeBytewise(uint64_t, uint64_t size, uintptr_t buffer, bool) override {
338 const size_t slot = (m_Calls += 1) - 1;
339 const bool raw = buffer >= m_UserBase && buffer < m_UserBase + m_MappingLength;
340 m_RawPointer = m_RawPointer || raw;
341 if (size) {
342 m_FirstValue = *reinterpret_cast<const char*>(buffer);
343 }
344
345 if (!slot) {
346 MemoryMapManager::instance().setPermissions(
347 m_UserBase + m_PageSize, m_MappingLength - m_PageSize, MemoryMappedObject::None);
348 }
349 return size;
350 }
351
352 private:
353 uintptr_t m_UserBase;
354 size_t m_MappingLength;
355 size_t m_PageSize;
356 Atomic<size_t> m_Calls;
357 bool m_RawPointer;
358 char m_FirstValue;
359};
360
361struct ScalarFaultContext {
362 ScalarFaultContext(Process* process, size_t writeFd, size_t readFd, FaultingScalarFile* writeFile,
363 FaultingScalarFile* readFile)
364 : process(process),
365 writeFd(writeFd),
366 readFd(readFd),
367 writeFile(writeFile),
368 readFile(readFile),
369 setup(false),
370 writeResult(-2),
371 writeError(0),
372 firstWriteFaultResult(-2),
373 firstWriteFaultError(0),
374 readResult(-2),
375 readError(0),
376 firstReadFaultResult(-2),
377 firstReadFaultError(0),
378 returned(0) {}
379
380 Process* process;
381 size_t writeFd;
382 size_t readFd;
383 FaultingScalarFile* writeFile;
384 FaultingScalarFile* readFile;
385 bool setup;
386 int writeResult;
387 int writeError;
388 int firstWriteFaultResult;
389 int firstWriteFaultError;
390 int readResult;
391 int readError;
392 int firstReadFaultResult;
393 int firstReadFaultError;
394 Atomic<size_t> returned;
395};
396
397bool allocateUserMapping(Process* process, size_t length, uintptr_t& address) {
398 address = 0;
399 if (!process->allocateUserRange(Process::UserRegion::Normal, length, address)) {
400 return false;
401 }
402
403 uintptr_t mappedAddress = address;
405 mappedAddress, length, MemoryMappedObject::Read | MemoryMappedObject::Write);
406 if (!mapping || mappedAddress != address) {
407 MemoryMapManager::instance().remove(address, length);
408 process->freeUserRange(Process::UserRegion::Normal, address, length);
409 address = 0;
410 return false;
411 }
412 return true;
413}
414
415int scalarFaultWorker(void* parameter) {
416 ScalarFaultContext* context = reinterpret_cast<ScalarFaultContext*>(parameter);
417 Thread* thread = Processor::information().getCurrentThread();
418 const size_t pageSize = PhysicalMemoryManager::getPageSize();
419 const size_t mappingLength = pageSize * 3;
420 const size_t transferLength = BounceCapacity * 2;
421
422 uintptr_t writeAddress = 0;
423 if (!allocateUserMapping(context->process, mappingLength, writeAddress)) {
424 context->returned += 1;
425 return 1;
426 }
427 ByteSet(reinterpret_cast<void*>(writeAddress), 'w', mappingLength);
428 context->writeFile->configure(writeAddress, mappingLength, pageSize);
429 thread->setErrno(PreservedErrno);
430 context->writeResult = posix_write(static_cast<int>(context->writeFd),
431 reinterpret_cast<char*>(writeAddress), transferLength, false);
432 context->writeError = thread->getErrno();
433
434 const uintptr_t kernelStart = Processor::information().getVirtualAddressSpace().getKernelStart();
435 thread->setErrno(0);
436 context->firstWriteFaultResult = posix_write(static_cast<int>(context->writeFd),
437 reinterpret_cast<char*>(kernelStart), 1, false);
438 context->firstWriteFaultError = thread->getErrno();
439 MemoryMapManager::instance().remove(writeAddress, mappingLength);
440 context->process->freeUserRange(Process::UserRegion::Normal, writeAddress, mappingLength);
441
442 uintptr_t readAddress = 0;
443 if (!allocateUserMapping(context->process, mappingLength, readAddress)) {
444 context->returned += 1;
445 return 1;
446 }
447 ByteSet(reinterpret_cast<void*>(readAddress), 0, mappingLength);
448 context->readFile->configure(readAddress, mappingLength, pageSize);
449 thread->setErrno(PreservedErrno);
450 context->readResult = posix_read(static_cast<int>(context->readFd),
451 reinterpret_cast<char*>(readAddress), transferLength);
452 context->readError = thread->getErrno();
453
454 thread->setErrno(0);
455 context->firstReadFaultResult =
456 posix_read(static_cast<int>(context->readFd), reinterpret_cast<char*>(kernelStart), 1);
457 context->firstReadFaultError = thread->getErrno();
458 MemoryMapManager::instance().remove(readAddress, mappingLength);
459 context->process->freeUserRange(Process::UserRegion::Normal, readAddress, mappingLength);
460
461 context->setup = true;
462 context->returned += 1;
463 return 0;
464}
465
466bool scalarUsercopyFaultProgress(Process* kernelProcess) {
467 constexpr size_t WriteDescriptor = 84;
468 constexpr size_t ReadDescriptor = 85;
469
470 Process* process = new Process(kernelProcess);
471 PosixSubsystem* subsystem = new PosixSubsystem;
472 process->setSubsystem(subsystem);
473 FaultingScalarFile writeFile(false);
474 FaultingScalarFile readFile(true);
475 FileDescriptor* writer = new FileDescriptor(&writeFile, 0, WriteDescriptor, 0, O_WRONLY);
476 FileDescriptor* reader = new FileDescriptor(&readFile, 0, ReadDescriptor, 0, O_RDONLY);
477 subsystem->addFileDescriptor(WriteDescriptor, writer);
478 subsystem->addFileDescriptor(ReadDescriptor, reader);
480 FileDescriptor::OpenFileDescriptionLease readDescription = reader->acquireOpenFileDescription();
481
482 ScalarFaultContext context(process, WriteDescriptor, ReadDescriptor, &writeFile, &readFile);
483 Thread* worker = new Thread(process, scalarFaultWorker, &context, nullptr, false, true, true);
484 worker->setName("hosted scalar usercopy fault progress");
485 const bool started = worker->start();
486 const bool joined = started && worker->joinForCompletion();
487 if (!started) {
488 delete worker;
489 }
490
491 bool passed = started && joined && context.returned == 1 && context.setup &&
492 context.writeResult == static_cast<int>(BounceCapacity) &&
493 context.writeError == PreservedErrno && context.firstWriteFaultResult == -1 &&
494 context.firstWriteFaultError == Error::BadAddress && writeFile.calls() == 1 &&
495 !writeFile.sawRawPointer() && writeFile.firstValue() == 'w' &&
496 writer->getOffset() == BounceCapacity &&
497 context.readResult == static_cast<int>(BounceCapacity) &&
498 context.readError == PreservedErrno && context.firstReadFaultResult == -1 &&
499 context.firstReadFaultError == Error::BadAddress && readFile.calls() == 2 &&
500 !readFile.sawRawPointer() && reader->getOffset() == BounceCapacity;
501
502 passed = closeDescriptor(subsystem, WriteDescriptor) &&
503 closeDescriptor(subsystem, ReadDescriptor) && passed;
504 writeDescription.reset();
505 readDescription.reset();
506 delete process;
507
508 if (!passed) {
509 ERROR(
510 "HOSTED-SYSCALL-TEST: FAIL scalar-usercopy-fault-progress: "
511 "first/later EFAULT handling exposed pointers or lost partial offset semantics");
512 return false;
513 }
514
515 NOTICE("HOSTED-SYSCALL-TEST: PASS scalar-usercopy-fault-progress");
516 return true;
517}
518
519class SnapshotPipe final : public Pipe {
520 public:
521 SnapshotPipe()
522 : Pipe(String(""), 0, 0, 0, 0, nullptr, 0, nullptr, true),
523 m_Entered(0, false),
524 m_UserSource(nullptr),
525 m_Armed(false),
526 m_RawPointer(false),
527 m_FirstValue(0) {}
528
529 void arm(char* source) {
530 m_UserSource = source;
531 m_Armed = true;
532 }
533
534 bool waitUntilEntered() {
535 return m_Entered.acquireForCompletion();
536 }
537
538 bool sawRawPointer() const {
539 return m_RawPointer;
540 }
541
542 char firstValue() const {
543 return m_FirstValue;
544 }
545
546 protected:
547 uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer,
548 bool canBlock) override {
549 if (m_Armed) {
550 m_Armed = false;
551 m_RawPointer = buffer == reinterpret_cast<uintptr_t>(m_UserSource);
552 ByteSet(m_UserSource, 'z', PIPE_BUF_MAX);
553 if (size) {
554 m_FirstValue = *reinterpret_cast<const char*>(buffer);
555 }
556 m_Entered.release();
557 }
558 return Pipe::writeBytewise(location, size, buffer, canBlock);
559 }
560
561 private:
562 Semaphore m_Entered;
563 char* m_UserSource;
564 bool m_Armed;
565 bool m_RawPointer;
566 char m_FirstValue;
567};
568
569struct ScalarPipeContext {
570 ScalarPipeContext(size_t writeFd, SnapshotPipe* pipe)
571 : writeFd(writeFd),
572 pipe(pipe),
573 atomicComplete(0, false),
574 runLargeWrite(0, false),
575 atomicResult(-2),
576 largeResult(-2),
577 largeError(0),
578 returned(0) {}
579
580 size_t writeFd;
581 SnapshotPipe* pipe;
582 Semaphore atomicComplete;
583 Semaphore runLargeWrite;
584 int atomicResult;
585 int largeResult;
586 int largeError;
587 Atomic<size_t> returned;
588};
589
590int scalarPipeWriter(void* parameter) {
591 ScalarPipeContext* context = reinterpret_cast<ScalarPipeContext*>(parameter);
592 char atomicPayload[PIPE_BUF_MAX];
593 ByteSet(atomicPayload, 'a', sizeof(atomicPayload));
594 context->pipe->arm(atomicPayload);
595 context->atomicResult =
596 posix_write(static_cast<int>(context->writeFd), atomicPayload, sizeof(atomicPayload), false);
597 context->atomicComplete.release();
598
599 if (!context->runLargeWrite.acquireForCompletion()) {
600 context->returned += 1;
601 return 1;
602 }
603
604 char largePayload[PIPE_BUF_MAX + 1];
605 ByteSet(largePayload, 'l', sizeof(largePayload));
606 Thread* thread = Processor::information().getCurrentThread();
607 thread->setErrno(PreservedErrno);
608 context->largeResult =
609 posix_write(static_cast<int>(context->writeFd), largePayload, sizeof(largePayload), false);
610 context->largeError = thread->getErrno();
611 context->returned += 1;
612 return 0;
613}
614
615bool scalarPipeSnapshotAndLargePartial(Process* kernelProcess) {
616 constexpr size_t ReadDescriptor = 86;
617 constexpr size_t WriteDescriptor = 87;
618
619 Process* process = new Process(kernelProcess);
620 PosixSubsystem* subsystem = new PosixSubsystem;
621 process->setSubsystem(subsystem);
622 SnapshotPipe* pipe = new SnapshotPipe;
623 FileDescriptor* reader = new FileDescriptor(pipe, 0, ReadDescriptor, 0, O_RDONLY);
624 FileDescriptor* writer = new FileDescriptor(pipe, 0, WriteDescriptor, 0, O_WRONLY);
625 subsystem->addFileDescriptor(ReadDescriptor, reader);
626 subsystem->addFileDescriptor(WriteDescriptor, writer);
627
628 char fill[PIPE_BUF_MAX];
629 ByteSet(fill, 'f', sizeof(fill));
630 const bool filled =
631 writer->write(sizeof(fill), reinterpret_cast<uintptr_t>(fill), true) == sizeof(fill);
632
633 ScalarPipeContext context(WriteDescriptor, pipe);
634 Thread* worker = new Thread(process, scalarPipeWriter, &context, nullptr, false, true, true);
635 worker->setName("hosted scalar pipe bounce writer");
636 const bool started = filled && worker->start();
637 const bool entered = started && pipe->waitUntilEntered();
638
639 char drain[PIPE_BUF_MAX];
640 const bool initialDrained =
641 entered &&
642 reader->read(sizeof(drain), reinterpret_cast<uintptr_t>(drain), true) == sizeof(drain);
643 const bool atomicCompleted = initialDrained && context.atomicComplete.acquireForCompletion();
644 const bool atomicDrained =
645 atomicCompleted &&
646 reader->read(sizeof(drain), reinterpret_cast<uintptr_t>(drain), true) == sizeof(drain);
647 bool atomicContents = atomicDrained;
648 for (size_t i = 0; i < sizeof(drain) && atomicContents; ++i) {
649 atomicContents = drain[i] == 'a';
650 }
651
652 const bool refilled =
653 atomicDrained &&
654 writer->write(sizeof(fill), reinterpret_cast<uintptr_t>(fill), true) == sizeof(fill);
655 char byte = 0;
656 const bool oneByteFreed =
657 refilled && reader->read(1, reinterpret_cast<uintptr_t>(&byte), true) == 1;
658 writer->addStatusFlag(O_NONBLOCK);
659 context.runLargeWrite.release();
660
661 const bool joined = started && worker->joinForCompletion();
662 if (!started) {
663 delete worker;
664 }
665 const bool finalDrained =
666 joined &&
667 reader->read(sizeof(drain), reinterpret_cast<uintptr_t>(drain), true) == sizeof(drain);
668
669 bool passed = started && entered && initialDrained && atomicCompleted && atomicDrained &&
670 atomicContents && refilled && oneByteFreed && joined && finalDrained &&
671 context.returned == 1 && context.atomicResult == PIPE_BUF_MAX &&
672 !pipe->sawRawPointer() && pipe->firstValue() == 'a' && context.largeResult == 1 &&
673 context.largeError == PreservedErrno && drain[PIPE_BUF_MAX - 1] == 'l';
674
675 passed = closeDescriptor(subsystem, WriteDescriptor) &&
676 closeDescriptor(subsystem, ReadDescriptor) && passed;
677 delete process;
678
679 if (!passed) {
680 ERROR(
681 "HOSTED-SYSCALL-TEST: FAIL scalar-pipe-bounce: "
682 "PIPE_BUF snapshot atomicity or the 4097-byte nonblocking partial write regressed");
683 return false;
684 }
685
686 NOTICE("HOSTED-SYSCALL-TEST: PASS scalar-pipe-bounce");
687 return true;
688}
689
690Atomic<size_t> g_ScalarSigpipeHandlerCalls(0);
691File* g_ScalarSigpipeTarget = nullptr;
692
693void scalarSigpipeHandler(size_t) {
694 File::WriteGuard guard = g_ScalarSigpipeTarget->lockWrites();
695 g_ScalarSigpipeHandlerCalls += 1;
696}
697
698struct ScalarSigpipeContext {
699 explicit ScalarSigpipeContext(size_t descriptor)
700 : descriptor(descriptor), result(-2), error(0), returned(0) {}
701
702 size_t descriptor;
703 int result;
704 int error;
705 Atomic<size_t> returned;
706};
707
708int scalarSigpipeWriter(void* parameter) {
709 ScalarSigpipeContext* context = reinterpret_cast<ScalarSigpipeContext*>(parameter);
710 char value = 's';
711 Thread* thread = Processor::information().getCurrentThread();
712 thread->setErrno(PreservedErrno);
713 context->result =
714 posix_write(static_cast<int>(context->descriptor), &value, sizeof(value), false);
715 context->error = thread->getErrno();
716 context->returned += 1;
717 return 0;
718}
719
720bool scalarSigpipeAfterWriteGuard(Process* kernelProcess) {
721 constexpr size_t WriteDescriptor = 88;
722
723 Process* process = new Process(kernelProcess);
724 PosixSubsystem* subsystem = new PosixSubsystem;
725 process->setSubsystem(subsystem);
726 Pipe* pipe = new Pipe;
727 subsystem->addFileDescriptor(WriteDescriptor,
728 new FileDescriptor(pipe, 0, WriteDescriptor, 0, O_WRONLY));
729
731 handler->pEvent = new SignalEvent(reinterpret_cast<uintptr_t>(&scalarSigpipeHandler), SIGPIPE);
732 subsystem->setSignalHandler(SIGPIPE, handler);
733
734 g_ScalarSigpipeHandlerCalls = 0;
735 g_ScalarSigpipeTarget = pipe;
736 ScalarSigpipeContext context(WriteDescriptor);
737 Thread* worker = new Thread(process, scalarSigpipeWriter, &context, nullptr, false, true, true);
738 worker->setName("hosted scalar SIGPIPE guard release");
739 const bool started = worker->start();
740 const bool joined = started && worker->joinForCompletion();
741 if (!started) {
742 delete worker;
743 }
744 g_ScalarSigpipeTarget = nullptr;
745
746 bool passed = started && joined && context.returned == 1 && context.result == -1 &&
747 context.error == Error::BrokenPipe && g_ScalarSigpipeHandlerCalls == 1;
748 passed = closeDescriptor(subsystem, WriteDescriptor) && passed;
749 delete process;
750
751 if (!passed) {
752 ERROR(
753 "HOSTED-SYSCALL-TEST: FAIL scalar-sigpipe-after-write-guard: "
754 "SIGPIPE ran before scalar write serialization was released");
755 return false;
756 }
757
758 NOTICE("HOSTED-SYSCALL-TEST: PASS scalar-sigpipe-after-write-guard");
759 return true;
760}
761} // namespace
762
763bool runHostedScalarIoRegressions(Process* process) {
764 return scalarWriteBounceAndLifetime(process) && scalarUsercopyFaultProgress(process) &&
765 scalarPipeSnapshotAndLargePartial(process) && scalarSigpipeAfterWriteGuard(process);
766}
Memory-mapped file interface.
OpenFileDescriptionLease acquireOpenFileDescription() const
void addStatusFlag(int newFlag)
Helper to add a single flag to the status flags.
size_t fd
Descriptor number.
uint64_t write(uint64_t size, uintptr_t buffer, bool canBlock=true)
uint64_t getOffset() const
Definition File.h:74
WriteGuard lockWrites()
Definition File.cc:364
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()
Definition Pipe.h:36
virtual uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition Pipe.cc:152
bool acquireFileDescriptor(size_t fd, DescriptorLease &descriptor)
bool closeFileDescriptor(size_t fd, const DescriptorLease &descriptor)
void addFileDescriptor(size_t fd, FileDescriptor *pFd)
void setSignalHandler(size_t sig, SignalHandler *handler)
static ProcessorInformation & information()
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:226
void setErrno(size_t err)
Definition Thread.h:478
size_t getErrno()
Definition Thread.h:473
bool joinForCompletion()
Definition Thread.cc:2771
bool start()
Definition Thread.cc:794
SignalEvent * pEvent
Event for the signal handler.
Definition waits.c:9