Skip to main content

erofs/
format.rs

1// Copyright 2026 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.
4
5//! Raw on-disk format structs for erofs. See
6//! https://erofs.docs.kernel.org/en/latest/ondisk/core_ondisk.html for more details.
7
8use static_assertions::assert_eq_size;
9use zerocopy::byteorder::little_endian::{U16 as LEU16, U32 as LEU32, U64 as LEU64};
10use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned};
11
12/// Magic number for erofs filesystems.
13pub const EROFS_MAGIC: u32 = 0xE0F5E1E2;
14pub const SUPERBLOCK_OFFSET: u64 = 1024;
15pub const INODE_SLOT_SIZE: u64 = 32;
16pub const DIRENT_SIZE: usize = std::mem::size_of::<Dirent>();
17
18/// The on-disk format of an erofs superblock.
19#[derive(Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
20#[repr(C)]
21pub struct SuperBlock {
22    /// Magic number. Should be equal to EROFS_MAGIC.
23    pub magic: LEU32,
24    /// CRC-32 checksum of the block containing the superblock. This field is set to zero in the
25    /// checksummed version.
26    pub checksum: LEU32,
27    /// Feature flags. If any flags in here are not recognized the filesystem can still mount
28    /// without a loss of correctness.
29    pub feature_compat: LEU32,
30    /// Block size stored as a power of two.
31    pub block_size_bits: u8,
32    /// Number of 16-byte superblock extension slots.
33    pub sb_ext_slots: u8,
34    /// Node ID of the root directory.
35    pub root_nid: LEU16,
36    /// Total number of inodes - primarily for statfs. Can potentially be set to zero, don't rely
37    /// on it for validation.
38    pub inode_count: LEU64,
39    /// UNIX timestamp of when the filesystem was created, used as mtime for compact inodes.
40    pub epoch: LEU64,
41    /// Fixed nanosecond timestamp used as mtime for compact inodes.
42    pub fixed_nsec: LEU32,
43    /// Total number of blocks - primarily for statfs. Can potentially be set to zero, don't rely
44    /// on it for validation.
45    pub blocks: LEU32,
46    /// Start block address of inode metadata zone. This is essentially an offset for node id
47    /// calculations, not a guarantee that the inode data actually starts at this offset.
48    pub meta_block_addr: LEU32,
49    /// Start block address of the xattr zone. Similar to meta_block_addr.
50    pub xattr_block_addr: LEU32,
51    /// 128-bit UUID for this volume.
52    pub uuid: [u8; 16],
53    /// The volume name, zero-padded.
54    pub volume_name: [u8; 16],
55    /// Feature flags. If any flags here are not recognized, the filesystem can _not_ be mounted.
56    pub feature_incompat: LEU32,
57    /// Info about compression algorithms. Set to zero if image is not compressed. Otherwise it is
58    /// either an indication of what the available compression algorithms are in a bitmap (if
59    /// COMPR_CFGS is set in the incompat flags), or the lz4_max_distance.
60    pub available_compr_algs: LEU16,
61    /// External device support, ignored in core format.
62    pub extra_devices: LEU32,
63    /// Set to zero in the core format. This can be used to make directory blocks larger than
64    /// regular blocks (it modifies the block_size_bits field).
65    pub dirblkbits: u8,
66    /// There are some other fields we will care about when we implement xattr and compression
67    /// support, but for now with the core format we don't care about the rest of the superblock.
68    // TODO(https://fxbug.dev/479841115): Implement xattr support.
69    // TODO(https://fxbug.dev/479841115): Implement compression support.
70    pub reserved: [u8; 37],
71}
72assert_eq_size!(SuperBlock, [u8; 128]);
73
74/// Compact inode on-disk format. Fits within a single inode slot.
75#[derive(Debug, Clone, Copy, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
76#[repr(C)]
77pub struct InodeCompact {
78    /// Format information about this particular inode. Indicates if it is compact or extended, and
79    /// the data layout (i.e. what i_u means for this node).
80    pub format: LEU16,
81    /// Size of the inline xattr region, or zero if there are no xattrs. This is not a count of
82    /// xattrs, it is plugged into a formula that determines the size in bytes of the xattr
83    /// section.
84    pub xattr_icount: LEU16,
85    /// Standard unix file type and permission bits.
86    pub mode: LEU16,
87    /// Number of hard links.
88    pub link_count: LEU16,
89    /// File size in bytes.
90    pub size: LEU32,
91    /// Reserved section.
92    pub reserved_1: [u8; 4],
93    /// Inode data union - the exact meaning of this field is dependent on the inode format field.
94    /// For uncompressed data layouts (FlatPlain, FlatInline), this is the raw block address. For
95    /// compressed data layouts (CompressedFull, CompressedCompact), this is the compressed block
96    /// count.
97    pub i_u: [u8; 4],
98    /// Inode number for stat compatibility.
99    pub ino: LEU32,
100    /// Owner UID.
101    pub uid: LEU16,
102    /// Owner GID.
103    pub gid: LEU16,
104    /// Reserved section.
105    pub reserved_2: [u8; 4],
106}
107assert_eq_size!(InodeCompact, [u8; 32]);
108
109/// Extended inode on-disk format. Uses two inode slots. Allows for more metadata and also larger
110/// files.
111#[derive(Debug, Clone, Copy, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
112#[repr(C)]
113pub struct InodeExtended {
114    /// Format information about this particular inode. Indicates if it is compact or extended, and
115    /// the data layout (i.e. what i_u means for this node).
116    pub format: LEU16,
117    /// Size of the inline xattr region, or zero if there are no xattrs. This is not a count of
118    /// xattrs, it is plugged into a formula that determines the size in bytes of the xattr
119    /// section.
120    pub xattr_icount: LEU16,
121    /// Standard unix file type and permission bits.
122    pub mode: LEU16,
123    /// Reserved section.
124    pub reserved_1: [u8; 2],
125    /// File size in bytes.
126    pub size: LEU64,
127    /// Inode data union - the exact meaning of this field is dependent on the inode format field.
128    /// For uncompressed data layouts (FlatPlain, FlatInline), this is the raw block address. For
129    /// compressed data layouts (CompressedFull, CompressedCompact), this is the compressed block
130    /// count.
131    pub i_u: [u8; 4],
132    /// Inode number for stat compatibility.
133    pub ino: LEU32,
134    /// Owner UID.
135    pub uid: LEU32,
136    /// Owner GID.
137    pub gid: LEU32,
138    /// Last modification time in seconds since unix epoch.
139    pub mtime: LEU64,
140    /// Nanosecond part of the last modification time.
141    pub mtime_ns: LEU32,
142    /// Number of hard links.
143    pub link_count: LEU32,
144    /// Reserved section.
145    pub reserved_2: [u8; 16],
146}
147assert_eq_size!(InodeExtended, [u8; 64]);
148
149/// Directory entry on-disk format. Contained within the directory data blocks.
150#[derive(Debug, Clone, Copy, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
151#[repr(C)]
152pub struct Dirent {
153    /// Node number of the target inode for this entry.
154    pub nid: LEU64,
155    /// Byte offset of the filename, relative to the start of this block. The name offset of the
156    /// first dirent in a directory block indicates how many entries there are in that block.
157    pub nameoff: LEU16,
158    /// File type code.
159    pub file_type: u8,
160    /// Reserved section.
161    pub reserved: u8,
162}
163assert_eq_size!(Dirent, [u8; 12]);
164
165/// Inlined xattr body header. This sits immediately following the inode metadata if the
166/// xattr_icount is non-zero, providing information about the structure of the following xattr
167/// entries.
168#[derive(Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
169#[repr(C)]
170pub struct XattrInlineBodyHeader {
171    /// If EROFS_FEATURE_COMPAT_XATTR_FILTER is enabled and supported, this is an inverted bloom
172    /// filter on the xattr key, which can be used to determine if a key is either definitely
173    /// absent (fast failure without reading/parsing) or may exist.
174    pub name_filter: LEU32,
175    /// The number of 4 byte entries immediately after this header that are 32-bit indexes into the
176    /// global shared xattr pool, located at the xattr_block_addr from the superblock. These shared
177    /// xattrs dedupe identical key-value pairs across inodes to save space.
178    pub shared_count: u8,
179    /// Reserved section. Must be zero.
180    pub reserved: [u8; 7],
181}
182assert_eq_size!(XattrInlineBodyHeader, [u8; 12]);
183
184/// Xattr entry record header. Describes an individual xattr key-value pair for this inode. The
185/// name suffix and value data immediately follow this header, padded to a 4-byte boundary.
186#[derive(Debug, Clone, Copy, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
187#[repr(C)]
188pub struct XattrEntry {
189    /// Length in bytes of the name suffix (i.e. this length does not include the prefix section).
190    /// The data is stored immediately following this entry.
191    pub name_len: u8,
192    /// Index of the attribute's namespace prefix. There are 5 built-in prefixes -
193    ///  - 1 -> "user."
194    ///  - 2 -> "system.posix_acl_access"
195    ///  - 3 -> "system.posix_acl_default"
196    ///  - 4 -> "trusted."
197    ///  - 6 -> "security."
198    /// If EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES is set, this value is interpreted differently, but
199    /// we don't support that right now.
200    pub name_index: u8,
201    /// Length in bytes of the value that follows the name suffix after this header.
202    pub value_size: LEU16,
203}
204assert_eq_size!(XattrEntry, [u8; 4]);
205
206/// Size of the compression map header area for legacy compressed inodes (8-byte header + 8-byte
207/// reserved gap). This is for historical reasons.
208pub const LEGACY_MAP_HEADER_SIZE: u64 = 16;
209
210/// Compression metadata header. For inodes with compressed data layouts, this header is written
211/// after the core metadata and extended attributes, padded to the next 8-byte boundary. It
212/// contains the compression info for this particular inode.
213#[derive(Debug, Clone, Copy, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
214#[repr(C)]
215pub struct CompressionMapHeader {
216    /// Reserved 2 byte section. There are incompat features that change the interpretation of the
217    /// first four bytes of this header but we don't implement them, so this is always ignored
218    /// today.
219    pub reserved_1: [u8; 2],
220    /// If the advisory flags indicate there is an inline pcluster, this indicates the size of the
221    /// inline data in bytes. This is gated behind an incompat flag that we don't implement so it
222    /// is not used in practice.
223    pub inline_data_size: LEU16,
224    /// Advisory flags for decompression.
225    pub advisory_flags: LEU16,
226    /// Compression algorithm types for logical clusters. We only support lz4 today so this should
227    /// always be zero.
228    pub algorithm_type: u8,
229    /// The first 4 bits of this are a modifier to the block size bits in the superblock. This
230    /// allows files to have logical cluster sizes that are larger than the block size. The default
231    /// is to match block size. We ignore this value right now and only default to block size.
232    pub lcluster_bits: u8,
233}
234assert_eq_size!(CompressionMapHeader, [u8; 8]);
235
236/// Logical cluster index entry, for the legacy CompressedFull data layout. One of these exists per
237/// logical cluster in the inode data.
238#[derive(Debug, Clone, Copy, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
239#[repr(C)]
240pub struct LClusterIndex {
241    /// Advisory bits (including cluster type).
242    pub advisory_flags: LEU16,
243    /// If this logical cluster is a HEAD cluster, this holds the offset into the cluster where the
244    /// data actually starts. The variable-length extents that map to physical clusters are not
245    /// aligned in any meaningful way and can start in the middle of a logical cluster.
246    pub extent_start_offset: LEU16,
247    /// Depending on if this logical cluster is a HEAD/PLAIN or NONHEAD type, this has two
248    /// different interpretations -
249    ///  - For HEAD and PLAIN types, this is the 4-byte block address for the physical cluster.
250    ///  - For NONHEAD, this is two 2-byte values. [0] is the distance back to its HEAD lcluster,
251    ///    and [1] is the distance forward to the next HEAD lcluster.
252    pub data_union: [u8; 4],
253}
254assert_eq_size!(LClusterIndex, [u8; 8]);