Skip to main content

f2fs_reader/
checkpoint.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::superblock::{BLOCK_SIZE, F2FS_MAGIC, SEGMENT_SIZE, f2fs_crc32};
5use anyhow::{Error, anyhow, ensure};
6use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
7
8const MAX_ACTIVE_NODE_LOGS: usize = 8;
9const MAX_ACTIVE_DATA_LOGS: usize = 8;
10const MAX_ACTIVE_LOGS: usize = 16;
11pub const CP_ORPHAN_PRESENT_FLAG: u32 = 0x2;
12pub const CKPT_FLAG_COMPACT_SUMMARY: u32 = 0x4;
13
14#[derive(Debug, Eq, PartialEq, FromBytes, Immutable, IntoBytes, KnownLayout)]
15#[repr(C, packed)]
16pub struct CheckpointHeader {
17    pub checkpoint_ver: u64,
18    pub user_block_count: u64,
19    pub valid_block_count: u64,
20    pub rsvd_segment_count: u32,
21    pub overprov_segment_count: u32,
22    pub free_segment_count: u32,
23    pub cur_node_segno: [u32; MAX_ACTIVE_NODE_LOGS],
24    pub cur_node_blkoff: [u16; MAX_ACTIVE_NODE_LOGS],
25    pub cur_data_segno: [u32; MAX_ACTIVE_DATA_LOGS],
26    pub cur_data_blkoff: [u16; MAX_ACTIVE_DATA_LOGS],
27    pub ckpt_flags: u32,
28    pub cp_pack_total_block_count: u32,
29    pub cp_pack_start_sum: u32,
30    pub valid_node_count: u32,
31    pub valid_inode_count: u32,
32    pub next_free_nid: u32,
33    pub sit_ver_bitmap_bytesize: u32,
34    pub nat_ver_bitmap_bytesize: u32,
35    pub checksum_offset: u32,
36    pub elapsed_time: u64,
37    pub alloc_type: [u8; MAX_ACTIVE_LOGS],
38    // SIT bitmap follows.
39    // NAT bitmap follows.
40}
41
42#[derive(Debug)]
43pub struct CheckpointPack {
44    pub header: CheckpointHeader,
45    pub nat_bitmap: Vec<u8>,
46}
47
48impl CheckpointPack {
49    pub async fn read_from_device(
50        device: &dyn storage_device::Device,
51        offset: u64,
52    ) -> Result<Self, Error> {
53        let mut segment = device.allocate_buffer(SEGMENT_SIZE).await;
54        device.read(offset, segment.as_mut()).await?;
55        let segment = segment.as_slice();
56        Self::parse_checkpoint(segment)
57    }
58
59    fn parse_checkpoint(segment: &[u8]) -> Result<Self, Error> {
60        ensure!(
61            segment.len() >= std::mem::size_of::<CheckpointHeader>(),
62            "Segment too short for checkpoint"
63        );
64        let header =
65            CheckpointHeader::read_from_bytes(&segment[..std::mem::size_of::<CheckpointHeader>()])
66                .map_err(|_| anyhow!("Invalid checkpoint header"))?;
67        let len = header.checksum_offset as usize;
68        ensure!(len <= segment.len() - std::mem::size_of::<u32>(), "Bad checkpoint offset");
69        #[cfg(not(fuzz))]
70        {
71            let mut checksum: u32 = 0;
72            checksum
73                .as_mut_bytes()
74                .copy_from_slice(&segment[len..len + std::mem::size_of::<u32>()]);
75            let crc32 = f2fs_crc32(F2FS_MAGIC, &segment[..len]);
76            ensure!(crc32 == checksum, "Bad Checkpoint checksum ({crc32:08x} != {checksum:08x})");
77        }
78        let sit_bitmap_start = std::mem::size_of::<CheckpointHeader>();
79        let nat_bitmap_start = sit_bitmap_start + header.sit_ver_bitmap_bytesize as usize;
80        let nat_bitmap_end = nat_bitmap_start + header.nat_ver_bitmap_bytesize as usize;
81        ensure!(sit_bitmap_start < nat_bitmap_start, "Invalid sit_bitmap size");
82        ensure!(nat_bitmap_start < nat_bitmap_end, "Invalid nat_bitmap size");
83        ensure!(nat_bitmap_end <= SEGMENT_SIZE, "Invalid nat_bitmap range");
84        let nat_bitmap = segment[nat_bitmap_start..nat_bitmap_end].to_vec();
85
86        ensure!(
87            header.cp_pack_total_block_count > 0,
88            "Invalid cp_pack_total_block_count (must be > 0)"
89        );
90        let backup_header_offset =
91            (header.cp_pack_total_block_count as usize - 1) * BLOCK_SIZE as usize;
92        ensure!(
93            backup_header_offset + std::mem::size_of::<CheckpointHeader>() <= SEGMENT_SIZE,
94            "Invalid cp_pack_total_block_count"
95        );
96        let backup_header = CheckpointHeader::read_from_bytes(
97            &segment[backup_header_offset
98                ..backup_header_offset + std::mem::size_of::<CheckpointHeader>()],
99        )
100        .map_err(|_| anyhow!("Invalid backup header"))?;
101        // If the backup copy is bad, fail this checkpoint (same as f2fs Fuchsia).
102        ensure!(backup_header == header, "CheckpointHeader and backup differ");
103        Ok(Self { header, nat_bitmap })
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    // Some basic robustness coverage.
112    #[test]
113    fn test_checkpoint_parsing() {
114        assert!(CheckpointPack::parse_checkpoint(&[]).is_err());
115
116        let mut segment = Vec::new();
117        segment.resize(SEGMENT_SIZE, 0);
118        let mut header =
119            CheckpointHeader::read_from_bytes(&segment[..std::mem::size_of::<CheckpointHeader>()])
120                .unwrap();
121
122        // helper to copy header into segment and set the checksum to a valid value.
123        let set_header = |segment: &mut [u8], header: &CheckpointHeader| {
124            segment[..std::mem::size_of::<CheckpointHeader>()].copy_from_slice(header.as_bytes());
125            let crc32 = f2fs_crc32(F2FS_MAGIC, &segment[..header.checksum_offset as usize]);
126            segment[header.checksum_offset as usize..header.checksum_offset as usize + 4]
127                .copy_from_slice(crc32.as_bytes());
128        };
129
130        // Bad checksum offset.
131        {
132            header.checksum_offset = SEGMENT_SIZE as u32 - 3;
133            segment[..std::mem::size_of::<CheckpointHeader>()].copy_from_slice(header.as_bytes());
134            assert!(CheckpointPack::parse_checkpoint(&segment).is_err());
135        }
136        // Bad SIT size.
137        {
138            header.checksum_offset = std::mem::size_of::<CheckpointHeader>() as u32;
139            header.sit_ver_bitmap_bytesize = SEGMENT_SIZE as u32;
140            set_header(&mut segment, &header);
141            assert!(CheckpointPack::parse_checkpoint(&segment).is_err());
142        }
143        // Bad NAT size.
144        {
145            header.sit_ver_bitmap_bytesize = 0;
146            header.nat_ver_bitmap_bytesize = SEGMENT_SIZE as u32;
147            set_header(&mut segment, &header);
148            assert!(CheckpointPack::parse_checkpoint(&segment).is_err());
149        }
150        // Bad SIT+NAT size.
151        {
152            header.sit_ver_bitmap_bytesize = SEGMENT_SIZE as u32 / 2;
153            header.nat_ver_bitmap_bytesize = SEGMENT_SIZE as u32 / 2;
154            set_header(&mut segment, &header);
155            assert!(CheckpointPack::parse_checkpoint(&segment).is_err());
156        }
157        // Bad cp_pack_total_block_count (more than one segment).
158        {
159            header.sit_ver_bitmap_bytesize = SEGMENT_SIZE as u32 / 4;
160            header.nat_ver_bitmap_bytesize = SEGMENT_SIZE as u32 / 4;
161            header.cp_pack_total_block_count = 2048;
162            set_header(&mut segment, &header);
163            assert!(CheckpointPack::parse_checkpoint(&segment).is_err());
164        }
165        // Different backup checkpoint.
166        {
167            header.cp_pack_total_block_count = 100;
168            set_header(&mut segment, &header);
169            assert!(CheckpointPack::parse_checkpoint(&segment).is_err());
170        }
171        // Success.
172        {
173            segment.copy_within(..std::mem::size_of::<CheckpointHeader>(), BLOCK_SIZE * 99);
174            let result = CheckpointPack::parse_checkpoint(&segment);
175            assert!(result.is_ok(), "{:?}", result);
176        }
177    }
178}