Skip to main content

f2fs_reader/
reader.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::block_cache::BlockCache;
5use crate::checkpoint::*;
6use crate::crypto;
7use crate::dir::{DentryBlock, DirEntry};
8use crate::inode::{self, Inode};
9use crate::nat::{Nat, NatJournal, RawNatEntry, SummaryBlock};
10use crate::superblock::{
11    BLOCK_SIZE, BLOCKS_PER_SEGMENT, F2FS_MAGIC, SEGMENT_SIZE, SUPERBLOCK_OFFSET, SuperBlock,
12    f2fs_crc32,
13};
14use anyhow::{Error, anyhow, bail, ensure};
15use async_trait::async_trait;
16use std::collections::{HashMap, HashSet};
17use std::sync::Arc;
18use storage_device::Device;
19use storage_device::buffer::Buffer;
20use zerocopy::FromBytes;
21
22// Used to indicate zero pages (when used as block_addr) and end of list (when used as nid).
23pub const NULL_ADDR: u32 = 0;
24// Used to indicate a new page that hasn't been allocated yet.
25pub const NEW_ADDR: u32 = 0xffffffff;
26
27/// This trait is exposed to allow unit testing of Inode and other structs.
28/// It is implemented by F2fsReader.
29#[async_trait]
30pub(super) trait Reader {
31    /// Read a raw block from disk.
32    /// `block_addr` is the physical block offset on the device.
33    async fn read_raw_block(&self, block_addr: u32) -> Result<Buffer<'_>, Error>;
34
35    /// Reads a logical 'node' block from the disk (i.e. via NAT indirection)
36    async fn read_node(&self, nid: u32) -> Result<Buffer<'_>, Error>;
37
38    /// Attempt to retrieve a key given its identifier.
39    fn get_key(&self, _identifier: &[u8; 16]) -> Option<&[u8; 64]> {
40        None
41    }
42
43    /// Returns the filesystem UUID. This is needed for some decryption policies.
44    fn fs_uuid(&self) -> &[u8; 16];
45
46    /// Attempt to obtain a decryptor for a given crypto context.
47    /// Will return None if the main key is not known.
48    fn get_decryptor_for_inode(&self, inode: &Inode) -> Option<crypto::PerFileDecryptor> {
49        if let Some(context) = inode.context {
50            if let Some(main_key) = self.get_key(&context.main_key_identifier) {
51                return Some(crypto::PerFileDecryptor::new(main_key, context, self.fs_uuid()));
52            }
53        }
54        None
55    }
56
57    /// Look up a raw NAT entry given a node ID.
58    async fn get_nat_entry(&self, nid: u32) -> Result<RawNatEntry, Error>;
59}
60
61pub struct F2fsReader {
62    device: Arc<dyn Device>,
63    superblock: SuperBlock,     // 1kb, points at checkpoints
64    checkpoint: CheckpointPack, // pair of a/b segments (alternating versions)
65    cp_start_block: u32,        // Start block of the active checkpoint
66    nat: Nat,
67    orphan_inodes: HashSet<u32>,
68
69    // A simple key store.
70    keys: HashMap<[u8; 16], [u8; 64]>,
71    cache: BlockCache,
72}
73
74impl Drop for F2fsReader {
75    fn drop(&mut self) {
76        // Zero keys in RAM for extra safety.
77        self.keys.values_mut().for_each(|v| {
78            *v = [0u8; 64];
79        });
80    }
81}
82
83impl F2fsReader {
84    pub fn superblock(&self) -> &SuperBlock {
85        &self.superblock
86    }
87
88    pub fn checkpoint(&self) -> &CheckpointPack {
89        &self.checkpoint
90    }
91
92    pub async fn open_device(device: Arc<dyn Device>) -> Result<Self, Error> {
93        let (superblock, checkpoints) =
94            match Self::try_from_superblock(device.as_ref(), SUPERBLOCK_OFFSET).await {
95                Ok(x) => x,
96                Err(e) => Self::try_from_superblock(device.as_ref(), SUPERBLOCK_OFFSET * 2)
97                    .await
98                    .map_err(|_| e)?,
99            };
100
101        let mut last_error = anyhow!("No checkpoints found");
102
103        for (checkpoint, cp_start_block) in checkpoints {
104            let mut this = Self {
105                device: device.clone(),
106                superblock,
107                checkpoint,
108                cp_start_block,
109                nat: Nat::new(0, vec![], HashMap::new()),
110                orphan_inodes: HashSet::new(),
111                keys: HashMap::with_capacity(16),
112                cache: BlockCache::new(1024, BLOCK_SIZE),
113            };
114
115            let nat_journal = match this.read_nat_journal().await {
116                Ok(j) => j,
117                Err(e) => {
118                    let ver = this.checkpoint.header.checkpoint_ver;
119                    log::warn!(
120                        "Failed to initialize NAT journal from checkpoint (Ver {} at {}): {}. Trying next.",
121                        ver,
122                        cp_start_block,
123                        e
124                    );
125                    last_error = e;
126                    continue;
127                }
128            };
129
130            match this.read_orphan_inodes().await {
131                Ok(orphans) => {
132                    this.orphan_inodes = orphans;
133                    this.nat = Nat::new(
134                        this.superblock.nat_blkaddr,
135                        this.checkpoint.nat_bitmap.clone(),
136                        nat_journal,
137                    );
138                    return Ok(this);
139                }
140                Err(e) => {
141                    let ver = this.checkpoint.header.checkpoint_ver;
142                    log::warn!(
143                        "Failed to read orphan inodes from checkpoint (Ver {} at {}): {}. Trying next.",
144                        ver,
145                        cp_start_block,
146                        e
147                    );
148                    last_error = e;
149                    continue;
150                }
151            }
152        }
153
154        Err(last_error)
155    }
156
157    async fn try_from_superblock(
158        device: &dyn Device,
159        superblock_offset: u64,
160    ) -> Result<(SuperBlock, Vec<(CheckpointPack, u32)>), Error> {
161        let superblock = SuperBlock::read_from_device(device, superblock_offset).await?;
162        let checkpoint_addr = superblock.cp_blkaddr;
163        let checkpoint_a_offset = BLOCK_SIZE as u64 * checkpoint_addr as u64;
164        let checkpoint_b_offset = checkpoint_a_offset + SEGMENT_SIZE as u64;
165
166        let mut checkpoints = Vec::new();
167
168        // Read both checkpoints and collect valid ones with their block addresses
169        if let Ok(cp) =
170            CheckpointPack::read_from_device(device, checkpoint_a_offset, superblock.cp_payload)
171                .await
172        {
173            checkpoints.push((cp, checkpoint_addr));
174        }
175        if let Ok(cp) =
176            CheckpointPack::read_from_device(device, checkpoint_b_offset, superblock.cp_payload)
177                .await
178        {
179            checkpoints.push((cp, checkpoint_addr + BLOCKS_PER_SEGMENT as u32));
180        }
181
182        if checkpoints.is_empty() {
183            bail!("Failed to read any valid checkpoint");
184        }
185
186        // Sort by version descending (newest first)
187        checkpoints.sort_by(|(a, _), (b, _)| {
188            let va = a.header.checkpoint_ver;
189            let vb = b.header.checkpoint_ver;
190            vb.cmp(&va)
191        });
192
193        // Min metadata segment count is 1 superblock, 1 ssa, (ckpt + sit + nat) * 2
194        const MIN_METADATA_SEGMENT_COUNT: u32 = 8;
195
196        // Use newest for validation
197        let first_cp = &checkpoints[0].0;
198
199        // Make sure the metadata fits on the device
200        let metadata_segment_count = superblock
201            .segment_count_sit
202            .checked_add(superblock.segment_count_nat)
203            .and_then(|v| v.checked_add(first_cp.header.rsvd_segment_count))
204            .and_then(|v| v.checked_add(superblock.segment_count_ssa))
205            .and_then(|v| v.checked_add(superblock.segment_count_ckpt))
206            .ok_or_else(|| anyhow::anyhow!("Segment counts overflow"))?;
207        ensure!(
208            metadata_segment_count <= superblock.segment_count
209                && metadata_segment_count >= MIN_METADATA_SEGMENT_COUNT,
210            "Bad segment counts in checkpoint"
211        );
212        Ok((superblock, checkpoints))
213    }
214
215    /// Returns the block address that the checkpoint starts at.
216    pub fn checkpoint_start_addr(&self) -> u32 {
217        self.cp_start_block
218    }
219
220    fn nat(&self) -> &Nat {
221        &self.nat
222    }
223    /// Returns the absolute block address of the summary block (default or compact).
224    pub fn summary_block_addr(&self) -> u32 {
225        self.checkpoint_start_addr() + self.checkpoint.header.cp_pack_start_sum
226    }
227
228    async fn read_nat_journal(&mut self) -> Result<HashMap<u32, RawNatEntry>, Error> {
229        if self.checkpoint.header.ckpt_flags & CKPT_FLAG_COMPACT_SUMMARY != 0 {
230            // The "compact summary" feature packs NAT/SIT/summary into one block.
231            // The NAT journal entries come first.
232            let summary_addr = self.summary_block_addr();
233            let block = self.read_raw_block(summary_addr).await?;
234            let n_nats = block.as_ptr_slice().read::<u16>().unwrap();
235            let nat_journal = block
236                .as_ptr_slice()
237                .subslice(2..2 + std::mem::size_of::<NatJournal>())
238                .read::<NatJournal>()
239                .unwrap();
240            ensure!(
241                (n_nats as usize) <= nat_journal.entries.len(),
242                "n_nats {} larger than block size {}",
243                n_nats,
244                nat_journal.entries.len()
245            );
246            Ok(HashMap::from_iter(
247                nat_journal.entries[..n_nats as usize].iter().map(|e| (e.ino, e.entry)),
248            ))
249        } else {
250            // Read the default summary block location from the "hot data" segment.
251            // If orphans are present, `summary_block_addr` automatically skips the orphan block.
252            let summary_addr = self.summary_block_addr();
253            let block = self.read_raw_block(summary_addr).await?;
254
255            let summary = block
256                .as_ptr_slice()
257                .read::<SummaryBlock>()
258                .ok_or_else(|| anyhow!("Block size too small"))?;
259            ensure!(summary.footer.entry_type == 0u8, "sum_type != 0 in summary footer");
260            #[cfg(not(fuzz))]
261            {
262                let data = block.to_vec();
263                let actual_checksum = f2fs_crc32(F2FS_MAGIC, &data[..BLOCK_SIZE - 4]);
264                let expected_checksum = summary.footer.check_sum;
265                if actual_checksum != expected_checksum {
266                    // TODO(b/487023899): Confirm semantics.
267                    log::warn!(
268                        "Summary block checksum mismatch (actual: 0x{:x}, expected: 0x{:x}). \
269                     This is normal for checkpoints with CP_CRC_RECOVERY_FLAG.",
270                        actual_checksum,
271                        expected_checksum
272                    );
273                }
274            }
275            let n_nats = summary.n_nats;
276            ensure!(
277                (n_nats as usize) <= summary.nat_journal.entries.len(),
278                "n_nats {} larger than block size {}",
279                n_nats,
280                summary.nat_journal.entries.len()
281            );
282            let mut out = HashMap::new();
283            for i in 0..n_nats as usize {
284                out.insert(
285                    summary.nat_journal.entries[i].ino,
286                    summary.nat_journal.entries[i].entry,
287                );
288            }
289            Ok(out)
290        }
291    }
292
293    async fn read_orphan_inodes(&mut self) -> Result<HashSet<u32>, Error> {
294        let mut orphans = HashSet::new();
295        if self.checkpoint.header.ckpt_flags & CP_ORPHAN_PRESENT_FLAG != 0 {
296            let start_blk = self.checkpoint_start_addr() + 1 + self.superblock.cp_payload;
297            let end_blk = self.summary_block_addr();
298            ensure!(start_blk < end_blk, "CP_ORPHAN_PRESENT_FLAG set with zero orphan blocks");
299            let total_orphan_blocks = (end_blk - start_blk) as u16;
300            let root_ino = self.superblock.root_ino;
301            for blk_addr in start_blk..end_blk {
302                let block = self.read_raw_block(blk_addr).await?;
303                let orphan_block = block
304                    .as_ptr_slice()
305                    .read::<OrphanBlock>()
306                    .ok_or_else(|| anyhow!("Block size too small for OrphanBlock"))?;
307                let blk_index = (blk_addr - start_blk + 1) as u16;
308                let blk_addr_val = orphan_block.blk_addr;
309                let blk_count_val = orphan_block.blk_count;
310                ensure!(
311                    blk_addr_val == blk_index,
312                    "Invalid orphan block blk_addr: {blk_addr_val} != {blk_index}"
313                );
314                ensure!(
315                    blk_count_val == total_orphan_blocks,
316                    "Invalid orphan block blk_count: {blk_count_val} != {total_orphan_blocks}"
317                );
318                #[cfg(not(fuzz))]
319                if orphan_block.check_sum != 0 {
320                    let data = block.to_vec();
321                    let expected_crc = orphan_block.check_sum;
322                    let actual_crc = f2fs_crc32(F2FS_MAGIC, &data[..BLOCK_SIZE - 4]);
323                    ensure!(
324                        actual_crc == expected_crc,
325                        "Bad OrphanBlock checksum ({actual_crc:08x} != {expected_crc:08x})"
326                    );
327                }
328                let entry_count = orphan_block.entry_count as usize;
329                ensure!(
330                    entry_count <= ORPHANS_PER_BLOCK,
331                    "Invalid orphan entry count: {entry_count} > {ORPHANS_PER_BLOCK}"
332                );
333                for i in 0..entry_count {
334                    let ino = orphan_block.ino[i];
335                    ensure!(ino >= root_ino && ino != NEW_ADDR, "Invalid orphan ino {ino}");
336                    orphans.insert(ino);
337                }
338            }
339        }
340        Ok(orphans)
341    }
342
343    /// Returns the set of orphan inode numbers recorded in the active checkpoint.
344    pub fn orphan_inodes(&self) -> &HashSet<u32> {
345        &self.orphan_inodes
346    }
347
348    /// Returns true if the specified inode number is recorded as an orphan.
349    pub fn is_orphan(&self, ino: u32) -> bool {
350        self.orphan_inodes.contains(&ino)
351    }
352
353    pub fn root_ino(&self) -> u32 {
354        self.superblock.root_ino
355    }
356
357    /// Gives the maximum addressable inode. This can be used to ensure we don't have namespace
358    /// collisions when building hybrid images.
359    pub fn max_ino(&self) -> u32 {
360        (self.checkpoint.nat_bitmap.len() * 8) as u32
361    }
362
363    /// Registers a new main key.
364    /// This 'unlocks' any files using this key.
365    pub fn add_key(&mut self, main_key: &[u8; 64]) -> [u8; 16] {
366        let identifier = fscrypt::main_key_to_identifier(main_key);
367        println!("Adding key with identifier {}", hex::encode(identifier));
368        self.keys.insert(identifier.clone(), main_key.clone());
369        identifier
370    }
371
372    /// Read an inode for a directory and return entries.
373    pub async fn readdir(&self, ino: u32) -> Result<Vec<DirEntry>, Error> {
374        let inode = Inode::try_load(self, ino).await?;
375        let decryptor = self.get_decryptor_for_inode(&inode);
376        let mode = inode.header.mode;
377        let advise_flags = inode.header.advise_flags;
378        let flags = inode.header.flags;
379        ensure!(mode.contains(inode::Mode::Directory), "not a directory");
380        if let Some(entries) = inode.get_inline_dir_entries(
381            advise_flags.contains(inode::AdviseFlags::Encrypted),
382            flags.contains(inode::Flags::Casefold),
383            &decryptor,
384        )? {
385            Ok(entries)
386        } else {
387            let mut entries = Vec::new();
388
389            // Entries are stored in a series of increasingly larger hash tables.
390            // The number of these that exist are based on inode.dir_depth.
391            // Thankfully, we don't need to worry about this as the total number of blocks is
392            // bound to inode.header.size and we can just skip NULL blocks.
393            for mut extent in inode.data_blocks() {
394                for _ in 0..extent.length {
395                    let dentry_block = self
396                        .read_raw_block(extent.physical_block_num)
397                        .await?
398                        .as_ptr_slice()
399                        .read::<DentryBlock>()
400                        .ok_or_else(|| anyhow!("Block size too small"))?;
401                    entries.append(&mut dentry_block.get_entries(
402                        ino,
403                        advise_flags.contains(inode::AdviseFlags::Encrypted),
404                        flags.contains(inode::Flags::Casefold),
405                        &decryptor,
406                    )?);
407                    extent.physical_block_num += 1;
408                }
409            }
410            Ok(entries)
411        }
412    }
413
414    /// Read an inode and associated blocks from disk.
415    pub async fn read_inode(&self, ino: u32) -> Result<Box<Inode>, Error> {
416        Inode::try_load(self, ino).await
417    }
418
419    /// Takes an inode for a symlink and the link as a set of bytes, decrypted if possible.
420    pub fn read_symlink(&self, inode: &Inode) -> Result<Box<[u8]>, Error> {
421        if let Some(inline_data) = inode.inline_data.as_deref() {
422            let mut filename = inline_data.to_vec();
423            if inode.header.advise_flags.contains(inode::AdviseFlags::Encrypted) {
424                // Encrypted symlinks have a 2-byte length prefix.
425                ensure!(filename.len() >= 2, "invalid encrypted symlink");
426                let symlink_len = u16::read_from_bytes(&filename[..2]).unwrap();
427                filename.drain(..2);
428                filename.truncate(symlink_len as usize);
429                ensure!(symlink_len == filename.len() as u16, "invalid encrypted symlink");
430                if let Some(decryptor) = self.get_decryptor_for_inode(inode) {
431                    decryptor.decrypt_filename_data(inode.footer.ino, &mut filename);
432                } else {
433                    // Symlinks don't have a hash code, so we just use 0.
434                    let proxy_filename: String =
435                        fscrypt::proxy_filename::ProxyFilename::new_with_hash_code(0, &filename)
436                            .into();
437                    filename = proxy_filename.as_bytes().to_vec();
438                }
439                // Unfortunately, it seems we still have to remove trailing nulls.
440                // fscrypt + f2fs publishes a file size equal to padded symlink length + 2 bytes.
441                while let Some(0) = filename.last() {
442                    filename.pop();
443                }
444            }
445            Ok(filename.into_boxed_slice())
446        } else {
447            bail!("Not a valid symlink");
448        }
449    }
450
451    /// Reads and returns a data block of a file.
452    /// On success, this will return Some(Buffer) containing the data or None if the file is sparse.
453    pub async fn read_data(&self, inode: &Inode, block_num: u32) -> Result<Option<Vec<u8>>, Error> {
454        let inline_flags = inode.header.inline_flags;
455        ensure!(
456            !inline_flags.contains(crate::InlineFlags::Data),
457            "Can't use read_data() on inline file."
458        );
459        let block_addr = inode.data_block_addr(block_num);
460        if block_addr == NULL_ADDR || block_addr == NEW_ADDR {
461            // Treat as an empty page
462            return Ok(None);
463        }
464        let buffer = self.read_raw_block(block_addr).await?;
465        let mut data = buffer.to_vec();
466        if let Some(decryptor) = self.get_decryptor_for_inode(inode) {
467            decryptor.decrypt_data(inode.footer.ino, block_num, &mut data);
468        }
469        Ok(Some(data))
470    }
471}
472
473#[async_trait]
474impl Reader for F2fsReader {
475    /// `block_addr` is the physical block offset on the device.
476    async fn read_raw_block(&self, block_addr: u32) -> Result<Buffer<'_>, Error> {
477        if let Some(block) = self.cache.get_buffer(block_addr, self.device.as_ref()).await {
478            return Ok(block);
479        }
480
481        const READAHEAD: u64 = 16;
482        let end = std::cmp::min(block_addr as u64 + READAHEAD, self.device.block_count());
483        let count = end.saturating_sub(block_addr as u64).max(1) as usize;
484
485        let mut buffer = self.device.allocate_buffer(count * BLOCK_SIZE).await;
486        self.device
487            .read(block_addr as u64 * BLOCK_SIZE as u64, buffer.as_mut())
488            .await
489            .map_err(|_| anyhow!("device read failed"))?;
490
491        for i in 0..count {
492            let subslice = buffer.as_ptr_slice().subslice(i * BLOCK_SIZE..(i + 1) * BLOCK_SIZE);
493            self.cache.insert(block_addr + i as u32, subslice.to_vec());
494        }
495        Ok(self.cache.get_buffer(block_addr, self.device.as_ref()).await.unwrap())
496    }
497
498    async fn read_node(&self, nid: u32) -> Result<Buffer<'_>, Error> {
499        let nat_entry = self.get_nat_entry(nid).await?;
500        self.read_raw_block(nat_entry.block_addr).await
501    }
502
503    fn get_key(&self, identifier: &[u8; 16]) -> Option<&[u8; 64]> {
504        self.keys.get(identifier)
505    }
506
507    fn fs_uuid(&self) -> &[u8; 16] {
508        &self.superblock.uuid
509    }
510
511    async fn get_nat_entry(&self, nid: u32) -> Result<RawNatEntry, Error> {
512        if let Some(entry) = self.nat().nat_journal.get(&nid) {
513            return Ok(*entry);
514        }
515        let nat_block_addr = self.nat().get_nat_block_for_entry(nid)?;
516        let offset = self.nat().get_nat_block_offset_for_entry(nid);
517        let block = self.read_raw_block(nat_block_addr).await?;
518        let entry = block
519            .as_ptr_slice()
520            .subslice(offset..offset + std::mem::size_of::<RawNatEntry>())
521            .read::<RawNatEntry>()
522            .ok_or_else(|| anyhow!("Block size too small"))?;
523        Ok(entry)
524    }
525}
526
527#[cfg(test)]
528mod test {
529    use super::*;
530    use crate::dir::FileType;
531    use crate::{open_f2fs_test_image, xattr};
532    use std::collections::HashSet;
533    use std::path::PathBuf;
534    use std::sync::Arc;
535
536    #[fuchsia::test]
537    async fn test_open_fs() {
538        let device = open_f2fs_test_image();
539
540        let f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
541        // Root inode is a known constant.
542        assert_eq!(f2fs.root_ino(), 3);
543        let superblock = &f2fs.superblock;
544        let major_ver = superblock.major_ver;
545        let minor_ver = superblock.minor_ver;
546        assert_eq!(major_ver, 1);
547        assert_eq!(minor_ver, 16);
548        assert_eq!(superblock.get_total_size(), 256 << 20);
549        assert_eq!(superblock.get_volume_name().expect("get volume name"), "testimage");
550    }
551
552    // Helper method to walk paths.
553    async fn resolve_inode_path(f2fs: &F2fsReader, path: &str) -> Result<u32, Error> {
554        let path = PathBuf::from(path.strip_prefix("/").unwrap());
555        let mut ino = f2fs.root_ino();
556        for filename in &path {
557            let entries = f2fs.readdir(ino).await?;
558            if let Some(entry) = entries.iter().filter(|e| *e.filename == *filename).next() {
559                ino = entry.ino;
560            } else {
561                bail!("Not found.");
562            }
563        }
564        Ok(ino)
565    }
566
567    #[fuchsia::test]
568    async fn test_basic_dirs() {
569        let device = open_f2fs_test_image();
570
571        let f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
572        let root_ino = f2fs.root_ino();
573        let root_entries = f2fs.readdir(root_ino).await.expect("readdir");
574        assert_eq!(root_entries.len(), 7);
575        assert_eq!(root_entries[0].filename, "a");
576        assert_eq!(root_entries[0].file_type, FileType::Directory);
577        assert_eq!(root_entries[1].filename, "large_dir");
578        assert_eq!(root_entries[2].filename, "large_dir2");
579        assert_eq!(root_entries[3].filename, "sparse.dat");
580        assert_eq!(root_entries[4].filename, "verity");
581        assert_eq!(root_entries[5].filename, "fscrypt");
582        assert_eq!(root_entries[6].filename, "large_zero");
583
584        let inlined_file_ino =
585            resolve_inode_path(&f2fs, "/a/b/c/inlined").await.expect("resolve inlined");
586        let inode = Inode::try_load(&f2fs, inlined_file_ino).await.expect("load inode");
587        let block_size = inode.header.block_size;
588        let size = inode.header.size;
589        assert_eq!(block_size, 1);
590        assert_eq!(size, 12);
591        assert_eq!(inode.inline_data.unwrap().as_ref(), "inline_data\n".as_bytes());
592
593        const REG_FILE_SIZE: u64 = 8 * BLOCK_SIZE as u64 + 8;
594        const REG_FILE_BLOCKS: u64 = 9 + 1;
595        let regular_file_ino =
596            resolve_inode_path(&f2fs, "/a/b/c/regular").await.expect("resolve regular");
597        let inode = Inode::try_load(&f2fs, regular_file_ino).await.expect("load inode");
598        let block_size = inode.header.block_size;
599        let size = inode.header.size;
600        assert_eq!(block_size, REG_FILE_BLOCKS);
601        assert_eq!(size, REG_FILE_SIZE);
602        assert!(inode.inline_data.is_none());
603        for i in 0..8 {
604            assert_eq!(
605                f2fs.read_data(&inode, i).await.expect("read data").unwrap(),
606                vec![0u8; BLOCK_SIZE]
607            );
608        }
609        assert_eq!(
610            &f2fs.read_data(&inode, 8).await.expect("read data").unwrap()[..9],
611            b"01234567\0"
612        );
613
614        let symlink_ino =
615            resolve_inode_path(&f2fs, "/a/b/c/symlink").await.expect("resolve symlink");
616        let inode = Inode::try_load(&f2fs, symlink_ino).await.expect("load inode");
617        assert_eq!(f2fs.read_symlink(&inode).expect("read_symlink").as_ref(), b"regular");
618
619        let hardlink_ino =
620            resolve_inode_path(&f2fs, "/a/b/c/hardlink").await.expect("resolve hardlink");
621        let inode = Inode::try_load(&f2fs, hardlink_ino).await.expect("load inode");
622        let block_size = inode.header.block_size;
623        let size = inode.header.size;
624        assert_eq!(block_size, REG_FILE_BLOCKS);
625        assert_eq!(size, REG_FILE_SIZE);
626
627        let chowned_ino =
628            resolve_inode_path(&f2fs, "/a/b/c/chowned").await.expect("resolve chowned");
629        let inode = Inode::try_load(&f2fs, chowned_ino).await.expect("load inode");
630        let uid = inode.header.uid;
631        let gid = inode.header.gid;
632        assert_eq!(uid, 999);
633        assert_eq!(gid, 999);
634
635        let large_dir = resolve_inode_path(&f2fs, "/large_dir").await.expect("resolve large_dir");
636        assert_eq!(f2fs.readdir(large_dir).await.expect("readdir").len(), 2001);
637
638        let large_dir2 = resolve_inode_path(&f2fs, "/large_dir2").await.expect("resolve large_dir");
639        assert_eq!(f2fs.readdir(large_dir2).await.expect("readdir").len(), 1);
640
641        let sparse_dat =
642            resolve_inode_path(&f2fs, "/sparse.dat").await.expect("resolve sparse.dat");
643        let inode = Inode::try_load(&f2fs, sparse_dat).await.expect("load inode");
644        let data_blocks: Vec<_> = inode.data_blocks().into_iter().collect();
645        assert_eq!(data_blocks.len(), 6);
646        assert_eq!(data_blocks[0].logical_block_num, 0);
647        assert_eq!(data_blocks[0].length, 1);
648        // Raw read of block.
649        let block =
650            f2fs.read_raw_block(data_blocks[0].physical_block_num).await.expect("read sparse");
651        assert_eq!(&block.to_vec()[..3], b"foo");
652        // The following chain of blocks are designed to land in each of the self.nids[] ranges.
653        assert_eq!(data_blocks[1].logical_block_num, 923);
654        assert_eq!(data_blocks[1].length, 1);
655        assert_eq!(data_blocks[2].logical_block_num, 1941);
656        assert_eq!(data_blocks[2].length, 1);
657        assert_eq!(data_blocks[3].logical_block_num, 2959);
658        assert_eq!(data_blocks[3].length, 1);
659        assert_eq!(data_blocks[4].logical_block_num, 1039283);
660        assert_eq!(data_blocks[4].length, 1);
661        assert_eq!(data_blocks[5].logical_block_num, 104671683);
662        assert_eq!(data_blocks[5].length, 2);
663        let block =
664            f2fs.read_raw_block(data_blocks[5].physical_block_num).await.expect("read sparse");
665        assert_eq!(block.to_vec(), vec![0; BLOCK_SIZE]);
666        // Exercise helper method to read block.
667        assert_eq!(
668            &f2fs.read_data(&inode, 104671684).await.expect("read data block").unwrap()[..3],
669            b"bar"
670        );
671        // Exercise helper method on zero page. Expect to get back 'None'.
672        assert!(f2fs.read_data(&inode, 104671684 - 10).await.expect("read data block").is_none());
673    }
674
675    #[fuchsia::test]
676    async fn test_xattr() {
677        let device = open_f2fs_test_image();
678
679        let f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
680        let sparse_dat =
681            resolve_inode_path(&f2fs, "/sparse.dat").await.expect("resolve sparse.dat");
682        let inode = Inode::try_load(&f2fs, sparse_dat).await.expect("load inode");
683        assert_eq!(
684            inode.xattr,
685            vec![
686                xattr::XattrEntry {
687                    index: xattr::Index::User,
688                    name: Box::new(b"a".to_owned()),
689                    value: Box::new(b"value".to_owned())
690                },
691                xattr::XattrEntry {
692                    index: xattr::Index::User,
693                    name: Box::new(b"c".to_owned()),
694                    value: Box::new(b"value".to_owned())
695                },
696                xattr::XattrEntry {
697                    index: xattr::Index::User,
698                    name: Box::new(b"padding_test_1".to_owned()),
699                    value: Box::new(b"v".to_owned())
700                },
701                xattr::XattrEntry {
702                    index: xattr::Index::User,
703                    name: Box::new(b"padding_test_2".to_owned()),
704                    value: Box::new(b"va".to_owned())
705                },
706                xattr::XattrEntry {
707                    index: xattr::Index::User,
708                    name: Box::new(b"padding_test_3".to_owned()),
709                    value: Box::new(b"val".to_owned())
710                },
711                xattr::XattrEntry {
712                    index: xattr::Index::User,
713                    name: Box::new(b"padding_test_4".to_owned()),
714                    value: Box::new(b"valu".to_owned())
715                },
716                xattr::XattrEntry {
717                    index: xattr::Index::User,
718                    name: Box::new(b"padding_test_5".to_owned()),
719                    value: Box::new(b"value".to_owned())
720                },
721            ]
722        );
723    }
724
725    #[fuchsia::test]
726    async fn test_fsverity() {
727        let device = open_f2fs_test_image();
728        let mut f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
729        f2fs.add_key(&[0u8; 64]);
730        let verity_files = vec![
731            "/verity/inlined",
732            "/verity/regular",
733            "/verity/merkle_layers.dat",
734            "/fscrypt/a/b/regular",
735        ];
736        for file_path in verity_files {
737            let file = resolve_inode_path(&f2fs, file_path).await.expect("resolve file");
738            let inode = Inode::try_load(&f2fs, file).await.expect("load inode");
739            assert!(inode.header.advise_flags.contains(inode::AdviseFlags::Verity));
740        }
741        // Verify other files aren't marked for verity.
742        let file = resolve_inode_path(&f2fs, "/a/b/c/regular").await.expect("resolve file");
743        let inode = Inode::try_load(&f2fs, file).await.expect("load inode");
744        assert!(!inode.header.advise_flags.contains(inode::AdviseFlags::Verity));
745        // TODO(https://fxbug.dev/399727919): Handle the verity descriptor and merkle tree parsing.
746    }
747
748    #[fuchsia::test]
749    async fn test_fbe() {
750        // Note: The synthetic filenames below are based on the nonce generated at file/directory
751        // creation time. This will differ each time a new image is generated.
752        // They can be extracted with a simple 'ls -l' by mounting the generated image. i.e.
753        //   $ zstd -d testdata/f2fs.img.st
754        //   $ sudo mount testdata/f2fs.img /mnt
755        //   $ ls /mnt/fscrypt -lR
756
757        // /fscrypt/<a>/<b>/<symlink>
758        let str_a = "AlHTPgAAAAATu-OD4ljvFNw4Xpas_OeI";
759        let str_b = "GeiwsgAAAADc8JQtaJ7UbZ0GcT5yeHTZ";
760        let str_symlink = "QL5PAgAAAAAZjFRhVvAC80KXi6rlzmfr";
761        let bytes_symlink_content = b"AAAAAAAAAACavudfzv7yT0fMluSoe0NC";
762
763        let mut expected: HashSet<_> = [
764            // files in fscrypt/ dir.
765            "0KaBCgAAAADG-AqRst0R8y9D-kCCD14F",
766            "9g1xNQAAAAC2nhbquKF00IMYQ7Rbv25_",
767            "a5gOXwAAAADZWT1MUGPQdgNHBaXlkhT7",
768            "AlHTPgAAAAATu-OD4ljvFNw4Xpas_OeI",
769            "Jn1pZAAAAAB3bJB-bzY1dlQxj0NSkyU_u0-fevU6zycmvnjbTtHRwm3od3n2wl621OGeZhjQSYn2HlSwshPGwIzUQVeCv0Zb247T_qPD0EiM4PcaKLrr6gSt7-PrSVC2R9EsCKj8yAnwvi2bJKJvnghFE8wLV6pLN11nmbOI9q6yDB1ELRj2l2yke4iH_9zOSD-8PvBySzdx3L-h-V79-T0EFAOvlVLlqVfMIR9xgsXwe_xpjgHsFculb9Le",
770            "qHmmggAAAAAwObeaSgYbdMa0L9iXDYIN",
771            "UJqOTwAAAACAFLttEsV6RiVStfTM6q94",
772            "VERkwQAAAADytl0b_Ou0EoBXyYA9e_qI",
773            "WPM5KgAAAACfvfszUbrR943loZZ-__gO",
774        ]
775        .into_iter()
776        .collect();
777
778        let device = open_f2fs_test_image();
779
780        let mut f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
781
782        // First without the key...
783        // (The filenames below have been extracted from the generated image by
784        // mounting it and manually inspecting.)
785        resolve_inode_path(&f2fs, "/fscrypt/a/b/regular")
786            .await
787            .expect_err("resolve fscrypt regular");
788        let fscrypt_dir_ino =
789            resolve_inode_path(&f2fs, "/fscrypt").await.expect("resolve encrypted dir");
790        let entries = f2fs.readdir(fscrypt_dir_ino).await.expect("readdir");
791        println!("entries {entries:?}");
792
793        for entry in entries {
794            assert!(expected.remove(entry.filename.as_str()), "unexpected entry {entry:?}");
795        }
796        assert!(expected.is_empty());
797
798        resolve_inode_path(&f2fs, &format!("/fscrypt/{str_a}"))
799            .await
800            .expect("resolve encrypted dir");
801        let enc_symlink_ino =
802            resolve_inode_path(&f2fs, &format!("/fscrypt/{str_a}/{str_b}/{str_symlink}"))
803                .await
804                .expect("resolve encrypted symlink");
805        let symlink_inode =
806            Inode::try_load(&f2fs, enc_symlink_ino).await.expect("load symlink inode");
807        assert_eq!(
808            &*f2fs.read_symlink(&symlink_inode).expect("read_symlink"),
809            bytes_symlink_content
810        );
811
812        // ...now try with the key
813        f2fs.add_key(&[0u8; 64]);
814        resolve_inode_path(&f2fs, "/fscrypt/a/b/regular").await.expect("resolve fscrypt regular");
815        let inlined_ino = resolve_inode_path(&f2fs, "/fscrypt/a/b/inlined")
816            .await
817            .expect("resolve fscrypt inlined");
818        let short_file = Inode::try_load(&f2fs, inlined_ino).await.expect("load symlink inode");
819        assert!(
820            !short_file.header.inline_flags.contains(inode::InlineFlags::Data),
821            "encrypted files shouldn't be inlined"
822        );
823        let short_data =
824            f2fs.read_data(&short_file, 0).await.expect("read_data").expect("non-empty page");
825        assert_eq!(&short_data[..short_file.header.size as usize], b"test45678abcdef_12345678");
826
827        let symlink_ino = resolve_inode_path(&f2fs, "/fscrypt/a/b/symlink")
828            .await
829            .expect("resolve fscrypt symlink");
830        assert_eq!(symlink_ino, enc_symlink_ino);
831
832        let symlink_inode = Inode::try_load(&f2fs, symlink_ino).await.expect("load symlink inode");
833        let symlink = f2fs.read_symlink(&symlink_inode).expect("read_symlink");
834        assert_eq!(*symlink, *b"inlined");
835    }
836
837    #[fuchsia::test]
838    async fn test_summary_block_addr() {
839        let device = open_f2fs_test_image();
840        let mut f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
841
842        // Case 1: No Orphan Flag
843        f2fs.checkpoint.header.ckpt_flags = 0; // Clear all
844        f2fs.checkpoint.header.cp_pack_start_sum = 100;
845        let base = f2fs.checkpoint_start_addr();
846        assert_eq!(f2fs.summary_block_addr(), base + 100);
847
848        // Case 2: With Orphan Flag (cp_pack_start_sum already points to summary block)
849        f2fs.checkpoint.header.ckpt_flags = CP_ORPHAN_PRESENT_FLAG;
850        assert_eq!(f2fs.summary_block_addr(), base + 100);
851
852        // Case 3: Compact Summary + Orphan
853        f2fs.checkpoint.header.ckpt_flags = CP_ORPHAN_PRESENT_FLAG | CKPT_FLAG_COMPACT_SUMMARY;
854        assert_eq!(f2fs.summary_block_addr(), base + 100);
855    }
856
857    #[fuchsia::test]
858    async fn test_orphan_inodes() {
859        let device = open_f2fs_test_image();
860        let f2fs = F2fsReader::open_device(Arc::new(device)).await.expect("open ok");
861        assert_eq!(f2fs.orphan_inodes().len(), 3);
862        for &ino in f2fs.orphan_inodes() {
863            assert!(f2fs.is_orphan(ino));
864        }
865        assert!(!f2fs.is_orphan(f2fs.root_ino()));
866        assert!(!f2fs.is_orphan(99999));
867
868        // Invariant: Orphan files are unlinked and therefore must not appear in directory dentries.
869        let entries = f2fs.readdir(f2fs.root_ino()).await.expect("readdir ok");
870        for entry in entries {
871            assert!(
872                !f2fs.is_orphan(entry.ino),
873                "Orphan inode {} unexpectedly found in root dentry",
874                entry.ino
875            );
876        }
877    }
878}