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, BLOCKS_PER_SEGMENT, 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;
13pub const ORPHANS_PER_BLOCK: usize = 1020;
14
15#[derive(Clone, Copy, Debug, Eq, PartialEq, FromBytes, Immutable, IntoBytes, KnownLayout)]
16#[repr(C, packed)]
17pub struct OrphanBlock {
18    pub ino: [u32; ORPHANS_PER_BLOCK],
19    pub reserved: u32,
20    pub blk_addr: u16,
21    pub blk_count: u16,
22    pub entry_count: u32,
23    pub check_sum: u32,
24}
25
26#[derive(Debug, Eq, PartialEq, FromBytes, Immutable, IntoBytes, KnownLayout)]
27#[repr(C, packed)]
28pub struct CheckpointHeader {
29    pub checkpoint_ver: u64,
30    pub user_block_count: u64,
31    pub valid_block_count: u64,
32    pub rsvd_segment_count: u32,
33    pub overprov_segment_count: u32,
34    pub free_segment_count: u32,
35    pub cur_node_segno: [u32; MAX_ACTIVE_NODE_LOGS],
36    pub cur_node_blkoff: [u16; MAX_ACTIVE_NODE_LOGS],
37    pub cur_data_segno: [u32; MAX_ACTIVE_DATA_LOGS],
38    pub cur_data_blkoff: [u16; MAX_ACTIVE_DATA_LOGS],
39    pub ckpt_flags: u32,
40    pub cp_pack_total_block_count: u32,
41    pub cp_pack_start_sum: u32,
42    pub valid_node_count: u32,
43    pub valid_inode_count: u32,
44    pub next_free_nid: u32,
45    pub sit_ver_bitmap_bytesize: u32,
46    pub nat_ver_bitmap_bytesize: u32,
47    pub checksum_offset: u32,
48    pub elapsed_time: u64,
49    pub alloc_type: [u8; MAX_ACTIVE_LOGS],
50    // SIT bitmap follows.
51    // NAT bitmap follows.
52}
53
54#[derive(Debug)]
55pub struct CheckpointPack {
56    pub header: CheckpointHeader,
57    pub nat_bitmap: Vec<u8>,
58}
59
60pub const MAX_BITMAP_BYTES: usize =
61    BLOCK_SIZE - std::mem::size_of::<u32>() - std::mem::size_of::<CheckpointHeader>(); // 3900
62
63impl CheckpointPack {
64    pub async fn read_from_device(
65        device: &dyn storage_device::Device,
66        offset: u64,
67        cp_payload: u32,
68    ) -> Result<Self, Error> {
69        let mut segment = device.allocate_buffer(SEGMENT_SIZE).await;
70        device.read(offset, segment.as_mut()).await?;
71        let data = segment.to_vec();
72        Self::parse_checkpoint(&data, cp_payload)
73    }
74
75    pub fn parse_checkpoint(segment: &[u8], cp_payload: u32) -> Result<Self, Error> {
76        ensure!(
77            (cp_payload as usize) <= BLOCKS_PER_SEGMENT - 3,
78            "cp_payload exceeds segment capacity"
79        );
80        ensure!(
81            segment.len() >= std::mem::size_of::<CheckpointHeader>(),
82            "Segment too short for checkpoint"
83        );
84        let header =
85            CheckpointHeader::read_from_bytes(&segment[..std::mem::size_of::<CheckpointHeader>()])
86                .map_err(|_| anyhow!("Invalid checkpoint header"))?;
87        ensure!(
88            header.cp_pack_total_block_count > 0
89                && (header.cp_pack_total_block_count as usize) <= BLOCKS_PER_SEGMENT,
90            "Invalid cp_pack_total_block_count"
91        );
92        ensure!(
93            segment.len() >= header.cp_pack_total_block_count as usize * BLOCK_SIZE,
94            "Segment too short for checkpoint pack"
95        );
96        let len = header.checksum_offset as usize;
97        ensure!(len == BLOCK_SIZE - std::mem::size_of::<u32>(), "Bad checkpoint offset");
98        #[cfg(not(fuzz))]
99        {
100            let mut checksum: u32 = 0;
101            checksum
102                .as_mut_bytes()
103                .copy_from_slice(&segment[len..len + std::mem::size_of::<u32>()]);
104            let crc32 = f2fs_crc32(F2FS_MAGIC, &segment[..len]);
105            ensure!(crc32 == checksum, "Bad Checkpoint checksum ({crc32:08x} != {checksum:08x})");
106        }
107        let sit_ver_bitmap_bytesize = header.sit_ver_bitmap_bytesize as usize;
108        let nat_ver_bitmap_bytesize = header.nat_ver_bitmap_bytesize as usize;
109        ensure!(sit_ver_bitmap_bytesize > 0, "Invalid sit_bitmap size");
110        ensure!(nat_ver_bitmap_bytesize > 0, "Invalid nat_bitmap size");
111        ensure!(
112            nat_ver_bitmap_bytesize <= MAX_BITMAP_BYTES,
113            "NAT bitmap exceeds checkpoint capacity"
114        );
115
116        let nat_bitmap = if cp_payload == 0 {
117            ensure!(
118                sit_ver_bitmap_bytesize + nat_ver_bitmap_bytesize <= MAX_BITMAP_BYTES,
119                "SIT and NAT bitmaps exceed checkpoint capacity"
120            );
121            let nat_bitmap_start =
122                std::mem::size_of::<CheckpointHeader>() + sit_ver_bitmap_bytesize;
123            let nat_bitmap_end = nat_bitmap_start + nat_ver_bitmap_bytesize;
124            segment[nat_bitmap_start..nat_bitmap_end].to_vec()
125        } else {
126            ensure!(
127                sit_ver_bitmap_bytesize <= cp_payload as usize * BLOCK_SIZE,
128                "SIT bitmap exceeds cp_payload capacity"
129            );
130            let nat_bitmap_start = std::mem::size_of::<CheckpointHeader>();
131            let nat_bitmap_end = nat_bitmap_start + nat_ver_bitmap_bytesize;
132            segment[nat_bitmap_start..nat_bitmap_end].to_vec()
133        };
134
135        ensure!(header.cp_pack_start_sum > cp_payload, "cp_pack_start_sum too small");
136        ensure!(
137            header.cp_pack_start_sum < header.cp_pack_total_block_count,
138            "cp_pack_start_sum exceeds total blocks in checkpoint pack"
139        );
140        let backup_header_offset =
141            (header.cp_pack_total_block_count as usize - 1) * BLOCK_SIZE as usize;
142        let backup_header = CheckpointHeader::read_from_bytes(
143            &segment[backup_header_offset
144                ..backup_header_offset + std::mem::size_of::<CheckpointHeader>()],
145        )
146        .map_err(|_| anyhow!("Invalid backup header"))?;
147        // If the backup copy is bad, fail this checkpoint (same as f2fs Fuchsia).
148        ensure!(backup_header == header, "CheckpointHeader and backup differ");
149        Ok(Self { header, nat_bitmap })
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    // Some basic robustness coverage.
158    #[test]
159    fn test_checkpoint_parsing() {
160        assert!(CheckpointPack::parse_checkpoint(&[], 0).is_err());
161
162        let mut segment = Vec::new();
163        segment.resize(SEGMENT_SIZE, 0);
164        let mut header =
165            CheckpointHeader::read_from_bytes(&segment[..std::mem::size_of::<CheckpointHeader>()])
166                .unwrap();
167        header.checksum_offset = (BLOCK_SIZE - 4) as u32;
168        header.cp_pack_start_sum = 1;
169        header.cp_pack_total_block_count = 100;
170
171        // helper to copy header into segment and set the checksum to a valid value.
172        let set_header = |segment: &mut [u8], header: &CheckpointHeader| {
173            segment[..std::mem::size_of::<CheckpointHeader>()].copy_from_slice(header.as_bytes());
174            let crc32 = f2fs_crc32(F2FS_MAGIC, &segment[..header.checksum_offset as usize]);
175            segment[header.checksum_offset as usize..header.checksum_offset as usize + 4]
176                .copy_from_slice(crc32.as_bytes());
177        };
178
179        // Bad checksum offset.
180        {
181            header.checksum_offset = SEGMENT_SIZE as u32 - 3;
182            segment[..std::mem::size_of::<CheckpointHeader>()].copy_from_slice(header.as_bytes());
183            assert!(CheckpointPack::parse_checkpoint(&segment, 0).is_err());
184        }
185        // Bad SIT size.
186        {
187            header.checksum_offset = (BLOCK_SIZE - 4) as u32;
188            header.sit_ver_bitmap_bytesize = SEGMENT_SIZE as u32;
189            set_header(&mut segment, &header);
190            assert!(CheckpointPack::parse_checkpoint(&segment, 0).is_err());
191        }
192        // Bad NAT size.
193        {
194            header.sit_ver_bitmap_bytesize = 64;
195            header.nat_ver_bitmap_bytesize = SEGMENT_SIZE as u32;
196            set_header(&mut segment, &header);
197            assert!(CheckpointPack::parse_checkpoint(&segment, 0).is_err());
198        }
199        // Bad SIT+NAT size when cp_payload == 0 (e.g. 2000 + 2000 > 3900).
200        {
201            header.sit_ver_bitmap_bytesize = 2000;
202            header.nat_ver_bitmap_bytesize = 2000;
203            set_header(&mut segment, &header);
204            assert!(CheckpointPack::parse_checkpoint(&segment, 0).is_err());
205        }
206        // Bad cp_pack_total_block_count (more than one segment).
207        {
208            header.sit_ver_bitmap_bytesize = 64;
209            header.nat_ver_bitmap_bytesize = 256;
210            header.cp_pack_total_block_count = 2048;
211            set_header(&mut segment, &header);
212            assert!(CheckpointPack::parse_checkpoint(&segment, 0).is_err());
213        }
214        // Bad checksum offset in block 0 (e.g. pointing into block 1 or 2).
215        {
216            header.checksum_offset = 8192 - 4;
217            header.cp_pack_total_block_count = 100;
218            set_header(&mut segment, &header);
219            assert!(CheckpointPack::parse_checkpoint(&segment, 0).is_err());
220        }
221        // Bad cp_pack_start_sum (<= cp_payload).
222        {
223            header.checksum_offset = (BLOCK_SIZE - 4) as u32;
224            header.cp_pack_start_sum = 0;
225            set_header(&mut segment, &header);
226            assert!(CheckpointPack::parse_checkpoint(&segment, 0).is_err());
227        }
228        // Bad cp_pack_start_sum (>= cp_pack_total_block_count).
229        {
230            header.cp_pack_start_sum = 100;
231            header.cp_pack_total_block_count = 100;
232            set_header(&mut segment, &header);
233            assert!(CheckpointPack::parse_checkpoint(&segment, 0).is_err());
234        }
235        // Success when cp_payload == 0.
236        {
237            header.checksum_offset = (BLOCK_SIZE - 4) as u32;
238            header.sit_ver_bitmap_bytesize = 64;
239            header.nat_ver_bitmap_bytesize = 256;
240            header.cp_pack_start_sum = 1;
241            header.cp_pack_total_block_count = 100;
242            set_header(&mut segment, &header);
243            segment.copy_within(..std::mem::size_of::<CheckpointHeader>(), BLOCK_SIZE * 99);
244            let result = CheckpointPack::parse_checkpoint(&segment, 0);
245            assert!(result.is_ok(), "{:?}", result);
246        }
247        // Bad cp_payload (> BLOCKS_PER_SEGMENT - 3).
248        {
249            assert!(CheckpointPack::parse_checkpoint(&segment, 1000).is_err());
250        }
251        // Success when cp_payload > 0 (NAT bitmap read from start of bitmap area).
252        {
253            header.sit_ver_bitmap_bytesize = 4096;
254            header.nat_ver_bitmap_bytesize = 256;
255            header.cp_pack_start_sum = 2;
256            header.cp_pack_total_block_count = 100;
257            // Write a test pattern to NAT bitmap location (offset 192).
258            let nat_start = std::mem::size_of::<CheckpointHeader>();
259            segment[nat_start..nat_start + 256].fill(0x5A);
260            set_header(&mut segment, &header);
261            segment.copy_within(..std::mem::size_of::<CheckpointHeader>(), BLOCK_SIZE * 99);
262            let result = CheckpointPack::parse_checkpoint(&segment, 1);
263            assert!(result.is_ok(), "{:?}", result);
264            assert_eq!(result.unwrap().nat_bitmap, vec![0x5A; 256]);
265        }
266        // Truncated slice smaller than BLOCK_SIZE does not panic.
267        {
268            assert!(CheckpointPack::parse_checkpoint(&segment[..512], 0).is_err());
269        }
270        // Truncated slice smaller than cp_pack_total_block_count * BLOCK_SIZE.
271        {
272            assert!(CheckpointPack::parse_checkpoint(&segment[..BLOCK_SIZE * 50], 0).is_err());
273        }
274    }
275}