The Pedigree Project 0.1
pipe-transfer-vmsplice.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/process/Process.h"
3#include "pedigree/kernel/process/TerminationDeferral.h"
4#include "pedigree/kernel/process/Thread.h"
5#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
6#include "pedigree/kernel/processor/Processor.h"
7#include "pedigree/kernel/processor/ProcessorInformation.h"
8#include "pedigree/kernel/processor/VirtualAddressSpace.h"
9#include "pedigree/kernel/syscallError.h"
10#include "pedigree/kernel/utilities/Pointers.h"
11#include "pedigree/kernel/utilities/assert.h"
12
13#include <fcntl.h>
14
15#include "FileDescriptor.h"
16#include "PosixSubsystem.h"
17#include "modules/system/vfs/Pipe.h"
18#include "pipe-transfer-syscalls.h"
19#include <sys/uio.h>
20
21namespace {
22constexpr unsigned KnownFlags = 0xf;
23constexpr unsigned Nonblock = 2;
24constexpr unsigned Gift = 8;
25constexpr size_t MaximumVectors = 1024;
26constexpr size_t MaximumTransfer = 0x7ffff000;
27using Status = PipeBuffer::Status;
28
29bool importVectors(const struct iovec* user, size_t count, bool gift,
30 UniqueArray<struct iovec>& vectors, size_t& total) {
31 total = 0;
32 if (count > MaximumVectors) {
33 SYSCALL_ERROR(InvalidArgument);
34 return false;
35 }
36 if (!count)
37 return true;
39 if (!vectors) {
40 SYSCALL_ERROR(OutOfMemory);
41 return false;
42 }
43 if (!PosixSubsystem::copyFromUser(vectors.get(), user, count, sizeof(struct iovec))) {
44 SYSCALL_ERROR(BadAddress);
45 return false;
46 }
47 const size_t pageSize = PhysicalMemoryManager::getPageSize();
48 VirtualAddressSpace& space = Processor::information().getVirtualAddressSpace();
49 for (size_t i = 0; i < count; ++i) {
50 auto& vector = vectors.get()[i];
51 const uintptr_t base = reinterpret_cast<uintptr_t>(vector.iov_base);
52 if (vector.iov_len > (~size_t(0) >> 1)) {
53 SYSCALL_ERROR(InvalidArgument);
54 return false;
55 }
56 if (vector.iov_len) {
57 const size_t checkedLength =
58 count == 1 && vector.iov_len > MaximumTransfer ? MaximumTransfer : vector.iov_len;
59 if (checkedLength - 1 > ~uintptr_t(0) - base || base < space.getUserStart() ||
60 base >= space.getKernelStart() || base + checkedLength - 1 >= space.getKernelStart() ||
61 !space.isAddressValid(reinterpret_cast<void*>(base)) ||
62 !space.isAddressValid(reinterpret_cast<void*>(base + checkedLength - 1))) {
63 SYSCALL_ERROR(BadAddress);
64 return false;
65 }
66 if (gift && (base % pageSize || vector.iov_len % pageSize)) {
67 SYSCALL_ERROR(InvalidArgument);
68 return false;
69 }
70 }
71 if (vector.iov_len > MaximumTransfer - total)
72 vector.iov_len = MaximumTransfer - total;
73 total += vector.iov_len;
74 }
75 return true;
76}
77
78ssize_t vmspliceCopy(Thread* thread, Pipe* pipe, bool writing, const struct iovec* vectors,
79 size_t vectorCount, size_t maximum, bool canBlock, bool& pipeSignal) {
80 auto scratch = UniqueArray<uint8_t>::allocate(maximum);
81 if (!scratch) {
82 SYSCALL_ERROR(OutOfMemory);
83 return -1;
84 }
85 size_t copied = 0;
86 for (size_t i = 0; i < vectorCount && copied < maximum; ++i) {
87 if (!vectors[i].iov_len)
88 continue;
89 if (thread->getInterruptionReason() == Thread::InterruptedBySignal ||
90 thread->getUnwindState() != Thread::Continue) {
91 SYSCALL_ERROR(Interrupted);
92 break;
93 }
94 const size_t remaining = maximum - copied;
95 const size_t requested = vectors[i].iov_len < remaining ? vectors[i].iov_len : remaining;
96 Pipe::ReadReservation readReservation;
97 Pipe::WriteReservation writeReservation;
98 const auto reserved = writing
99 ? pipe->reserveWrite(requested, canBlock && !copied, writeReservation)
100 : pipe->reserveRead(requested, canBlock && !copied, readReservation);
101 if (reserved.status != Status::Ready) {
102 switch (reserved.status) {
103 case Status::Closed:
104 if (writing) {
105 pipeSignal = true;
106 SYSCALL_ERROR(BrokenPipe);
107 }
108 break;
109 case Status::WouldBlock:
110 SYSCALL_ERROR(NoMoreProcesses);
111 break;
112 case Status::Interrupted:
113 SYSCALL_ERROR(Interrupted);
114 break;
115 case Status::Invalid:
116 SYSCALL_ERROR(InvalidArgument);
117 break;
118 case Status::Eof:
119 case Status::Ready:
120 break;
121 }
122 break;
123 }
124 const size_t amount = writing ? writeReservation.size() : readReservation.size();
125 assert(amount && amount <= requested);
126 bool success;
127 if (writing) {
128 success = PosixSubsystem::copyFromUser(scratch.get(), vectors[i].iov_base, amount);
129 } else {
130 readReservation.copyTo(scratch.get(), amount);
131 success = PosixSubsystem::copyToUser(vectors[i].iov_base, scratch.get(), amount);
132 }
133 if (!success) {
134 SYSCALL_ERROR(BadAddress);
135 break;
136 }
137 if (writing) {
138 if (thread->getInterruptionReason() == Thread::InterruptedBySignal ||
139 thread->getUnwindState() != Thread::Continue) {
140 SYSCALL_ERROR(Interrupted);
141 break;
142 }
143 const auto committed = writeReservation.commit(scratch.get(), amount);
144 if (committed.status != Status::Ready) {
145 if (committed.status == Status::Closed) {
146 pipeSignal = true;
147 SYSCALL_ERROR(BrokenPipe);
148 } else {
149 SYSCALL_ERROR(InvalidArgument);
150 }
151 break;
152 }
153 assert(committed.count == amount);
154 } else {
155 readReservation.consume(amount);
156 }
157 copied += amount;
158 thread->setErrno(0);
159 if (amount < requested)
160 break;
161 }
162 if (copied) {
163 thread->setErrno(0);
164 return static_cast<ssize_t>(copied);
165 }
166 return thread->getErrno() ? -1 : 0;
167}
168} // namespace
169
170ssize_t posix_vmsplice(int fd, const struct iovec* userVectors, size_t vectorCount,
171 unsigned flags) {
172 TerminationDeferral lifetime;
173 Thread* thread = Processor::information().getCurrentThread();
174 auto* subsystem = static_cast<PosixSubsystem*>(thread->getParent()->getSubsystem());
175 thread->clearInterruption();
176 thread->setErrno(0);
177 if (flags & ~KnownFlags) {
178 SYSCALL_ERROR(InvalidArgument);
179 return -1;
180 }
181 bool pipeSignal = false;
182 const ssize_t result = [&]() -> ssize_t {
183 DescriptorLease descriptor;
184 if (!subsystem || !subsystem->acquireFileDescriptor(fd, descriptor)) {
185 SYSCALL_ERROR(BadFileDescriptor);
186 return -1;
187 }
188 const int status = descriptor->getStatusFlags();
189 const int mode = status & O_ACCMODE;
190 if ((status & O_PATH) || (mode != O_RDONLY && mode != O_WRONLY && mode != O_RDWR)) {
191 SYSCALL_ERROR(BadFileDescriptor);
192 return -1;
193 }
194 const bool writing = mode != O_RDONLY;
196 size_t total;
197 if (!importVectors(userVectors, vectorCount, writing && (flags & Gift), vectors, total))
198 return -1;
199 if (!total)
200 return 0;
201 if (!descriptor->getFile() ||
202 !(descriptor->getFile()->isPipe() || descriptor->getFile()->isFifo())) {
203 SYSCALL_ERROR(BadFileDescriptor);
204 return -1;
205 }
206 const size_t maximum = total < PipeBuffer::Capacity ? total : PipeBuffer::Capacity;
207 // Linux vmsplice uses its explicit NONBLOCK flag, not the OFD status bit.
208 return vmspliceCopy(thread, Pipe::fromFile(descriptor->getFile()), writing, vectors.get(),
209 vectorCount, maximum, !(flags & Nonblock), pipeSignal);
210 }();
211 const size_t error = result < 0 ? thread->getErrno() : 0;
212 thread->clearInterruption();
213 if (pipeSignal)
214 subsystem->threadException(thread, Subsystem::Pipe);
215 thread->setErrno(error);
216 return result;
217}
int getStatusFlags() const
Get current status flags.
virtual bool isFifo() const
Definition File.cc:812
virtual bool isPipe() const
Definition File.cc:808
Definition Pipe.h:36
static Pipe * fromFile(File *pF)
Definition Pipe.h:42
static bool copyFromUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static ProcessorInformation & information()
void setErrno(size_t err)
Definition Thread.h:482
@ Continue
No unwind necessary, carry on as normal.
Definition Thread.h:517
size_t getErrno()
Definition Thread.h:477
UnwindType getUnwindState()
Definition Thread.h:535
Process * getParent() const
Definition Thread.h:340
virtual uintptr_t getUserStart() const =0
virtual uintptr_t getKernelStart() const =0
virtual bool isAddressValid(void *virtualAddress)=0