Skip to main content

luci/storage/
block.rs

1/// Size of the file header in bytes: 4 KB.
2///
3/// The header is page-aligned and sized to fit within a single disk sector,
4/// enabling sector-atomic writes for crash-safe commit. Blocks start
5/// immediately after the header at this byte offset.
6///
7/// See [[architecture-storage-format#Atomic Commit Protocol]].
8pub const HEADER_SIZE: u32 = 4096;
9
10/// Block size in bytes: 256 KB.
11///
12/// Fixed for the format — stored in the file header for self-description but
13/// not configurable at runtime. Balances manageable segment block counts
14/// against free-list fragmentation, and aligns to OS page sizes and I/O
15/// boundaries.
16///
17/// See [[architecture-storage-format#Block Size]].
18pub const BLOCK_SIZE: u32 = 256 * 1024;
19
20/// Identifies a block by its zero-based index within the data region.
21///
22/// Block 0 is the first data block, starting at byte offset `HEADER_SIZE`.
23/// The byte offset of block N is `HEADER_SIZE + N * BLOCK_SIZE`.
24///
25/// See [[architecture-storage-format#Block Allocator]].
26#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
27pub struct BlockId(pub u64);
28
29impl BlockId {
30    pub const fn new(id: u64) -> Self {
31        Self(id)
32    }
33
34    pub const fn as_u64(self) -> u64 {
35        self.0
36    }
37
38    /// Byte offset of this block within the file.
39    ///
40    /// Accounts for the 4 KB header that precedes the block region.
41    pub const fn byte_offset(self) -> u64 {
42        HEADER_SIZE as u64 + self.0 * BLOCK_SIZE as u64
43    }
44}
45
46impl std::fmt::Display for BlockId {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        write!(f, "block:{}", self.0)
49    }
50}
51
52/// A contiguous range of blocks: `[start, start + count)`.
53///
54/// Extents are the unit of allocation and free-list tracking. Each segment's
55/// data is stored as one or more extents in the segment directory.
56///
57/// See [[architecture-storage-format#Extent Tracking]].
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
59pub struct Extent {
60    /// First block in the range.
61    pub start: BlockId,
62    /// Number of contiguous blocks.
63    pub count: u32,
64}
65
66impl Extent {
67    pub const fn new(start: BlockId, count: u32) -> Self {
68        Self { start, count }
69    }
70
71    /// One past the last block in the extent.
72    pub const fn end(&self) -> BlockId {
73        BlockId(self.start.0 + self.count as u64)
74    }
75
76    /// Total bytes covered by this extent.
77    pub const fn byte_len(&self) -> u64 {
78        self.count as u64 * BLOCK_SIZE as u64
79    }
80
81    /// Whether this extent is immediately followed by `other`.
82    pub const fn is_adjacent_to(&self, other: &Extent) -> bool {
83        self.end().0 == other.start.0
84    }
85}
86
87impl std::fmt::Display for Extent {
88    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89        write!(
90            f,
91            "blocks:[{}..{})",
92            self.start.0,
93            self.start.0 + self.count as u64
94        )
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn block_id_byte_offset() {
104        // Block 0 starts right after the 4 KB header.
105        assert_eq!(BlockId(0).byte_offset(), 4096);
106        assert_eq!(BlockId(1).byte_offset(), 4096 + 256 * 1024);
107        assert_eq!(BlockId(4).byte_offset(), 4096 + 4 * 256 * 1024);
108    }
109
110    #[test]
111    fn block_id_display() {
112        assert_eq!(format!("{}", BlockId(42)), "block:42");
113    }
114
115    #[test]
116    fn extent_end() {
117        let ext = Extent::new(BlockId(10), 5);
118        assert_eq!(ext.end(), BlockId(15));
119    }
120
121    #[test]
122    fn extent_byte_len() {
123        let ext = Extent::new(BlockId(0), 4);
124        assert_eq!(ext.byte_len(), 4 * 256 * 1024);
125    }
126
127    #[test]
128    fn extent_adjacency() {
129        let a = Extent::new(BlockId(10), 5);
130        let b = Extent::new(BlockId(15), 3);
131        let c = Extent::new(BlockId(20), 2);
132        assert!(a.is_adjacent_to(&b));
133        assert!(!a.is_adjacent_to(&c));
134        assert!(b.is_adjacent_to(&Extent::new(BlockId(18), 2)));
135        assert!(!b.is_adjacent_to(&c));
136    }
137
138    #[test]
139    fn extent_display() {
140        let ext = Extent::new(BlockId(3), 7);
141        assert_eq!(format!("{ext}"), "blocks:[3..10)");
142    }
143
144    #[test]
145    fn block_size_is_256kb() {
146        assert_eq!(BLOCK_SIZE, 262_144);
147    }
148
149    #[test]
150    fn header_size_is_4kb() {
151        assert_eq!(HEADER_SIZE, 4096);
152    }
153}