Skip to main content

f2fs_reader/
superblock.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::checkpoint::MAX_BITMAP_BYTES;
5use anyhow::{Error, anyhow, ensure};
6use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
7
8pub const F2FS_MAGIC: u32 = 0xf2f52010;
9// There are two consecutive superblocks, 1kb each.
10pub const SUPERBLOCK_OFFSET: u64 = 1024;
11// We only support 4kB blocks.
12pub const BLOCK_SIZE: usize = 4096;
13// We only support 2MB segments.
14pub const BLOCKS_PER_SEGMENT: usize = 512;
15pub const SEGMENT_SIZE: usize = BLOCK_SIZE * BLOCKS_PER_SEGMENT;
16
17// Simple CRC used to validate data structures.
18pub fn f2fs_crc32(mut seed: u32, buf: &[u8]) -> u32 {
19    const CRC_POLY: u32 = 0xedb88320;
20    for ch in buf {
21        seed ^= *ch as u32;
22        for _ in 0..8 {
23            seed = (seed >> 1) ^ (if (seed & 1) == 1 { CRC_POLY } else { 0 });
24        }
25    }
26    seed
27}
28
29#[repr(C, packed)]
30#[derive(Copy, Clone, Debug, PartialEq, FromBytes, Immutable, IntoBytes, KnownLayout)]
31pub struct SuperBlock {
32    pub magic: u32,                 // F2FS_MAGIC
33    pub major_ver: u16,             // Major Version
34    pub minor_ver: u16,             // Minor Version
35    pub log_sectorsize: u32,        // log2 sector size in bytes
36    pub log_sectors_per_block: u32, // log2 # of sectors per block
37    pub log_blocksize: u32,         // log2 block size in bytes
38    pub log_blocks_per_seg: u32,    // log2 # of blocks per segment
39    pub segs_per_sec: u32,          // # of segments per section
40    pub secs_per_zone: u32,         // # of sections per zone
41    pub checksum_offset: u32,       // checksum offset in super block
42    pub block_count: u64,           // total # of user blocks
43    pub section_count: u32,         // total # of sections
44    pub segment_count: u32,         // total # of segments
45    pub segment_count_ckpt: u32,    // # of segments for checkpoint
46    pub segment_count_sit: u32,     // # of segments for SIT
47    pub segment_count_nat: u32,     // # of segments for NAT
48    pub segment_count_ssa: u32,     // # of segments for SSA
49    pub segment_count_main: u32,    // # of segments for main area
50    pub segment0_blkaddr: u32,      // start block address of segment 0
51    pub cp_blkaddr: u32,            // start block address of checkpoint
52    pub sit_blkaddr: u32,           // start block address of SIT
53    pub nat_blkaddr: u32,           // start block address of NAT
54    pub ssa_blkaddr: u32,           // start block address of SSA
55    pub main_blkaddr: u32,          // start block address of main area
56    pub root_ino: u32,              // root inode number
57    pub node_ino: u32,              // node inode number
58    pub meta_ino: u32,              // meta inode number
59    pub uuid: [u8; 16],             // 128-bit uuid for volume
60    pub volume_name: [u16; 512],    // volume name
61    pub extension_count: u32,       // # of extensions
62    pub extension_list: [[u8; 8]; 64],
63    pub cp_payload: u32, // # of checkpoint trailing blocks for SIT bitmap
64
65    // The following fields are not in the Fuchsia fork.
66    pub kernel_version: [u8; 256],
67    pub init_kernel_version: [u8; 256],
68    pub feature: u32,
69    pub encryption_level: u8,
70    pub encryption_salt: [u8; 16],
71    pub devices: [Device; 8],
72    pub quota_file_ino: [u32; 3],
73    pub hot_extension_count: u8,
74    pub charset_encoding: u16,
75    pub charset_encoding_flags: u16,
76    pub stop_checkpoint_reason: [u8; 32],
77    pub errors: [u8; 16],
78    _reserved: [u8; 258],
79    pub crc: u32,
80}
81
82pub const FEATURE_ENCRYPT: u32 = 0x00000001;
83pub const FEATURE_EXTRA_ATTR: u32 = 0x00000008;
84pub const FEATURE_PROJECT_QUOTA: u32 = 0x00000010;
85pub const FEATURE_QUOTA_INO: u32 = 0x00000080;
86pub const FEATURE_VERITY: u32 = 0x00000400;
87pub const FEATURE_SB_CHKSUM: u32 = 0x00000800;
88pub const FEATURE_CASEFOLD: u32 = 0x00001000;
89
90pub const SUPPORTED_FEATURES: u32 = FEATURE_ENCRYPT
91    | FEATURE_EXTRA_ATTR
92    | FEATURE_PROJECT_QUOTA
93    | FEATURE_QUOTA_INO
94    | FEATURE_VERITY
95    | FEATURE_SB_CHKSUM
96    | FEATURE_CASEFOLD;
97
98#[repr(C, packed)]
99#[derive(Copy, Clone, Debug, PartialEq, FromBytes, Immutable, IntoBytes, KnownLayout)]
100pub struct Device {
101    pub path: [u8; 64],
102    pub total_segments: u32,
103}
104
105impl SuperBlock {
106    /// Reads the superblock from an device/image.
107    pub async fn read_from_device(
108        device: &dyn storage_device::Device,
109        offset: u64,
110    ) -> Result<Self, Error> {
111        // Reads must be block aligned. Superblock is always first block of device.
112        assert!(offset < BLOCK_SIZE as u64);
113        let mut block = device.allocate_buffer(BLOCK_SIZE).await;
114        device.read(0, block.as_mut()).await?;
115        let data = block.to_vec();
116        let buffer = &data[offset as usize..];
117        let superblock =
118            Self::read_from_bytes(buffer).map_err(|e| anyhow!("Failed to read superblock {e}"))?;
119        ensure!(superblock.magic == F2FS_MAGIC, "Invalid F2fs magic number");
120
121        // We only support 4kB block size so we can make some simplifying assumptions.
122        ensure!(superblock.log_blocksize == 12, "Unsupported block size");
123        // So many of the data structures assume 2MB segment size so just require that.
124        ensure!(superblock.log_blocks_per_seg == 9, "Unsupported segment size");
125
126        let feature = superblock.feature;
127        ensure!(feature & !SUPPORTED_FEATURES == 0, "Unsupported feature set {feature:08x}");
128        if superblock.feature & FEATURE_ENCRYPT != 0 {
129            // We don't support encryption_level > 0 or salts.
130            ensure!(
131                superblock.encryption_level == 0 && superblock.encryption_salt == [0u8; 16],
132                "Unsupported encryption features"
133            );
134        }
135
136        #[cfg(not(fuzz))]
137        if superblock.feature & FEATURE_SB_CHKSUM != 0 {
138            let offset = superblock.checksum_offset as usize;
139            ensure!(offset <= std::mem::size_of::<SuperBlock>(), "Invalid checksum_offset");
140            let actual_checksum = f2fs_crc32(F2FS_MAGIC, &superblock.as_bytes()[..offset]);
141            ensure!(superblock.crc == actual_checksum, "Bad superblock checksum");
142        }
143        if superblock.feature & FEATURE_CASEFOLD != 0 {
144            // 1 here means 'UTF8 12.1.0' which is the version we support in Fxfs.
145            ensure!(superblock.charset_encoding == 1, "Unsupported unicode charset");
146            // We expect NO_COMPAT_FALLBACK to always be set.
147            // Without this flag, missing hashes will be handled by exhaustive search of directories.
148            const NO_COMPAT_FALLBACK: u16 = 2;
149            let charset_encoding_flags = superblock.charset_encoding_flags;
150            if charset_encoding_flags != NO_COMPAT_FALLBACK {
151                log::warn!("Unsupported charset_encoding_flags {charset_encoding_flags:04x}");
152            }
153        }
154
155        let cp_payload = superblock.cp_payload as usize;
156        let sit_ver_bitmap_bytesize = (((superblock.segment_count_sit as u64 / 2)
157            << superblock.log_blocks_per_seg)
158            / 8) as usize;
159        let nat_ver_bitmap_bytesize = (((superblock.segment_count_nat as u64 / 2)
160            << superblock.log_blocks_per_seg)
161            / 8) as usize;
162        if cp_payload == 0 {
163            ensure!(
164                sit_ver_bitmap_bytesize + nat_ver_bitmap_bytesize <= MAX_BITMAP_BYTES,
165                "SIT and NAT bitmaps exceed checkpoint capacity"
166            );
167        } else {
168            ensure!(
169                sit_ver_bitmap_bytesize <= cp_payload * BLOCK_SIZE
170                    && nat_ver_bitmap_bytesize <= MAX_BITMAP_BYTES,
171                "SIT or NAT bitmap exceeds capacity"
172            );
173        }
174
175        Ok(superblock)
176    }
177
178    /// Gets the volume name as a string.
179    pub fn get_volume_name(&self) -> Result<String, Error> {
180        let volume_name = self.volume_name;
181        let end = volume_name.iter().position(|&x| x == 0).unwrap_or(volume_name.len());
182        String::from_utf16(&volume_name[..end]).map_err(|_| anyhow!("Bad UTF16 in volume name"))
183    }
184
185    /// Gets the total size of the filesystem in bytes.
186    pub fn get_total_size(&self) -> u64 {
187        (self.block_count as u64) * BLOCK_SIZE as u64
188    }
189}