The Pedigree Project 0.1
metadata-syscalls.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "metadata-syscalls.h"
3#include "pedigree/kernel/process/Process.h"
4#include "pedigree/kernel/process/TerminationDeferral.h"
5#include "pedigree/kernel/process/Thread.h"
6#include "pedigree/kernel/processor/Processor.h"
7#include "pedigree/kernel/processor/ProcessorInformation.h"
8#include "pedigree/kernel/syscallError.h"
9
10#include <fcntl.h>
11#include <limits.h>
12
13#include "FileDescriptor.h"
14#include "PosixSubsystem.h"
15#include "file-metadata.h"
16#include "file-syscalls.h"
17#include "landlock.h"
18#include "metadata-abi.h"
19#include "modules/system/vfs/MountView.h"
20#include "modules/system/vfs/VFS.h"
21#include "user-namespace.h"
22#include <sys/stat.h>
23
24namespace {
25struct MetadataResult {
26 MetadataResult(int result)
27 : value(result),
28 error(result < 0 ? Processor::information().getCurrentThread()->getErrno() : 0) {}
29 int value, error;
30};
31
32class MetadataPath {
33 private:
34 TerminationDeferral lifetime;
35
36 public:
37 enum class Input { Path, OpenDescriptor, NullablePath };
38 bool resolve(int dirfd, const char* user, int flags, Input input = Input::Path) {
39 const bool descriptorOnly = input == Input::OpenDescriptor;
40 if (!descriptorOnly && (user || input != Input::NullablePath)) {
41 const auto status = PosixSubsystem::copyUserString(user, copied, PATH_MAX);
42 if (status != PosixSubsystem::UserStringSuccess) {
43 syscallError(status == PosixSubsystem::UserStringBadAddress ? Error::BadAddress
44 : Error::NameTooLong);
45 return false;
46 }
47 if (!copied.length() && !(flags & AT_EMPTY_PATH)) {
48 SYSCALL_ERROR(DoesNotExist);
49 return false;
50 }
51 }
52 auto* process = Processor::information().getCurrentThread()->getParent();
53 auto* subsystem = static_cast<PosixSubsystem*>(process->getSubsystem());
54 context = process->acquireFilesystemContext();
55 auto* view = VFS::instance().mountView();
56 const bool absolute = copied.length() && copied[0] == '/';
58 if (!absolute && (descriptorOnly || dirfd != AT_FDCWD)) {
59 if (!subsystem || !subsystem->acquireFileDescriptor(dirfd, descriptor) ||
60 !descriptor->getFile() || (descriptorOnly && (descriptor->getStatusFlags() & O_PATH))) {
61 SYSCALL_ERROR(BadFileDescriptor);
62 return false;
63 }
64 file = descriptor->getFile();
65 retainedPath = descriptor->openingPath();
66 if (!copied.length())
67 return true;
68 if (!file->isDirectory() || !retainedPath) {
69 SYSCALL_ERROR(NotADirectory);
70 return false;
71 }
72 start = retainedPath;
73 }
74 if (!context || !view) {
75 SYSCALL_ERROR(DoesNotExist);
76 return false;
77 }
78 if (!copied.length()) {
80 if (!context->snapshot(snapshot) || !snapshot.cwd) {
81 SYSCALL_ERROR(DoesNotExist);
82 return false;
83 }
84 retainedPath = snapshot.cwd;
85 } else {
86 String normalised;
87 normalisePath(normalised, copied.cstr());
89 options.followFinal = !(flags & AT_SYMLINK_NOFOLLOW);
90 if (!view->resolve(context, start, normalised, options, retainedPath))
91 return false;
92 }
93 file = retainedPath->node();
94 return file != nullptr;
95 }
96
97 File* file = nullptr;
98 String copied;
99 FilesystemPathRef retainedPath;
100
101 private:
102 DescriptorLease descriptor;
103 FilesystemContextRef context;
104};
105
106bool writableFilesystem(File* file) {
107 if (file->getFilesystem() && file->getFilesystem()->isReadOnly()) {
108 SYSCALL_ERROR(ReadOnlyFilesystem);
109 return false;
110 }
111 return true;
112}
113
114MetadataResult truncatePath(const char* path, off_t length) {
115 if (length < 0) {
116 SYSCALL_ERROR(InvalidArgument);
117 return -1;
118 }
119 MetadataPath target;
120 VfsMountView::WriteLease mountWrite;
121 if (!target.resolve(AT_FDCWD, path, 0))
122 return -1;
123 if (target.file->isDirectory()) {
124 SYSCALL_ERROR(IsADirectory);
125 return -1;
126 }
127 if (!target.file->supportsRegularFileOperations()) {
128 SYSCALL_ERROR(InvalidArgument);
129 return -1;
130 }
131 if ((!writableFilesystem(target.file) ||
132 (target.retainedPath && !mountWrite.acquire(target.retainedPath))) ||
133 !VFS::checkAccess(target.file, false, true, false))
134 return -1;
135 if (!posix_landlock_check(target.retainedPath, LandlockAccess::Truncate)) {
136 return -1;
137 }
138 return target.file->resize(static_cast<size_t>(length)) ? 0 : -1;
139}
140
141MetadataResult linkOwnership(const char* path, uid_t owner, gid_t group) {
142 MetadataPath target;
143 VfsMountView::WriteLease mountWrite;
144 if (!target.resolve(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW) ||
145 (!writableFilesystem(target.file) ||
146 (target.retainedPath && !mountWrite.acquire(target.retainedPath))))
147 return -1;
148 return posix_chown_file(target.file, owner, group) ? 0 : -1;
149}
150
151MetadataResult chmodAt(int dirfd, const char* path, mode_t mode, int flags) {
152 if (flags & ~(AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) {
153 SYSCALL_ERROR(InvalidArgument);
154 return -1;
155 }
156 MetadataPath target;
157 VfsMountView::WriteLease mountWrite;
158 if (!target.resolve(dirfd, path, flags) ||
159 (!writableFilesystem(target.file) ||
160 (target.retainedPath && !mountWrite.acquire(target.retainedPath))))
161 return -1;
162 if (target.file->isSymlink()) {
163 SYSCALL_ERROR(OperationNotSupported);
164 return -1;
165 }
166 return posix_chmod_file(target.file, mode) ? 0 : -1;
167}
168
169MetadataResult updateTimes(int dirfd, const char* path, const void* userTimes, int flags) {
170 using namespace PosixMetadata;
171 Timespec times[2] = {{0, TimeNow}, {0, TimeNow}};
172 if (userTimes && !PosixSubsystem::copyFromUser(times, userTimes, sizeof(times))) {
173 SYSCALL_ERROR(BadAddress);
174 return -1;
175 }
176 // The Linux ABI treats two OMIT values as a no-op before path or flag lookup.
177 if (times[0].nanoseconds == TimeOmit && times[1].nanoseconds == TimeOmit)
178 return 0;
179 for (const auto& time : times) {
180 if (!validNanoseconds(time.nanoseconds)) {
181 SYSCALL_ERROR(InvalidArgument);
182 return -1;
183 }
184 if (time.nanoseconds != TimeNow && time.nanoseconds != TimeOmit &&
185 (time.seconds < 0 || static_cast<uint64_t>(time.seconds) > UINT32_MAX)) {
186 SYSCALL_ERROR(ValueTooLarge);
187 return -1;
188 }
189 }
190 const bool descriptorOnly = !path && dirfd != AT_FDCWD;
191 if ((flags & ~(AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW)) || (descriptorOnly && flags)) {
192 SYSCALL_ERROR(InvalidArgument);
193 return -1;
194 }
195 MetadataPath target;
196 VfsMountView::WriteLease mountWrite;
197 if (!target.resolve(
198 dirfd, path, flags,
199 descriptorOnly ? MetadataPath::Input::OpenDescriptor : MetadataPath::Input::Path) ||
200 (!writableFilesystem(target.file) ||
201 (target.retainedPath && !mountWrite.acquire(target.retainedPath))))
202 return -1;
203 FilesystemCredentials credentials;
204 if (!Process::currentFilesystemCredentials(credentials)) {
205 SYSCALL_ERROR(NotEnoughPermissions);
206 return -1;
207 }
208 const bool owner =
209 posix_global_capable(PosixCapabilities::Fowner) || credentials.uid == target.file->getUid();
210 const bool touch = times[0].nanoseconds == TimeNow && times[1].nanoseconds == TimeNow;
211 if (!owner && !touch) {
212 SYSCALL_ERROR(NotEnoughPermissions);
213 return -1;
214 }
215 if (!owner && !VFS::checkAccess(target.file, false, true, false))
216 return -1;
217 const Time::Timestamp now = Time::getTime();
218 target.file->setTimes(times[0].nanoseconds == TimeNow ? now : times[0].seconds,
219 times[1].nanoseconds == TimeNow ? now : times[1].seconds,
220 times[0].nanoseconds != TimeOmit, times[1].nanoseconds != TimeOmit);
221 return 0;
222}
223
224MetadataResult extendedStat(int dirfd, const char* path, int flags, unsigned mask, void* output) {
225 constexpr unsigned SyncFlags = 0x6000, NoAutomount = 0x800, ReservedMask = 0x80000000U;
226 if ((static_cast<unsigned>(flags) &
227 ~(AT_EMPTY_PATH | AT_SYMLINK_NOFOLLOW | NoAutomount | SyncFlags)) ||
228 (flags & SyncFlags) == SyncFlags || (mask & ReservedMask)) {
229 SYSCALL_ERROR(InvalidArgument);
230 return -1;
231 }
232 MetadataPath target;
233 if (!target.resolve(
234 dirfd, path, flags,
235 (flags & AT_EMPTY_PATH) ? MetadataPath::Input::NullablePath : MetadataPath::Input::Path))
236 return -1;
237 struct stat ordinary = {};
238 if (!posix_stat_file(target.copied.cstr(), target.file, &ordinary))
239 return -1;
240 PosixMetadata::Statx snapshot = {};
241 snapshot.mask = 0x7ff;
242 snapshot.blockSize = ordinary.st_blksize;
243 snapshot.links = ordinary.st_nlink;
244 snapshot.uid = ordinary.st_uid;
245 snapshot.gid = ordinary.st_gid;
246 snapshot.mode = ordinary.st_mode;
247 snapshot.inode = ordinary.st_ino;
248 snapshot.size = ordinary.st_size;
249 snapshot.blocks = ordinary.st_blocks;
250 snapshot.accessed.seconds = ordinary.st_atime;
251 snapshot.changed.seconds = ordinary.st_ctime;
252 snapshot.modified.seconds = ordinary.st_mtime;
253 if (target.retainedPath) {
254 snapshot.mountId = VFS::instance().mountView()->attachmentId(target.retainedPath);
255 if (snapshot.mountId)
256 snapshot.mask |= 0x1000;
257 }
258 snapshot.deviceMajor = PosixMetadata::deviceMajor(ordinary.st_rdev);
259 snapshot.deviceMinor = PosixMetadata::deviceMinor(ordinary.st_rdev);
260 snapshot.filesystemMajor = PosixMetadata::deviceMajor(ordinary.st_dev);
261 snapshot.filesystemMinor = PosixMetadata::deviceMinor(ordinary.st_dev);
262 if (!PosixSubsystem::copyToUser(output, &snapshot, sizeof(snapshot))) {
263 SYSCALL_ERROR(BadAddress);
264 return -1;
265 }
266 return 0;
267}
268} // namespace
269
270int posix_truncate(const char* path, off_t length) {
271 const MetadataResult result = truncatePath(path, length);
272 syscallError(result.error);
273 return result.value;
274}
275int posix_lchown(const char* path, uid_t owner, gid_t group) {
276 const MetadataResult result = linkOwnership(path, owner, group);
277 syscallError(result.error);
278 return result.value;
279}
280int posix_utimensat(int dirfd, const char* path, const void* times, int flags) {
281 const MetadataResult result = updateTimes(dirfd, path, times, flags);
282 syscallError(result.error);
283 return result.value;
284}
285int posix_statx(int dirfd, const char* path, int flags, unsigned mask, void* output) {
286 const MetadataResult result = extendedStat(dirfd, path, flags, mask, output);
287 syscallError(result.error);
288 return result.value;
289}
290int posix_fchmodat2(int dirfd, const char* path, mode_t mode, int flags) {
291 const MetadataResult result = chmodAt(dirfd, path, mode, flags);
292 syscallError(result.error);
293 return result.value;
294}
Definition File.h:75
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)
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