Serialise `entries` into the on-disk filesystem image format and return the raw bytes. See _`OS_SPECIFICATION.md` for block layout. Layout constants live in [`fs_layout`] and are checked against `fs.hll`.
(entries: &[FsEntry<'_>])
| 1476 | |
| 1477 | let inode_offset = |idx: usize| -> usize { BLOCK_SIZE + idx * INODE_SIZE }; |
| 1478 | let block_offset = |blk: usize| -> usize { blk * BLOCK_SIZE }; |
| 1479 | |
| 1480 | let write_u16 = |buf: &mut Vec<u8>, off: usize, val: u16| { |
| 1481 | buf[off] = (val & 0xFF) as u8; |
| 1482 | buf[off + 1] = (val >> 8) as u8; |
| 1483 | }; |
| 1484 | let write_u32 = |buf: &mut Vec<u8>, off: usize, val: u32| { |
| 1485 | buf[off] = (val & 0xFF) as u8; |
| 1486 | buf[off + 1] = ((val >> 8) & 0xFF) as u8; |
| 1487 | buf[off + 2] = ((val >> 16) & 0xFF) as u8; |
| 1488 | buf[off + 3] = ((val >> 24) & 0xFF) as u8; |
| 1489 | }; |
| 1490 | let write_u64 = |buf: &mut Vec<u8>, off: usize, val: u64| { |
| 1491 | for i in 0..8usize { |
| 1492 | buf[off + i] = ((val >> (i * 8)) & 0xFF) as u8; |
| 1493 | } |
| 1494 | }; |
| 1495 | |
| 1496 | // next_inode and next_data_block are mutable state we thread through. |
| 1497 | let mut next_inode: usize = 1; // 0 = root |
| 1498 | let mut next_data_block: usize = 0; // relative to DATA_BLOCK_START |
| 1499 | |
| 1500 | // Allocate a data block and return its absolute block index. |
| 1501 | let alloc_block = |next: &mut usize| -> usize { |
| 1502 | let blk = DATA_BLOCK_START + *next; |
| 1503 | *next += 1; |
| 1504 | blk |
| 1505 | }; |
| 1506 | |
| 1507 | // Allocate an inode index. |
| 1508 | let alloc_inode = |next: &mut usize| -> usize { |
| 1509 | let idx = *next; |
| 1510 | *next += 1; |
| 1511 | idx |
| 1512 | }; |
| 1513 | |
| 1514 | // Write a name into a 32-byte field at buf[off..off+32]. |
| 1515 | let write_name = |buf: &mut Vec<u8>, off: usize, name: &str| { |
| 1516 | let bytes = name.as_bytes(); |
| 1517 | let len = bytes.len().min(31); |
| 1518 | buf[off..off + len].copy_from_slice(&bytes[..len]); |
| 1519 | buf[off + len] = 0; |
| 1520 | }; |
| 1521 | |
| 1522 | // Root directory (inode 0) |
| 1523 | let root_data_blk = alloc_block(&mut next_data_block); |
| 1524 | { |
| 1525 | let off = inode_offset(0); |
| 1526 | image[off + IN_TYPE] = 2; // directory |
| 1527 | write_u16(&mut image, off + IN_PARENT, 0); // root's parent = self |
| 1528 | write_u32(&mut image, off + IN_SIZE, 0); // entry count updated later |
| 1529 | write_name(&mut image, off + IN_NAME, "/"); |
| 1530 | write_u16(&mut image, off + IN_BLOCKS, root_data_blk as u16); |
| 1531 | } |
| 1532 | |
| 1533 | // Add a DirEntry to a directory inode's data block |
| 1534 | // Returns false if block is full (not handled for simplicity; 113 entries per block). |
| 1535 | let add_dirent = |image: &mut Vec<u8>, dir_inode: usize, child_inode: usize, name: &str| { |