The Pedigree Project 0.1
xattr-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
10#include <fcntl.h>
11#include <limits.h>
12
13#include "FileDescriptor.h"
14#include "PosixSubsystem.h"
15#include "ResolvedPath.h"
16#include "file-syscalls.h"
17#include "modules/system/vfs/ExtendedAttributes.h"
18#include "modules/system/vfs/MountView.h"
19#include "user-namespace.h"
20#include "xattr-syscalls.h"
21
22namespace {
23constexpr size_t MaximumName = Xattr::MaximumNameLength, MaximumValue = Xattr::MaximumValueLength;
24enum class TargetKind { Follow, NoFollow, Descriptor };
25
26// Capture before path and descriptor retirement can replace the selected error.
27struct XattrResult {
28 XattrResult(ssize_t result)
29 : value(result),
30 error(result < 0 ? Processor::information().getCurrentThread()->getErrno() : 0) {}
31 ssize_t value;
32 int error;
33};
34
35ssize_t finishResult(const XattrResult& result) {
36 syscallError(result.error);
37 return result.value;
38}
39
40bool copyString(const char* user, String& snapshot, size_t limit, Error::PosixError tooLong) {
41 const auto result = PosixSubsystem::copyUserString(user, snapshot, limit);
42 if (result == PosixSubsystem::UserStringSuccess)
43 return true;
44 syscallError(result == PosixSubsystem::UserStringBadAddress ? Error::BadAddress : tooLong);
45 return false;
46}
47
48bool copyName(const char* user, String& snapshot) {
49 if (!copyString(user, snapshot, MaximumName + 1, Error::BadRange))
50 return false;
51 if (!snapshot.length()) {
52 SYSCALL_ERROR(BadRange);
53 return false;
54 }
55 return true;
56}
57
58struct Target {
59 ResolvedPath pathLease;
60 File* file = nullptr;
61 DescriptorLease descriptor;
62
63 bool resolve(TargetKind kind, const char* userPath, int fd) {
64 auto* thread = Processor::information().getCurrentThread();
65 auto* subsystem = static_cast<PosixSubsystem*>(thread->getParent()->getSubsystem());
66 if (kind == TargetKind::Descriptor) {
67 if (!subsystem || !subsystem->acquireFileDescriptor(fd, descriptor) ||
68 (descriptor->getStatusFlags() & O_PATH)) {
69 SYSCALL_ERROR(BadFileDescriptor);
70 return false;
71 }
72 file = descriptor->getFile();
73 if (!file) {
74 SYSCALL_ERROR(OperationNotSupported);
75 return false;
76 }
77 return true;
78 }
79
80 String path;
81 if (!copyString(userPath, path, PATH_MAX, Error::NameTooLong))
82 return false;
83 if (!path.length()) {
84 SYSCALL_ERROR(DoesNotExist);
85 return false;
86 }
87 String normalised;
88 normalisePath(normalised, path.cstr());
89 const bool requireDirectory = path[path.length() - 1] == '/';
90 thread->setErrno(0);
91 file = findFilePath(normalised, pathLease, FilesystemPathRef(),
92 kind == TargetKind::Follow || requireDirectory);
93 if (!file) {
94 if (!thread->getErrno())
95 SYSCALL_ERROR(DoesNotExist);
96 return false;
97 }
98 if (requireDirectory && !file->isDirectory()) {
99 SYSCALL_ERROR(NotADirectory);
100 return false;
101 }
102 return true;
103 }
104};
105
106bool permitted(File* file, const String& name, bool write) {
107 if (write && file->getFilesystem() && file->getFilesystem()->isReadOnly()) {
108 SYSCALL_ERROR(ReadOnlyFilesystem);
109 return false;
110 }
111 // Storing policy namespaces requires the corresponding access-control
112 // implementation, not just a place to keep their bytes.
113 if (name.length() < 5 || !StringView(name.cstr(), 5).compare("user.", 5)) {
114 SYSCALL_ERROR(OperationNotSupported);
115 return false;
116 }
117 if (!file->isDirectory() && !file->supportsRegularFileOperations()) {
118 syscallError(write ? Error::NotEnoughPermissions : Error::NoData);
119 return false;
120 }
121 if (file->isDirectory() && write) {
122 const auto attributes = file->getAttributes();
123 FilesystemCredentials credentials;
124 if (!Process::currentFilesystemCredentials(credentials)) {
125 SYSCALL_ERROR(NotEnoughPermissions);
126 return false;
127 }
128 const uint32_t uid = credentials.uid;
129 if ((attributes.permissions & FILE_STICKY) &&
130 !posix_global_capable(PosixCapabilities::Fowner) &&
131 static_cast<uint64_t>(uid) != attributes.uid) {
132 SYSCALL_ERROR(NotEnoughPermissions);
133 return false;
134 }
135 }
136 if (!VFS::checkAccess(file, !write, write, false))
137 return false;
138 if (name.length() == 5) {
139 SYSCALL_ERROR(InvalidArgument);
140 return false;
141 }
142 return true;
143}
144
145ssize_t finish(XattrStatus status, size_t count = 0) {
146 switch (status) {
147 case XattrStatus::Success:
148 Processor::information().getCurrentThread()->setErrno(0);
149 return static_cast<ssize_t>(count);
150 case XattrStatus::Missing:
151 SYSCALL_ERROR(NoData);
152 break;
153 case XattrStatus::Exists:
154 SYSCALL_ERROR(FileExists);
155 break;
156 case XattrStatus::Range:
157 SYSCALL_ERROR(BadRange);
158 break;
159 case XattrStatus::Quota:
160 SYSCALL_ERROR(QuotaExceeded);
161 break;
162 case XattrStatus::Overflow:
163 SYSCALL_ERROR(ValueTooLarge);
164 break;
165 case XattrStatus::NoSpace:
166 SYSCALL_ERROR(NoSpaceLeftOnDevice);
167 break;
168 case XattrStatus::NoMemory:
169 SYSCALL_ERROR(OutOfMemory);
170 break;
171 case XattrStatus::Unsupported:
172 SYSCALL_ERROR(OperationNotSupported);
173 break;
174 case XattrStatus::Denied:
175 SYSCALL_ERROR(PermissionDenied);
176 break;
177 case XattrStatus::IoError:
178 SYSCALL_ERROR(IoError);
179 break;
180 case XattrStatus::Invalid:
181 SYSCALL_ERROR(InvalidArgument);
182 break;
183 case XattrStatus::ReadOnly:
184 SYSCALL_ERROR(ReadOnlyFilesystem);
185 break;
186 }
187 return -1;
188}
189
190XattrResult readAttribute(TargetKind kind, const char* path, int fd, const char* userName,
191 void* output, size_t size, bool list) {
192 TerminationDeferral lifetime;
193 Target target;
194 if (!target.resolve(kind, path, fd))
195 return -1;
196 String name;
197 if (!list && !copyName(userName, name))
198 return -1;
199 const size_t capacity = size < MaximumValue ? size : MaximumValue;
200 auto buffer = UniqueArray<uint8_t>::allocate(capacity);
201 if (capacity && !buffer) {
202 SYSCALL_ERROR(OutOfMemory);
203 return -1;
204 }
205 // Listing reports supported names even when the caller cannot read values.
206 if (!list && !permitted(target.file, name, false))
207 return -1;
208 size_t required = 0;
209 const auto status =
210 list ? target.file->listExtendedAttributes(buffer.get(), capacity, required)
211 : target.file->getExtendedAttribute(name.view(), buffer.get(), capacity, required);
212 if ((status == XattrStatus::Range && capacity == MaximumValue) ||
213 (status == XattrStatus::Success && required > MaximumValue)) {
214 SYSCALL_ERROR(TooBig);
215 return -1;
216 }
217 if (status != XattrStatus::Success)
218 return finish(status);
219 if (capacity && required) {
220 if (required > capacity) {
221 SYSCALL_ERROR(IoError);
222 return -1;
223 }
224 if (!PosixSubsystem::copyToUser(output, buffer.get(), required)) {
225 SYSCALL_ERROR(BadAddress);
226 return -1;
227 }
228 }
229 return finish(status, required);
230}
231
232XattrResult changeAttribute(TargetKind kind, const char* path, int fd, const char* userName,
233 const void* value, size_t size, int flags, bool remove) {
234 TerminationDeferral lifetime;
235 Target target;
236 if (kind == TargetKind::Descriptor && !target.resolve(kind, path, fd))
237 return -1;
238 if (flags & ~3) {
239 SYSCALL_ERROR(InvalidArgument);
240 return -1;
241 }
242 String name;
243 if (!copyName(userName, name))
244 return -1;
245 if (size > MaximumValue) {
246 SYSCALL_ERROR(TooBig);
247 return -1;
248 }
249 auto buffer = UniqueArray<uint8_t>::allocate(size);
250 if (size) {
251 if (!buffer) {
252 SYSCALL_ERROR(OutOfMemory);
253 return -1;
254 }
255 if (!PosixSubsystem::copyFromUser(buffer.get(), value, size)) {
256 SYSCALL_ERROR(BadAddress);
257 return -1;
258 }
259 }
260 if (kind != TargetKind::Descriptor && !target.resolve(kind, path, fd))
261 return -1;
262 VfsMountView::WriteLease mountWrite;
263 const auto retainedPath = target.pathLease.path() ? target.pathLease.path()
264 : target.descriptor ? target.descriptor->openingPath()
266 if (retainedPath && !mountWrite.acquire(retainedPath)) {
267 return -1;
268 }
269 if (!permitted(target.file, name, true))
270 return -1;
271 return static_cast<int>(
272 finish(remove ? target.file->removeExtendedAttribute(name.view())
273 : target.file->setExtendedAttribute(name.view(), buffer.get(), size, flags)));
274}
275} // namespace
276
277int posix_setxattr(const char* path, const char* name, const void* value, size_t size, int flags) {
278 return finishResult(
279 changeAttribute(TargetKind::Follow, path, -1, name, value, size, flags, false));
280}
281int posix_lsetxattr(const char* path, const char* name, const void* value, size_t size, int flags) {
282 return finishResult(
283 changeAttribute(TargetKind::NoFollow, path, -1, name, value, size, flags, false));
284}
285int posix_fsetxattr(int fd, const char* name, const void* value, size_t size, int flags) {
286 return finishResult(
287 changeAttribute(TargetKind::Descriptor, nullptr, fd, name, value, size, flags, false));
288}
289ssize_t posix_getxattr(const char* path, const char* name, void* value, size_t size) {
290 return finishResult(readAttribute(TargetKind::Follow, path, -1, name, value, size, false));
291}
292ssize_t posix_lgetxattr(const char* path, const char* name, void* value, size_t size) {
293 return finishResult(readAttribute(TargetKind::NoFollow, path, -1, name, value, size, false));
294}
295ssize_t posix_fgetxattr(int fd, const char* name, void* value, size_t size) {
296 return finishResult(readAttribute(TargetKind::Descriptor, nullptr, fd, name, value, size, false));
297}
298ssize_t posix_listxattr(const char* path, char* list, size_t size) {
299 return finishResult(readAttribute(TargetKind::Follow, path, -1, nullptr, list, size, true));
300}
301ssize_t posix_llistxattr(const char* path, char* list, size_t size) {
302 return finishResult(readAttribute(TargetKind::NoFollow, path, -1, nullptr, list, size, true));
303}
304ssize_t posix_flistxattr(int fd, char* list, size_t size) {
305 return finishResult(
306 readAttribute(TargetKind::Descriptor, nullptr, fd, nullptr, list, size, true));
307}
308int posix_removexattr(const char* path, const char* name) {
309 return finishResult(changeAttribute(TargetKind::Follow, path, -1, name, nullptr, 0, 0, true));
310}
311int posix_lremovexattr(const char* path, const char* name) {
312 return finishResult(changeAttribute(TargetKind::NoFollow, path, -1, name, nullptr, 0, 0, true));
313}
314int posix_fremovexattr(int fd, const char* name) {
315 return finishResult(
316 changeAttribute(TargetKind::Descriptor, nullptr, fd, name, nullptr, 0, 0, true));
317}
int getStatusFlags() const
Get current status flags.
Definition File.h:75
bool supportsRegularFileOperations()
Definition File.cc:824
virtual bool isDirectory()
Definition File.cc:804
bool isReadOnly()
Definition Filesystem.h:150
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)
static ProcessorInformation & information()
bool compare(const char *s, size_t length) const
Definition StringView.cc:91
static bool checkAccess(File *pFile, bool bRead, bool bWrite, bool bExecute)
Definition VFS.cc:1502