The Pedigree Project 0.1
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/machine/Disk.h"
12#include "pedigree/kernel/process/Process.h"
13#include "pedigree/kernel/process/Scheduler.h"
14#include "pedigree/kernel/process/Semaphore.h"
15#include "pedigree/kernel/process/Thread.h"
16#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
17#include "pedigree/kernel/processor/Processor.h"
18#include "pedigree/kernel/processor/VirtualAddressSpace.h"
19#include "pedigree/kernel/utilities/utility.h"
20
21#include <fcntl.h>
22#include <signal.h>
23
24#include "modules/subsys/posix/FileDescriptor.h"
25#include "modules/subsys/posix/PosixSubsystem.h"
26#include "modules/subsys/posix/file-syscalls.h"
27#include "modules/system/vfs/File.h"
28#include "modules/system/vfs/Filesystem.h"
30#include "modules/system/vfs/Pipe.h"
31
32namespace {
33constexpr size_t BounceCapacity = PIPE_BUF_MAX + 1;
34constexpr size_t FirstVectorLength = BounceCapacity + 5;
35constexpr size_t SecondVectorLength = BounceCapacity - 3;
36constexpr size_t VectorWriteLength = FirstVectorLength + SecondVectorLength;
37constexpr int PreservedErrno = 123;
38
39bool closeDescriptor(PosixSubsystem* subsystem, size_t descriptor) {
40 DescriptorLease lease;
41 return subsystem->acquireFileDescriptor(descriptor, lease) &&
42 subsystem->closeFileDescriptor(descriptor, lease);
43}
44
45bool pointsInto(uintptr_t pointer, const void* base, size_t length) {
46 const uintptr_t start = reinterpret_cast<uintptr_t>(base);
47 return length && pointer >= start && pointer < start + length;
48}
49
50class VectorWriteProbeFile final : public File {
51 public:
52 VectorWriteProbeFile()
53 : File(String("vector-write-bounce"), 0, 0, 0, 1, nullptr, 0, nullptr),
54 m_FirstWriteEntered(0, false),
55 m_ReleaseFirstWrite(0, false),
56 m_FirstSource(nullptr),
57 m_SecondSource(nullptr),
58 m_WriteCount(0),
59 m_RawPointer(false),
60 m_SecondChunkBeforeBoundary(0),
61 m_SecondChunkAfterBoundary(0),
62 m_Offsets{0, 0, 0, 0},
63 m_Sizes{0, 0, 0, 0},
64 m_FirstValues{0, 0, 0, 0} {}
65
66 void setSources(char* first, char* second) {
67 m_FirstSource = first;
68 m_SecondSource = second;
69 }
70
71 bool waitForFirstWrite() {
72 return m_FirstWriteEntered.acquireForCompletion();
73 }
74
75 void releaseFirstWrite() {
76 m_ReleaseFirstWrite.release();
77 }
78
79 size_t writeCount() const {
80 return m_WriteCount;
81 }
82
83 uint64_t offset(size_t index) const {
84 return m_Offsets[index];
85 }
86
87 size_t size(size_t index) const {
88 return m_Sizes[index];
89 }
90
91 char firstValue(size_t index) const {
92 return m_FirstValues[index];
93 }
94
95 bool sawRawPointer() const {
96 return m_RawPointer;
97 }
98
99 char secondChunkBeforeBoundary() const {
100 return m_SecondChunkBeforeBoundary;
101 }
102
103 char secondChunkAfterBoundary() const {
104 return m_SecondChunkAfterBoundary;
105 }
106
107 protected:
108 bool isBytewise() const override {
109 return true;
110 }
111
112 uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
113 const size_t slot = (m_WriteCount += 1) - 1;
114 if (slot < 4) {
115 m_Offsets[slot] = location;
116 m_Sizes[slot] = size;
117 m_FirstValues[slot] = size ? *reinterpret_cast<const char*>(buffer) : 0;
118 }
119 m_RawPointer = m_RawPointer || pointsInto(buffer, m_FirstSource, FirstVectorLength) ||
120 pointsInto(buffer, m_SecondSource, SecondVectorLength);
121 if (slot == 1 && size > 5) {
122 m_SecondChunkBeforeBoundary = reinterpret_cast<const char*>(buffer)[4];
123 m_SecondChunkAfterBoundary = reinterpret_cast<const char*>(buffer)[5];
124 }
125
126 if (!slot) {
127 // Only the already-copied first chunk may retain its old value.
128 ByteSet(m_FirstSource, 'z', FirstVectorLength);
129 ByteSet(m_SecondSource, 'y', SecondVectorLength);
130 m_FirstWriteEntered.release();
131 if (!m_ReleaseFirstWrite.acquireForCompletion()) {
132 return 0;
133 }
134 }
135
136 if (location + size > getSize()) {
137 setSize(location + size);
138 }
139 return size;
140 }
141
142 private:
143 Semaphore m_FirstWriteEntered;
144 Semaphore m_ReleaseFirstWrite;
145 char* m_FirstSource;
146 char* m_SecondSource;
147 Atomic<size_t> m_WriteCount;
148 bool m_RawPointer;
149 char m_SecondChunkBeforeBoundary;
150 char m_SecondChunkAfterBoundary;
151 uint64_t m_Offsets[4];
152 size_t m_Sizes[4];
153 char m_FirstValues[4];
154};
155
156class VectorReplacementFile final : public File {
157 public:
158 VectorReplacementFile()
159 : File(String("vector-write-replacement"), 0, 0, 0, 2, nullptr, 0, nullptr), m_Writes(0) {}
160
161 size_t writes() const {
162 return m_Writes;
163 }
164
165 protected:
166 bool isBytewise() const override {
167 return true;
168 }
169
170 uint64_t writeBytewise(uint64_t, uint64_t size, uintptr_t, bool) override {
171 m_Writes += 1;
172 return size;
173 }
174
175 private:
176 Atomic<size_t> m_Writes;
177};
178
179struct VectorWriteContext {
180 VectorWriteContext(size_t descriptor, VectorWriteProbeFile* file)
181 : descriptor(descriptor), file(file), entered(0), result(-2), error(0), returned(0) {}
182
183 size_t descriptor;
184 VectorWriteProbeFile* file;
185 Atomic<size_t> entered;
186 int result;
187 int error;
188 Atomic<size_t> returned;
189};
190
191int chunkedVectorWrite(void* parameter) {
192 VectorWriteContext* context = reinterpret_cast<VectorWriteContext*>(parameter);
193 char first[FirstVectorLength];
194 char second[SecondVectorLength];
195 ByteSet(first, 'a', sizeof(first));
196 ByteSet(second, 'b', sizeof(second));
197 context->file->setSources(first, second);
198 context->entered += 1;
199
200 struct iovec vectors[2] = {{first, sizeof(first)}, {second, sizeof(second)}};
201 Thread* thread = Processor::information().getCurrentThread();
202 thread->setErrno(PreservedErrno);
203 context->result = posix_writev(static_cast<int>(context->descriptor), vectors, 2);
204 context->error = thread->getErrno();
205 context->returned += 1;
206 return 0;
207}
208
209int vectorAliasWrite(void* parameter) {
210 VectorWriteContext* context = reinterpret_cast<VectorWriteContext*>(parameter);
211 char value = 'q';
212 context->entered += 1;
213 Thread* thread = Processor::information().getCurrentThread();
214 thread->setErrno(PreservedErrno);
215 context->result = posix_write(static_cast<int>(context->descriptor), &value, 1, false);
216 context->error = thread->getErrno();
217 context->returned += 1;
218 return 0;
219}
220
221bool vectorWriteBounceAndLifetime(Process* kernelProcess) {
222 constexpr size_t SourceDescriptor = 90;
223 constexpr size_t AliasDescriptor = 91;
224
225 Process* process = new Process(kernelProcess);
226 PosixSubsystem* subsystem = new PosixSubsystem;
227 process->setSubsystem(subsystem);
228 VectorWriteProbeFile sourceFile;
229 VectorReplacementFile replacementFile;
230 FileDescriptor* source = new FileDescriptor(&sourceFile, 0, SourceDescriptor, 0, O_WRONLY);
231 FileDescriptor* alias = new FileDescriptor(*source);
232 alias->fd = AliasDescriptor;
233 subsystem->addFileDescriptor(SourceDescriptor, source);
234 subsystem->addFileDescriptor(AliasDescriptor, alias);
236
237 VectorWriteContext sourceContext(SourceDescriptor, &sourceFile);
238 VectorWriteContext aliasContext(AliasDescriptor, &sourceFile);
239 Thread* sourceWorker =
240 new Thread(process, chunkedVectorWrite, &sourceContext, nullptr, false, true, true);
241 Thread* aliasWorker =
242 new Thread(process, vectorAliasWrite, &aliasContext, nullptr, false, true, true);
243 sourceWorker->setName("hosted vector bounce writer");
244 aliasWorker->setName("hosted vector bounce alias");
245
246 const bool sourceStarted = sourceWorker->start();
247 const bool firstEntered = sourceStarted && sourceFile.waitForFirstWrite();
248 const bool aliasStarted = firstEntered && aliasWorker->start();
249 while (aliasStarted && !aliasContext.entered) {
251 }
252 for (size_t attempt = 0; attempt < 32 && aliasStarted && !aliasContext.returned; ++attempt) {
254 }
255 const bool aliasWasSerialized = aliasStarted && !aliasContext.returned;
256
257 const bool sourceClosed = firstEntered && closeDescriptor(subsystem, SourceDescriptor);
258 subsystem->addFileDescriptor(
259 SourceDescriptor, new FileDescriptor(&replacementFile, 0, SourceDescriptor, 0, O_WRONLY));
260
261 sourceFile.releaseFirstWrite();
262 const bool sourceJoined = sourceStarted && sourceWorker->joinForCompletion();
263 const bool aliasJoined = aliasStarted && aliasWorker->joinForCompletion();
264 if (!sourceStarted) {
265 delete sourceWorker;
266 }
267 if (!aliasStarted) {
268 delete aliasWorker;
269 }
270
271 bool passed = sourceStarted && firstEntered && aliasStarted && aliasWasSerialized &&
272 sourceClosed && sourceJoined && aliasJoined && sourceContext.returned == 1 &&
273 sourceContext.result == static_cast<int>(VectorWriteLength) &&
274 sourceContext.error == PreservedErrno && aliasContext.returned == 1 &&
275 aliasContext.result == 1 && aliasContext.error == PreservedErrno &&
276 !sourceFile.sawRawPointer() && sourceFile.writeCount() == 4 &&
277 sourceFile.offset(0) == 0 && sourceFile.size(0) == BounceCapacity &&
278 sourceFile.firstValue(0) == 'a' && sourceFile.offset(1) == BounceCapacity &&
279 sourceFile.size(1) == BounceCapacity && sourceFile.firstValue(1) == 'z' &&
280 sourceFile.secondChunkBeforeBoundary() == 'z' &&
281 sourceFile.secondChunkAfterBoundary() == 'y' &&
282 sourceFile.offset(2) == BounceCapacity * 2 && sourceFile.size(2) == 2 &&
283 sourceFile.firstValue(2) == 'y' && sourceFile.offset(3) == VectorWriteLength &&
284 sourceFile.size(3) == 1 && sourceFile.firstValue(3) == 'q' &&
285 replacementFile.writes() == 0 && alias->getOffset() == VectorWriteLength + 1;
286
287 passed = closeDescriptor(subsystem, AliasDescriptor) &&
288 closeDescriptor(subsystem, SourceDescriptor) && passed;
289 description.reset();
290 delete process;
291
292 if (!passed) {
293 ERROR(
294 "HOSTED-SYSCALL-TEST: FAIL vector-write-bounce-lifetime: "
295 "writev exposed a user pointer, interleaved chunks, or switched descriptor generations");
296 return false;
297 }
298
299 NOTICE("HOSTED-SYSCALL-TEST: PASS vector-write-bounce-lifetime");
300 return true;
301}
302
303char vectorPattern(size_t offset) {
304 return static_cast<char>('A' + (offset % 23));
305}
306
307class VectorReadProbeFile final : public File {
308 public:
309 VectorReadProbeFile()
310 : File(String("vector-read-bounce"), 0, 0, 0, 3, nullptr, 0, nullptr),
311 m_FirstDestination(nullptr),
312 m_FirstLength(0),
313 m_SecondDestination(nullptr),
314 m_SecondLength(0),
315 m_ReadCount(0),
316 m_RawPointer(false),
317 m_Offsets{0, 0, 0},
318 m_Sizes{0, 0, 0} {}
319
320 void setDestinations(void* first, size_t firstLength, void* second, size_t secondLength) {
321 m_FirstDestination = first;
322 m_FirstLength = firstLength;
323 m_SecondDestination = second;
324 m_SecondLength = secondLength;
325 }
326
327 size_t readCount() const {
328 return m_ReadCount;
329 }
330
331 bool sawRawPointer() const {
332 return m_RawPointer;
333 }
334
335 uint64_t offset(size_t index) const {
336 return m_Offsets[index];
337 }
338
339 size_t size(size_t index) const {
340 return m_Sizes[index];
341 }
342
343 protected:
344 bool isBytewise() const override {
345 return true;
346 }
347
348 uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
349 const size_t slot = (m_ReadCount += 1) - 1;
350 if (slot < 3) {
351 m_Offsets[slot] = location;
352 m_Sizes[slot] = size;
353 }
354 m_RawPointer = m_RawPointer || pointsInto(buffer, m_FirstDestination, m_FirstLength) ||
355 pointsInto(buffer, m_SecondDestination, m_SecondLength);
356 for (size_t i = 0; i < size; ++i) {
357 reinterpret_cast<char*>(buffer)[i] = vectorPattern(location + i);
358 }
359 return size;
360 }
361
362 private:
363 void* m_FirstDestination;
364 size_t m_FirstLength;
365 void* m_SecondDestination;
366 size_t m_SecondLength;
367 Atomic<size_t> m_ReadCount;
368 bool m_RawPointer;
369 uint64_t m_Offsets[3];
370 size_t m_Sizes[3];
371};
372
373struct VectorReadContext {
374 VectorReadContext(size_t descriptor, VectorReadProbeFile* file)
375 : descriptor(descriptor),
376 file(file),
377 contentsValid(false),
378 result(-2),
379 error(0),
380 returned(0) {}
381
382 size_t descriptor;
383 VectorReadProbeFile* file;
384 bool contentsValid;
385 int result;
386 int error;
387 Atomic<size_t> returned;
388};
389
390int chunkedVectorRead(void* parameter) {
391 constexpr size_t FirstLength = BounceCapacity + 7;
392 constexpr size_t SecondLength = 13;
393 VectorReadContext* context = reinterpret_cast<VectorReadContext*>(parameter);
394 char first[FirstLength];
395 char second[SecondLength];
396 ByteSet(first, 0, sizeof(first));
397 ByteSet(second, 0, sizeof(second));
398 context->file->setDestinations(first, sizeof(first), second, sizeof(second));
399
400 struct iovec vectors[2] = {{first, sizeof(first)}, {second, sizeof(second)}};
401 Thread* thread = Processor::information().getCurrentThread();
402 thread->setErrno(PreservedErrno);
403 context->result = posix_readv(static_cast<int>(context->descriptor), vectors, 2);
404 context->error = thread->getErrno();
405
406 bool valid = true;
407 for (size_t i = 0; i < sizeof(first) && valid; ++i) {
408 valid = first[i] == vectorPattern(i);
409 }
410 for (size_t i = 0; i < sizeof(second) && valid; ++i) {
411 valid = second[i] == vectorPattern(sizeof(first) + i);
412 }
413 context->contentsValid = valid;
414 context->returned += 1;
415 return 0;
416}
417
418bool vectorReadBounceAndScatter(Process* kernelProcess) {
419 constexpr size_t Descriptor = 92;
420 constexpr size_t FirstLength = BounceCapacity + 7;
421 constexpr size_t TotalLength = FirstLength + 13;
422
423 Process* process = new Process(kernelProcess);
424 PosixSubsystem* subsystem = new PosixSubsystem;
425 process->setSubsystem(subsystem);
426 VectorReadProbeFile file;
427 FileDescriptor* descriptor = new FileDescriptor(&file, 0, Descriptor, 0, O_RDONLY);
428 subsystem->addFileDescriptor(Descriptor, descriptor);
430
431 VectorReadContext context(Descriptor, &file);
432 Thread* worker = new Thread(process, chunkedVectorRead, &context, nullptr, false, true, true);
433 worker->setName("hosted vector bounce reader");
434 const bool started = worker->start();
435 const bool joined = started && worker->joinForCompletion();
436 if (!started) {
437 delete worker;
438 }
439
440 bool passed = started && joined && context.returned == 1 &&
441 context.result == static_cast<int>(TotalLength) &&
442 context.error == PreservedErrno && context.contentsValid && !file.sawRawPointer() &&
443 file.readCount() == 3 && file.offset(0) == 0 && file.size(0) == BounceCapacity &&
444 file.offset(1) == BounceCapacity && file.size(1) == 7 &&
445 file.offset(2) == FirstLength && file.size(2) == 13 &&
446 descriptor->getOffset() == TotalLength;
447 passed = closeDescriptor(subsystem, Descriptor) && passed;
448 description.reset();
449 delete process;
450
451 if (!passed) {
452 ERROR(
453 "HOSTED-SYSCALL-TEST: FAIL vector-read-bounce-scatter: "
454 "readv exposed a user pointer or scattered bounce chunks incorrectly");
455 return false;
456 }
457
458 NOTICE("HOSTED-SYSCALL-TEST: PASS vector-read-bounce-scatter");
459 return true;
460}
461
462bool allocateUserMapping(Process* process, size_t length, uintptr_t& address) {
463 address = 0;
464 if (!process->allocateUserRange(Process::UserRegion::Normal, length, address)) {
465 return false;
466 }
467
468 uintptr_t mappedAddress = address;
470 mappedAddress, length, MemoryMappedObject::Read | MemoryMappedObject::Write);
471 if (!mapping || mappedAddress != address) {
472 MemoryMapManager::instance().remove(address, length);
473 process->freeUserRange(Process::UserRegion::Normal, address, length);
474 address = 0;
475 return false;
476 }
477 return true;
478}
479
480class VectorReadDisk final : public Disk {
481 public:
482 bool pin(uint64_t) override {
483 return false;
484 }
485 void unpin(uint64_t) override {}
486};
487
488class VectorReadFilesystem final : public Filesystem {
489 public:
490 bool initialise(Disk* disk) override {
491 m_pDisk = disk;
492 return true;
493 }
494 File* getRoot() const override {
495 return nullptr;
496 }
497 const String& getVolumeLabel() const override {
498 return m_Label;
499 }
500
501 protected:
502 bool createFile(File*, const String&, uint32_t) override {
503 return false;
504 }
505 bool createDirectory(File*, const String&, uint32_t) override {
506 return false;
507 }
508 bool createSymlink(File*, const String&, const String&) override {
509 return false;
510 }
511 bool removeNode(File*, const String&, File*) override {
512 return false;
513 }
514
515 private:
516 String m_Label;
517};
518
519constexpr size_t DiskReadCapacity = 64 * 1024;
520enum class DiskVectorReadMode { Complete, Short, PrefixFault, CopyFault };
521
522class DiskVectorReadFile final : public File {
523 public:
524 explicit DiskVectorReadFile(Filesystem* filesystem)
525 : File(String("disk-vector-read"), 0, 0, 0, 3, filesystem, DiskReadCapacity * 3, nullptr),
526 m_Data(UniqueArray<char>::allocate(DiskReadCapacity * 3)) {
527 for (size_t i = 0; i < DiskReadCapacity * 3; ++i) {
528 m_Data.get()[i] = vectorPattern(i);
529 }
530 }
531
532 size_t readCount() const {
533 return m_ReadCount;
534 }
535
536 void denyWritesAfterFirstRead(uintptr_t address, size_t length) {
537 m_DenyAddress = address;
538 m_DenyLength = length;
539 }
540
541 protected:
542 Mutex& dataMutationLock() override {
543 // Each File::read owns this guard once, including reads served by its cache.
544 if (!m_ReadCount++ && m_DenyLength) {
545 MemoryMapManager::instance().setPermissions(m_DenyAddress, m_DenyLength,
546 MemoryMappedObject::Read);
547 }
548 return File::dataMutationLock();
549 }
550
551 uintptr_t readBlock(uint64_t location) override {
552 return reinterpret_cast<uintptr_t>(m_Data.get() + location);
553 }
554
555 bool pinBlock(uint64_t) override {
556 return true;
557 }
558
559 private:
560 UniqueArray<char> m_Data;
561 size_t m_ReadCount = 0;
562 uintptr_t m_DenyAddress = 0;
563 size_t m_DenyLength = 0;
564};
565
566struct DiskVectorReadContext {
567 Process* process;
568 DiskVectorReadFile* file;
569 DiskVectorReadMode mode;
570 bool positional;
571 ssize_t result = -2;
572 int error = 0;
573 bool valid = false;
574};
575
576int diskVectorReader(void* parameter) {
577 auto& context = *static_cast<DiskVectorReadContext*>(parameter);
578 const size_t pageSize = PhysicalMemoryManager::getPageSize();
579 const size_t mappingLength = DiskReadCapacity * 2 + pageSize * 3;
580 uintptr_t address = 0;
581 if (!allocateUserMapping(context.process, mappingLength, address)) {
582 return 1;
583 }
584 auto* first = reinterpret_cast<char*>(address);
585 const bool prefixFault = context.mode == DiskVectorReadMode::PrefixFault;
586 const size_t firstLength = prefixFault ? 13 : DiskReadCapacity + 7;
587 const size_t secondLength = prefixFault ? DiskReadCapacity * 2 : 13;
588 auto* second = first + (prefixFault ? pageSize : DiskReadCapacity + pageSize);
589 ByteSet(first, 0, mappingLength);
590 if (context.mode == DiskVectorReadMode::Short) {
591 context.file->setSize((context.positional ? 29 : 17) + 23);
592 } else if (prefixFault) {
593 // The vector snapshot passes; the next large precheck must still deliver
594 // the prefix that the previous 4097-byte chunks could copy.
595 const uintptr_t denied = reinterpret_cast<uintptr_t>(second) + pageSize * 2;
596 context.file->denyWritesAfterFirstRead(denied, address + mappingLength - denied);
597 } else if (context.mode == DiskVectorReadMode::CopyFault) {
598 const uintptr_t denied = address + pageSize * 2;
599 context.file->denyWritesAfterFirstRead(denied, address + mappingLength - denied);
600 }
601
602 struct iovec vectors[2] = {{first, firstLength}, {second, secondLength}};
603 Thread* thread = Processor::information().getCurrentThread();
604 thread->setErrno(PreservedErrno);
605 context.result =
606 context.positional ? posix_preadv(92, vectors, 2, 29) : posix_readv(92, vectors, 2);
607 context.error = thread->getErrno();
608
609 const size_t firstCopied = context.mode == DiskVectorReadMode::CopyFault ? 0
610 : context.mode == DiskVectorReadMode::Short ? 23
611 : firstLength;
612 const size_t secondCopied = prefixFault ? BounceCapacity
613 : context.mode == DiskVectorReadMode::Complete ? secondLength
614 : 0;
615 const size_t start = context.positional ? 29 : 17;
616 bool valid = true;
617 for (size_t i = 0; i < firstLength && valid; ++i) {
618 valid = first[i] == (i < firstCopied ? vectorPattern(start + i) : 0);
619 }
620 for (size_t i = 0; i < secondLength && valid; ++i) {
621 valid = second[i] == (i < secondCopied ? vectorPattern(start + firstCopied + i) : 0);
622 }
623 context.valid = valid;
624 MemoryMapManager::instance().remove(address, mappingLength);
625 context.process->freeUserRange(Process::UserRegion::Normal, address, mappingLength);
626 return 0;
627}
628
629bool diskVectorReadCase(Process* kernelProcess, DiskVectorReadMode mode, bool positional) {
630 Process* process = new Process(kernelProcess);
631 auto* subsystem = new PosixSubsystem;
632 process->setSubsystem(subsystem);
633 VectorReadDisk disk;
634 VectorReadFilesystem filesystem;
635 filesystem.initialise(&disk);
636 DiskVectorReadFile file(&filesystem);
637 auto* descriptor = new FileDescriptor(&file, 17, 92, 0, O_RDONLY);
638 subsystem->addFileDescriptor(92, descriptor);
639 auto description = descriptor->acquireOpenFileDescription();
640
641 DiskVectorReadContext context{process, &file, mode, positional};
642 Thread* worker = new Thread(process, diskVectorReader, &context, nullptr, false, true, true);
643 worker->setName("hosted disk vector reader");
644 const bool started = worker->start();
645 const bool joined = started && worker->joinForCompletion();
646 if (!started) {
647 delete worker;
648 }
649
650 const ssize_t expected = mode == DiskVectorReadMode::CopyFault ? -1
651 : mode == DiskVectorReadMode::Short ? 23
652 : mode == DiskVectorReadMode::PrefixFault ? 13 + BounceCapacity
653 : DiskReadCapacity + 20;
654 const size_t calls = mode == DiskVectorReadMode::Complete ? 3
655 : mode == DiskVectorReadMode::PrefixFault ? 2
656 : 1;
657 const uint64_t finalOffset = positional || expected < 0 ? 17 : 17 + expected;
658 bool passed = started && joined && context.valid && context.result == expected &&
659 context.error == (expected < 0 ? Error::BadAddress : PreservedErrno) &&
660 file.readCount() == calls && descriptor->getOffset() == finalOffset;
661 passed = closeDescriptor(subsystem, 92) && passed;
662 description.reset();
663 delete process;
664 if (!passed) {
665 ERROR("HOSTED-SYSCALL-TEST: FAIL disk-vector-read-batching: mode="
666 << static_cast<int>(mode) << " positional=" << positional << " result=" << context.result
667 << " errno=" << context.error << " calls=" << file.readCount());
668 }
669 return passed;
670}
671
672bool diskVectorReadBatching(Process* kernelProcess) {
673 const bool positions[] = {false, true};
674 const DiskVectorReadMode modes[] = {DiskVectorReadMode::Complete, DiskVectorReadMode::Short,
675 DiskVectorReadMode::PrefixFault,
676 DiskVectorReadMode::CopyFault};
677 for (bool positional : positions) {
678 for (DiskVectorReadMode mode : modes) {
679 if (!diskVectorReadCase(kernelProcess, mode, positional)) {
680 return false;
681 }
682 }
683 }
684 NOTICE("HOSTED-SYSCALL-TEST: PASS disk-vector-read-batching");
685 return true;
686}
687
688class FaultingVectorFile final : public File {
689 public:
690 explicit FaultingVectorFile(bool reading)
691 : File(String(reading ? "vector-read-fault" : "vector-write-fault"), 0, 0, 0, reading ? 4 : 5,
692 nullptr, 0, nullptr),
693 m_UserBase(0),
694 m_PageSize(0),
695 m_Calls(0),
696 m_RawPointer(false) {}
697
698 void configure(uintptr_t userBase, size_t pageSize) {
699 m_UserBase = userBase;
700 m_PageSize = pageSize;
701 }
702
703 size_t calls() const {
704 return m_Calls;
705 }
706
707 bool sawRawPointer() const {
708 return m_RawPointer;
709 }
710
711 protected:
712 bool isBytewise() const override {
713 return true;
714 }
715
716 uint64_t readBytewise(uint64_t, uint64_t size, uintptr_t buffer, bool) override {
717 const size_t slot = (m_Calls += 1) - 1;
718 m_RawPointer =
719 m_RawPointer || pointsInto(buffer, reinterpret_cast<void*>(m_UserBase), m_PageSize * 2);
720 if (slot == 1) {
721 MemoryMapManager::instance().setPermissions(m_UserBase + m_PageSize, m_PageSize,
722 MemoryMappedObject::Read);
723 }
724 ByteSet(reinterpret_cast<void*>(buffer), 'r', size);
725 return size;
726 }
727
728 uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool) override {
729 const size_t slot = (m_Calls += 1) - 1;
730 m_RawPointer =
731 m_RawPointer || pointsInto(buffer, reinterpret_cast<void*>(m_UserBase), m_PageSize * 2);
732 if (!slot) {
733 MemoryMapManager::instance().setPermissions(m_UserBase + m_PageSize, m_PageSize,
734 MemoryMappedObject::None);
735 }
736 if (location + size > getSize()) {
737 setSize(location + size);
738 }
739 return size;
740 }
741
742 private:
743 uintptr_t m_UserBase;
744 size_t m_PageSize;
745 Atomic<size_t> m_Calls;
746 bool m_RawPointer;
747};
748
749struct VectorFaultContext {
750 VectorFaultContext(Process* process, size_t writeFd, size_t readFd, FaultingVectorFile* writeFile,
751 FaultingVectorFile* readFile)
752 : process(process),
753 writeFd(writeFd),
754 readFd(readFd),
755 writeFile(writeFile),
756 readFile(readFile),
757 setup(false),
758 firstReadPageValid(false),
759 writeResult(-2),
760 writeError(0),
761 firstWriteFaultResult(-2),
762 firstWriteFaultError(0),
763 readResult(-2),
764 readError(0),
765 firstReadFaultResult(-2),
766 firstReadFaultError(0),
767 validationSemantics(false),
768 returned(0) {}
769
770 Process* process;
771 size_t writeFd;
772 size_t readFd;
773 FaultingVectorFile* writeFile;
774 FaultingVectorFile* readFile;
775 bool setup;
776 bool firstReadPageValid;
777 int writeResult;
778 int writeError;
779 int firstWriteFaultResult;
780 int firstWriteFaultError;
781 int readResult;
782 int readError;
783 int firstReadFaultResult;
784 int firstReadFaultError;
785 bool validationSemantics;
786 Atomic<size_t> returned;
787};
788
789int vectorFaultWorker(void* parameter) {
790 VectorFaultContext* context = reinterpret_cast<VectorFaultContext*>(parameter);
791 Thread* thread = Processor::information().getCurrentThread();
792 const size_t pageSize = PhysicalMemoryManager::getPageSize();
793 const size_t mappingLength = pageSize * 2;
794
795 uintptr_t writeAddress = 0;
796 if (!allocateUserMapping(context->process, mappingLength, writeAddress)) {
797 context->returned += 1;
798 return 1;
799 }
800 ByteSet(reinterpret_cast<void*>(writeAddress), 'w', mappingLength);
801 context->writeFile->configure(writeAddress, pageSize);
802 struct iovec writeVectors[2] = {{reinterpret_cast<void*>(writeAddress), pageSize},
803 {reinterpret_cast<void*>(writeAddress + pageSize), pageSize}};
804 thread->setErrno(PreservedErrno);
805 context->writeResult = posix_writev(static_cast<int>(context->writeFd), writeVectors, 2);
806 context->writeError = thread->getErrno();
807
808 const uintptr_t kernelStart = Processor::information().getVirtualAddressSpace().getKernelStart();
809 struct iovec badVector = {reinterpret_cast<void*>(kernelStart), 1};
810 thread->setErrno(0);
811 context->firstWriteFaultResult = posix_writev(static_cast<int>(context->writeFd), &badVector, 1);
812 context->firstWriteFaultError = thread->getErrno();
813 MemoryMapManager::instance().remove(writeAddress, mappingLength);
814 context->process->freeUserRange(Process::UserRegion::Normal, writeAddress, mappingLength);
815
816 const size_t progressMappingLength = pageSize * 3;
817 uintptr_t writeProgressAddress = 0;
818 if (!allocateUserMapping(context->process, progressMappingLength, writeProgressAddress)) {
819 context->returned += 1;
820 return 1;
821 }
822 ByteSet(reinterpret_cast<void*>(writeProgressAddress), 'p', progressMappingLength);
823 MemoryMapManager::instance().setPermissions(writeProgressAddress + pageSize * 2, pageSize,
824 MemoryMappedObject::None);
825 context->writeFile->configure(writeProgressAddress, pageSize);
826 struct iovec writeProgressVectors[2] = {
827 {reinterpret_cast<void*>(writeProgressAddress), BounceCapacity},
828 {reinterpret_cast<void*>(writeProgressAddress + pageSize * 2), 1}};
829 thread->setErrno(PreservedErrno);
830 const int writeProgressResult =
831 posix_writev(static_cast<int>(context->writeFd), writeProgressVectors, 2);
832 const int writeProgressError = thread->getErrno();
833
834 struct iovec inaccessibleWriteVector = {
835 reinterpret_cast<void*>(writeProgressAddress + pageSize * 2), 1};
836 thread->setErrno(0);
837 const int inaccessibleWriteResult =
838 posix_writev(static_cast<int>(context->writeFd), &inaccessibleWriteVector, 1);
839 const int inaccessibleWriteError = thread->getErrno();
840
841 struct iovec laterInvalidWriteVectors[2] = {{reinterpret_cast<void*>(writeProgressAddress), 1},
842 {reinterpret_cast<void*>(kernelStart), 1}};
843 thread->setErrno(0);
844 const int laterInvalidWriteResult =
845 posix_writev(static_cast<int>(context->writeFd), laterInvalidWriteVectors, 2);
846 const int laterInvalidWriteError = thread->getErrno();
847
848 const struct iovec* invalidVectorArray = reinterpret_cast<const struct iovec*>(kernelStart);
849 thread->setErrno(0);
850 const int invalidWriteArrayResult =
851 posix_writev(static_cast<int>(context->writeFd), invalidVectorArray, 1);
852 const int invalidWriteArrayError = thread->getErrno();
853
854 struct iovec zeroLengthInvalidVector = {reinterpret_cast<void*>(kernelStart), 0};
855 thread->setErrno(PreservedErrno);
856 const int zeroLengthWriteResult =
857 posix_writev(static_cast<int>(context->writeFd), &zeroLengthInvalidVector, 1);
858 const int zeroLengthWriteError = thread->getErrno();
859
860 thread->setErrno(0);
861 const int badWriteDescriptorResult = posix_writev(-1, invalidVectorArray, 1);
862 const int badWriteDescriptorError = thread->getErrno();
863
864 bool validationSemantics =
865 writeProgressResult == static_cast<int>(BounceCapacity) &&
866 writeProgressError == PreservedErrno && inaccessibleWriteResult == -1 &&
867 inaccessibleWriteError == Error::BadAddress && laterInvalidWriteResult == -1 &&
868 laterInvalidWriteError == Error::BadAddress && invalidWriteArrayResult == -1 &&
869 invalidWriteArrayError == Error::BadAddress && zeroLengthWriteResult == 0 &&
870 zeroLengthWriteError == PreservedErrno && badWriteDescriptorResult == -1 &&
871 badWriteDescriptorError == Error::BadFileDescriptor;
872
873 MemoryMapManager::instance().remove(writeProgressAddress, progressMappingLength);
874 context->process->freeUserRange(Process::UserRegion::Normal, writeProgressAddress,
875 progressMappingLength);
876
877 uintptr_t readAddress = 0;
878 if (!allocateUserMapping(context->process, mappingLength, readAddress)) {
879 context->returned += 1;
880 return 1;
881 }
882 ByteSet(reinterpret_cast<void*>(readAddress), 0, mappingLength);
883 context->readFile->configure(readAddress, pageSize);
884 struct iovec readVectors[2] = {{reinterpret_cast<void*>(readAddress), pageSize},
885 {reinterpret_cast<void*>(readAddress + pageSize), pageSize}};
886 thread->setErrno(PreservedErrno);
887 context->readResult = posix_readv(static_cast<int>(context->readFd), readVectors, 2);
888 context->readError = thread->getErrno();
889 bool firstPageValid = true;
890 for (size_t i = 0; i < pageSize && firstPageValid; ++i) {
891 firstPageValid = reinterpret_cast<char*>(readAddress)[i] == 'r';
892 }
893 context->firstReadPageValid = firstPageValid;
894
895 thread->setErrno(0);
896 context->firstReadFaultResult = posix_readv(static_cast<int>(context->readFd), &badVector, 1);
897 context->firstReadFaultError = thread->getErrno();
898 MemoryMapManager::instance().remove(readAddress, mappingLength);
899 context->process->freeUserRange(Process::UserRegion::Normal, readAddress, mappingLength);
900
901 uintptr_t readProgressAddress = 0;
902 if (!allocateUserMapping(context->process, progressMappingLength, readProgressAddress)) {
903 context->returned += 1;
904 return 1;
905 }
906 ByteSet(reinterpret_cast<void*>(readProgressAddress), 0, progressMappingLength);
907 MemoryMapManager::instance().setPermissions(readProgressAddress + pageSize * 2, pageSize,
908 MemoryMappedObject::Read);
909 context->readFile->configure(readProgressAddress, pageSize);
910 struct iovec readProgressVectors[2] = {
911 {reinterpret_cast<void*>(readProgressAddress), pageSize},
912 {reinterpret_cast<void*>(readProgressAddress + pageSize * 2), 1}};
913 thread->setErrno(PreservedErrno);
914 const int readProgressResult =
915 posix_readv(static_cast<int>(context->readFd), readProgressVectors, 2);
916 const int readProgressError = thread->getErrno();
917 bool readProgressContentsValid = true;
918 for (size_t i = 0; i < pageSize && readProgressContentsValid; ++i) {
919 readProgressContentsValid = reinterpret_cast<char*>(readProgressAddress)[i] == 'r';
920 }
921
922 struct iovec inaccessibleReadVector = {
923 reinterpret_cast<void*>(readProgressAddress + pageSize * 2), 1};
924 thread->setErrno(0);
925 const int inaccessibleReadResult =
926 posix_readv(static_cast<int>(context->readFd), &inaccessibleReadVector, 1);
927 const int inaccessibleReadError = thread->getErrno();
928
929 struct iovec laterInvalidReadVectors[2] = {{reinterpret_cast<void*>(readProgressAddress), 1},
930 {reinterpret_cast<void*>(kernelStart), 1}};
931 thread->setErrno(0);
932 const int laterInvalidReadResult =
933 posix_readv(static_cast<int>(context->readFd), laterInvalidReadVectors, 2);
934 const int laterInvalidReadError = thread->getErrno();
935
936 thread->setErrno(0);
937 const int invalidReadArrayResult =
938 posix_readv(static_cast<int>(context->readFd), invalidVectorArray, 1);
939 const int invalidReadArrayError = thread->getErrno();
940
941 thread->setErrno(PreservedErrno);
942 const int zeroLengthReadResult =
943 posix_readv(static_cast<int>(context->readFd), &zeroLengthInvalidVector, 1);
944 const int zeroLengthReadError = thread->getErrno();
945
946 thread->setErrno(0);
947 const int badReadDescriptorResult = posix_readv(-1, invalidVectorArray, 1);
948 const int badReadDescriptorError = thread->getErrno();
949
950 validationSemantics =
951 readProgressResult == static_cast<int>(pageSize) && readProgressError == PreservedErrno &&
952 readProgressContentsValid && inaccessibleReadResult == -1 &&
953 inaccessibleReadError == Error::BadAddress && laterInvalidReadResult == -1 &&
954 laterInvalidReadError == Error::BadAddress && invalidReadArrayResult == -1 &&
955 invalidReadArrayError == Error::BadAddress && zeroLengthReadResult == 0 &&
956 zeroLengthReadError == PreservedErrno && badReadDescriptorResult == -1 &&
957 badReadDescriptorError == Error::BadFileDescriptor && validationSemantics;
958
959 MemoryMapManager::instance().remove(readProgressAddress, progressMappingLength);
960 context->process->freeUserRange(Process::UserRegion::Normal, readProgressAddress,
961 progressMappingLength);
962
963 context->validationSemantics = validationSemantics;
964 context->setup = true;
965 context->returned += 1;
966 return 0;
967}
968
969bool vectorUsercopyFaultProgress(Process* kernelProcess) {
970 constexpr size_t WriteDescriptor = 93;
971 constexpr size_t ReadDescriptor = 94;
972
973 Process* process = new Process(kernelProcess);
974 PosixSubsystem* subsystem = new PosixSubsystem;
975 process->setSubsystem(subsystem);
976 FaultingVectorFile writeFile(false);
977 FaultingVectorFile readFile(true);
978 FileDescriptor* writer = new FileDescriptor(&writeFile, 0, WriteDescriptor, 0, O_WRONLY);
979 FileDescriptor* reader = new FileDescriptor(&readFile, 0, ReadDescriptor, 0, O_RDONLY);
980 subsystem->addFileDescriptor(WriteDescriptor, writer);
981 subsystem->addFileDescriptor(ReadDescriptor, reader);
983 FileDescriptor::OpenFileDescriptionLease readDescription = reader->acquireOpenFileDescription();
984
985 VectorFaultContext context(process, WriteDescriptor, ReadDescriptor, &writeFile, &readFile);
986 Thread* worker = new Thread(process, vectorFaultWorker, &context, nullptr, false, true, true);
987 worker->setName("hosted vector usercopy fault progress");
988 const bool started = worker->start();
989 const bool joined = started && worker->joinForCompletion();
990 if (!started) {
991 delete worker;
992 }
993
994 const size_t pageSize = PhysicalMemoryManager::getPageSize();
995 bool passed = started && joined && context.returned == 1 && context.setup &&
996 context.writeResult == static_cast<int>(BounceCapacity) &&
997 context.writeError == PreservedErrno && context.firstWriteFaultResult == -1 &&
998 context.firstWriteFaultError == Error::BadAddress && writeFile.calls() == 2 &&
999 !writeFile.sawRawPointer() && writer->getOffset() == BounceCapacity * 2 &&
1000 context.readResult == static_cast<int>(pageSize) &&
1001 context.readError == PreservedErrno && context.firstReadPageValid &&
1002 context.firstReadFaultResult == -1 &&
1003 context.firstReadFaultError == Error::BadAddress && readFile.calls() == 3 &&
1004 !readFile.sawRawPointer() && reader->getOffset() == pageSize * 2 &&
1005 context.validationSemantics;
1006 passed = closeDescriptor(subsystem, WriteDescriptor) &&
1007 closeDescriptor(subsystem, ReadDescriptor) && passed;
1008 writeDescription.reset();
1009 readDescription.reset();
1010 delete process;
1011
1012 if (!passed) {
1013 ERROR(
1014 "HOSTED-SYSCALL-TEST: FAIL vector-usercopy-fault-progress: "
1015 "writev/readv validation, partial progress, or offsets regressed");
1016 return false;
1017 }
1018
1019 NOTICE("HOSTED-SYSCALL-TEST: PASS vector-usercopy-fault-progress");
1020 return true;
1021}
1022
1023struct VectorPipeFaultContext {
1024 VectorPipeFaultContext(Process* process, size_t readFd, size_t writeFd)
1025 : process(process),
1026 readFd(readFd),
1027 writeFd(writeFd),
1028 writeResult(-2),
1029 writeError(0),
1030 readResult(-2),
1031 readError(0),
1032 setup(false),
1033 returned(0) {}
1034
1035 Process* process;
1036 size_t readFd;
1037 size_t writeFd;
1038 int writeResult;
1039 int writeError;
1040 int readResult;
1041 int readError;
1042 bool setup;
1043 Atomic<size_t> returned;
1044};
1045
1046int vectorPipeFaultWorker(void* parameter) {
1047 VectorPipeFaultContext* context = reinterpret_cast<VectorPipeFaultContext*>(parameter);
1048 Thread* thread = Processor::information().getCurrentThread();
1049 const size_t pageSize = PhysicalMemoryManager::getPageSize();
1050 const size_t mappingLength = pageSize * 2;
1051 uintptr_t address = 0;
1052 if (!allocateUserMapping(context->process, mappingLength, address)) {
1053 context->returned += 1;
1054 return 1;
1055 }
1056
1057 ByteSet(reinterpret_cast<void*>(address), 'v', mappingLength);
1058 MemoryMapManager::instance().setPermissions(address + pageSize, pageSize,
1059 MemoryMappedObject::None);
1060 struct iovec writeVectors[2] = {{reinterpret_cast<void*>(address), 1},
1061 {reinterpret_cast<void*>(address + pageSize), 1}};
1062 thread->setErrno(0);
1063 context->writeResult = posix_writev(static_cast<int>(context->writeFd), writeVectors, 2);
1064 context->writeError = thread->getErrno();
1065
1066 struct iovec readVectors[2] = {{reinterpret_cast<void*>(address + 16), 1},
1067 {reinterpret_cast<void*>(address + pageSize), 1}};
1068 thread->setErrno(0);
1069 context->readResult = posix_readv(static_cast<int>(context->readFd), readVectors, 2);
1070 context->readError = thread->getErrno();
1071
1072 MemoryMapManager::instance().remove(address, mappingLength);
1073 context->process->freeUserRange(Process::UserRegion::Normal, address, mappingLength);
1074 context->setup = true;
1075 context->returned += 1;
1076 return 0;
1077}
1078
1079bool vectorPipePayloadPrevalidation(Process* kernelProcess) {
1080 constexpr size_t ReadDescriptor = 99;
1081 constexpr size_t WriteDescriptor = 100;
1082
1083 Process* process = new Process(kernelProcess);
1084 PosixSubsystem* subsystem = new PosixSubsystem;
1085 process->setSubsystem(subsystem);
1086 Pipe* pipe = new Pipe;
1087 FileDescriptor* reader = new FileDescriptor(pipe, 0, ReadDescriptor, 0, O_RDONLY | O_NONBLOCK);
1088 FileDescriptor* writer = new FileDescriptor(pipe, 0, WriteDescriptor, 0, O_WRONLY | O_NONBLOCK);
1089 subsystem->addFileDescriptor(ReadDescriptor, reader);
1090 subsystem->addFileDescriptor(WriteDescriptor, writer);
1091
1092 char contents[2] = {'x', 'y'};
1093 const bool filled = writer->write(sizeof(contents), reinterpret_cast<uintptr_t>(contents),
1094 true) == sizeof(contents);
1095 VectorPipeFaultContext context(process, ReadDescriptor, WriteDescriptor);
1096 Thread* worker = new Thread(process, vectorPipeFaultWorker, &context, nullptr, false, true, true);
1097 worker->setName("hosted vector pipe payload prevalidation");
1098 const bool started = filled && worker->start();
1099 const bool joined = started && worker->joinForCompletion();
1100 if (!started) {
1101 delete worker;
1102 }
1103
1104 char drained[4] = {};
1105 const uint64_t drainResult =
1106 joined ? reader->read(sizeof(drained), reinterpret_cast<uintptr_t>(drained), false) : 0;
1107 bool passed = started && joined && context.returned == 1 && context.setup &&
1108 context.writeResult == -1 && context.writeError == Error::BadAddress &&
1109 context.readResult == -1 && context.readError == Error::BadAddress &&
1110 drainResult == sizeof(contents) && drained[0] == 'x' && drained[1] == 'y';
1111 passed = closeDescriptor(subsystem, WriteDescriptor) &&
1112 closeDescriptor(subsystem, ReadDescriptor) && passed;
1113 delete process;
1114
1115 if (!passed) {
1116 ERROR(
1117 "HOSTED-SYSCALL-TEST: FAIL vector-pipe-payload-prevalidation: "
1118 "faulting vectors changed pipe contents");
1119 return false;
1120 }
1121
1122 NOTICE("HOSTED-SYSCALL-TEST: PASS vector-pipe-payload-prevalidation");
1123 return true;
1124}
1125
1126class InterruptingVectorFile final : public File {
1127 public:
1128 InterruptingVectorFile()
1129 : File(String("vector-interruption"), 0, 0, 0, 6, nullptr, 0, nullptr),
1130 m_MakeProgress(false),
1131 m_Calls(0) {}
1132
1133 void setMakeProgress(bool makeProgress) {
1134 m_MakeProgress = makeProgress;
1135 }
1136
1137 size_t calls() const {
1138 return m_Calls;
1139 }
1140
1141 protected:
1142 bool isBytewise() const override {
1143 return true;
1144 }
1145
1146 uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t, bool) override {
1147 m_Calls += 1;
1148 Processor::information().getCurrentThread()->setInterruptionReason(Thread::InterruptedBySignal);
1149 if (!m_MakeProgress) {
1150 return 0;
1151 }
1152 const size_t amount = size < 3 ? size : 3;
1153 if (location + amount > getSize()) {
1154 setSize(location + amount);
1155 }
1156 return amount;
1157 }
1158
1159 private:
1160 bool m_MakeProgress;
1161 Atomic<size_t> m_Calls;
1162};
1163
1164struct VectorSignalContext {
1165 VectorSignalContext(size_t descriptor, InterruptingVectorFile* file)
1166 : descriptor(descriptor),
1167 file(file),
1168 noProgressResult(-2),
1169 noProgressError(0),
1170 partialResult(-2),
1171 partialError(0),
1172 returned(0) {}
1173
1174 size_t descriptor;
1175 InterruptingVectorFile* file;
1176 int noProgressResult;
1177 int noProgressError;
1178 int partialResult;
1179 int partialError;
1180 Atomic<size_t> returned;
1181};
1182
1183int vectorSignalWorker(void* parameter) {
1184 VectorSignalContext* context = reinterpret_cast<VectorSignalContext*>(parameter);
1185 char first[3] = {'a', 'b', 'c'};
1186 char second[2] = {'d', 'e'};
1187 struct iovec vectors[2] = {{first, sizeof(first)}, {second, sizeof(second)}};
1188 Thread* thread = Processor::information().getCurrentThread();
1189
1190 context->file->setMakeProgress(false);
1191 thread->setErrno(PreservedErrno);
1192 context->noProgressResult = posix_writev(static_cast<int>(context->descriptor), vectors, 2);
1193 context->noProgressError = thread->getErrno();
1194
1195 context->file->setMakeProgress(true);
1196 thread->setErrno(PreservedErrno);
1197 context->partialResult = posix_writev(static_cast<int>(context->descriptor), vectors, 2);
1198 context->partialError = thread->getErrno();
1199 context->returned += 1;
1200 return 0;
1201}
1202
1203bool vectorSignalProgress(Process* kernelProcess) {
1204 constexpr size_t Descriptor = 95;
1205
1206 Process* process = new Process(kernelProcess);
1207 PosixSubsystem* subsystem = new PosixSubsystem;
1208 process->setSubsystem(subsystem);
1209 InterruptingVectorFile file;
1210 FileDescriptor* descriptor = new FileDescriptor(&file, 0, Descriptor, 0, O_WRONLY);
1211 subsystem->addFileDescriptor(Descriptor, descriptor);
1213
1214 VectorSignalContext context(Descriptor, &file);
1215 Thread* worker = new Thread(process, vectorSignalWorker, &context, nullptr, false, true, true);
1216 worker->setName("hosted vector signal progress");
1217 const bool started = worker->start();
1218 const bool joined = started && worker->joinForCompletion();
1219 if (!started) {
1220 delete worker;
1221 }
1222
1223 bool passed = started && joined && context.returned == 1 && context.noProgressResult == -1 &&
1224 context.noProgressError == Error::Interrupted && context.partialResult == 3 &&
1225 context.partialError == PreservedErrno && file.calls() == 2 &&
1226 descriptor->getOffset() == 3;
1227 passed = closeDescriptor(subsystem, Descriptor) && passed;
1228 description.reset();
1229 delete process;
1230
1231 if (!passed) {
1232 ERROR(
1233 "HOSTED-SYSCALL-TEST: FAIL vector-signal-progress: "
1234 "writev did not distinguish interruption before and after progress");
1235 return false;
1236 }
1237
1238 NOTICE("HOSTED-SYSCALL-TEST: PASS vector-signal-progress");
1239 return true;
1240}
1241
1242struct LargeVectorPipeContext {
1243 LargeVectorPipeContext(size_t descriptor, bool largeFirst)
1244 : descriptor(descriptor), largeFirst(largeFirst), result(-2), error(0), returned(0) {}
1245
1246 size_t descriptor;
1247 bool largeFirst;
1248 int result;
1249 int error;
1250 Atomic<size_t> returned;
1251};
1252
1253int largeVectorPipeWriter(void* parameter) {
1254 LargeVectorPipeContext* context = reinterpret_cast<LargeVectorPipeContext*>(parameter);
1255 char large[PIPE_BUF_MAX];
1256 char small = context->largeFirst ? 'b' : 'a';
1257 ByteSet(large, context->largeFirst ? 'a' : 'b', sizeof(large));
1258 struct iovec vectors[2] = {
1259 {context->largeFirst ? static_cast<void*>(large) : static_cast<void*>(&small),
1260 context->largeFirst ? sizeof(large) : 1},
1261 {context->largeFirst ? static_cast<void*>(&small) : static_cast<void*>(large),
1262 context->largeFirst ? 1 : sizeof(large)}};
1263
1264 Thread* thread = Processor::information().getCurrentThread();
1265 thread->setErrno(PreservedErrno);
1266 context->result = posix_writev(static_cast<int>(context->descriptor), vectors, 2);
1267 context->error = thread->getErrno();
1268 context->returned += 1;
1269 return 0;
1270}
1271
1272bool runLargeVectorPipeCase(Process* process, FileDescriptor* reader, FileDescriptor* writer,
1273 size_t descriptor, bool largeFirst) {
1274 char fill[PIPE_BUF_MAX];
1275 ByteSet(fill, 'f', sizeof(fill));
1276 if (writer->write(sizeof(fill), reinterpret_cast<uintptr_t>(fill), true) != sizeof(fill)) {
1277 return false;
1278 }
1279
1280 char byte = 0;
1281 if (reader->read(1, reinterpret_cast<uintptr_t>(&byte), true) != 1 || byte != 'f') {
1282 return false;
1283 }
1284
1285 LargeVectorPipeContext context(descriptor, largeFirst);
1286 Thread* worker = new Thread(process, largeVectorPipeWriter, &context, nullptr, false, true, true);
1287 worker->setName(largeFirst ? "hosted large-first vector pipe" : "hosted small-first vector pipe");
1288 const bool started = worker->start();
1289 const bool joined = started && worker->joinForCompletion();
1290 if (!started) {
1291 delete worker;
1292 }
1293
1294 char drain[PIPE_BUF_MAX];
1295 const bool drained = joined && reader->read(sizeof(drain), reinterpret_cast<uintptr_t>(drain),
1296 true) == sizeof(drain);
1297 return started && joined && drained && context.returned == 1 && context.result == 1 &&
1298 context.error == PreservedErrno && drain[PIPE_BUF_MAX - 1] == 'a';
1299}
1300
1301bool vectorPipeBoundaryIndependentPartial(Process* kernelProcess) {
1302 constexpr size_t ReadDescriptor = 96;
1303 constexpr size_t WriteDescriptor = 97;
1304
1305 Process* process = new Process(kernelProcess);
1306 PosixSubsystem* subsystem = new PosixSubsystem;
1307 process->setSubsystem(subsystem);
1308 Pipe* pipe = new Pipe;
1309 FileDescriptor* reader = new FileDescriptor(pipe, 0, ReadDescriptor, 0, O_RDONLY);
1310 FileDescriptor* writer = new FileDescriptor(pipe, 0, WriteDescriptor, 0, O_WRONLY | O_NONBLOCK);
1311 subsystem->addFileDescriptor(ReadDescriptor, reader);
1312 subsystem->addFileDescriptor(WriteDescriptor, writer);
1313
1314 bool passed = runLargeVectorPipeCase(process, reader, writer, WriteDescriptor, true) &&
1315 runLargeVectorPipeCase(process, reader, writer, WriteDescriptor, false);
1316 passed = closeDescriptor(subsystem, WriteDescriptor) &&
1317 closeDescriptor(subsystem, ReadDescriptor) && passed;
1318 delete process;
1319
1320 if (!passed) {
1321 ERROR(
1322 "HOSTED-SYSCALL-TEST: FAIL vector-pipe-boundary-independent-partial: "
1323 "large nonblocking writev behavior depended on an iovec boundary");
1324 return false;
1325 }
1326
1327 NOTICE("HOSTED-SYSCALL-TEST: PASS vector-pipe-boundary-independent-partial");
1328 return true;
1329}
1330
1331Atomic<size_t> g_VectorSigpipeHandlerCalls(0);
1332File* g_VectorSigpipeTarget = nullptr;
1333
1334void vectorSigpipeHandler(size_t) {
1335 File::WriteGuard guard = g_VectorSigpipeTarget->lockWrites();
1336 g_VectorSigpipeHandlerCalls += 1;
1337}
1338
1339struct VectorSigpipeContext {
1340 explicit VectorSigpipeContext(size_t descriptor)
1341 : descriptor(descriptor), result(-2), error(0), returned(0) {}
1342
1343 size_t descriptor;
1344 int result;
1345 int error;
1346 Atomic<size_t> returned;
1347};
1348
1349int vectorSigpipeWriter(void* parameter) {
1350 VectorSigpipeContext* context = reinterpret_cast<VectorSigpipeContext*>(parameter);
1351 char first = 's';
1352 char second = 'p';
1353 struct iovec vectors[2] = {{&first, 1}, {&second, 1}};
1354 Thread* thread = Processor::information().getCurrentThread();
1355 thread->setErrno(PreservedErrno);
1356 context->result = posix_writev(static_cast<int>(context->descriptor), vectors, 2);
1357 context->error = thread->getErrno();
1358 context->returned += 1;
1359 return 0;
1360}
1361
1362bool vectorSigpipeAfterWriteGuard(Process* kernelProcess) {
1363 constexpr size_t WriteDescriptor = 98;
1364
1365 Process* process = new Process(kernelProcess);
1366 PosixSubsystem* subsystem = new PosixSubsystem;
1367 process->setSubsystem(subsystem);
1368 Pipe* pipe = new Pipe;
1369 subsystem->addFileDescriptor(WriteDescriptor,
1370 new FileDescriptor(pipe, 0, WriteDescriptor, 0, O_WRONLY));
1371
1373 handler->pEvent = new SignalEvent(reinterpret_cast<uintptr_t>(&vectorSigpipeHandler), SIGPIPE);
1374 subsystem->setSignalHandler(SIGPIPE, handler);
1375
1376 g_VectorSigpipeHandlerCalls = 0;
1377 g_VectorSigpipeTarget = pipe;
1378 VectorSigpipeContext context(WriteDescriptor);
1379 Thread* worker = new Thread(process, vectorSigpipeWriter, &context, nullptr, false, true, true);
1380 worker->setName("hosted vector SIGPIPE guard release");
1381 const bool started = worker->start();
1382 const bool joined = started && worker->joinForCompletion();
1383 if (!started) {
1384 delete worker;
1385 }
1386 g_VectorSigpipeTarget = nullptr;
1387
1388 bool passed = started && joined && context.returned == 1 && context.result == -1 &&
1389 context.error == Error::BrokenPipe && g_VectorSigpipeHandlerCalls == 1;
1390 passed = closeDescriptor(subsystem, WriteDescriptor) && passed;
1391 delete process;
1392
1393 if (!passed) {
1394 ERROR(
1395 "HOSTED-SYSCALL-TEST: FAIL vector-sigpipe-after-write-guard: "
1396 "SIGPIPE ran before vector write serialization was released");
1397 return false;
1398 }
1399
1400 NOTICE("HOSTED-SYSCALL-TEST: PASS vector-sigpipe-after-write-guard");
1401 return true;
1402}
1403} // namespace
1404
1405bool runHostedVectorIoRegressions(Process* process) {
1406 return vectorWriteBounceAndLifetime(process) && vectorReadBounceAndScatter(process) &&
1407 diskVectorReadBatching(process) && vectorUsercopyFaultProgress(process) &&
1408 vectorPipePayloadPrevalidation(process) && vectorSignalProgress(process) &&
1409 vectorPipeBoundaryIndependentPartial(process) && vectorSigpipeAfterWriteGuard(process);
1410}
Memory-mapped file interface.
Definition Disk.h:35
virtual void unpin(uint64_t location)=0
virtual MUST_USE_RESULT bool pin(uint64_t location)=0
Pins a cache page.
OpenFileDescriptionLease acquireOpenFileDescription() const
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 uintptr_t readBlock(uint64_t location)
Definition File.cc:1247
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
virtual MUST_USE_RESULT bool pinBlock(uint64_t location)
Definition File.cc:1279
virtual bool removeNode(File *parent, const String &filename, File *file)=0
virtual bool initialise(Disk *pDisk)=0
virtual const String & getVolumeLabel() const =0
bool createSymlink(const StringView &path, const String &value, File *pStartNode=0)
bool createDirectory(const StringView &path, uint32_t mask, File *pStartNode=0)
virtual File * getRoot() const =0
bool createFile(const StringView &path, uint32_t mask, File *pStartNode=0)
MemoryMappedObject * mapAnon(uintptr_t &address, size_t length, MemoryMappedObject::Permissions perms)
size_t remove(uintptr_t base, size_t length)
static MemoryMapManager & instance()
Definition Mutex.h:56
Definition Pipe.h:36
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