The Pedigree Project 0.1
mount-view-syscalls.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "mount-view-syscalls.h"
3#include "pedigree/kernel/process/Thread.h"
4#include "pedigree/kernel/processor/Processor.h"
5#include "pedigree/kernel/processor/ProcessorInformation.h"
6#include "pedigree/kernel/syscallError.h"
7
8#include <limits.h>
9
10#include "DevFs.h"
11#include "PosixSubsystem.h"
12#include "ProcFs.h"
13#include "ResolvedPath.h"
14#include "file-syscalls.h"
16#include "modules/system/vfs/MountView.h"
17#include "user-namespace.h"
18
19extern ProcFs* g_pProcFs;
20
21namespace {
22uint32_t permissionsForMode(uint32_t mode) {
23 uint32_t permissions = mode & FILE_AMASK;
24 if (mode & 0400) {
25 permissions |= FILE_UR;
26 }
27 if (mode & 0200) {
28 permissions |= FILE_UW;
29 }
30 if (mode & 0100) {
31 permissions |= FILE_UX;
32 }
33 if (mode & 0040) {
34 permissions |= FILE_GR;
35 }
36 if (mode & 0020) {
37 permissions |= FILE_GW;
38 }
39 if (mode & 0010) {
40 permissions |= FILE_GX;
41 }
42 if (mode & 0004) {
43 permissions |= FILE_OR;
44 }
45 if (mode & 0002) {
46 permissions |= FILE_OW;
47 }
48 if (mode & 0001) {
49 permissions |= FILE_OX;
50 }
51 return permissions;
52}
53struct MountResult {
54 MountResult(int result)
55 : value(result),
56 error(result < 0 ? Processor::information().getCurrentThread()->getErrno() : 0) {}
57 int value;
58 size_t error;
59};
60bool privileged() {
61 auto* view = VFS::instance().mountView();
62 if (!view) {
63 SYSCALL_ERROR(NoSuchDevice);
64 return false;
65 }
66 auto owner = posix_user_namespace(*Processor::information().getCurrentThread());
67 while (owner && owner->identity() != view->ownerNamespace()) {
68 owner = owner->parent();
69 }
70 if ((owner ? owner->identity() : 0) == view->ownerNamespace() &&
71 posix_namespace_capable(owner, PosixCapabilities::SysAdmin)) {
72 return true;
73 }
74 SYSCALL_ERROR(NotEnoughPermissions);
75 return false;
76}
77
78class DevPts final : public Filesystem {
79 public:
80 ~DevPts() override {
81 delete m_Root;
82 }
83 bool initialise(Disk*) override {
84 m_Root = new DevFsDirectory(String(""), 0, 0, 0, 1, this, 0, nullptr);
85 if (!m_Root) {
86 return false;
87 }
88 m_Root->setPermissions(permissionsForMode(0755));
89 auto* multiplexer =
90 new PtmxFile(String("ptmx"), 2, this, m_Root, m_Root, permissionsForMode(0620), true);
91 if (!multiplexer) {
92 return false;
93 }
94 if (!m_Root->addEntry(String("ptmx"), multiplexer)) {
95 delete multiplexer;
96 return false;
97 }
98 return true;
99 }
100 File* getRoot() const override {
101 return m_Root;
102 }
103 const String& getVolumeLabel() const override {
104 static String name("devpts");
105 return name;
106 }
107 SyncStatus sync() override {
108 return SyncStatus::Success;
109 }
110 bool createFile(File*, const String&, uint32_t) override {
111 return false;
112 }
113 bool createDirectory(File*, const String&, uint32_t) override {
114 return false;
115 }
116 bool createSymlink(File*, const String&, const String&) override {
117 return false;
118 }
119 bool removeNode(File*, const String&, File*) override {
120 return false;
121 }
122
123 private:
124 DevFsDirectory* m_Root = nullptr;
125};
126
127bool copyPath(const char* user, String& result) {
128 auto status = PosixSubsystem::copyUserString(user, result, PATH_MAX);
129 if (status == PosixSubsystem::UserStringSuccess)
130 return true;
131 syscallError(status == PosixSubsystem::UserStringBadAddress ? Error::BadAddress
132 : Error::NameTooLong);
133 return false;
134}
135bool initialiseSelinux(RamFs& filesystem) {
136 struct Entry {
137 const char* name;
138 uint32_t mode;
139 const char* value;
140 };
141 const Entry entries[] = {
142 {"enforce", 0444, "0\n"}, {"policyvers", 0444, "0\n"}, {"disable", 0666, ""}};
143 for (const auto& entry : entries) {
144 if (!filesystem.Filesystem::createFile(String(entry.name), entry.mode, filesystem.getRoot()))
145 return false;
147 if (Directory::fromFile(filesystem.getRoot())
148 ->lookupChild(HashedStringView(entry.name), file) != Directory::LookupStatus::Found)
149 return false;
150 const size_t bytes = StringLength(entry.value);
151 if (bytes && file.get()->write(0, bytes, reinterpret_cast<uintptr_t>(entry.value)) != bytes)
152 return false;
153 }
154 return true;
155}
156MountResult mount(const char* source, const char* target, const char* type, size_t flags,
157 const void* data) {
158 ResolvedPath selected;
159 if (!privileged())
160 return -1;
161 constexpr size_t Bind = 4096, Recursive = 16384, Remount = 32;
162 constexpr size_t Silent = 32768, Private = 1UL << 18, Slave = 1UL << 19;
163 constexpr size_t Policy = VfsMountView::SupportedMountFlags;
164 if ((flags & 0xffff0000UL) == 0xc0ed0000UL) {
165 flags &= 0xffff;
166 }
167 flags &= ~Silent;
168 if (flags & ~(Policy | Bind | Recursive | Remount | Private | Slave)) {
169 SYSCALL_ERROR(OperationNotSupported);
170 return -1;
171 }
172 String sourceCopy, targetCopy, typeCopy, options;
173 if ((source && !copyPath(source, sourceCopy)) || !copyPath(target, targetCopy) ||
174 (type && !copyPath(type, typeCopy)) ||
175 (data && !copyPath(static_cast<const char*>(data), options))) {
176 return -1;
177 }
178 auto* view = VFS::instance().mountView();
179 auto context =
180 Processor::information().getCurrentThread()->getParent()->acquireFilesystemContext();
181 if (!view || !context || !findFilePath(targetCopy, selected, FilesystemPathRef(), true)) {
182 return -1;
183 }
184 if (flags & (Private | Slave)) {
185 if ((flags & ~(Private | Slave | Recursive)) ||
186 (flags & (Private | Slave)) == (Private | Slave)) {
187 SYSCALL_ERROR(InvalidArgument);
188 return -1;
189 }
190 // Graph copies never propagate mount mutations to another namespace.
191 return 0;
192 }
193 if (flags & Remount) {
194 if (!(flags & Bind)) {
195 SYSCALL_ERROR(OperationNotSupported);
196 return -1;
197 }
198 return view->remount(context, selected.path(), flags & Policy, flags & Recursive) ? 0 : -1;
199 }
200 if (flags & Bind) {
201 ResolvedPath original;
202 if (!source || !findFilePath(sourceCopy, original, FilesystemPathRef(), true)) {
203 return -1;
204 }
205 return view->bind(context, original.path(), selected.path(), flags & Recursive) ? 0 : -1;
206 }
207 if ((flags & Recursive) || !selected.get()->isDirectory()) {
208 syscallError((flags & Recursive) ? Error::InvalidArgument : Error::NotADirectory);
209 return -1;
210 }
211 uint32_t rootMode = 0755;
212 if (options.length() && typeCopy != "devpts") {
213 if (options.length() < 6 || String(options.cstr(), 5) != "mode=") {
214 SYSCALL_ERROR(OperationNotSupported);
215 return -1;
216 }
217 rootMode = 0;
218 for (size_t i = 5; i < options.length(); ++i) {
219 if (options[i] < '0' || options[i] > '7' || rootMode > 0777) {
220 SYSCALL_ERROR(InvalidArgument);
221 return -1;
222 }
223 rootMode = rootMode * 8 + options[i] - '0';
224 }
225 }
226 Filesystem* backing = nullptr;
227 bool owned = false;
228 if (typeCopy == "proc") {
229 auto* filesystem =
230 new ProcFs(Processor::information().getCurrentThread()->getParent()->pidNamespace());
231 if (!filesystem || !filesystem->initialise(nullptr)) {
232 delete filesystem;
233 SYSCALL_ERROR(OutOfMemory);
234 return -1;
235 }
236 backing = filesystem;
237 owned = true;
238 } else if (typeCopy == "devpts") {
239 if (options != "newinstance,ptmxmode=0666,mode=620") {
240 SYSCALL_ERROR(OperationNotSupported);
241 return -1;
242 }
243 auto* filesystem = new DevPts;
244 if (!filesystem || !filesystem->initialise(nullptr)) {
245 delete filesystem;
246 SYSCALL_ERROR(OutOfMemory);
247 return -1;
248 }
249 backing = filesystem;
250 owned = true;
251 } else if (typeCopy == "tmpfs" || typeCopy == "ramfs" || typeCopy == "selinuxfs") {
252 auto* filesystem = new RamFs;
253 if (!filesystem) {
254 SYSCALL_ERROR(OutOfMemory);
255 return -1;
256 }
257 if (!filesystem->initialise(nullptr) ||
258 (typeCopy == "selinuxfs" && !initialiseSelinux(*filesystem))) {
259 delete filesystem;
260 SYSCALL_ERROR(IoError);
261 return -1;
262 }
263 filesystem->getRoot()->setPermissions(permissionsForMode(rootMode));
264 backing = filesystem;
265 owned = true;
266 } else {
267 SYSCALL_ERROR(DeviceDoesNotExist);
268 return -1;
269 }
270 if (!backing) {
271 SYSCALL_ERROR(DeviceDoesNotExist);
272 return -1;
273 }
274 if (owned && !VFS::instance().registerFilesystem(backing, typeCopy).length()) {
275 delete backing;
276 SYSCALL_ERROR(OutOfMemory);
277 return -1;
278 }
279 const auto ownership =
280 owned ? VfsMountView::BackingOwnership::Attachment : VfsMountView::BackingOwnership::External;
281 if (view->attach(context, selected.path(), backing, ownership, flags & Policy))
282 return 0;
283 const auto error = Processor::information().getCurrentThread()->getErrno();
284 if (owned && !VFS::instance().retireOwnedFilesystem(backing))
285 FATAL("Unpublished mount backing retained unexpectedly");
286 syscallError(error);
287 return -1;
288}
289MountResult unmount(const char* target, int flags) {
290 TerminationDeferral lifetime;
291 if (!privileged())
292 return -1;
293 constexpr int lazy = 2;
294 if (flags & ~lazy) {
295 SYSCALL_ERROR(OperationNotSupported);
296 return -1;
297 }
298 String copied;
299 if (!copyPath(target, copied))
300 return -1;
301 auto context =
302 Processor::information().getCurrentThread()->getParent()->acquireFilesystemContext();
303 auto* view = VFS::instance().mountView();
304 if (!view || !context) {
305 SYSCALL_ERROR(DoesNotExist);
306 return -1;
307 }
308 return view->detach(context, copied, flags & lazy) ? 0 : -1;
309}
310MountResult pivot(const char* newRoot, const char* putOld) {
311 TerminationDeferral lifetime;
312 if (!privileged())
313 return -1;
314 String next, previous;
315 if (!copyPath(newRoot, next) || !copyPath(putOld, previous))
316 return -1;
317 auto context =
318 Processor::information().getCurrentThread()->getParent()->acquireFilesystemContext();
319 auto* view = VFS::instance().mountView();
320 if (!view || !context) {
321 SYSCALL_ERROR(DoesNotExist);
322 return -1;
323 }
324 return view->pivot(context, next, previous) ? 0 : -1;
325}
326} // namespace
327int posix_mount(const char* source, const char* target, const char* type, size_t flags,
328 const void* data) {
329 const MountResult result = mount(source, target, type, flags, data);
330 syscallError(result.error);
331 return result.value;
332}
333int posix_umount2(const char* target, int flags) {
334 const MountResult result = unmount(target, flags);
335 syscallError(result.error);
336 return result.value;
337}
338int posix_pivot_root(const char* newRoot, const char* putOld) {
339 const MountResult result = pivot(newRoot, putOld);
340 syscallError(result.error);
341 return result.value;
342}
An in-RAM filesystem.
static Directory * fromFile(File *pF)
Definition Directory.h:151
MUST_USE_RESULT LookupStatus lookupChild(const HashedStringView &s, ChildLease &child) const
Definition Directory.cc:355
Definition Disk.h:35
Definition File.h:75
virtual uint64_t write(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true) final
Definition File.cc:342
virtual bool isDirectory()
Definition File.cc:804
virtual bool removeNode(File *parent, const String &filename, File *file)=0
virtual bool initialise(Disk *pDisk)=0
virtual const String & getVolumeLabel() const =0
bool createSymlink(const StringView &path, const String &value, File *pStartNode=0)
bool createDirectory(const StringView &path, uint32_t mask, File *pStartNode=0)
virtual SyncStatus sync()
virtual File * getRoot() const =0
bool createFile(const StringView &path, uint32_t mask, File *pStartNode=0)
static UserStringResult copyUserString(const char *userString, String &copy, size_t maxLength)
static ProcessorInformation & information()
Definition RamFs.h:118
virtual bool initialise(Disk *pDisk) override
Definition RamFs.cc:441
virtual File * getRoot() const override
Definition RamFs.h:135
static VFS & instance()
Definition VFS.cc:311