Skip to main content

f2fs_reader/
inode.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4use crate::crypto;
5use crate::dir::InlineDentry;
6use crate::reader::{NEW_ADDR, NULL_ADDR, Reader};
7use crate::superblock::BLOCK_SIZE;
8use crate::xattr::{XattrEntry, decode_xattr};
9use anyhow::{Error, anyhow, ensure};
10use bitflags::bitflags;
11use std::collections::HashMap;
12use std::fmt::Debug;
13use storage_device::buffer::Buffer;
14use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Ref, Unaligned};
15
16const NAME_MAX: usize = 255;
17// The number of addresses that fit in an Inode block with a header and footer.
18const INODE_BLOCK_MAX_ADDR: usize = 923;
19// Hard coded constant from layout.h -- Number of 32-bit addresses that fit in an address block.
20const ADDR_BLOCK_NUM_ADDR: u32 = 1018;
21
22/// F2fs supports an extent tree and cached the largest extent for the file here.
23/// (We don't make use of this.)
24#[repr(C, packed)]
25#[derive(Copy, Clone, Debug, Immutable, KnownLayout, FromBytes, IntoBytes, Unaligned)]
26pub struct Extent {
27    file_offset: u32,
28    block_address: u32,
29    len: u32,
30}
31
32#[derive(Copy, Clone, Debug, Immutable, FromBytes, IntoBytes)]
33pub struct Mode(u16);
34bitflags! {
35    impl Mode: u16 {
36        const RegularFile = 0o100000;
37        const Directory = 0o040000;
38    }
39}
40
41#[derive(Copy, Clone, Debug, Immutable, FromBytes, IntoBytes)]
42pub struct AdviseFlags(u8);
43bitflags! {
44    impl AdviseFlags: u8 {
45        const Encrypted = 0x04;
46        const EncryptedName = 0x08;
47        const Verity = 0x40;
48    }
49}
50
51#[derive(Copy, Clone, Debug, Immutable, FromBytes, IntoBytes)]
52pub struct InlineFlags(u8);
53bitflags! {
54    impl InlineFlags: u8 {
55        const Xattr = 0b00000001;
56        const Data = 0b00000010;
57        const Dentry = 0b00000100;
58        const ExtraAttr = 0b00100000;
59    }
60}
61
62#[derive(Copy, Clone, Debug, Immutable, FromBytes, IntoBytes)]
63pub struct Flags(u32);
64bitflags! {
65    impl Flags: u32 {
66        const Casefold = 0x40000000;
67    }
68}
69
70#[repr(C, packed)]
71#[derive(Copy, Clone, Debug, Immutable, KnownLayout, FromBytes, IntoBytes, Unaligned)]
72pub struct InodeHeader {
73    pub mode: Mode,
74    pub advise_flags: AdviseFlags,
75    pub inline_flags: InlineFlags,
76    pub uid: u32,
77    pub gid: u32,
78    pub links: u32,
79    pub size: u64,
80    pub block_size: u64,
81    pub atime: i64,
82    pub ctime: i64,
83    pub mtime: i64,
84    pub atime_nanos: u32,
85    pub ctime_nanos: u32,
86    pub mtime_nanos: u32,
87    pub generation: u32,
88    pub dir_depth: u32,
89    pub xattr_nid: u32,
90    pub flags: Flags,
91    pub parent_inode: u32,
92    pub name_len: u32,
93    pub name: [u8; NAME_MAX],
94    pub dir_level: u8,
95
96    ext: Extent, // Holds the largest extent of this file, if using read extents. We ignore this.
97}
98
99/// This is optionally written after the header and before 'addr[0]' in Inode.
100#[repr(C, packed)]
101#[derive(Copy, Clone, Debug, Immutable, KnownLayout, FromBytes, IntoBytes, Unaligned)]
102pub struct InodeExtraAttr {
103    pub extra_size: u16,
104    pub inline_xattr_size: u16,
105    pub project_id: u32,
106    pub inode_checksum: u32,
107    pub creation_time: u64,
108    pub creation_time_nanos: u32,
109    pub compressed_blocks: u64,
110    pub compression_algorithm: u8,
111    pub log_cluster_size: u8,
112    pub compression_flags: u16,
113}
114
115#[repr(C, packed)]
116#[derive(Copy, Clone, Debug, Immutable, KnownLayout, FromBytes, IntoBytes, Unaligned)]
117pub struct InodeFooter {
118    pub nid: u32,
119    pub ino: u32,
120    pub flag: u32,
121    pub cp_ver: u64,
122    pub next_blkaddr: u32,
123}
124
125/// Inode represents a file or directory and consumes one 4kB block in the metadata region.
126///
127/// An Inode's basic layout is as follows:
128///    +--------------+
129///    | InodeHeader  |
130///    +--------------+
131///    | i_addrs[923] |
132///    +--------------+
133///    | nids[5]      |
134///    +--------------+
135///    | InodeFooter  |
136///    +--------------+
137///
138/// The i_addrs region consists of 32-bit block addresses to data associated with the inode.
139/// Some or all of this may be repurposed for optional structures based on header flags:
140///
141///   * extra: Contains additional metadata. Consumes the first 9 entries of i_addrs.
142///   * xattr: Extended attributes. Consumes the last 50 entries of i_addrs.
143///   * inline_data: Consumes all remaining i_addrs. If used, no external data blocks are used.
144///   * inline_dentry: Consumes all remaining i_addrs. If used, no external data blocks are used.
145///
146/// For inodes that do not contain inline data or inline dentry, the remaining i_addrs[] list
147/// the block offsets for data blocks that contain the contents of the inode. A value of NULL_ADDR
148/// indicates a zero page. A value of NEW_ADDR indicates a page that has not yet been allocated and
149/// should be treated the same as a zero page for our purposes.
150///
151/// If a file contains more data than available i_addrs[], nids[] will be used.
152///
153/// nids[0] and nids[1] are what F2fs called "direct node" blocks. These contain nids (i.e. NAT
154/// translated block addresses) to RawAddrBlock. Each RawAddrBlock contains up to 1018 block
155/// offsets to data blocks.
156///
157/// If that is insufficient, nids[2] and nids[3] contain what F2fs calls "indirect node" blocks.
158/// This is the same format as RawAddrBlock but each entry contains the nid of another
159/// RawAddrBlock, providing another layer of indirection and thus the ability to reference
160/// 1018^2 further blocks.
161///
162/// Finally, nids[4] may point at a "double indirect node" block. This adds one more layer of
163/// indirection, allowing for a further 1018^3 blocks.
164///
165/// For sparse files, any individual blocks or pages of blocks (at any indirection level) may be
166/// replaced with NULL_ADDR.
167///
168/// Block addressing starts at i_addrs and flows through each of nids[0..5] in order.
169pub struct Inode {
170    pub header: InodeHeader,
171    pub extra: Option<InodeExtraAttr>,
172    pub inline_data: Option<Box<[u8]>>,
173    pub(super) inline_dentry: Option<InlineDentry>,
174    pub(super) i_addrs: Vec<u32>,
175    nids: [u32; 5],
176    pub footer: InodeFooter,
177
178    // These are loaded from additional nodes.
179    nid_pages: HashMap<u32, Box<RawAddrBlock>>,
180    pub xattr: Vec<XattrEntry>,
181
182    // Crypto context, if present in xattr.
183    pub context: Option<fscrypt::Context>,
184
185    // Contains the set of block addresses in the data segment used by this inode.
186    // This includes nids, indirect and double indirect address pages, and the xattr page
187    pub block_addrs: Vec<u32>,
188}
189
190/// Both direct and indirect node address pages use this same format.
191/// In the case of direct nodes, the addrs point to data blocks.
192/// In the case of indirect and double-indirect nodes, the addrs point to nids of the next layer.
193#[repr(C, packed)]
194#[derive(Copy, Clone, Debug, Immutable, KnownLayout, FromBytes, IntoBytes, Unaligned)]
195pub struct RawAddrBlock {
196    pub addrs: [u32; ADDR_BLOCK_NUM_ADDR as usize],
197    _reserved:
198        [u8; BLOCK_SIZE - std::mem::size_of::<InodeFooter>() - 4 * ADDR_BLOCK_NUM_ADDR as usize],
199    pub footer: InodeFooter,
200}
201
202impl TryFrom<Buffer<'_>> for Box<RawAddrBlock> {
203    type Error = Error;
204    fn try_from(block: Buffer<'_>) -> Result<Self, Self::Error> {
205        let raw = block
206            .as_ptr_slice()
207            .read::<RawAddrBlock>()
208            .ok_or_else(|| anyhow!("Block size too small"))?;
209        Ok(Box::new(raw))
210    }
211}
212
213impl Debug for Inode {
214    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215        let mut out = f.debug_struct("Inode");
216        out.field("header", &self.header);
217        if let Some(extra) = &self.extra {
218            out.field("extra", &extra);
219        }
220        if let Some(inline_dentry) = &self.inline_dentry {
221            out.field("inline_dentry", &inline_dentry);
222        }
223        out.field("i_addrs", &self.i_addrs).field("footer", &self.footer);
224        out.field("xattr", &self.xattr);
225        out.finish()
226    }
227}
228
229impl Inode {
230    /// Attempt to load (and validate) an inode at a given nid.
231    pub(super) async fn try_load(f2fs: &impl Reader, ino: u32) -> Result<Box<Inode>, Error> {
232        let mut block_addrs = vec![];
233        let mut raw_xattr = vec![];
234        let mut this = {
235            let block = f2fs.read_node(ino).await?;
236            block_addrs.push(f2fs.get_nat_entry(ino).await?.block_addr);
237            let data = block.to_vec();
238            // Layout:
239            //   header: InodeHeader
240            //   extra: InodeExtraAttr # optional, based on header flag.
241            //   i_addr: [u32; N]       # N = <=923 addr data or repurposed for inline fields.
242            //   [u8; 200]      # optional, inline_xattr.
243            //   [u32; 5]       # nids (for large block maps)
244            //   InodeFooter
245            let (header, rest): (Ref<_, InodeHeader>, _) = Ref::from_prefix(&data[..]).unwrap();
246            let (rest, footer): (_, Ref<_, InodeFooter>) = Ref::from_suffix(rest).unwrap();
247            ensure!(footer.ino == ino, "Footer ino doesn't match.");
248
249            // nids are additional nodes pointing to data blocks. index has a specific meaning:
250            //  - 0..2 => nid of nodes that contain addresses to data blocks.
251            //  - 2..4 => nid of nodes that contain addresses to addresses of data blocks.
252            //  - 5 => nid of a node that contains double-indirect addresses ot data blocks.
253            let mut nids = [0u32; 5];
254            nids.as_mut_bytes()
255                .copy_from_slice(&rest[INODE_BLOCK_MAX_ADDR * 4..(INODE_BLOCK_MAX_ADDR + 5) * 4]);
256            let rest = &rest[..INODE_BLOCK_MAX_ADDR * 4];
257
258            let (extra, rest) = if header.inline_flags.contains(InlineFlags::ExtraAttr) {
259                let (extra, _): (Ref<_, InodeExtraAttr>, _) = Ref::from_prefix(rest).unwrap();
260                let extra_size = extra.extra_size as usize;
261                ensure!(extra_size <= rest.len(), "Bad extra_size in inode");
262                (Some((*extra).clone()), &rest[extra_size..])
263            } else {
264                (None, rest)
265            };
266            let rest = if header.inline_flags.contains(InlineFlags::Xattr) {
267                // xattr always take up the last 50 i_addr slots. i.e. 200 bytes.
268                ensure!(
269                    rest.len() >= 200,
270                    "Insufficient space for inline xattr. Likely bad extra_size."
271                );
272                raw_xattr.extend_from_slice(&rest[rest.len() - 200..]);
273                &rest[..rest.len() - 200]
274            } else {
275                rest
276            };
277
278            let mut inline_data = None;
279            let mut inline_dentry = None;
280            let mut i_addrs: Vec<u32> = Vec::new();
281
282            if header.inline_flags.contains(InlineFlags::Data) {
283                // Inline data skips the first address slot then repurposes the remainder as data.
284                ensure!(rest.len() >= 4, "Invalid inline data (insufficient remaining space)");
285                let data = &rest[4..];
286                ensure!(header.size <= data.len() as u64, "Invalid or corrupt inode.");
287                inline_data = Some(data[..header.size as usize].to_vec().into_boxed_slice());
288            } else if header.inline_flags.contains(InlineFlags::Dentry) {
289                // Repurposes i_addr to store a set of directory entry records.
290                inline_dentry = Some(InlineDentry::try_from_bytes(rest)?);
291            } else {
292                // &rest[..] is not necessarily 4-byte aligned so can't simply cast to [u32].
293                i_addrs.resize(rest.len() / 4, 0);
294                i_addrs.as_mut_bytes().copy_from_slice(&rest[..rest.len() / 4 * 4]);
295            };
296
297            Box::new(Self {
298                header: (*header).clone(),
299                extra,
300                inline_data: inline_data.map(|x| x.into()),
301                inline_dentry,
302                i_addrs,
303                nids,
304                footer: (*footer).clone(),
305
306                nid_pages: HashMap::new(),
307                xattr: vec![],
308                context: None,
309
310                block_addrs,
311            })
312        };
313
314        // Note that this call is done outside the above block to reduce the size of the future
315        // that '.await' produces by ensuring any unnecessary local variables are out of scope.
316        if this.header.xattr_nid != 0 {
317            let node_block = f2fs.read_node(this.header.xattr_nid).await?;
318            raw_xattr.extend_from_slice(&node_block.to_vec());
319            this.block_addrs.push(f2fs.get_nat_entry(this.header.xattr_nid).await?.block_addr);
320        }
321        this.xattr = decode_xattr(&raw_xattr)?;
322
323        this.context = crypto::try_read_context_from_xattr(&this.xattr)?;
324
325        // The set of blocks making up the file begin with i_addrs. If more blocks are required
326        // nids[0..5] are used. Zero pages (nid == NULL_ADDR) can be omitted at any level.
327        for (i, nid) in this.nids.into_iter().enumerate() {
328            if nid == NULL_ADDR {
329                continue;
330            }
331            match i {
332                0..2 => {
333                    this.nid_pages.insert(nid, f2fs.read_node(nid).await?.try_into()?);
334                    this.block_addrs.push(f2fs.get_nat_entry(nid).await?.block_addr);
335                }
336                2..4 => {
337                    let indirect = Box::<RawAddrBlock>::try_from(f2fs.read_node(nid).await?)?;
338                    this.block_addrs.push(f2fs.get_nat_entry(nid).await?.block_addr);
339                    for nid in indirect.addrs {
340                        if nid != NULL_ADDR {
341                            this.nid_pages.insert(nid, f2fs.read_node(nid).await?.try_into()?);
342                            this.block_addrs.push(f2fs.get_nat_entry(nid).await?.block_addr);
343                        }
344                    }
345                    this.nid_pages.insert(nid, indirect);
346                }
347                4 => {
348                    let double_indirect =
349                        Box::<RawAddrBlock>::try_from(f2fs.read_node(nid).await?)?;
350                    this.block_addrs.push(f2fs.get_nat_entry(nid).await?.block_addr);
351                    for nid in double_indirect.addrs {
352                        if nid != NULL_ADDR {
353                            let indirect =
354                                Box::<RawAddrBlock>::try_from(f2fs.read_node(nid).await?)?;
355                            this.block_addrs.push(f2fs.get_nat_entry(nid).await?.block_addr);
356                            for nid in indirect.addrs {
357                                if nid != NULL_ADDR {
358                                    this.nid_pages
359                                        .insert(nid, f2fs.read_node(nid).await?.try_into()?);
360                                    this.block_addrs
361                                        .push(f2fs.get_nat_entry(nid).await?.block_addr);
362                                }
363                            }
364                            this.nid_pages.insert(nid, indirect);
365                        }
366                    }
367                    this.nid_pages.insert(nid, double_indirect);
368                }
369                _ => unreachable!(),
370            }
371        }
372
373        Ok(this)
374    }
375
376    /// Walks through the data blocks of the file in order, handling sparse regions.
377    /// Emits extents of (logical_block_num, physical_block_num, length).
378    pub fn data_blocks(&self) -> DataBlocksIter<'_> {
379        DataBlocksIter {
380            iter: BlockIter { inode: self, stage: 0, offset: 0, a: 0, b: 0, c: 0 },
381            next_block: None,
382        }
383    }
384
385    /// Get the address of a specific logical data block.
386    /// NULL_ADDR and NEW_ADDR should be considered sparse (unallocated) zero blocks.
387    pub fn data_block_addr(&self, mut block_num: u32) -> u32 {
388        let offset = block_num;
389
390        if block_num < self.i_addrs.len() as u32 {
391            return self.i_addrs[block_num as usize];
392        }
393        block_num -= self.i_addrs.len() as u32;
394
395        // After we adjust for i_addrs, all offsets are simple constants.
396        const NID0_END: u32 = ADDR_BLOCK_NUM_ADDR;
397        const NID1_END: u32 = NID0_END + ADDR_BLOCK_NUM_ADDR;
398        const NID2_END: u32 = NID1_END + ADDR_BLOCK_NUM_ADDR * ADDR_BLOCK_NUM_ADDR;
399        const NID3_END: u32 = NID2_END + ADDR_BLOCK_NUM_ADDR * ADDR_BLOCK_NUM_ADDR;
400
401        let mut iter = match block_num {
402            ..NID0_END => {
403                let a = block_num;
404                BlockIter { inode: self, stage: 1, offset, a, b: 0, c: 0 }
405            }
406            ..NID1_END => {
407                let a = block_num - NID0_END;
408                BlockIter { inode: self, stage: 2, offset, a, b: 0, c: 0 }
409            }
410            ..NID2_END => {
411                block_num -= NID1_END;
412                let a = block_num / ADDR_BLOCK_NUM_ADDR;
413                let b = block_num % ADDR_BLOCK_NUM_ADDR;
414                BlockIter { inode: self, stage: 3, offset, a, b, c: 0 }
415            }
416            ..NID3_END => {
417                block_num -= NID2_END;
418                let a = block_num / ADDR_BLOCK_NUM_ADDR;
419                let b = block_num % ADDR_BLOCK_NUM_ADDR;
420                BlockIter { inode: self, stage: 4, offset, a, b, c: 0 }
421            }
422            _ => {
423                block_num -= NID3_END;
424                let a = block_num / ADDR_BLOCK_NUM_ADDR / ADDR_BLOCK_NUM_ADDR;
425                let b = (block_num / ADDR_BLOCK_NUM_ADDR) % ADDR_BLOCK_NUM_ADDR;
426                let c = block_num % ADDR_BLOCK_NUM_ADDR;
427                BlockIter { inode: self, stage: 5, offset, a, b, c }
428            }
429        };
430        if let Some((logical, physical)) = iter.next() {
431            if logical == offset { physical } else { NULL_ADDR }
432        } else {
433            NULL_ADDR
434        }
435    }
436}
437
438#[derive(Copy, Clone, Debug, PartialEq, Eq)]
439pub struct DataBlockExtent {
440    /// The starting logical block number.
441    pub logical_block_num: u32,
442    /// The starting physical block number.
443    pub physical_block_num: u32,
444    /// The number of contiguous blocks in this extent.
445    pub length: u32,
446}
447
448/// Iterates extents in the file. Will always create an extent break at end of file.
449pub struct DataBlocksIter<'a> {
450    iter: BlockIter<'a>,
451    next_block: Option<(u32, u32)>,
452}
453
454impl Iterator for DataBlocksIter<'_> {
455    type Item = DataBlockExtent;
456    fn next(&mut self) -> Option<Self::Item> {
457        let (log_start, phys_start) = self.next_block.take().or_else(|| self.iter.next())?;
458        let mut len = 1;
459
460        // Maximum file size is 2^32 blocks.
461        // Iterators don't return errors so we ignore over-sized files to avoid overflow issues.
462        if self.iter.inode.header.size > BLOCK_SIZE as u64 * u32::MAX as u64 {
463            return None;
464        }
465        let file_end = (self.iter.inode.header.size.next_multiple_of(BLOCK_SIZE as u64)
466            / BLOCK_SIZE as u64) as u32;
467
468        loop {
469            match self.iter.next() {
470                Some((log, phys))
471                    if Some(log) == log_start.checked_add(len)
472                        && Some(phys) == phys_start.checked_add(len)
473                        && log != file_end =>
474                {
475                    len += 1;
476                }
477                other => {
478                    self.next_block = other;
479                    return Some(DataBlockExtent {
480                        logical_block_num: log_start,
481                        physical_block_num: phys_start,
482                        length: len,
483                    });
484                }
485            }
486        }
487    }
488}
489
490struct BlockIter<'a> {
491    inode: &'a Inode,
492    stage: u32, // 0 -> i_addr, 1-> nids[0], 2 -> nids[1] -> ...
493    offset: u32,
494    a: u32, // depends on stage
495    b: u32, // used for nids 2+ for indirection
496    c: u32, // used for nids[4] for double-indirection.
497}
498
499impl Iterator for BlockIter<'_> {
500    type Item = (u32, u32);
501    fn next(&mut self) -> Option<Self::Item> {
502        loop {
503            match self.stage {
504                0 => {
505                    // i_addrs
506                    while let Some(&addr) = self.inode.i_addrs.get(self.a as usize) {
507                        self.a += 1;
508                        self.offset += 1;
509                        if addr != NULL_ADDR && addr != NEW_ADDR {
510                            return Some((self.offset - 1, addr));
511                        }
512                    }
513                    self.stage += 1;
514                    self.a = 0;
515                }
516                1..3 => {
517                    // "direct"
518                    let nid = self.inode.nids[self.stage as usize - 1];
519
520                    if nid == NULL_ADDR || nid == NEW_ADDR {
521                        self.stage += 1;
522                        self.offset += ADDR_BLOCK_NUM_ADDR;
523                    } else {
524                        let addrs = self.inode.nid_pages.get(&nid).unwrap().addrs;
525                        while let Some(&addr) = addrs.get(self.a as usize) {
526                            self.a += 1;
527                            self.offset += 1;
528                            if addr != NULL_ADDR && addr != NEW_ADDR {
529                                return Some((self.offset - 1, addr));
530                            }
531                        }
532                        self.stage += 1;
533                        self.a = 0;
534                    }
535                }
536
537                3..5 => {
538                    let nid = self.inode.nids[self.stage as usize - 1];
539                    // "indirect"
540                    if nid == NULL_ADDR || nid == NEW_ADDR {
541                        self.stage += 1;
542                        self.offset += ADDR_BLOCK_NUM_ADDR * ADDR_BLOCK_NUM_ADDR;
543                    } else {
544                        let addrs = self.inode.nid_pages.get(&nid).unwrap().addrs;
545                        while let Some(&nid) = addrs.get(self.a as usize) {
546                            if nid == NULL_ADDR || nid == NEW_ADDR {
547                                self.a += 1;
548                                self.offset += ADDR_BLOCK_NUM_ADDR;
549                            } else {
550                                let addrs = self.inode.nid_pages.get(&nid).unwrap().addrs;
551                                while let Some(&addr) = addrs.get(self.b as usize) {
552                                    self.b += 1;
553                                    self.offset += 1;
554                                    if addr != NULL_ADDR && addr != NEW_ADDR {
555                                        return Some((self.offset - 1, addr));
556                                    }
557                                }
558                                self.a += 1;
559                                self.b = 0;
560                            }
561                        }
562                        self.stage += 1;
563                        self.a = 0;
564                    }
565                }
566
567                5 => {
568                    let nid = self.inode.nids[4];
569                    // "double-indirect"
570                    if nid != NULL_ADDR && nid != NEW_ADDR {
571                        let addrs = self.inode.nid_pages.get(&nid).unwrap().addrs;
572                        while let Some(&nid) = addrs.get(self.a as usize) {
573                            if nid == NULL_ADDR || nid == NEW_ADDR {
574                                self.a += 1;
575                                self.offset += ADDR_BLOCK_NUM_ADDR * ADDR_BLOCK_NUM_ADDR;
576                            } else {
577                                let addrs = self.inode.nid_pages.get(&nid).unwrap().addrs;
578                                while let Some(&nid) = addrs.get(self.b as usize) {
579                                    if nid == NULL_ADDR || nid == NEW_ADDR {
580                                        self.b += 1;
581                                        self.offset += ADDR_BLOCK_NUM_ADDR;
582                                    } else {
583                                        let addrs = self.inode.nid_pages.get(&nid).unwrap().addrs;
584                                        while let Some(&addr) = addrs.get(self.c as usize) {
585                                            self.c += 1;
586                                            self.offset += 1;
587                                            if addr != NULL_ADDR && addr != NEW_ADDR {
588                                                return Some((self.offset - 1, addr));
589                                            }
590                                        }
591                                        self.b += 1;
592                                        self.c = 0;
593                                    }
594                                }
595
596                                self.a += 1;
597                                self.b = 0;
598                            }
599                        }
600                    }
601                    self.stage += 1;
602                }
603                _ => {
604                    break;
605                }
606            }
607        }
608        None
609    }
610}
611
612#[cfg(test)]
613mod test {
614    use super::*;
615    use crate::nat::RawNatEntry;
616    use crate::reader;
617    use anyhow;
618    use async_trait::async_trait;
619    use storage_device::buffer_allocator::{BufferAllocator, BufferSource};
620    use zerocopy::FromZeros;
621
622    /// A simple reader that can be filled explicitly with blocks to exercise inode.
623    struct FakeReader {
624        data: HashMap<u32, Box<[u8; 4096]>>,
625        nids: HashMap<u32, Box<[u8; 4096]>>,
626        allocator: BufferAllocator,
627    }
628
629    #[async_trait]
630    impl reader::Reader for FakeReader {
631        async fn read_raw_block(&self, block_addr: u32) -> Result<Buffer<'_>, Error> {
632            match self.data.get(&block_addr) {
633                None => Err(anyhow!("unexpected block {block_addr}")),
634                Some(value) => {
635                    let mut block = self.allocator.allocate_buffer(BLOCK_SIZE).await;
636                    block.copy_from_slice(value.as_ref());
637                    Ok(block)
638                }
639            }
640        }
641
642        async fn read_node(&self, nid: u32) -> Result<Buffer<'_>, Error> {
643            match self.nids.get(&nid) {
644                None => Err(anyhow!("unexpected nid {nid}")),
645                Some(value) => {
646                    let mut block = self.allocator.allocate_buffer(BLOCK_SIZE).await;
647                    block.copy_from_slice(value.as_ref());
648                    Ok(block)
649                }
650            }
651        }
652
653        fn fs_uuid(&self) -> &[u8; 16] {
654            &[0; 16]
655        }
656
657        async fn get_nat_entry(&self, nid: u32) -> Result<RawNatEntry, Error> {
658            Ok(RawNatEntry { ino: nid, block_addr: 0, ..Default::default() })
659        }
660    }
661
662    // Builds a bare-bones inode block.
663    fn build_inode(ino: u32) -> Box<[u8; BLOCK_SIZE]> {
664        let mut header = InodeHeader::new_zeroed();
665        let mut footer = InodeFooter::new_zeroed();
666        let mut extra = InodeExtraAttr::new_zeroed();
667
668        extra.extra_size = std::mem::size_of::<InodeExtraAttr>().try_into().unwrap();
669
670        header.mode = Mode::RegularFile;
671        header.inline_flags.set(InlineFlags::ExtraAttr, true);
672        header.inline_flags.set(InlineFlags::Xattr, true);
673        footer.ino = ino;
674
675        let mut out = [0u8; BLOCK_SIZE];
676        out[..std::mem::size_of::<InodeHeader>()].copy_from_slice(&header.as_bytes());
677        out[std::mem::size_of::<InodeHeader>()
678            ..std::mem::size_of::<InodeHeader>() + std::mem::size_of::<InodeExtraAttr>()]
679            .copy_from_slice(&extra.as_bytes());
680        out[BLOCK_SIZE - std::mem::size_of::<InodeFooter>()..].copy_from_slice(&footer.as_bytes());
681        Box::new(out)
682    }
683
684    #[fuchsia::test]
685    async fn test_xattr_bounds() {
686        let mut reader = FakeReader {
687            data: [].into(),
688            nids: [(1, build_inode(1)), (2, [0u8; 4096].into()), (3, [0u8; 4096].into())].into(),
689            allocator: BufferAllocator::new(BLOCK_SIZE, BufferSource::new(BLOCK_SIZE * 10)),
690        };
691        assert!(Inode::try_load(&reader, 1).await.is_ok());
692
693        let header_len = std::mem::size_of::<InodeHeader>();
694        let footer_len = std::mem::size_of::<InodeFooter>();
695        let nids_len = std::mem::size_of::<u32>() * 5;
696        let overheads = header_len + footer_len + nids_len;
697
698        // Just enough room for xattrs.
699        let mut extra = InodeExtraAttr::new_zeroed();
700        extra.extra_size = (BLOCK_SIZE - overheads - 200) as u16;
701        reader.nids.get_mut(&1).unwrap()[std::mem::size_of::<InodeHeader>()
702            ..std::mem::size_of::<InodeHeader>() + std::mem::size_of::<InodeExtraAttr>()]
703            .copy_from_slice(&extra.as_bytes());
704        assert!(Inode::try_load(&reader, 1).await.is_ok());
705
706        // No room for xattrs.
707        let mut extra = InodeExtraAttr::new_zeroed();
708        extra.extra_size = (BLOCK_SIZE - overheads - 199) as u16;
709        reader.nids.get_mut(&1).unwrap()[std::mem::size_of::<InodeHeader>()
710            ..std::mem::size_of::<InodeHeader>() + std::mem::size_of::<InodeExtraAttr>()]
711            .copy_from_slice(&extra.as_bytes());
712        assert!(Inode::try_load(&reader, 1).await.is_err());
713    }
714}
715
716#[cfg(test)]
717mod tests {
718    use zerocopy::FromZeros;
719
720    use super::*;
721
722    fn last_addr_block(addr: u32) -> Box<RawAddrBlock> {
723        let mut addr_block = RawAddrBlock::new_zeroed();
724        addr_block.addrs[ADDR_BLOCK_NUM_ADDR as usize - 1] = addr;
725        Box::new(addr_block)
726    }
727
728    #[test]
729    fn test_data_iter() {
730        // Fake up an inode with datablocks for the last block in each layer.
731        //   1. The last i_addrs.
732        //   2. The last nids[0] and nids[1].
733        //   3. The last block of the last nids[2] and nids[3] blocks.
734        //   4. The last block of the last block of nids[4] block.
735        // All other blocks are unallocated.
736        let header = InodeHeader::new_zeroed();
737        let footer = InodeFooter::new_zeroed();
738        let mut nids = [0u32; 5];
739        let mut nid_pages = HashMap::new();
740        nid_pages.insert(101, last_addr_block(1001));
741        nid_pages.insert(102, last_addr_block(1002));
742
743        let mut i_addrs: Vec<u32> = Vec::new();
744        i_addrs.resize(INODE_BLOCK_MAX_ADDR, 0);
745        i_addrs[0] = 100;
746        i_addrs[1] = 101;
747        i_addrs[2] = 102;
748        i_addrs[INODE_BLOCK_MAX_ADDR - 1] = 1000;
749
750        nids[0] = 101;
751        nid_pages.insert(101, last_addr_block(1001));
752
753        nids[1] = 102;
754        nid_pages.insert(102, last_addr_block(1002));
755
756        nids[2] = 103;
757        nid_pages.insert(103, last_addr_block(104));
758        nid_pages.insert(104, last_addr_block(1003));
759
760        nids[3] = 105;
761        nid_pages.insert(105, last_addr_block(106));
762        nid_pages.insert(106, last_addr_block(1004));
763
764        nids[4] = 107;
765        nid_pages.insert(107, last_addr_block(108));
766        nid_pages.insert(108, last_addr_block(109));
767        nid_pages.insert(109, last_addr_block(1005));
768
769        let inode = Box::new(Inode {
770            header,
771            extra: None,
772            inline_data: None,
773            inline_dentry: None,
774            i_addrs,
775            nids,
776            footer: footer,
777
778            nid_pages,
779            xattr: vec![],
780            context: None,
781
782            block_addrs: vec![],
783        });
784
785        // Also test data_block_addr while we're walking.
786        assert_eq!(inode.data_block_addr(0), 100);
787
788        let mut iter = inode.data_blocks();
789        assert_eq!(
790            iter.next(),
791            Some(DataBlockExtent { logical_block_num: 0, physical_block_num: 100, length: 3 })
792        );
793
794        let mut block_num = 922;
795        assert_eq!(
796            iter.next(),
797            Some(DataBlockExtent {
798                logical_block_num: block_num,
799                physical_block_num: 1000,
800                length: 1
801            })
802        ); // i_addrs
803        assert_eq!(inode.data_block_addr(block_num), 1000);
804        block_num += ADDR_BLOCK_NUM_ADDR;
805        assert_eq!(
806            iter.next(),
807            Some(DataBlockExtent {
808                logical_block_num: block_num,
809                physical_block_num: 1001,
810                length: 1
811            })
812        ); // nids[0]
813        assert_eq!(inode.data_block_addr(block_num), 1001);
814        block_num += ADDR_BLOCK_NUM_ADDR;
815        assert_eq!(
816            iter.next(),
817            Some(DataBlockExtent {
818                logical_block_num: block_num,
819                physical_block_num: 1002,
820                length: 1
821            })
822        ); // nids[1]
823        assert_eq!(inode.data_block_addr(block_num), 1002);
824        block_num += ADDR_BLOCK_NUM_ADDR * ADDR_BLOCK_NUM_ADDR;
825        assert_eq!(
826            iter.next(),
827            Some(DataBlockExtent {
828                logical_block_num: block_num,
829                physical_block_num: 1003,
830                length: 1
831            })
832        ); // nids[2]
833        assert_eq!(inode.data_block_addr(block_num), 1003);
834        block_num += ADDR_BLOCK_NUM_ADDR * ADDR_BLOCK_NUM_ADDR;
835        assert_eq!(
836            iter.next(),
837            Some(DataBlockExtent {
838                logical_block_num: block_num,
839                physical_block_num: 1004,
840                length: 1
841            })
842        ); // nids[3]
843        assert_eq!(inode.data_block_addr(block_num), 1004);
844        block_num += ADDR_BLOCK_NUM_ADDR * ADDR_BLOCK_NUM_ADDR * ADDR_BLOCK_NUM_ADDR;
845        assert_eq!(
846            iter.next(),
847            Some(DataBlockExtent {
848                logical_block_num: block_num,
849                physical_block_num: 1005,
850                length: 1
851            })
852        ); // nids[4]
853        assert_eq!(inode.data_block_addr(block_num), 1005);
854        assert_eq!(iter.next(), None);
855        assert_eq!(inode.data_block_addr(block_num - 1), 0);
856        assert_eq!(inode.data_block_addr(block_num + 1), 0);
857    }
858}