The Pedigree Project 0.1
file-handle-syscalls.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "file-handle-syscalls.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
9#include <fcntl.h>
10#include <limits.h>
11
12#include "file-syscalls.h"
13#include "modules/system/vfs/MountView.h"
14
15namespace {
16struct UserHandleHeader {
17 uint32_t length;
18 int32_t type;
19};
20static_assert(sizeof(UserHandleHeader) == 8, "Linux file_handle header");
21static_assert(__builtin_offsetof(FileHandle, bytes) == 8, "FileHandle payload layout");
22constexpr size_t MaximumHandle = sizeof(FileHandle::bytes);
23
24Thread* currentThread() {
25 return Processor::information().getCurrentThread();
26}
27
28// Backing retirement can run on the last lexical File release. Preserve the
29// syscall's selected result across those callbacks, including late copy faults.
30class HandleResult {
31 public:
32 int finish(int value) {
33 error = value < 0 ? currentThread()->getErrno() : 0;
34 return value;
35 }
36 ~HandleResult() {
37 currentThread()->setErrno(error);
38 }
39
40 private:
41 int error = 0;
42};
43
44bool importHeader(const void* user, UserHandleHeader& header, bool allowZero) {
45 if (!PosixSubsystem::copyFromUser(&header, user, sizeof(header))) {
46 SYSCALL_ERROR(BadAddress);
47 return false;
48 }
49 if (header.length > MaximumHandle || (!allowZero && !header.length)) {
50 SYSCALL_ERROR(InvalidArgument);
51 return false;
52 }
53 return true;
54}
55
56bool validOpenFlags(int flags) {
57 constexpr int accepted = O_RDONLY | O_WRONLY | O_RDWR | O_CLOEXEC | O_NONBLOCK | O_APPEND |
58 O_TRUNC | O_LARGEFILE | O_DIRECTORY;
59 constexpr int unsupported = O_CREAT | O_EXCL | O_NOCTTY | O_DSYNC | O_ASYNC | O_DIRECT | O_SYNC |
60 O_NOFOLLOW | O_NOATIME | O_PATH | (O_TMPFILE & ~O_DIRECTORY);
61 if ((flags & ~(accepted | unsupported)) || (flags & 3) == 3) {
62 SYSCALL_ERROR(InvalidArgument);
63 return false;
64 }
65 if (flags & unsupported) {
66 SYSCALL_ERROR(OperationNotSupported);
67 return false;
68 }
69 return true;
70}
71} // namespace
72
73int posix_handle_error(FileHandleStatus status) {
74 switch (status) {
75 case FileHandleStatus::Success:
76 currentThread()->setErrno(0);
77 return 0;
78 case FileHandleStatus::Unsupported:
79 SYSCALL_ERROR(OperationNotSupported);
80 break;
81 case FileHandleStatus::Stale:
82 SYSCALL_ERROR(StaleFileHandle);
83 break;
84 case FileHandleStatus::Invalid:
85 SYSCALL_ERROR(InvalidArgument);
86 break;
87 case FileHandleStatus::NoMemory:
88 SYSCALL_ERROR(OutOfMemory);
89 break;
90 case FileHandleStatus::IoError:
91 SYSCALL_ERROR(IoError);
92 break;
93 }
94 return -1;
95}
96
97bool posix_effective_root() {
98 Process* process = currentThread()->getParent();
99 int64_t uid = process->getEffectiveUserId();
100 if (uid < 0)
101 uid = process->getUserId();
102 return uid == 0;
103}
104
105bool PosixHandleTarget::resolve(int dirfd, const char* userPath, bool follow, bool allowEmpty,
106 bool nullAsDescriptor) {
107 file = nullptr;
108 pathLease.reset();
109 descriptor.reset();
110 mount.reset();
111 attachmentId = 0;
112 Process* process = currentThread()->getParent();
113 auto* subsystem = static_cast<PosixSubsystem*>(process->getSubsystem());
114 if (!subsystem) {
115 SYSCALL_ERROR(InvalidArgument);
116 return false;
117 }
118 String path;
119 const bool nullTarget = !userPath && nullAsDescriptor;
120 if (!nullTarget) {
121 const auto status = PosixSubsystem::copyUserString(userPath, path, PATH_MAX);
122 if (status != PosixSubsystem::UserStringSuccess) {
123 syscallError(status == PosixSubsystem::UserStringTooLong ? Error::NameTooLong
124 : Error::BadAddress);
125 return false;
126 }
127 if (!path.length() && !allowEmpty) {
128 SYSCALL_ERROR(DoesNotExist);
129 return false;
130 }
131 }
132 const bool direct = nullTarget || !path.length();
133 const bool absolute = !direct && path[0] == '/';
134 FilesystemPathRef start;
135 if (!absolute) {
136 if (dirfd == AT_FDCWD) {
137 if (direct) {
138 auto context = process->acquireFilesystemContext();
140 if (!context || !context->snapshot(snapshot) || !snapshot.cwd) {
141 SYSCALL_ERROR(DoesNotExist);
142 return false;
143 }
144 start = snapshot.cwd;
145 file = start->node();
146 }
147 } else {
148 if (!subsystem->acquireFileDescriptor(dirfd, descriptor)) {
149 SYSCALL_ERROR(BadFileDescriptor);
150 return false;
151 }
152 file = descriptor->getFile();
153 start = descriptor->openingPath();
154 if (!file) {
155 syscallError(direct ? Error::OperationNotSupported : Error::NotADirectory);
156 return false;
157 }
158 if (!direct && (!start || !file->isDirectory())) {
159 SYSCALL_ERROR(NotADirectory);
160 return false;
161 }
162 }
163 }
164 if (direct) {
165 pathLease.retain(start);
166 } else {
167 String normalised;
168 normalisePath(normalised, path.cstr());
169 currentThread()->setErrno(0);
170 const bool directoryRequired = path[path.length() - 1] == '/';
171 file = findFilePath(normalised, pathLease, start, follow || directoryRequired);
172 if (!file) {
173 if (!currentThread()->getErrno())
174 SYSCALL_ERROR(DoesNotExist);
175 return false;
176 }
177 if (directoryRequired && !file->isDirectory()) {
178 SYSCALL_ERROR(NotADirectory);
179 return false;
180 }
181 }
182 if (!file->getFilesystem()) {
183 SYSCALL_ERROR(OperationNotSupported);
184 return false;
185 }
186 if (!VFS::instance().acquireMount(file->getFilesystem(), mount)) {
187 // Private filesystems such as memfd have no mounted export lifetime.
188 SYSCALL_ERROR(OperationNotSupported);
189 return false;
190 }
191 auto* view = VFS::instance().mountView();
192 attachmentId = view ? view->attachmentId(pathLease.path()) : 0;
193 return true;
194}
195
196int posix_name_to_handle_at(int dirfd, const char* path, void* userHandle, int* userMount,
197 int flags) {
198 HandleResult completion;
199 TerminationDeferral lifetime;
200 if (flags & ~(AT_SYMLINK_FOLLOW | AT_EMPTY_PATH)) {
201 SYSCALL_ERROR(InvalidArgument);
202 return completion.finish(-1);
203 }
204 PosixHandleTarget target;
205 if (!target.resolve(dirfd, path, flags & AT_SYMLINK_FOLLOW, flags & AT_EMPTY_PATH))
206 return completion.finish(-1);
207 if (!target.attachmentId) {
208 SYSCALL_ERROR(OperationNotSupported);
209 return completion.finish(-1);
210 }
211 if (target.attachmentId > INT_MAX) {
212 SYSCALL_ERROR(ValueTooLarge);
213 return completion.finish(-1);
214 }
215 UserHandleHeader input;
216 if (!importHeader(userHandle, input, true))
217 return completion.finish(-1);
218 FileHandle encoded;
219 const auto status = target.mount.filesystem()->encodeFileHandle(*target.file, encoded);
220 if (status != FileHandleStatus::Success)
221 return completion.finish(posix_handle_error(status));
222 if (!encoded.length || encoded.length > MaximumHandle) {
223 SYSCALL_ERROR(IoError);
224 return completion.finish(-1);
225 }
226 const bool overflow = input.length < encoded.length;
227 if (overflow)
228 encoded.type = 255;
229 const int mountId = static_cast<int>(target.attachmentId);
230 if (!PosixSubsystem::copyToUser(userMount, &mountId, sizeof(mountId)) ||
231 !PosixSubsystem::copyToUser(userHandle, &encoded,
232 sizeof(UserHandleHeader) + (overflow ? 0 : encoded.length))) {
233 SYSCALL_ERROR(BadAddress);
234 return completion.finish(-1);
235 }
236 if (overflow) {
237 SYSCALL_ERROR(ValueTooLarge);
238 return completion.finish(-1);
239 }
240 return completion.finish(0);
241}
242
243int posix_open_by_handle_at(int mountfd, const void* userHandle, int flags) {
244 HandleResult completion;
245 TerminationDeferral lifetime;
246 PosixHandleTarget target;
247 if (!target.resolve(mountfd, nullptr, false, false, true))
248 return completion.finish(-1);
249 if (!posix_effective_root()) {
250 SYSCALL_ERROR(NotEnoughPermissions);
251 return completion.finish(-1);
252 }
253 if (!target.attachmentId) {
254 SYSCALL_ERROR(OperationNotSupported);
255 return completion.finish(-1);
256 }
257 UserHandleHeader input;
258 if (!importHeader(userHandle, input, false))
259 return completion.finish(-1);
260 FileHandle handle;
261 handle.length = input.length;
262 handle.type = input.type;
263 const uintptr_t user = reinterpret_cast<uintptr_t>(userHandle);
264 if (user > ~uintptr_t(0) - sizeof(input) ||
266 handle.bytes, reinterpret_cast<const void*>(user + sizeof(input)), handle.length)) {
267 SYSCALL_ERROR(BadAddress);
268 return completion.finish(-1);
269 }
270 RetainedFile decoded;
271 const auto status = target.mount.filesystem()->decodeFileHandle(handle, decoded);
272 if (status != FileHandleStatus::Success)
273 return completion.finish(posix_handle_error(status));
274 if (!validOpenFlags(flags))
275 return completion.finish(-1);
276 File* file = decoded.get();
277 if (!file || !file->supportsRegularFileOperations()) {
278 SYSCALL_ERROR(OperationNotSupported);
279 return completion.finish(-1);
280 }
281 if (flags & O_DIRECTORY) {
282 SYSCALL_ERROR(NotADirectory);
283 return completion.finish(-1);
284 }
285 const int access = flags & 3;
286 const bool writes = access != O_RDONLY || (flags & O_TRUNC);
287 if (writes && target.mount.filesystem()->isReadOnly()) {
288 SYSCALL_ERROR(ReadOnlyFilesystem);
289 return completion.finish(-1);
290 }
291 if (!VFS::checkAccess(file, access != O_WRONLY, writes, false))
292 return completion.finish(-1);
293 FilesystemPathRef openedPath;
294 auto* view = VFS::instance().mountView();
295 if (!view || !view->pathForNode(target.pathLease.path(), file, openedPath)) {
296 if (!currentThread()->getErrno())
297 SYSCALL_ERROR(StaleFileHandle);
298 return completion.finish(-1);
299 }
300 const int statusFlags = flags & (3 | O_APPEND | O_NONBLOCK | O_LARGEFILE);
301 auto* descriptor = new FileDescriptor(openedPath, 0, 0xffffffff,
302 flags & O_CLOEXEC ? FD_CLOEXEC : 0, statusFlags);
303 if (!descriptor || !descriptor->acquireOpenFileDescription()) {
304 const int result = completion.finish(posix_handle_error(FileHandleStatus::NoMemory));
305 delete descriptor;
306 return result;
307 }
308 if ((flags & O_TRUNC) && !file->resize(0)) {
309 if (!currentThread()->getErrno())
310 SYSCALL_ERROR(IoError);
311 const int result = completion.finish(-1);
312 delete descriptor;
313 return result;
314 }
315 auto* subsystem = static_cast<PosixSubsystem*>(currentThread()->getParent()->getSubsystem());
316 DescriptorLease published;
317 const size_t fd = subsystem->installFileDescriptor(descriptor, published);
318 file->publishEvent(FileEvents::Open);
319 return completion.finish(static_cast<int>(fd));
320}
Definition File.h:74
bool resize(size_t size)
Definition File.cc:1129
bool supportsRegularFileOperations()
Definition File.cc:756
virtual bool isDirectory()
Definition File.cc:736
void publishEvent(FileEventMask mask, const StringView &name=StringView(), bool targetIsDirectory=false)
Definition File.cc:793
bool isReadOnly()
Definition Filesystem.h:142
virtual FileHandleStatus encodeFileHandle(File &file, FileHandle &handle)
Definition Filesystem.cc:53
static bool copyFromUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static UserStringResult copyUserString(const char *userString, String &copy, size_t maxLength)
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
Process * getParent()
Definition Process.h:567
virtual int64_t getUserId() const
Definition Process.cc:2084
static ProcessorInformation & information()
static bool checkAccess(File *pFile, bool bRead, bool bWrite, bool bExecute)
Definition VFS.cc:1404
static VFS & instance()
Definition VFS.cc:311