Nothing to see here, move along meow
1#![no_std]
2
3pub mod handle;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6#[repr(u8)]
7pub enum VfsOpcode {
8 Open = 1,
9 Read = 2,
10 Write = 3,
11 Close = 4,
12 Stat = 5,
13 Mkdir = 6,
14 Unlink = 7,
15 ReadDir = 8,
16 Sync = 11,
17 StatFs = 12,
18 Rename = 14,
19 Truncate = 15,
20 MountInfo = 16,
21 Extended = 17,
22}
23
24impl VfsOpcode {
25 pub const fn from_u8(v: u8) -> Option<Self> {
26 match v {
27 1 => Some(Self::Open),
28 2 => Some(Self::Read),
29 3 => Some(Self::Write),
30 4 => Some(Self::Close),
31 5 => Some(Self::Stat),
32 6 => Some(Self::Mkdir),
33 7 => Some(Self::Unlink),
34 8 => Some(Self::ReadDir),
35 11 => Some(Self::Sync),
36 12 => Some(Self::StatFs),
37 14 => Some(Self::Rename),
38 15 => Some(Self::Truncate),
39 16 => Some(Self::MountInfo),
40 17 => Some(Self::Extended),
41 _ => None,
42 }
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47#[repr(u8)]
48pub enum FsType {
49 LancerFS = 0,
50 RamFS = 1,
51 Generic = 2,
52}
53
54impl FsType {
55 pub const fn from_u8(v: u8) -> Option<Self> {
56 match v {
57 0 => Some(Self::LancerFS),
58 1 => Some(Self::RamFS),
59 2 => Some(Self::Generic),
60 _ => None,
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub struct FsRights(u8);
67
68impl FsRights {
69 pub const READ: Self = Self(0x01);
70 pub const WRITE: Self = Self(0x02);
71 pub const CREATE: Self = Self(0x04);
72 pub const DELETE: Self = Self(0x08);
73 pub const LIST: Self = Self(0x10);
74 pub const TRAVERSE: Self = Self(0x20);
75 pub const SNAPSHOT: Self = Self(0x40);
76
77 pub const ALL: Self = Self(0x7F);
78 pub const EMPTY: Self = Self(0);
79
80 pub const TRAVERSE_INHERITABLE: Self =
81 Self(Self::READ.0 | Self::WRITE.0 | Self::LIST.0 | Self::TRAVERSE.0);
82
83 pub const fn raw(self) -> u8 {
84 self.0
85 }
86
87 pub const fn from_raw(v: u8) -> Self {
88 Self(v)
89 }
90
91 pub const fn contains(self, other: Self) -> bool {
92 self.0 & other.0 == other.0
93 }
94
95 pub const fn restrict(self, mask: Self) -> Self {
96 Self(self.0 & mask.0)
97 }
98
99 pub const fn is_empty(self) -> bool {
100 self.0 == 0
101 }
102}
103
104pub const VFS_STATUS_OK: u8 = 0;
105pub const VFS_STATUS_NOT_FOUND: u8 = 1;
106pub const VFS_STATUS_PERMISSION_DENIED: u8 = 2;
107pub const VFS_STATUS_INVALID_HANDLE: u8 = 3;
108pub const VFS_STATUS_HANDLE_TABLE_FULL: u8 = 4;
109pub const VFS_STATUS_IO_ERROR: u8 = 5;
110pub const VFS_STATUS_DISK_FULL: u8 = 6;
111pub const VFS_STATUS_NOT_A_DIRECTORY: u8 = 7;
112pub const VFS_STATUS_NOT_A_FILE: u8 = 8;
113pub const VFS_STATUS_IS_A_DIRECTORY: u8 = 9;
114pub const VFS_STATUS_DIR_NOT_EMPTY: u8 = 10;
115pub const VFS_STATUS_FILE_EXISTS: u8 = 11;
116pub const VFS_STATUS_NAME_TOO_LONG: u8 = 12;
117pub const VFS_STATUS_PATH_TOO_DEEP: u8 = 13;
118pub const VFS_STATUS_INTEGRITY_ERROR: u8 = 14;
119pub const VFS_STATUS_STALE_CAP: u8 = 15;
120pub const VFS_STATUS_INVALID_OP: u8 = 16;
121pub const VFS_STATUS_SYMLINK_DEPTH: u8 = 17;
122pub const VFS_STATUS_CORRUPT: u8 = 18;
123pub const VFS_STATUS_UNSUPPORTED: u8 = 19;
124pub const VFS_STATUS_CROSS_DEVICE: u8 = 20;
125pub const VFS_STATUS_UNKNOWN: u8 = 255;
126
127pub const EXT_REGISTER_CLIENT: u8 = 1;
128pub const EXT_UNREGISTER_CLIENT: u8 = 2;
129
130#[derive(Debug, Clone, Copy)]
131#[repr(C)]
132pub struct VfsRequest {
133 pub opcode: u8,
134 pub handle: u8,
135 pub flags: u8,
136 pub _pad: u8,
137 pub tag: u32,
138 pub arg0: u64,
139 pub arg1: u64,
140 pub arg2: u64,
141}
142
143const _: () = assert!(core::mem::size_of::<VfsRequest>() == 32);
144
145impl VfsRequest {
146 pub const SIZE: usize = 32;
147
148 pub fn from_bytes(buf: &[u8]) -> Option<Self> {
149 match buf.len() >= Self::SIZE {
150 true => Some(unsafe { core::ptr::read_unaligned(buf.as_ptr() as *const Self) }),
151 false => None,
152 }
153 }
154
155 pub fn as_bytes(&self) -> &[u8] {
156 unsafe { core::slice::from_raw_parts(self as *const Self as *const u8, Self::SIZE) }
157 }
158
159 pub const fn opcode_enum(&self) -> Option<VfsOpcode> {
160 VfsOpcode::from_u8(self.opcode)
161 }
162
163 pub fn open(tag: u32, dir_handle: u8, mode: FsRights, name_offset: u64, name_len: u64) -> Self {
164 Self {
165 opcode: VfsOpcode::Open as u8,
166 handle: dir_handle,
167 flags: mode.raw(),
168 _pad: 0,
169 tag,
170 arg0: name_offset,
171 arg1: name_len,
172 arg2: 0,
173 }
174 }
175
176 pub fn close(tag: u32, handle: u8) -> Self {
177 Self {
178 opcode: VfsOpcode::Close as u8,
179 handle,
180 flags: 0,
181 _pad: 0,
182 tag,
183 arg0: 0,
184 arg1: 0,
185 arg2: 0,
186 }
187 }
188
189 pub fn read(tag: u32, handle: u8, offset: u64, len: u32, buf_offset: u32) -> Self {
190 Self {
191 opcode: VfsOpcode::Read as u8,
192 handle,
193 flags: 0,
194 _pad: 0,
195 tag,
196 arg0: offset,
197 arg1: len as u64,
198 arg2: buf_offset as u64,
199 }
200 }
201
202 pub fn write(tag: u32, handle: u8, offset: u64, len: u32, buf_offset: u32) -> Self {
203 Self {
204 opcode: VfsOpcode::Write as u8,
205 handle,
206 flags: 0,
207 _pad: 0,
208 tag,
209 arg0: offset,
210 arg1: len as u64,
211 arg2: buf_offset as u64,
212 }
213 }
214
215 pub fn stat(tag: u32, handle: u8) -> Self {
216 Self {
217 opcode: VfsOpcode::Stat as u8,
218 handle,
219 flags: 0,
220 _pad: 0,
221 tag,
222 arg0: 0,
223 arg1: 0,
224 arg2: 0,
225 }
226 }
227
228 pub fn mkdir(tag: u32, dir_handle: u8, name_offset: u64, name_len: u64) -> Self {
229 Self {
230 opcode: VfsOpcode::Mkdir as u8,
231 handle: dir_handle,
232 flags: 0,
233 _pad: 0,
234 tag,
235 arg0: name_offset,
236 arg1: name_len,
237 arg2: 0,
238 }
239 }
240
241 pub fn unlink(tag: u32, dir_handle: u8, name_offset: u64, name_len: u64) -> Self {
242 Self {
243 opcode: VfsOpcode::Unlink as u8,
244 handle: dir_handle,
245 flags: 0,
246 _pad: 0,
247 tag,
248 arg0: name_offset,
249 arg1: name_len,
250 arg2: 0,
251 }
252 }
253
254 pub fn readdir(tag: u32, dir_handle: u8, cursor: u64, buf_offset: u64, buf_len: u64) -> Self {
255 Self {
256 opcode: VfsOpcode::ReadDir as u8,
257 handle: dir_handle,
258 flags: 0,
259 _pad: 0,
260 tag,
261 arg0: cursor,
262 arg1: buf_offset,
263 arg2: buf_len,
264 }
265 }
266
267 pub fn rename(
268 tag: u32,
269 src_dir_handle: u8,
270 dst_dir_handle: u8,
271 src_name_offset: u32,
272 src_name_len: u32,
273 dst_name_offset: u32,
274 dst_name_len: u32,
275 ) -> Self {
276 Self {
277 opcode: VfsOpcode::Rename as u8,
278 handle: src_dir_handle,
279 flags: dst_dir_handle,
280 _pad: 0,
281 tag,
282 arg0: (src_name_offset as u64) | ((src_name_len as u64) << 32),
283 arg1: (dst_name_offset as u64) | ((dst_name_len as u64) << 32),
284 arg2: 0,
285 }
286 }
287
288 pub const fn rename_dst_dir_handle(&self) -> u8 {
289 self.flags
290 }
291
292 pub const fn rename_src_name_offset(&self) -> u32 {
293 self.arg0 as u32
294 }
295
296 pub const fn rename_src_name_len(&self) -> u32 {
297 (self.arg0 >> 32) as u32
298 }
299
300 pub const fn rename_dst_name_offset(&self) -> u32 {
301 self.arg1 as u32
302 }
303
304 pub const fn rename_dst_name_len(&self) -> u32 {
305 (self.arg1 >> 32) as u32
306 }
307
308 pub fn truncate(tag: u32, handle: u8, new_size: u64) -> Self {
309 Self {
310 opcode: VfsOpcode::Truncate as u8,
311 handle,
312 flags: 0,
313 _pad: 0,
314 tag,
315 arg0: new_size,
316 arg1: 0,
317 arg2: 0,
318 }
319 }
320
321 pub fn sync(tag: u32) -> Self {
322 Self {
323 opcode: VfsOpcode::Sync as u8,
324 handle: 0,
325 flags: 0,
326 _pad: 0,
327 tag,
328 arg0: 0,
329 arg1: 0,
330 arg2: 0,
331 }
332 }
333
334 pub fn mount_info(tag: u32) -> Self {
335 Self {
336 opcode: VfsOpcode::MountInfo as u8,
337 handle: 0,
338 flags: 0,
339 _pad: 0,
340 tag,
341 arg0: 0,
342 arg1: 0,
343 arg2: 0,
344 }
345 }
346
347 pub fn extended(tag: u32, fs_type: FsType, arg0: u64, arg1: u64, arg2: u64) -> Self {
348 Self {
349 opcode: VfsOpcode::Extended as u8,
350 handle: 0,
351 flags: fs_type as u8,
352 _pad: 0,
353 tag,
354 arg0,
355 arg1,
356 arg2,
357 }
358 }
359}
360
361#[derive(Debug, Clone, Copy)]
362#[repr(C)]
363pub struct VfsResponse {
364 pub opcode: u8,
365 pub status: u8,
366 pub _pad: [u8; 2],
367 pub tag: u32,
368 pub val0: u64,
369 pub val1: u64,
370}
371
372const _: () = assert!(core::mem::size_of::<VfsResponse>() == 24);
373
374impl VfsResponse {
375 pub const SIZE: usize = 24;
376
377 pub fn from_bytes(buf: &[u8]) -> Option<Self> {
378 match buf.len() >= Self::SIZE {
379 true => Some(unsafe { core::ptr::read_unaligned(buf.as_ptr() as *const Self) }),
380 false => None,
381 }
382 }
383
384 pub fn as_bytes(&self) -> &[u8] {
385 unsafe { core::slice::from_raw_parts(self as *const Self as *const u8, Self::SIZE) }
386 }
387
388 pub fn success(opcode: u8, tag: u32, val0: u64, val1: u64) -> Self {
389 Self {
390 opcode,
391 status: VFS_STATUS_OK,
392 _pad: [0; 2],
393 tag,
394 val0,
395 val1,
396 }
397 }
398
399 pub fn error(opcode: u8, tag: u32, status: u8) -> Self {
400 Self {
401 opcode,
402 status,
403 _pad: [0; 2],
404 tag,
405 val0: 0,
406 val1: 0,
407 }
408 }
409
410 pub const fn is_ok(&self) -> bool {
411 self.status == VFS_STATUS_OK
412 }
413}
414
415pub const READDIR_MAX_NAME: usize = 48;
416pub const READDIR_ENTRY_SIZE: usize = 72;
417
418#[derive(Debug, Clone, Copy)]
419#[repr(C)]
420pub struct ReadDirEntry {
421 pub object_id: u64,
422 pub size: u64,
423 pub name_len: u16,
424 pub inode_type: u8,
425 pub _pad: u8,
426 pub name: [u8; READDIR_MAX_NAME],
427 pub _pad2: [u8; 4],
428}
429
430const _: () = assert!(core::mem::size_of::<ReadDirEntry>() == READDIR_ENTRY_SIZE);
431
432impl ReadDirEntry {
433 pub fn from_bytes(buf: &[u8]) -> Option<Self> {
434 match buf.len() >= READDIR_ENTRY_SIZE {
435 true => Some(unsafe { core::ptr::read_unaligned(buf.as_ptr() as *const Self) }),
436 false => None,
437 }
438 }
439
440 pub fn as_bytes(&self) -> &[u8] {
441 unsafe { core::slice::from_raw_parts(self as *const Self as *const u8, READDIR_ENTRY_SIZE) }
442 }
443
444 pub fn name_slice(&self) -> &[u8] {
445 let len = (self.name_len as usize).min(self.name.len());
446 &self.name[..len]
447 }
448}
449
450pub const MOUNT_INFO_ENTRY_SIZE: usize = 88;
451
452#[derive(Debug, Clone, Copy)]
453#[repr(C)]
454pub struct MountInfoEntry {
455 pub prefix: [u8; 64],
456 pub prefix_len: u8,
457 pub fs_type: u8,
458 pub _pad: [u8; 6],
459 pub total_blocks: u64,
460 pub free_blocks: u64,
461}
462
463const _: () = assert!(core::mem::size_of::<MountInfoEntry>() == MOUNT_INFO_ENTRY_SIZE);
464
465impl MountInfoEntry {
466 pub fn from_bytes(buf: &[u8]) -> Option<Self> {
467 match buf.len() >= MOUNT_INFO_ENTRY_SIZE {
468 true => Some(unsafe { core::ptr::read_unaligned(buf.as_ptr() as *const Self) }),
469 false => None,
470 }
471 }
472
473 pub fn as_bytes(&self) -> &[u8] {
474 unsafe {
475 core::slice::from_raw_parts(self as *const Self as *const u8, MOUNT_INFO_ENTRY_SIZE)
476 }
477 }
478
479 pub fn prefix_slice(&self) -> &[u8] {
480 let len = (self.prefix_len as usize).min(self.prefix.len());
481 &self.prefix[..len]
482 }
483}
484
485#[cfg(test)]
486mod tests {
487 use super::*;
488
489 #[test]
490 fn request_roundtrip() {
491 let req = VfsRequest::open(42, 0, FsRights::READ, 0, 5);
492 let bytes = req.as_bytes();
493 let req2 = VfsRequest::from_bytes(bytes).unwrap();
494 assert_eq!(req2.opcode, VfsOpcode::Open as u8);
495 assert_eq!(req2.tag, 42);
496 assert_eq!(req2.handle, 0);
497 assert_eq!(req2.flags, FsRights::READ.raw());
498 }
499
500 #[test]
501 fn response_roundtrip() {
502 let resp = VfsResponse::success(VfsOpcode::Read as u8, 7, 1024, 0);
503 let bytes = resp.as_bytes();
504 let resp2 = VfsResponse::from_bytes(bytes).unwrap();
505 assert_eq!(resp2.tag, 7);
506 assert_eq!(resp2.val0, 1024);
507 assert!(resp2.is_ok());
508 }
509
510 #[test]
511 fn response_error_status() {
512 let resp = VfsResponse::error(VfsOpcode::Open as u8, 5, VFS_STATUS_NOT_FOUND);
513 assert!(!resp.is_ok());
514 assert_eq!(resp.status, VFS_STATUS_NOT_FOUND);
515 }
516
517 #[test]
518 fn rename_field_packing() {
519 let req = VfsRequest::rename(1, 2, 3, 100, 5, 200, 8);
520 assert_eq!(req.handle, 2);
521 assert_eq!(req.rename_dst_dir_handle(), 3);
522 assert_eq!(req.rename_src_name_offset(), 100);
523 assert_eq!(req.rename_src_name_len(), 5);
524 assert_eq!(req.rename_dst_name_offset(), 200);
525 assert_eq!(req.rename_dst_name_len(), 8);
526 }
527
528 #[test]
529 fn opcode_roundtrip() {
530 assert_eq!(VfsOpcode::from_u8(1), Some(VfsOpcode::Open));
531 assert_eq!(VfsOpcode::from_u8(2), Some(VfsOpcode::Read));
532 assert_eq!(VfsOpcode::from_u8(3), Some(VfsOpcode::Write));
533 assert_eq!(VfsOpcode::from_u8(4), Some(VfsOpcode::Close));
534 assert_eq!(VfsOpcode::from_u8(12), Some(VfsOpcode::StatFs));
535 assert_eq!(VfsOpcode::from_u8(14), Some(VfsOpcode::Rename));
536 assert_eq!(VfsOpcode::from_u8(17), Some(VfsOpcode::Extended));
537 assert_eq!(VfsOpcode::from_u8(0), None);
538 assert_eq!(VfsOpcode::from_u8(9), None);
539 assert_eq!(VfsOpcode::from_u8(10), None);
540 assert_eq!(VfsOpcode::from_u8(13), None);
541 assert_eq!(VfsOpcode::from_u8(18), None);
542 }
543
544 #[test]
545 fn fs_rights_operations() {
546 let rw = FsRights::from_raw(FsRights::READ.raw() | FsRights::WRITE.raw());
547 assert!(rw.contains(FsRights::READ));
548 assert!(rw.contains(FsRights::WRITE));
549 assert!(!rw.contains(FsRights::CREATE));
550 let restricted = FsRights::ALL.restrict(rw);
551 assert_eq!(restricted.raw(), rw.raw());
552 }
553
554 #[test]
555 fn fs_type_roundtrip() {
556 assert_eq!(FsType::from_u8(0), Some(FsType::LancerFS));
557 assert_eq!(FsType::from_u8(1), Some(FsType::RamFS));
558 assert_eq!(FsType::from_u8(2), Some(FsType::Generic));
559 assert_eq!(FsType::from_u8(3), None);
560 }
561
562 #[test]
563 fn readdir_entry_name_slice() {
564 let mut entry = ReadDirEntry {
565 object_id: 1,
566 size: 42,
567 name_len: 5,
568 inode_type: 0,
569 _pad: 0,
570 name: [0; READDIR_MAX_NAME],
571 _pad2: [0; 4],
572 };
573 entry.name[..5].copy_from_slice(b"hello");
574 assert_eq!(entry.name_slice(), b"hello");
575 }
576
577 #[test]
578 fn request_from_short_buffer_fails() {
579 let buf = [0u8; 16];
580 assert!(VfsRequest::from_bytes(&buf).is_none());
581 }
582
583 #[test]
584 fn response_from_short_buffer_fails() {
585 let buf = [0u8; 8];
586 assert!(VfsResponse::from_bytes(&buf).is_none());
587 }
588
589 #[test]
590 fn truncate_carries_new_size() {
591 let req = VfsRequest::truncate(10, 3, 4096);
592 assert_eq!(req.opcode, VfsOpcode::Truncate as u8);
593 assert_eq!(req.handle, 3);
594 assert_eq!(req.arg0, 4096);
595 }
596
597 #[test]
598 fn extended_carries_fs_type() {
599 let req = VfsRequest::extended(1, FsType::LancerFS, 100, 200, 300);
600 assert_eq!(req.opcode, VfsOpcode::Extended as u8);
601 assert_eq!(req.flags, FsType::LancerFS as u8);
602 assert_eq!(req.arg0, 100);
603 assert_eq!(req.arg1, 200);
604 assert_eq!(req.arg2, 300);
605 }
606}