Nothing to see here, move along meow
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 16 kB View raw
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}