Skip to main content

orbis_pfs/
inode.rs

1use crate::image::Image;
2
3use snafu::{ResultExt, Snafu, ensure};
4use zerocopy::{
5    FromBytes, Immutable, KnownLayout,
6    little_endian::{U16, U32, U64},
7};
8
9/// Raw inode header (100 bytes).
10#[derive(Clone, Copy, FromBytes, KnownLayout, Immutable)]
11#[repr(C)]
12pub struct InodeRaw {
13    /// 0x00: Inode mode (file=0x8000, dir=0x4000).
14    pub mode: U16,
15    /// 0x02: Number of links.
16    pub nlink: U16,
17    /// 0x04: Flags (compressed, readonly, etc.).
18    pub flags: U32,
19    /// 0x08: Size in bytes.
20    pub size: U64,
21    /// 0x10: Compressed size (same as size for uncompressed).
22    pub size_compressed: U64,
23    /// 0x18: Access time.
24    pub atime: U64,
25    /// 0x20: Modification time.
26    pub mtime: U64,
27    /// 0x28: Change time.
28    pub ctime: U64,
29    /// 0x30: Creation time.
30    pub birthtime: U64,
31    /// 0x38: Modification time nanoseconds.
32    pub mtimensec: U32,
33    /// 0x3C: Access time nanoseconds.
34    pub atimensec: U32,
35    /// 0x40: Change time nanoseconds.
36    pub ctimensec: U32,
37    /// 0x44: Creation time nanoseconds.
38    pub birthnsec: U32,
39    /// 0x48: User ID.
40    pub uid: U32,
41    /// 0x4C: Group ID.
42    pub gid: U32,
43    /// 0x50: Reserved.
44    pub spare: [u8; 16],
45    /// 0x60: Number of blocks.
46    pub blocks: U32,
47}
48
49/// Errors when loading inode blocks.
50#[derive(Debug, Snafu)]
51#[non_exhaustive]
52pub enum LoadBlocksError {
53    #[snafu(display("cannot read block #{block}"))]
54    Read { block: u32, source: std::io::Error },
55
56    #[snafu(display("block #{block} does not exist"))]
57    NotExists { block: u32 },
58
59    #[snafu(display("double indirect block is not supported for inode #{inode}"))]
60    DoubleIndirectBlockNotSupported { inode: usize },
61}
62
63/// Contains information for an inode.
64pub struct Inode {
65    index: usize,
66    raw: InodeRaw,
67    direct_blocks: [u32; 12],
68    #[allow(dead_code)] // Reserved for future signature verification
69    direct_sigs: [Option<[u8; 32]>; 12],
70    indirect_blocks: [u32; 5],
71    #[allow(dead_code)] // Reserved for future signature verification
72    indirect_sigs: [Option<[u8; 32]>; 5],
73    /// Whether this inode uses signed (36-byte) indirect block entries.
74    /// When `false`, indirect entries are plain 4-byte block pointers.
75    signed: bool,
76}
77
78impl Inode {
79    fn blocks(&self) -> u32 {
80        self.raw.blocks.get()
81    }
82
83    /// If the inode's data blocks are contiguous, returns `(start_block, block_count)`.
84    ///
85    /// Returns `None` if the blocks are non-contiguous, require indirect lookups,
86    /// or the inode has no blocks.
87    pub(crate) fn contiguous_blocks(&self) -> Option<(u32, u32)> {
88        let count = self.blocks();
89
90        if count == 0 {
91            return None;
92        }
93
94        // Explicitly marked as contiguous.
95        if self.direct_blocks[1] == 0xffffffff {
96            return Some((self.direct_blocks[0], count));
97        }
98
99        // Single block is trivially contiguous.
100        if count == 1 {
101            return Some((self.direct_blocks[0], 1));
102        }
103
104        None
105    }
106
107    pub(super) fn from_raw32_unsigned(index: usize, src: &mut &[u8]) -> Result<Self, FromRawError> {
108        // Parse header directly from slice.
109        let (raw, rest) = InodeRaw::read_from_prefix(src).map_err(|_| FromRawError::TooSmall)?;
110        *src = rest;
111
112        // Read block pointers (12 direct + 5 indirect = 17 × 4 = 68 bytes).
113        ensure!(src.len() >= 68, from_raw_error::TooSmallSnafu);
114
115        let block_data = &src[..68];
116        *src = &src[68..];
117
118        let mut direct_blocks = [0u32; 12];
119        let mut indirect_blocks = [0u32; 5];
120
121        for (i, block) in direct_blocks.iter_mut().enumerate() {
122            let offset = i * 4;
123            *block = u32::from_le_bytes(block_data[offset..offset + 4].try_into().unwrap());
124        }
125
126        for (i, block) in indirect_blocks.iter_mut().enumerate() {
127            let offset = 48 + i * 4;
128            *block = u32::from_le_bytes(block_data[offset..offset + 4].try_into().unwrap());
129        }
130
131        Ok(Self {
132            index,
133            raw,
134            direct_blocks,
135            direct_sigs: [None; 12],
136            indirect_blocks,
137            indirect_sigs: [None; 5],
138            signed: false,
139        })
140    }
141
142    pub(super) fn from_raw32_signed(index: usize, src: &mut &[u8]) -> Result<Self, FromRawError> {
143        // Parse header directly from slice.
144        let (raw, rest) = InodeRaw::read_from_prefix(src).map_err(|_| FromRawError::TooSmall)?;
145        *src = rest;
146
147        // Read block pointers with signatures.
148        // 12 direct: 12 × (32 sig + 4 ptr) = 432 bytes
149        // 5 indirect: 5 × (32 sig + 4 ptr) = 180 bytes
150        // Total: 612 bytes
151        ensure!(src.len() >= 612, from_raw_error::TooSmallSnafu);
152
153        let block_data = &src[..612];
154        *src = &src[612..];
155
156        let mut direct_blocks = [0u32; 12];
157        let mut direct_sigs: [Option<[u8; 32]>; 12] = [None; 12];
158        let mut indirect_blocks = [0u32; 5];
159        let mut indirect_sigs: [Option<[u8; 32]>; 5] = [None; 5];
160
161        let mut offset = 0;
162        for (sig, block) in direct_sigs.iter_mut().zip(direct_blocks.iter_mut()) {
163            *sig = Some(block_data[offset..offset + 32].try_into().unwrap());
164            *block = u32::from_le_bytes(block_data[offset + 32..offset + 36].try_into().unwrap());
165            offset += 36;
166        }
167
168        for (sig, block) in indirect_sigs.iter_mut().zip(indirect_blocks.iter_mut()) {
169            *sig = Some(block_data[offset..offset + 32].try_into().unwrap());
170            *block = u32::from_le_bytes(block_data[offset + 32..offset + 36].try_into().unwrap());
171            offset += 36;
172        }
173
174        Ok(Self {
175            index,
176            raw,
177            direct_blocks,
178            direct_sigs,
179            indirect_blocks,
180            indirect_sigs,
181            signed: true,
182        })
183    }
184
185    /// Loads the block map for this inode using positional reads.
186    ///
187    /// Returns a vector mapping logical block index -> physical block number.
188    pub fn load_block_map(
189        &self,
190        image: &dyn Image,
191        block_size: u32,
192    ) -> Result<Vec<u32>, LoadBlocksError> {
193        let block_count = self.blocks() as usize;
194        let mut blocks: Vec<u32> = Vec::with_capacity(block_count);
195
196        if block_count == 0 {
197            return Ok(blocks);
198        }
199
200        // Check if inode uses contiguous blocks.
201        if self.direct_blocks[1] == 0xffffffff {
202            let start = self.direct_blocks[0];
203            for block in start..(start + self.blocks()) {
204                blocks.push(block);
205            }
206            return Ok(blocks);
207        }
208
209        // Load direct pointers.
210        for i in 0..12 {
211            blocks.push(self.direct_blocks[i]);
212            if blocks.len() == block_count {
213                return Ok(blocks);
214            }
215        }
216
217        let bs = block_size as u64;
218
219        // Load indirect 0.
220        let block_num = self.indirect_blocks[0];
221        let offset = (block_num as u64) * bs;
222
223        let mut block0 = vec![0; block_size as usize];
224
225        image
226            .read_exact_at(offset, &mut block0)
227            .context(ReadSnafu { block: block_num })?;
228
229        let mut data = block0.as_slice();
230
231        while let Some(i) = self.read_indirect(&mut data) {
232            blocks.push(i);
233            if blocks.len() == block_count {
234                return Ok(blocks);
235            }
236        }
237
238        // Load indirect 1 (double indirect).
239        let block_num = self.indirect_blocks[1];
240        let offset = (block_num as u64) * bs;
241
242        image
243            .read_exact_at(offset, &mut block0)
244            .context(ReadSnafu { block: block_num })?;
245
246        let mut block1 = vec![0; block_size as usize];
247        let mut data0 = block0.as_slice();
248
249        while let Some(i) = self.read_indirect(&mut data0) {
250            let offset = (i as u64) * bs;
251
252            image
253                .read_exact_at(offset, &mut block1)
254                .context(ReadSnafu { block: block_num })?;
255
256            let mut data1 = block1.as_slice();
257
258            while let Some(j) = self.read_indirect(&mut data1) {
259                blocks.push(j);
260                if blocks.len() == block_count {
261                    return Ok(blocks);
262                }
263            }
264        }
265
266        DoubleIndirectBlockNotSupportedSnafu { inode: self.index }.fail()
267    }
268
269    /// Reads one indirect block pointer from `raw`, advancing past the entry.
270    ///
271    /// For unsigned inodes the entry is a plain 4-byte LE u32.
272    /// For signed inodes the entry is a 32-byte signature followed by a 4-byte LE u32.
273    fn read_indirect(&self, raw: &mut &[u8]) -> Option<u32> {
274        let (entry_size, value_offset) = if self.signed { (36, 32) } else { (4, 0) };
275
276        if raw.len() < entry_size {
277            return None;
278        }
279
280        let value = u32::from_le_bytes(raw[value_offset..value_offset + 4].try_into().unwrap());
281        *raw = &raw[entry_size..];
282        Some(value)
283    }
284
285    pub fn mode(&self) -> u16 {
286        self.raw.mode.get()
287    }
288
289    pub fn flags(&self) -> InodeFlags {
290        InodeFlags(self.raw.flags.get())
291    }
292
293    pub fn size(&self) -> u64 {
294        self.raw.size.get()
295    }
296
297    pub fn compressed_len(&self) -> u64 {
298        self.raw.size_compressed.get()
299    }
300
301    pub fn atime(&self) -> u64 {
302        self.raw.atime.get()
303    }
304
305    pub fn mtime(&self) -> u64 {
306        self.raw.mtime.get()
307    }
308
309    pub fn ctime(&self) -> u64 {
310        self.raw.ctime.get()
311    }
312
313    pub fn birthtime(&self) -> u64 {
314        self.raw.birthtime.get()
315    }
316
317    pub fn mtimensec(&self) -> u32 {
318        self.raw.mtimensec.get()
319    }
320
321    pub fn atimensec(&self) -> u32 {
322        self.raw.atimensec.get()
323    }
324
325    pub fn ctimensec(&self) -> u32 {
326        self.raw.ctimensec.get()
327    }
328
329    pub fn birthnsec(&self) -> u32 {
330        self.raw.birthnsec.get()
331    }
332
333    pub fn uid(&self) -> u32 {
334        self.raw.uid.get()
335    }
336
337    pub fn gid(&self) -> u32 {
338        self.raw.gid.get()
339    }
340
341    pub const fn raw(&self) -> &InodeRaw {
342        &self.raw
343    }
344}
345
346/// Flags of the inode.
347#[derive(Clone, Copy, Debug)]
348#[repr(transparent)]
349pub struct InodeFlags(u32);
350
351impl InodeFlags {
352    pub fn is_compressed(self) -> bool {
353        self.0 & 0x00000001 != 0
354    }
355
356    pub fn value(self) -> u32 {
357        self.0
358    }
359}
360
361/// Errors when parsing an inode from raw bytes.
362#[derive(Debug, Snafu)]
363#[snafu(module)]
364#[non_exhaustive]
365pub enum FromRawError {
366    /// I/O operation failed.
367    #[snafu(display("i/o failed"))]
368    IoFailed { source: std::io::Error },
369    /// Input data was too small.
370    #[snafu(display("data too small"))]
371    TooSmall,
372}