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::{f2fs_crc32, BLOCK_SIZE, F2FS_MAGIC, SEGMENT_SIZE};
5use anyhow::{anyhow, ensure, Error};
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 CKPT_FLAG_UNMOUNT: u32 = 0x1;
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 mut checksum: u32 = 0;
68        let len = header.checksum_offset as usize;
69        ensure!(len <= segment.len() - std::mem::size_of::<u32>(), "Bad checkpoint offset");
70        checksum.as_mut_bytes().copy_from_slice(&segment[len..len + std::mem::size_of::<u32>()]);
71        let crc32 = f2fs_crc32(F2FS_MAGIC, &segment[..len]);
72        ensure!(crc32 == checksum, "Bad Checkpoint checksum ({crc32:08x} != {checksum:08x})");
73        let sit_bitmap_start = std::mem::size_of::<CheckpointHeader>();
74        let nat_bitmap_start = sit_bitmap_start + header.sit_ver_bitmap_bytesize as usize;
75        let nat_bitmap_end = nat_bitmap_start + header.nat_ver_bitmap_bytesize as usize;
76        ensure!(sit_bitmap_start < nat_bitmap_start, "Invalid sit_bitmap size");
77        ensure!(nat_bitmap_start < nat_bitmap_end, "Invalid nat_bitmap size");
78        ensure!(nat_bitmap_end <= SEGMENT_SIZE, "Invalid nat_bitmap range");
79        let nat_bitmap = segment[nat_bitmap_start..nat_bitmap_end].to_vec();
80
81        let backup_header_offset =
82            (header.cp_pack_total_block_count as usize - 1) * BLOCK_SIZE as usize;
83        ensure!(
84            backup_header_offset + std::mem::size_of::<CheckpointHeader>() < SEGMENT_SIZE,
85            "Invalid cp_pack_total_block_count"
86        );
87        let backup_header = CheckpointHeader::read_from_bytes(
88            &segment[backup_header_offset
89                ..backup_header_offset + std::mem::size_of::<CheckpointHeader>()],
90        )
91        .map_err(|_| anyhow!("Invalid backup header"))?;
92        // If the backup copy is bad, fail this checkpoint (same as f2fs Fuchsia).
93        ensure!(backup_header == header, "CheckpointHeader and backup differ");
94        Ok(Self { header, nat_bitmap })
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    // Some basic robustness coverage.
103    #[test]
104    fn test_checkpoint_parsing() {
105        assert!(CheckpointPack::parse_checkpoint(&[]).is_err());
106
107        let mut segment = Vec::new();
108        segment.resize(SEGMENT_SIZE, 0);
109        let mut header =
110            CheckpointHeader::read_from_bytes(&segment[..std::mem::size_of::<CheckpointHeader>()])
111                .unwrap();
112
113        // helper to copy header into segment and set the checksum to a valid value.
114        let set_header = |segment: &mut [u8], header: &CheckpointHeader| {
115            segment[..std::mem::size_of::<CheckpointHeader>()].copy_from_slice(header.as_bytes());
116            let crc32 = f2fs_crc32(F2FS_MAGIC, &segment[..header.checksum_offset as usize]);
117            segment[header.checksum_offset as usize..header.checksum_offset as usize + 4]
118                .copy_from_slice(crc32.as_bytes());
119        };
120
121        // Bad checksum offset.
122        {
123            header.checksum_offset = SEGMENT_SIZE as u32 - 3;
124            segment[..std::mem::size_of::<CheckpointHeader>()].copy_from_slice(header.as_bytes());
125            assert!(CheckpointPack::parse_checkpoint(&segment).is_err());
126        }
127        // Bad SIT size.
128        {
129            header.checksum_offset = std::mem::size_of::<CheckpointHeader>() as u32;
130            header.sit_ver_bitmap_bytesize = SEGMENT_SIZE as u32;
131            set_header(&mut segment, &header);
132            assert!(CheckpointPack::parse_checkpoint(&segment).is_err());
133        }
134        // Bad NAT size.
135        {
136            header.sit_ver_bitmap_bytesize = 0;
137            header.nat_ver_bitmap_bytesize = SEGMENT_SIZE as u32;
138            set_header(&mut segment, &header);
139            assert!(CheckpointPack::parse_checkpoint(&segment).is_err());
140        }
141        // Bad SIT+NAT size.
142        {
143            header.sit_ver_bitmap_bytesize = SEGMENT_SIZE as u32 / 2;
144            header.nat_ver_bitmap_bytesize = SEGMENT_SIZE as u32 / 2;
145            set_header(&mut segment, &header);
146            assert!(CheckpointPack::parse_checkpoint(&segment).is_err());
147        }
148        // Bad cp_pack_total_block_count (more than one segment).
149        {
150            header.sit_ver_bitmap_bytesize = SEGMENT_SIZE as u32 / 4;
151            header.nat_ver_bitmap_bytesize = SEGMENT_SIZE as u32 / 4;
152            header.cp_pack_total_block_count = 2048;
153            set_header(&mut segment, &header);
154            assert!(CheckpointPack::parse_checkpoint(&segment).is_err());
155        }
156        // Different backup checkpoint.
157        {
158            header.cp_pack_total_block_count = 100;
159            set_header(&mut segment, &header);
160            assert!(CheckpointPack::parse_checkpoint(&segment).is_err());
161        }
162        // Success.
163        {
164            segment.copy_within(..std::mem::size_of::<CheckpointHeader>(), BLOCK_SIZE * 99);
165            let result = CheckpointPack::parse_checkpoint(&segment);
166            assert!(result.is_ok(), "{:?}", result);
167        }
168    }
169}