The Pedigree Project 0.1
quota-syscalls.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/process/FilesystemCredentials.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
11#include "DevFs-block.h"
12#include "file-handle-syscalls.h"
13#include "modules/system/vfs/Quota.h"
14#include "quota-syscalls.h"
15
16namespace {
17constexpr uint32_t Sync = 0x800001, Enable = 0x800002, Disable = 0x800003;
18constexpr uint32_t GetFormat = 0x800004, GetInfo = 0x800005, SetInfo = 0x800006;
19constexpr uint32_t GetQuota = 0x800007, SetQuota = 0x800008;
20static_assert(sizeof(QuotaRecord) == 72, "Linux dqblk size");
21static_assert(__builtin_offsetof(QuotaRecord, valid) == 64, "Linux dqblk validity mask");
22
23struct QuotaResult {
24 QuotaResult(int result)
25 : value(result),
26 error(result < 0 ? Processor::information().getCurrentThread()->getErrno() : 0) {}
27 int value, error;
28};
29
30bool permitted(uint32_t command, QuotaType type, uint32_t id) {
31 if (command == GetFormat || command == Sync)
32 return true;
33 if (posix_effective_root())
34 return true;
35 if (command == GetQuota) {
36 Process* process = Processor::information().getCurrentThread()->getParent();
37 int64_t uid = process->getEffectiveUserId(), gid = process->getEffectiveGroupId();
38 if (uid < 0)
39 uid = process->getUserId();
40 if (gid < 0)
41 gid = process->getGroupId();
42 if (type == QuotaType::User && uid >= 0 && static_cast<uint64_t>(uid) == id)
43 return true;
44 FilesystemCredentials credentials;
45 if (type == QuotaType::Group && gid >= 0 &&
46 Process::currentFilesystemCredentials(credentials)) {
47 credentials.gid = static_cast<uint32_t>(gid);
48 if (credentials.inGroup(id))
49 return true;
50 }
51 }
52 SYSCALL_ERROR(NotEnoughPermissions);
53 return false;
54}
55
56int syncAll(const QuotaRequest& request) {
58 if (!VFS::instance().snapshotDiskMounts(mounts)) {
59 SYSCALL_ERROR(OutOfMemory);
60 return -1;
61 }
62 int error = 0;
63 for (const auto& identity : mounts) {
65 if (!identity.pin(pin)) {
66 if (!error)
67 error = Error::DeviceDoesNotExist;
68 continue;
69 }
70 QuotaResponse response;
71 const auto status = pin.filesystem()->quotaControl(request, response);
72 if (status != QuotaStatus::Unsupported && status != QuotaStatus::NotEnabled &&
73 status != QuotaStatus::Success && !error)
74 error = quotaError(status);
75 }
76 syscallError(error);
77 return error ? -1 : 0;
78}
79
80QuotaResult control(int encoded, const char* special, int signedId, void* address) {
81 TerminationDeferral lifetime;
82 const uint32_t type = static_cast<uint32_t>(encoded) & 0xff;
83 const uint32_t command = static_cast<uint32_t>(encoded) >> 8;
84 if (type > 1) {
85 SYSCALL_ERROR(InvalidArgument);
86 return -1;
87 }
88 QuotaRequest request;
89 request.type = type ? QuotaType::Group : QuotaType::User;
90 request.id = static_cast<uint32_t>(signedId);
91 switch (command) {
92 case Sync:
93 request.operation = QuotaOperation::Sync;
94 break;
95 case Enable:
96 request.operation = QuotaOperation::Enable;
97 request.format = request.id;
98 break;
99 case Disable:
100 request.operation = QuotaOperation::Disable;
101 break;
102 case GetFormat:
103 request.operation = QuotaOperation::GetFormat;
104 break;
105 case GetQuota:
106 request.operation = QuotaOperation::Get;
107 break;
108 case SetQuota:
109 request.operation = QuotaOperation::Set;
110 break;
111 case GetInfo:
112 case SetInfo:
113 SYSCALL_ERROR(OperationNotSupported);
114 return -1;
115 default:
116 SYSCALL_ERROR(InvalidArgument);
117 return -1;
118 }
119 if (!permitted(command, request.type, request.id))
120 return -1;
121 if ((command == GetQuota || command == SetQuota) && request.id == 0xffffffffU) {
122 SYSCALL_ERROR(InvalidArgument);
123 return -1;
124 }
125 if (command == SetQuota &&
126 !PosixSubsystem::copyFromUser(&request.record, address, sizeof(request.record))) {
127 SYSCALL_ERROR(BadAddress);
128 return -1;
129 }
130 if (!special) {
131 if (command == Sync)
132 return syncAll(request);
133 SYSCALL_ERROR(DeviceDoesNotExist);
134 return -1;
135 }
136
137 // Path resolution precedes backing admission and quota locks. The pin is
138 // declared first so it outlives both retained path owners during cleanup.
139 VFS::FilesystemPin filesystem;
140 PosixHandleTarget device, quota;
141 if (command == Enable && !quota.resolve(AT_FDCWD, static_cast<const char*>(address), true, false))
142 return -1;
143 if (!device.resolve(AT_FDCWD, special, true, false))
144 return -1;
145 if (!device.file->isBlockDevice()) {
146 SYSCALL_ERROR(NotABlockDevice);
147 return -1;
148 }
149 const uint64_t number = device.file->deviceNumber();
151 if (!PosixBlock::valid(number, PosixBlock::MountedMajor) ||
152 !VFS::instance().diskMount(PosixBlock::minor(number), identity) ||
153 !identity.pin(filesystem)) {
154 SYSCALL_ERROR(DeviceDoesNotExist);
155 return -1;
156 }
157 if (command == Enable && quota.file->getFilesystem() != filesystem.filesystem()) {
158 SYSCALL_ERROR(CrossDeviceLink);
159 return -1;
160 }
161 QuotaResponse response;
162 const auto status = filesystem.filesystem()->quotaControl(request, response, quota.file);
163 if (status != QuotaStatus::Success) {
164 syscallError(quotaError(status));
165 return -1;
166 }
167 if ((command == GetQuota &&
168 !PosixSubsystem::copyToUser(address, &response.record, sizeof(response.record))) ||
169 (command == GetFormat &&
170 !PosixSubsystem::copyToUser(address, &response.format, sizeof(response.format)))) {
171 SYSCALL_ERROR(BadAddress);
172 return -1;
173 }
174 return 0;
175}
176} // namespace
177
178int posix_quotactl(int command, const char* special, int id, void* address) {
179 const QuotaResult result = control(command, special, id, address);
180 syscallError(result.error);
181 return result.value;
182}
static bool copyFromUser(void *destination, const void *source, size_t count, size_t elementSize=1)
static bool copyToUser(void *destination, const void *source, size_t count, size_t elementSize=1)
Process * getParent()
Definition Process.h:620
virtual int64_t getUserId() const
Definition Process.cc:2238
static ProcessorInformation & information()
static VFS & instance()
Definition VFS.cc:311
A vector / dynamic array.
Definition Vector.h:33