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.
58 pub available_compr_algs: LEU16,
59 /// External device support, ignored in core format.
60 pub extra_devices: LEU32,
61 /// Set to zero in the core format. This can be used to make directory blocks larger than
62 /// regular blocks (it modifies the block_size_bits field).
63 pub dirblkbits: u8,
64 /// There are some other fields we will care about when we implement xattr and compression
65 /// support, but for now with the core format we don't care about the rest of the superblock.
66 // TODO(https://fxbug.dev/479841115): Implement xattr support.
67 // TODO(https://fxbug.dev/479841115): Implement compression support.
68 pub reserved: [u8; 37],
69}
70assert_eq_size!(SuperBlock, [u8; 128]);
71
72/// Compact inode on-disk format. Fits within a single inode slot.
73#[derive(Debug, Clone, Copy, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
74#[repr(C)]
75pub struct InodeCompact {
76 /// Format information about this particular inode. Indicates if it is compact or extended, and
77 /// the data layout (i.e. what i_u means for this node).
78 pub format: LEU16,
79 /// Size of the inline xattr region, or zero if there are no xattrs. This is not a count of
80 /// xattrs, it is plugged into a formula that determines the size in bytes of the xattr
81 /// section.
82 pub xattr_icount: LEU16,
83 /// Standard unix file type and permission bits.
84 pub mode: LEU16,
85 /// Number of hard links.
86 pub link_count: LEU16,
87 /// File size in bytes.
88 pub size: LEU32,
89 /// Reserved section.
90 pub reserved_1: [u8; 4],
91 /// Inode data union - the exact meaning of this field is dependent on the inode format field.
92 pub i_u: [u8; 4],
93 /// Inode number for stat compatibility.
94 pub ino: LEU32,
95 /// Owner UID.
96 pub uid: LEU16,
97 /// Owner GID.
98 pub gid: LEU16,
99 /// Reserved section.
100 pub reserved_2: [u8; 4],
101}
102assert_eq_size!(InodeCompact, [u8; 32]);
103
104/// Extended inode on-disk format. Uses two inode slots. Allows for more metadata and also larger
105/// files.
106#[derive(Debug, Clone, Copy, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
107#[repr(C)]
108pub struct InodeExtended {
109 /// Format information about this particular inode. Indicates if it is compact or extended, and
110 /// the data layout (i.e. what i_u means for this node).
111 pub format: LEU16,
112 /// Size of the inline xattr region, or zero if there are no xattrs. This is not a count of
113 /// xattrs, it is plugged into a formula that determines the size in bytes of the xattr
114 /// section.
115 pub xattr_icount: LEU16,
116 /// Standard unix file type and permission bits.
117 pub mode: LEU16,
118 /// Reserved section.
119 pub reserved_1: [u8; 2],
120 /// File size in bytes.
121 pub size: LEU64,
122 /// Inode data union - the exact meaning of this field is dependent on the inode format field.
123 pub i_u: [u8; 4],
124 /// Inode number for stat compatibility.
125 pub ino: LEU32,
126 /// Owner UID.
127 pub uid: LEU32,
128 /// Owner GID.
129 pub gid: LEU32,
130 /// Last modification time in seconds since unix epoch.
131 pub mtime: LEU64,
132 /// Nanosecond part of the last modification time.
133 pub mtime_ns: LEU32,
134 /// Number of hard links.
135 pub link_count: LEU32,
136 /// Reserved section.
137 pub reserved_2: [u8; 16],
138}
139assert_eq_size!(InodeExtended, [u8; 64]);
140
141/// Directory entry on-disk format. Contained within the directory data blocks.
142#[derive(Debug, Clone, Copy, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
143#[repr(C)]
144pub struct Dirent {
145 /// Node number of the target inode for this entry.
146 pub nid: LEU64,
147 /// Byte offset of the filename, relative to the start of this block. The name offset of the
148 /// first dirent in a directory block indicates how many entries there are in that block.
149 pub nameoff: LEU16,
150 /// File type code.
151 pub file_type: u8,
152 /// Reserved section.
153 pub reserved: u8,
154}
155assert_eq_size!(Dirent, [u8; 12]);
156
157/// Inlined xattr body header. This sits immediately following the inode metadata if the
158/// xattr_icount is non-zero, providing information about the structure of the following xattr
159/// entries.
160#[derive(Debug, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
161#[repr(C)]
162pub struct XattrInlineBodyHeader {
163 /// If EROFS_FEATURE_COMPAT_XATTR_FILTER is enabled and supported, this is an inverted bloom
164 /// filter on the xattr key, which can be used to determine if a key is either definitely
165 /// absent (fast failure without reading/parsing) or may exist.
166 pub name_filter: LEU32,
167 /// The number of 4 byte entries immediately after this header that are 32-bit indexes into the
168 /// global shared xattr pool, located at the xattr_block_addr from the superblock. These shared
169 /// xattrs dedupe identical key-value pairs across inodes to save space.
170 pub shared_count: u8,
171 /// Reserved section. Must be zero.
172 pub reserved: [u8; 7],
173}
174assert_eq_size!(XattrInlineBodyHeader, [u8; 12]);
175
176/// Xattr entry record header. Describes an individual xattr key-value pair for this inode. The
177/// name suffix and value data immediately follow this header, padded to a 4-byte boundary.
178#[derive(Debug, Clone, Copy, KnownLayout, FromBytes, IntoBytes, Immutable, Unaligned)]
179#[repr(C)]
180pub struct XattrEntry {
181 /// Length in bytes of the name suffix (i.e. this length does not include the prefix section).
182 /// The data is stored immediately following this entry.
183 pub name_len: u8,
184 /// Index of the attribute's namespace prefix. There are 5 built-in prefixes -
185 /// - 1 -> "user."
186 /// - 2 -> "system.posix_acl_access"
187 /// - 3 -> "system.posix_acl_default"
188 /// - 4 -> "trusted."
189 /// - 6 -> "security."
190 /// If EROFS_FEATURE_INCOMPAT_XATTR_PREFIXES is set, this value is interpreted differently, but
191 /// we don't support that right now.
192 pub name_index: u8,
193 /// Length in bytes of the value that follows the name suffix after this header.
194 pub value_size: LEU16,
195}
196assert_eq_size!(XattrEntry, [u8; 4]);