The Pedigree Project 0.1
DevFs-block.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "DevFs-block.h"
3#include "pedigree/kernel/machine/Disk.h"
4#include "pedigree/kernel/process/TerminationDeferral.h"
5#include "pedigree/kernel/syscallError.h"
6#include "pedigree/kernel/utilities/StaticString.h"
7#include "pedigree/kernel/utilities/utility.h"
8
9#include "DevFs.h"
10#include "modules/system/vfs/Symlink.h"
11#include "modules/system/vfs/VFS.h"
12
13namespace {
14bool parseId(const StringView& name, size_t start, uint32_t& id) {
15 if (name.length() <= start || name[start] == '0')
16 return false;
17 id = 0;
18 for (size_t i = start; i < name.length(); ++i) {
19 const char ch = name[i];
20 if (ch < '0' || ch > '9' || id > (PosixBlock::MaximumMinor - (ch - '0')) / 10)
21 return false;
22 id = id * 10 + ch - '0';
23 }
24 return true;
25}
26
27class BlockFile final : public File {
28 public:
29 BlockFile(DevFs& filesystem, File* parent, const String& name, uint32_t id, uint64_t bytes,
30 const VFS::MountIdentity& mount)
31 : File(name, 0, 0, 0, filesystem.getNextInode(), &filesystem, bytes, parent),
32 m_Id(id),
33 m_Mount(mount) {
34 setPermissionsOnly(FILE_UR | FILE_UW);
35 setUidOnly(0);
36 setGidOnly(0);
37 }
38 bool isBlockDevice() const override {
39 return true;
40 }
41 uint64_t deviceNumber() const override {
42 return PosixBlock::encode(m_Mount ? PosixBlock::MountedMajor : PosixBlock::PhysicalMajor, m_Id);
43 }
44 uint64_t readBytewise(uint64_t offset, uint64_t length, uintptr_t buffer,
45 bool canBlock = true) override {
46 return transfer(false, offset, length, buffer, canBlock);
47 }
48 uint64_t writeBytewise(uint64_t offset, uint64_t length, uintptr_t buffer,
49 bool canBlock = true) override {
50 return transfer(true, offset, length, buffer, canBlock);
51 }
52
53 private:
54 bool isBytewise() const override {
55 return true;
56 }
57 uint64_t transfer(bool write, uint64_t offset, uint64_t length, uintptr_t buffer, bool canBlock) {
58 if (!length)
59 return 0;
60 if (!canBlock) {
61 SYSCALL_ERROR(NoMoreProcesses);
62 return 0;
63 }
64 TerminationDeferral lifetime;
65 VFS::MountOperation mounted;
66 OperationBarrier::Lease filesystemOperation;
67 DiskUse use;
68 Disk* disk = nullptr;
69 if (m_Mount) {
70 if (m_Mount.acquire(mounted) &&
71 mounted.filesystem()->tryAcquireOperation(filesystemOperation)) {
72 disk = mounted.filesystem()->getDisk();
73 }
74 } else if (DiskEndpoints::acquire(m_Id, use)) {
75 disk = use.get();
76 }
77 if (!disk) {
78 uint64_t bytes = 0;
79 if (m_Mount) {
80 SYSCALL_ERROR(DeviceDoesNotExist);
81 } else {
82 syscallError(DiskEndpoints::describe(m_Id, bytes) ? Error::DeviceBusy
83 : Error::NoSuchDevice);
84 }
85 return 0;
86 }
87 const uint64_t bytes = disk->getSize();
88 if (offset >= bytes)
89 return 0;
90 if (length > bytes - offset)
91 length = bytes - offset;
92 uint64_t done = 0;
93 while (done < length) {
94 const uint64_t position = offset + done;
95 const uint64_t aligned = position - position % 512;
96 const size_t displacement = position - aligned;
97 const BufferView view = disk->read(aligned);
98 if (!view || view.size() <= displacement) {
99 if (view)
100 disk->unpin(aligned);
101 SYSCALL_ERROR(IoError);
102 return done;
103 }
104 const size_t amount = min(static_cast<uint64_t>(view.size() - displacement), length - done);
105 auto* page = reinterpret_cast<uint8_t*>(view.address()) + displacement;
106 if (write)
107 MemoryCopy(page, reinterpret_cast<void*>(buffer + done), amount);
108 else
109 MemoryCopy(reinterpret_cast<void*>(buffer + done), page, amount);
110 const bool completed = !write || disk->sync(aligned, false);
111 disk->unpin(aligned);
112 if (!completed) {
113 SYSCALL_ERROR(IoError);
114 return done;
115 }
116 done += amount;
117 }
118 return done;
119 }
120 uint32_t m_Id;
121 VFS::MountIdentity m_Mount;
122};
123
124class BlockDirectory final : public DevFsDirectory {
125 public:
126 BlockDirectory(DevFs& filesystem, File* parent)
127 : DevFsDirectory(String("block"), 0, 0, 0, filesystem.getNextInode(), &filesystem, 0,
128 parent) {
129 setPermissionsOnly(FILE_UR | FILE_UX | FILE_GR | FILE_GX | FILE_OR | FILE_OX);
130 }
131
132 protected:
133 bool cacheResolvedChildren() const override {
134 return false;
135 }
136 LookupStatus resolveChild(const StringView& name, File*& child) override {
137 child = nullptr;
138 uint32_t id = 0;
139 uint64_t bytes = 0;
140 VFS::MountIdentity mount;
141 const bool physical =
142 name.length() > 4 && name[0] == 'd' && name[1] == 'i' && name[2] == 's' && name[3] == 'k';
143 if (!parseId(name, physical ? 4 : 0, id))
144 return LookupStatus::NotFound;
145 if (physical) {
146 // An active paging endpoint remains nameable for swapoff, even though
147 // ordinary byte I/O is denied by its exclusive claim.
148 if (!DiskEndpoints::describe(id, bytes))
149 return LookupStatus::NotFound;
150 } else {
152 OperationBarrier::Lease filesystemOperation;
153 if (!VFS::instance().diskMount(id, mount) || !mount.acquire(pin) ||
154 !pin.filesystem()->tryAcquireOperation(filesystemOperation) ||
155 !pin.filesystem()->getDisk()) {
156 return LookupStatus::NotFound;
157 }
158 bytes = pin.filesystem()->getDisk()->getSize();
159 }
160 child =
161 new BlockFile(*static_cast<DevFs*>(getFilesystem()), this, String(name), id, bytes, mount);
162 return child ? LookupStatus::Found : LookupStatus::IoError;
163 }
164 ReadStatus readDirectory(uint64_t& cookie, DirectoryEntryEmitter emitter,
165 void* context) override {
167 if (!VFS::instance().snapshotDiskMounts(mounts))
168 return ReadStatus::IoError;
169 uint32_t physical[DiskEndpoints::Capacity];
170 const size_t physicalCount = DiskEndpoints::snapshot(physical, DiskEndpoints::Capacity);
171 while (true) {
172 uint64_t next = ~uint64_t(0);
173 for (const auto& mount : mounts) {
174 if (mount.id() >= cookie && mount.id() < next)
175 next = mount.id();
176 }
177 for (size_t i = 0; i < physicalCount; ++i) {
178 const uint64_t key = static_cast<uint64_t>(PosixBlock::MaximumMinor) + 1 + physical[i];
179 if (key >= cookie && key < next)
180 next = key;
181 }
182 if (next == ~uint64_t(0))
183 return ReadStatus::Complete;
185 if (next > PosixBlock::MaximumMinor) {
186 name += "disk";
187 name.append(next - PosixBlock::MaximumMinor - 1);
188 } else {
189 name.append(next);
190 }
191 const DirectoryEntryView entry{StringView(name, name.length()), static_cast<uintptr_t>(next),
192 EntryType::BlockDevice, next, next + 1};
193 if (!emitter(context, entry))
194 return ReadStatus::Stopped;
195 cookie = next + 1;
196 }
197 }
198};
199
200enum class AliasKind { FilesystemUuid, FilesystemLabel, PartitionUuid, PartitionLabel };
201
202struct AliasCandidate {
203 AliasCandidate() : key(0), name(), target() {}
204 AliasCandidate(uint32_t key, const String& name, const String& target)
205 : key(key), name(name), target(target) {}
206 uint32_t key;
207 String name;
208 String target;
209};
210
211String encodeAlias(const String& value) {
212 String result;
213 static constexpr char digits[] = "0123456789abcdef";
214 for (size_t i = 0; i < value.length(); ++i) {
215 const uint8_t character = value[i];
216 const bool safe = (character >= 'a' && character <= 'z') ||
217 (character >= 'A' && character <= 'Z') ||
218 (character >= '0' && character <= '9') || character == '#' ||
219 character == '+' || character == '-' || character == '.' ||
220 character == ':' || character == '=' || character == '@' || character == '_';
221 if (safe) {
222 char plain[2] = {static_cast<char>(character), 0};
223 result += plain;
224 } else {
225 char escaped[5] = {'\\', 'x', digits[character >> 4], digits[character & 0xf], 0};
226 result += escaped;
227 }
228 }
229 return result;
230}
231
232class BlockAlias final : public Symlink {
233 public:
234 BlockAlias(DevFs& filesystem, File* parent, const String& name, const String& target)
235 : Symlink(name, 0, 0, 0, filesystem.getNextInode(), &filesystem, target.length(), parent) {
236 m_sTarget = target;
237 setPermissions(FILE_UR | FILE_UW | FILE_UX | FILE_GR | FILE_GW | FILE_GX | FILE_OR | FILE_OW |
238 FILE_OX);
239 }
240};
241
242class AliasDirectory final : public DevFsDirectory {
243 public:
244 AliasDirectory(DevFs& filesystem, File* parent, const char* name, AliasKind kind)
245 : DevFsDirectory(String(name), 0, 0, 0, filesystem.getNextInode(), &filesystem, 0, parent),
246 m_Kind(kind) {
247 setPermissionsOnly(FILE_UR | FILE_UX | FILE_GR | FILE_GX | FILE_OR | FILE_OX);
248 }
249
250 protected:
251 bool cacheResolvedChildren() const override {
252 return false;
253 }
254
255 LookupStatus resolveChild(const StringView& name, File*& child) override {
256 child = nullptr;
257 Vector<AliasCandidate> candidates;
258 if (!snapshot(candidates))
259 return LookupStatus::IoError;
260 const AliasCandidate* selected = nullptr;
261 for (const auto& candidate : candidates) {
262 if (candidate.name.view() == name && (!selected || candidate.key < selected->key))
263 selected = &candidate;
264 }
265 if (!selected)
266 return LookupStatus::NotFound;
267 child = new BlockAlias(*static_cast<DevFs*>(getFilesystem()), this, selected->name,
268 selected->target);
269 return child ? LookupStatus::Found : LookupStatus::IoError;
270 }
271
272 ReadStatus readDirectory(uint64_t& cookie, DirectoryEntryEmitter emitter,
273 void* context) override {
274 Vector<AliasCandidate> candidates;
275 if (!snapshot(candidates))
276 return ReadStatus::IoError;
277 while (true) {
278 const AliasCandidate* selected = nullptr;
279 for (const auto& candidate : candidates) {
280 if (candidate.key < cookie || (selected && candidate.key >= selected->key))
281 continue;
282 bool shadowed = false;
283 for (const auto& other : candidates) {
284 if (other.key < candidate.key && other.name == candidate.name) {
285 shadowed = true;
286 break;
287 }
288 }
289 if (!shadowed)
290 selected = &candidate;
291 }
292 if (!selected)
293 return ReadStatus::Complete;
294 const DirectoryEntryView entry{selected->name.view(), selected->key, EntryType::Symlink,
295 selected->key, static_cast<uint64_t>(selected->key) + 1};
296 if (!emitter(context, entry))
297 return ReadStatus::Stopped;
298 cookie = static_cast<uint64_t>(selected->key) + 1;
299 }
300 }
301
302 private:
303 bool snapshot(Vector<AliasCandidate>& candidates) const {
305 if (!VFS::instance().snapshotDiskMounts(mounts))
306 return false;
307 for (const auto& mount : mounts) {
309 OperationBarrier::Lease filesystemOperation;
310 if (!mount.acquire(pin) || !pin.filesystem()->tryAcquireOperation(filesystemOperation)) {
311 continue;
312 }
313 Filesystem* filesystem = pin.filesystem();
314 Disk* disk = filesystem ? filesystem->getDisk() : nullptr;
315 if (!disk)
316 continue;
318 bool available = false;
319 switch (m_Kind) {
320 case AliasKind::FilesystemUuid:
321 available = filesystem->getUuid(identity);
322 break;
323 case AliasKind::FilesystemLabel:
324 identity = filesystem->getVolumeLabel();
325 available = identity.length() && !identity.startswith("no-volume-label@");
326 break;
327 case AliasKind::PartitionUuid:
328 available = disk->getPartitionUuid(identity);
329 break;
330 case AliasKind::PartitionLabel:
331 available = disk->getPartitionLabel(identity);
332 break;
333 }
334 if (!available || !identity.length())
335 continue;
336 String target;
337 target.Format("/dev/block/%u", mount.id());
338 candidates.createBack(mount.id(), encodeAlias(identity), target);
339 }
340 return true;
341 }
342
343 AliasKind m_Kind;
344};
345
346class DiskDirectory final : public DevFsDirectory {
347 public:
348 DiskDirectory(DevFs& filesystem, File* parent)
349 : DevFsDirectory(String("disk"), 0, 0, 0, filesystem.getNextInode(), &filesystem, 0, parent) {
350 setPermissionsOnly(FILE_UR | FILE_UX | FILE_GR | FILE_GX | FILE_OR | FILE_OX);
351 }
352};
353} // namespace
354File* posix_make_block_directory(DevFs& filesystem, File* parent) {
355 return new BlockDirectory(filesystem, parent);
356}
357
358File* posix_make_disk_directory(DevFs& filesystem, File* parent) {
359 auto* disk = new DiskDirectory(filesystem, parent);
360 if (!disk)
361 return nullptr;
362 struct DirectorySpec {
363 const char* name;
364 AliasKind kind;
365 } specs[] = {{"by-uuid", AliasKind::FilesystemUuid},
366 {"by-label", AliasKind::FilesystemLabel},
367 {"by-partuuid", AliasKind::PartitionUuid},
368 {"by-partlabel", AliasKind::PartitionLabel}};
369 for (const auto& spec : specs) {
370 auto* child = new AliasDirectory(filesystem, disk, spec.name, spec.kind);
371 if (!child) {
372 delete disk;
373 return nullptr;
374 }
375 disk->addEntry(child->getName(), child);
376 }
377 return disk;
378}
Definition DevFs.h:269
virtual bool cacheResolvedChildren() const
Definition Directory.h:299
virtual ReadStatus readDirectory(uint64_t &cookie, DirectoryEntryEmitter emitter, void *context)
virtual LookupStatus resolveChild(const StringView &name, File *&child)
Definition Disk.h:35
virtual bool sync(uint64_t location, bool async)
Definition Disk.cc:358
virtual bool getPartitionUuid(String &uuid) const
Definition Disk.cc:116
virtual BufferView read(uint64_t location)
Definition Disk.cc:163
virtual size_t getSize() const
Gets the size of the disk.
Definition Disk.cc:344
virtual void unpin(uint64_t location)=0
Definition File.h:75
virtual uint64_t readBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition File.cc:1369
String getName() const
Definition File.cc:782
virtual bool isBytewise() const
Definition File.cc:1365
void setGidOnly(size_t gid)
Definition File.cc:1448
void setPermissionsOnly(uint32_t perms)
Definition File.cc:1440
void setUidOnly(size_t uid)
Definition File.cc:1444
virtual uint64_t writeBytewise(uint64_t location, uint64_t size, uintptr_t buffer, bool bCanBlock=true)
Definition File.cc:1376
virtual const String & getVolumeLabel() const =0
virtual bool getUuid(String &uuid) const
Definition Filesystem.h:98
Disk * getDisk()
Definition Filesystem.h:145
static VFS & instance()
Definition VFS.cc:311
A vector / dynamic array.
Definition Vector.h:33
void createBack(Args &&... args)
Definition Vector.h:110