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