The Pedigree Project 0.1
transfer-syscalls.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/Processor.h"
6#include "pedigree/kernel/processor/ProcessorInformation.h"
7#include "pedigree/kernel/syscallError.h"
8#include "pedigree/kernel/utilities/Pointers.h"
9#include "pedigree/kernel/utilities/assert.h"
10
11#include <fcntl.h>
12
13#include "FileDescriptor.h"
14#include "PosixSubsystem.h"
15#include "modules/system/vfs/File.h"
16#include "modules/system/vfs/MountView.h"
17#include "net-syscalls.h"
18#include "transfer-syscalls.h"
19
20namespace {
21constexpr uint64_t MaximumPosition = 0x7fffffffffffffffULL;
22constexpr size_t MaximumTransfer = 0x7ffff000;
23constexpr size_t BufferCapacity = 64 * 1024;
24using PositionGuard = FileDescriptor::TransferPositionGuard;
25using Endpoint = PositionGuard::Endpoint;
26
27bool signedRange(uint64_t position, size_t count) {
28 if (position > MaximumPosition || count > MaximumPosition - position) {
29 SYSCALL_ERROR(InvalidArgument);
30 return false;
31 }
32 return true;
33}
34
35bool regularFile(File* file) {
36 if (file && file->isDirectory()) {
37 SYSCALL_ERROR(IsADirectory);
38 return false;
39 }
40 if (!file || !file->supportsRegularFileOperations()) {
41 SYSCALL_ERROR(InvalidArgument);
42 return false;
43 }
44 return true;
45}
46
47ssize_t moveBytes(Thread* thread, File* input, const DescriptorLease& output, File* outputFile,
48 uint64_t& inputPosition, uint64_t& outputPosition, size_t count,
49 bool& pipeSignal) {
50 if (!count)
51 return 0;
52 const size_t capacity = count < BufferCapacity ? count : BufferCapacity;
53 auto buffer = UniqueArray<uint8_t>::allocate(capacity);
54 if (!buffer) {
55 SYSCALL_ERROR(OutOfMemory);
56 return -1;
57 }
58
59 auto copy = [&](File::WriteGuard* writer) -> ssize_t {
60 size_t transferred = 0;
61 while (transferred < count) {
62 if (thread->getInterruptionReason() == Thread::InterruptedBySignal ||
63 thread->getUnwindState() != Thread::Continue) {
64 SYSCALL_ERROR(Interrupted);
65 break;
66 }
67 const size_t remaining = count - transferred;
68 const size_t requested = remaining < capacity ? remaining : capacity;
69 thread->setErrno(0);
70 const size_t read =
71 input->read(inputPosition, requested, reinterpret_cast<uintptr_t>(buffer.get()), true);
72 const size_t inputError = thread->getErrno();
73 assert(read <= requested);
74 if (!read)
75 break;
76 if (thread->getInterruptionReason() == Thread::InterruptedBySignal ||
77 thread->getUnwindState() != Thread::Continue) {
78 SYSCALL_ERROR(Interrupted);
79 break;
80 }
81
82 size_t writable = read;
83 if (writer) {
84 // A genuine input EOF precedes the backend's output-size check.
85 const uint64_t limit = outputFile->maximumFileSize();
86 if (outputPosition >= limit) {
87 SYSCALL_ERROR(FileTooLarge);
88 break;
89 }
90 if (writable > limit - outputPosition)
91 writable = static_cast<size_t>(limit - outputPosition);
92 }
93 thread->setErrno(0);
94 const ssize_t written =
95 writer ? static_cast<ssize_t>(writer->write(
96 outputPosition, writable, reinterpret_cast<uintptr_t>(buffer.get()), true))
97 : posix_send_descriptor(output, buffer.get(), read, 0, true);
98 const size_t outputError = thread->getErrno();
99 if (!writer && outputError == Error::BrokenPipe)
100 pipeSignal = true;
101 if (written <= 0) {
102 if (!outputError) {
103 syscallError(thread->getInterruptionReason() == Thread::InterruptedBySignal ||
105 ? Error::Interrupted
106 : Error::IoError);
107 }
108 break;
109 }
110 assert(static_cast<size_t>(written) <= writable);
111 // Reading the scratch suffix does not consume the input's position.
112 inputPosition += written;
113 outputPosition += written;
114 transferred += written;
115 thread->setErrno(0);
116 if (static_cast<size_t>(written) < read || read < requested || inputError || outputError)
117 break;
118 }
119 if (transferred) {
120 thread->setErrno(0);
121 return static_cast<ssize_t>(transferred);
122 }
123 if (thread->getErrno())
124 return -1;
125 if (thread->getInterruptionReason() == Thread::InterruptedBySignal ||
126 thread->getUnwindState() != Thread::Continue) {
127 SYSCALL_ERROR(Interrupted);
128 return -1;
129 }
130 return 0;
131 };
132
133 if (outputFile) {
134 auto writer = outputFile->lockWrites();
135 return copy(&writer);
136 }
137 return copy(nullptr);
138}
139
140ssize_t finishTransfer(Thread* thread, PosixSubsystem* subsystem, ssize_t result, bool pipeSignal) {
141 const size_t error = result < 0 ? thread->getErrno() : 0;
142 thread->clearInterruption();
143 if (pipeSignal)
144 subsystem->threadException(thread, Subsystem::Pipe);
145 thread->setErrno(error);
146 return result;
147}
148
149ssize_t sendFile(PosixSubsystem* subsystem, Thread* thread, int outputFd, int inputFd,
150 int64_t* explicitPosition, size_t count, bool& pipeSignal) {
151 DescriptorLease input;
152 if (!subsystem || !subsystem->acquireFileDescriptor(inputFd, input)) {
153 SYSCALL_ERROR(BadFileDescriptor);
154 return -1;
155 }
156 auto inputDescription = input->acquireOpenFileDescription();
157 if (!inputDescription) {
158 SYSCALL_ERROR(BadFileDescriptor);
159 return -1;
160 }
161 const int inputFlags = input->getStatusFlags();
162 const int inputAccess = inputFlags & O_ACCMODE;
163 if ((inputFlags & O_PATH) || (inputAccess != O_RDONLY && inputAccess != O_RDWR)) {
164 SYSCALL_ERROR(BadFileDescriptor);
165 return -1;
166 }
167 File* inputFile = inputDescription->getFile();
168 if (explicitPosition && inputFile && !inputFile->isSeekable()) {
169 SYSCALL_ERROR(IllegalSeek);
170 return -1;
171 }
172 if (!regularFile(inputFile))
173 return -1;
174 const uint64_t initialInput =
175 explicitPosition ? static_cast<uint64_t>(*explicitPosition) : input->getOffset();
176 if (!signedRange(initialInput, count))
177 return -1;
178
179 DescriptorLease output;
180 if (!subsystem->acquireFileDescriptor(outputFd, output)) {
181 SYSCALL_ERROR(BadFileDescriptor);
182 return -1;
183 }
184 VfsMountView::WriteLease mountWrite;
185 if (output->openingPath() && output->getFile() &&
186 output->getFile()->supportsRegularFileOperations() && !output->getFile()->isBlockDevice() &&
187 !mountWrite.acquire(output->openingPath())) {
188 return -1;
189 }
190 auto outputDescription = output->acquireOpenFileDescription();
191 if (!outputDescription) {
192 SYSCALL_ERROR(BadFileDescriptor);
193 return -1;
194 }
195 auto socket = outputDescription->getNetworkImpl();
196 File* outputFile = socket ? nullptr : outputDescription->getFile();
197 if (socket) {
198 if (socket->getType() != SOCK_STREAM ||
199 (socket->getDomain() != AF_UNIX && socket->getDomain() != AF_INET &&
200 socket->getDomain() != AF_INET6)) {
201 SYSCALL_ERROR(InvalidArgument);
202 return -1;
203 }
204 } else if (!regularFile(outputFile)) {
205 return -1;
206 }
207
208 PositionGuard positions(inputDescription, outputDescription, !explicitPosition, !socket);
209 const int outputFlags = positions.statusFlags(Endpoint::Output);
210 const int outputAccess = outputFlags & O_ACCMODE;
211 // Socket OFDs currently carry no access-mode bits; their endpoint supplies
212 // the write capability independently of regular-file open modes.
213 if ((outputFlags & O_PATH) || (!socket && outputAccess != O_WRONLY && outputAccess != O_RDWR)) {
214 SYSCALL_ERROR(BadFileDescriptor);
215 return -1;
216 }
217 if (outputFlags & O_APPEND) {
218 SYSCALL_ERROR(InvalidArgument);
219 return -1;
220 }
221 uint64_t inputPosition = explicitPosition ? static_cast<uint64_t>(*explicitPosition)
222 : positions.offset(Endpoint::Input);
223 uint64_t outputPosition = socket ? 0 : positions.offset(Endpoint::Output);
224 if (!signedRange(inputPosition, count))
225 return -1;
226 if (count > MaximumTransfer)
227 count = MaximumTransfer;
228 uint64_t limit = inputFile->maximumFileSize();
229 if (outputFile && outputFile->maximumFileSize() < limit)
230 limit = outputFile->maximumFileSize();
231 if (inputPosition > limit || count > limit - inputPosition) {
232 if (inputPosition >= limit) {
233 SYSCALL_ERROR(ValueTooLarge);
234 return -1;
235 }
236 count = static_cast<size_t>(limit - inputPosition);
237 }
238 if (!signedRange(outputPosition, count))
239 return -1;
240 if (socket) {
241 struct sockaddr_storage peer = {};
242 socklen_t length = sizeof(peer);
243 thread->setErrno(0);
244 if (socket->getpeername(&peer, &length) < 0)
245 return -1;
246 }
247 const ssize_t result = moveBytes(thread, inputFile, output, outputFile, inputPosition,
248 outputPosition, count, pipeSignal);
249 if (result > 0) {
250 if (!socket)
251 positions.commitOffset(Endpoint::Output, outputPosition);
252 if (explicitPosition)
253 *explicitPosition = static_cast<int64_t>(inputPosition);
254 else if (socket || !positions.sameDescription())
255 positions.commitOffset(Endpoint::Input, inputPosition);
256 }
257 return result;
258}
259} // namespace
260
261ssize_t posix_sendfile(int outputFd, int inputFd, int64_t* offset, size_t count) {
262 TerminationDeferral lifetime;
263 Thread* thread = Processor::information().getCurrentThread();
264 auto* subsystem = static_cast<PosixSubsystem*>(thread->getParent()->getSubsystem());
265 int64_t position = 0;
266 thread->clearInterruption();
267 thread->setErrno(0);
268 if (offset && !PosixSubsystem::copyFromUser(&position, offset, sizeof(position))) {
269 SYSCALL_ERROR(BadAddress);
270 return -1;
271 }
272 bool pipeSignal = false;
273 ssize_t result = sendFile(subsystem, thread, outputFd, inputFd, offset ? &position : nullptr,
274 count, pipeSignal);
275 // The amd64 sendfile wrapper exports its offset even after EOF or error.
276 if (offset && !PosixSubsystem::copyToUser(offset, &position, sizeof(position))) {
277 SYSCALL_ERROR(BadAddress);
278 result = -1;
279 }
280 return finishTransfer(thread, subsystem, result, pipeSignal);
281}
282
283ssize_t posix_copy_file_range(int inputFd, int64_t* inputOffset, int outputFd,
284 int64_t* outputOffset, size_t count, unsigned flags) {
285 TerminationDeferral lifetime;
286 Thread* thread = Processor::information().getCurrentThread();
287 auto* subsystem = static_cast<PosixSubsystem*>(thread->getParent()->getSubsystem());
288 thread->clearInterruption();
289 thread->setErrno(0);
290 bool pipeSignal = false;
291 const ssize_t result = [&]() -> ssize_t {
292 DescriptorLease input, output;
293 if (!subsystem || !subsystem->acquireFileDescriptor(inputFd, input) ||
294 !subsystem->acquireFileDescriptor(outputFd, output)) {
295 SYSCALL_ERROR(BadFileDescriptor);
296 return -1;
297 }
298 int64_t importedInput = 0, importedOutput = 0;
299 if ((inputOffset &&
300 !PosixSubsystem::copyFromUser(&importedInput, inputOffset, sizeof(importedInput))) ||
301 (outputOffset &&
302 !PosixSubsystem::copyFromUser(&importedOutput, outputOffset, sizeof(importedOutput)))) {
303 SYSCALL_ERROR(BadAddress);
304 return -1;
305 }
306 if (flags) {
307 SYSCALL_ERROR(InvalidArgument);
308 return -1;
309 }
310 auto inputDescription = input->acquireOpenFileDescription();
311 VfsMountView::WriteLease mountWrite;
312 if (output->openingPath() && output->getFile() &&
313 output->getFile()->supportsRegularFileOperations() && !output->getFile()->isBlockDevice() &&
314 !mountWrite.acquire(output->openingPath())) {
315 return -1;
316 }
317 auto outputDescription = output->acquireOpenFileDescription();
318 if (!inputDescription || !outputDescription) {
319 SYSCALL_ERROR(BadFileDescriptor);
320 return -1;
321 }
322 File* inputFile = inputDescription->getFile();
323 File* outputFile = outputDescription->getFile();
324 PositionGuard positions(inputDescription, outputDescription, !inputOffset, true);
325 const int inputFlags = positions.statusFlags(Endpoint::Input);
326 const int outputFlags = positions.statusFlags(Endpoint::Output);
327 if ((inputFlags & O_PATH) || (outputFlags & O_PATH)) {
328 SYSCALL_ERROR(BadFileDescriptor);
329 return -1;
330 }
331 if ((inputFile && inputFile->isDirectory()) || (outputFile && outputFile->isDirectory())) {
332 SYSCALL_ERROR(IsADirectory);
333 return -1;
334 }
335 if (!regularFile(inputFile) || !regularFile(outputFile))
336 return -1;
337 const int inputAccess = inputFlags & O_ACCMODE;
338 const int outputAccess = outputFlags & O_ACCMODE;
339 if ((inputAccess != O_RDONLY && inputAccess != O_RDWR) ||
340 (outputAccess != O_WRONLY && outputAccess != O_RDWR) || (outputFlags & O_APPEND)) {
341 SYSCALL_ERROR(BadFileDescriptor);
342 return -1;
343 }
344 if (inputFile->getFilesystem() != outputFile->getFilesystem()) {
345 SYSCALL_ERROR(CrossDeviceLink);
346 return -1;
347 }
348 uint64_t inputPosition =
349 inputOffset ? static_cast<uint64_t>(importedInput) : positions.offset(Endpoint::Input);
350 uint64_t outputPosition =
351 outputOffset ? static_cast<uint64_t>(importedOutput) : positions.offset(Endpoint::Output);
352 if (count > ~uint64_t(0) - inputPosition || count > ~uint64_t(0) - outputPosition) {
353 SYSCALL_ERROR(ValueTooLarge);
354 return -1;
355 }
356 const uint64_t inputSize = inputFile->getSize();
357 if (inputPosition <= MaximumPosition && inputPosition >= inputSize)
358 count = 0;
359 else if (count > inputSize - inputPosition)
360 count = static_cast<size_t>(inputSize - inputPosition);
361 const uint64_t outputLimit = outputFile->maximumFileSize();
362 if (outputPosition <= MaximumPosition) {
363 if (outputPosition >= outputLimit) {
364 SYSCALL_ERROR(FileTooLarge);
365 return -1;
366 }
367 if (count > outputLimit - outputPosition)
368 count = static_cast<size_t>(outputLimit - outputPosition);
369 }
370 if (inputFile->futexIdentity() == outputFile->futexIdentity() &&
371 outputPosition + count > inputPosition && outputPosition < inputPosition + count) {
372 SYSCALL_ERROR(InvalidArgument);
373 return -1;
374 }
375 if (!signedRange(inputPosition, count) || !signedRange(outputPosition, count))
376 return -1;
377 if (count > MaximumTransfer)
378 count = MaximumTransfer;
379 ssize_t moved = moveBytes(thread, inputFile, output, outputFile, inputPosition, outputPosition,
380 count, pipeSignal);
381 if (moved <= 0)
382 return moved;
383
384 // Both completions run, even when the first user store faults.
385 bool copied = true;
386 if (inputOffset) {
387 importedInput = static_cast<int64_t>(inputPosition);
388 copied = PosixSubsystem::copyToUser(inputOffset, &importedInput, sizeof(importedInput));
389 } else {
390 positions.commitOffset(Endpoint::Input, inputPosition);
391 }
392 if (outputOffset) {
393 importedOutput = static_cast<int64_t>(outputPosition);
394 if (!PosixSubsystem::copyToUser(outputOffset, &importedOutput, sizeof(importedOutput)))
395 copied = false;
396 } else {
397 positions.commitOffset(Endpoint::Output, outputPosition);
398 }
399 if (!copied) {
400 SYSCALL_ERROR(BadAddress);
401 return -1;
402 }
403 return moved;
404 }();
405 return finishTransfer(thread, subsystem, result, pipeSignal);
406}
OpenFileDescriptionLease acquireOpenFileDescription() const
int getStatusFlags() const
Get current status flags.
uint64_t getOffset() const
Definition File.h:75
WriteGuard lockWrites()
Definition File.cc:420
virtual bool isSeekable() const
Definition File.cc:820
virtual uint64_t read(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true) final
Definition File.cc:239
bool supportsRegularFileOperations()
Definition File.cc:824
virtual bool isDirectory()
Definition File.cc:804
virtual uintptr_t futexIdentity()
Definition File.cc:75
virtual uint64_t maximumFileSize() const
Definition File.cc:829
virtual void threadException(Thread *pThread, ExceptionType eType, InterruptState *pState=nullptr, uintptr_t faultAddress=0, uintptr_t errorCode=0)
bool acquireFileDescriptor(size_t fd, DescriptorLease &descriptor)
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