Skip to content
API Documentation
BastionGuard™
xfs_format.hpp
Go to the documentation of this file.
1// xfs_format.hpp -- XFS on-disk format definitions.
2//
3// Reference: "XFS Algorithms & Data Structures, 3rd Edition" (Red Hat,
4// SGI / Christoph Hellwig / Dave Chinner et al.) and the
5// Linux kernel sources at fs/xfs/.
6//
7// XFS uses BIG-ENDIAN on disk (heritage from SGI/IRIX). All multi-byte
8// integers in this file are stored big-endian. We provide be16/be32/be64
9// helpers below; do NOT memcpy directly into native structs.
10//
11// XFS supports two on-disk versions:
12// - V4: classic XFS, no CRCs, version_num low 4 bits = 4
13// - V5: introduced ~2013, all metadata blocks have CRC32 + UUID + LSN,
14// version_num low 4 bits = 5
15//
16// Both are still seen in the wild. V4 is still the default on RHEL 7,
17// V5 is the default on RHEL 8+ and modern distros. We support both.
18//
19// This header contains DEFINITIONS ONLY -- no implementation. The layout
20// in memory matches the on-disk layout (we use packed structs only where
21// strictly necessary; otherwise we read raw bytes and decode field by
22// field, which is more portable across compilers).
23
24#pragma once
25
26#include <array>
27#include <cstdint>
28#include <cstddef>
29#include <string>
30#include <vector>
31
32namespace xfs {
33
34// -----------------------------------------------------------------------------
35// Big-endian readers (XFS is big-endian on disk)
36// -----------------------------------------------------------------------------
37
38inline uint16_t be16(const unsigned char* p) {
39 return static_cast<uint16_t>((p[0] << 8) | p[1]);
40}
41inline uint32_t be32(const unsigned char* p) {
42 return (static_cast<uint32_t>(p[0]) << 24) |
43 (static_cast<uint32_t>(p[1]) << 16) |
44 (static_cast<uint32_t>(p[2]) << 8) |
45 static_cast<uint32_t>(p[3]);
46}
47inline uint64_t be64(const unsigned char* p) {
48 return (static_cast<uint64_t>(p[0]) << 56) |
49 (static_cast<uint64_t>(p[1]) << 48) |
50 (static_cast<uint64_t>(p[2]) << 40) |
51 (static_cast<uint64_t>(p[3]) << 32) |
52 (static_cast<uint64_t>(p[4]) << 24) |
53 (static_cast<uint64_t>(p[5]) << 16) |
54 (static_cast<uint64_t>(p[6]) << 8) |
55 static_cast<uint64_t>(p[7]);
56}
57
58// -----------------------------------------------------------------------------
59// Magic numbers (all values are the BIG-ENDIAN ASCII representation)
60// -----------------------------------------------------------------------------
61
62constexpr uint32_t XFS_SB_MAGIC = 0x58465342u; // "XFSB" -- superblock
63constexpr uint32_t XFS_AGF_MAGIC_V4 = 0x58414746u; // "XAGF" -- AG free space
64constexpr uint32_t XFS_AGF_MAGIC_V5 = 0x58414746u; // "XAGF" (same magic, V5 has CRC trailer)
65constexpr uint32_t XFS_AGI_MAGIC_V4 = 0x58414749u; // "XAGI" -- AG inode info
66constexpr uint32_t XFS_AGI_MAGIC_V5 = 0x58414749u; // "XAGI"
67constexpr uint32_t XFS_AGFL_MAGIC_V5 = 0x5841464Cu; // "XAFL" -- AG free list (V5 only)
68constexpr uint32_t XFS_DINODE_MAGIC = 0x494Eu; // "IN" -- dinode (16-bit)
69
70// B+tree magics
71constexpr uint32_t XFS_ABTB_MAGIC_V4 = 0x41425442u; // "ABTB" -- alloc B+ by block
72constexpr uint32_t XFS_ABTB_MAGIC_V5 = 0x41423342u; // "AB3B" -- V5 variant
73constexpr uint32_t XFS_ABTC_MAGIC_V4 = 0x41425443u; // "ABTC" -- alloc B+ by count
74constexpr uint32_t XFS_ABTC_MAGIC_V5 = 0x41423343u; // "AB3C"
75constexpr uint32_t XFS_IBT_MAGIC_V4 = 0x49414254u; // "IABT" -- inode B+
76constexpr uint32_t XFS_IBT_MAGIC_V5 = 0x49414233u; // "IAB3"
77constexpr uint32_t XFS_FIBT_MAGIC_V4 = 0x46494254u; // "FIBT" -- free inode B+
78constexpr uint32_t XFS_FIBT_MAGIC_V5 = 0x46494233u; // "FIB3"
79constexpr uint32_t XFS_BMAP_MAGIC_V4 = 0x424D4150u; // "BMAP" -- block-map B+
80constexpr uint32_t XFS_BMAP_MAGIC_V5 = 0x424D4133u; // "BMA3"
81
82// -----------------------------------------------------------------------------
83// Version flags (sb_versionnum)
84// -----------------------------------------------------------------------------
85
86constexpr uint16_t XFS_SB_VERSION_NUMBITS = 0x000F;
87constexpr uint16_t XFS_SB_VERSION_4 = 4;
88constexpr uint16_t XFS_SB_VERSION_5 = 5;
89
90// V4 feature flags (sb_versionnum bits 4..15)
91constexpr uint16_t XFS_SB_VERSION_ATTRBIT = 0x0010;
92constexpr uint16_t XFS_SB_VERSION_NLINKBIT = 0x0020;
93constexpr uint16_t XFS_SB_VERSION_QUOTABIT = 0x0040;
94constexpr uint16_t XFS_SB_VERSION_ALIGNBIT = 0x0080;
95constexpr uint16_t XFS_SB_VERSION_DALIGNBIT = 0x0100;
96constexpr uint16_t XFS_SB_VERSION_SHAREDBIT = 0x0200;
97constexpr uint16_t XFS_SB_VERSION_LOGV2BIT = 0x0400;
98constexpr uint16_t XFS_SB_VERSION_SECTORBIT = 0x0800;
99constexpr uint16_t XFS_SB_VERSION_EXTFLGBIT = 0x1000;
100constexpr uint16_t XFS_SB_VERSION_DIRV2BIT = 0x2000;
101constexpr uint16_t XFS_SB_VERSION_BORGBIT = 0x4000;
102constexpr uint16_t XFS_SB_VERSION_MOREBITSBIT= 0x8000;
103
104// V5 feature flags (sb_features_*) -- only meaningful when version==5
105constexpr uint32_t XFS_SB_FEAT_RO_COMPAT_FINOBT = 0x00000001;
106constexpr uint32_t XFS_SB_FEAT_RO_COMPAT_RMAPBT = 0x00000002;
107constexpr uint32_t XFS_SB_FEAT_RO_COMPAT_REFLINK = 0x00000004;
108constexpr uint32_t XFS_SB_FEAT_RO_COMPAT_INOBTCNT = 0x00000008;
109
110constexpr uint32_t XFS_SB_FEAT_INCOMPAT_FTYPE = 0x00000001;
111constexpr uint32_t XFS_SB_FEAT_INCOMPAT_SPINODES = 0x00000002;
112constexpr uint32_t XFS_SB_FEAT_INCOMPAT_META_UUID = 0x00000004;
113constexpr uint32_t XFS_SB_FEAT_INCOMPAT_BIGTIME = 0x00000008;
114constexpr uint32_t XFS_SB_FEAT_INCOMPAT_NEEDSREPAIR = 0x00000010;
115constexpr uint32_t XFS_SB_FEAT_INCOMPAT_NREXT64 = 0x00000020;
116
117// -----------------------------------------------------------------------------
118// Superblock (struct xfs_dsb)
119//
120// Sits at offset 0 of the volume. Each AG also has a copy of the superblock
121// at offset 0 of the AG (so AG0 SB == volume SB).
122//
123// Size: 264 bytes for V5 (with feature/UUID/etc fields), 208 bytes for V4
124// (older fields only). We always read 512 bytes (one sector) to be safe.
125//
126// IMPORTANT: V4 fields end at offset 0xC8 (200 bytes). V5-specific fields
127// start at offset 0xD0 (sb_features_compat, sb_features_ro_compat, etc.).
128// We expose ALL fields here as offset-based helpers below, not as a packed
129// struct, because compilers disagree on alignment of nested arrays.
130// -----------------------------------------------------------------------------
131
133 // V4 + V5 common fields
134 uint32_t magicnum {0}; // 0x00 -- must be XFS_SB_MAGIC
135 uint32_t blocksize {0}; // 0x04 -- bytes/block, power of 2 in [512..65536]
136 uint64_t dblocks {0}; // 0x08 -- total data blocks
137 uint64_t rblocks {0}; // 0x10 -- realtime blocks
138 uint64_t rextents {0}; // 0x18 -- realtime extents
139 std::array<uint8_t, 16> uuid {}; // 0x20 -- volume UUID
140 uint64_t logstart {0}; // 0x30 -- log start block (0 if external log)
141 uint64_t rootino {0}; // 0x38 -- root inode number
142 uint64_t rbmino {0}; // 0x40 -- realtime bitmap inode
143 uint64_t rsumino {0}; // 0x48 -- realtime summary inode
144 uint32_t rextsize {0}; // 0x50 -- realtime extent size (blocks)
145 uint32_t agblocks {0}; // 0x54 -- blocks per allocation group
146 uint32_t agcount {0}; // 0x58 -- number of allocation groups
147 uint32_t rbmblocks {0}; // 0x5C -- realtime bitmap blocks
148 uint32_t logblocks {0}; // 0x60 -- log size in blocks
149 uint16_t versionnum {0}; // 0x64 -- version + feature bits
150 uint16_t sectsize {0}; // 0x66 -- sector size (bytes)
151 uint16_t inodesize {0}; // 0x68 -- inode size (bytes)
152 uint16_t inopblock {0}; // 0x6A -- inodes per block
153 std::array<char, 12> fname {}; // 0x6C -- volume label (NUL-padded)
154 uint8_t blocklog {0}; // 0x78 -- log2(blocksize)
155 uint8_t sectlog {0}; // 0x79 -- log2(sectsize)
156 uint8_t inodelog {0}; // 0x7A -- log2(inodesize)
157 uint8_t inopblog {0}; // 0x7B -- log2(inopblock)
158 uint8_t agblklog {0}; // 0x7C -- log2(agblocks rounded up)
159 uint8_t rextslog {0}; // 0x7D -- log2(rextents)
160 uint8_t inprogress {0}; // 0x7E -- mkfs is in progress
161 uint8_t imax_pct {0}; // 0x7F -- max % of FS for inode space
162 uint64_t icount {0}; // 0x80 -- allocated inodes
163 uint64_t ifree {0}; // 0x88 -- free inodes
164 uint64_t fdblocks {0}; // 0x90 -- free data blocks
165 uint64_t frextents {0}; // 0x98 -- free realtime extents
166 uint64_t uquotino {0}; // 0xA0 -- user quota inode
167 uint64_t gquotino {0}; // 0xA8 -- group quota inode
168 uint16_t qflags {0}; // 0xB0 -- quota flags
169 uint8_t flags {0}; // 0xB2 -- misc flags
170 uint8_t shared_vn {0}; // 0xB3 -- reserved
171 uint32_t inoalignmt {0}; // 0xB4 -- inode chunk alignment
172 uint32_t unit {0}; // 0xB8 -- stripe/raid unit
173 uint32_t width {0}; // 0xBC -- stripe/raid width
174 uint8_t dirblklog {0}; // 0xC0 -- log2(dir-block factor)
175 uint8_t logsectlog {0}; // 0xC1 -- log2(log sector size) -- v2 logs
176 uint16_t logsectsize {0}; // 0xC2 -- log sector size in bytes
177 uint32_t logsunit {0}; // 0xC4 -- log device stripe unit
178 uint32_t features2 {0}; // 0xC8 -- additional feature flags
179
180 // V5-only fields (only valid if (versionnum & 0xF) == 5)
181 uint32_t bad_features2 {0}; // 0xCC -- mirrored copy for compat
182 uint32_t features_compat {0}; // 0xD0
183 uint32_t features_ro_compat {0}; // 0xD4
184 uint32_t features_incompat {0}; // 0xD8
185 uint32_t features_log_incompat {0};// 0xDC
186 uint32_t crc {0}; // 0xE0 -- CRC of the SB itself
187 uint32_t spino_align {0}; // 0xE4 -- sparse inode alignment
188 uint64_t pquotino {0}; // 0xE8 -- project quota inode
189 uint64_t lsn {0}; // 0xF0 -- log seq # of last write
190 std::array<uint8_t, 16> meta_uuid {}; // 0xF8 -- metadata UUID (V5 + META_UUID feature)
191
192 // ---- Convenience getters ----
193 unsigned format_version() const { return versionnum & XFS_SB_VERSION_NUMBITS; }
194 bool is_v5() const { return format_version() == XFS_SB_VERSION_5; }
195 bool is_v4() const { return format_version() == XFS_SB_VERSION_4; }
196 bool has_crc() const { return is_v5(); }
197 bool has_ftype() const {
198 // FTYPE bit on V5 lives in features_incompat; on V4 it's part of features2
200 // V4 stored ftype in features2 bit 0x80 (XFS_SB_VERSION2_FTYPE)
201 return (features2 & 0x00000080u) != 0;
202 }
203 uint64_t volume_size_bytes() const { return dblocks * blocksize; }
204 std::string label() const {
205 std::string s(fname.data(), fname.size());
206 while (!s.empty() && (s.back() == '\0' || s.back() == ' ')) s.pop_back();
207 return s;
208 }
209};
210
211// Parse a 512-byte sector into a Superblock. Returns true if magic matches
212// and the parsed values pass basic sanity. Does NOT verify CRC even on V5.
213//
214// `buf` must point to at least 512 bytes.
215bool parse_superblock(const unsigned char* buf, Superblock& out);
216
217// -----------------------------------------------------------------------------
218// AG header: AGF (allocation group free space)
219//
220// Sits at offset (sectsize) within each AG. Length: variable (V4=64 bytes,
221// V5=92 bytes header + sectsize-92 padding to a sector boundary).
222//
223// We only need a few fields for level-3 base recovery: pointers to the
224// free-space B+trees (we don't actually traverse them in this session,
225// but having them parsed keeps the scaffolding in place).
226// -----------------------------------------------------------------------------
227
228struct AGF {
229 uint32_t magicnum {0}; // 0x00 -- "XAGF"
230 uint32_t versionnum {0}; // 0x04 -- 1
231 uint32_t seqno {0}; // 0x08 -- AG number
232 uint32_t length {0}; // 0x0C -- size of AG in blocks
233 uint32_t roots[3] {}; // 0x10 -- roots of by-block, by-cnt, rmap (V5)
234 uint32_t levels[3] {}; // 0x1C -- depths of those B+trees
235 uint32_t flfirst {0}; // 0x28 -- AGFL first index
236 uint32_t fllast {0}; // 0x2C -- AGFL last index
237 uint32_t flcount {0}; // 0x30 -- AGFL count
238 uint32_t freeblks {0}; // 0x34 -- total free blocks in AG
239 uint32_t longest {0}; // 0x38 -- longest free extent
240 uint32_t btreeblks {0}; // 0x3C -- blocks used by B+trees
241 // V5-only fields (uuid/lsn/crc) skipped here -- we don't need them now.
242};
243
244bool parse_agf(const unsigned char* buf, AGF& out);
245
246// -----------------------------------------------------------------------------
247// AG header: AGI (allocation group inode info)
248//
249// Sits at offset (2 * sectsize) within each AG. Holds the root of the
250// inode B+tree and the unlinked-inode hash list.
251//
252// This is the entry point for ALL inode iteration. When we add inode
253// walking in session 2, we'll start from agi.root.
254// -----------------------------------------------------------------------------
255
256struct AGI {
257 uint32_t magicnum {0}; // 0x00 -- "XAGI"
258 uint32_t versionnum {0}; // 0x04 -- 1
259 uint32_t seqno {0}; // 0x08 -- AG number
260 uint32_t length {0}; // 0x0C -- size of AG in blocks
261 uint32_t count {0}; // 0x10 -- # allocated inodes in AG
262 uint32_t root {0}; // 0x14 -- root of inode B+tree
263 uint32_t level {0}; // 0x18 -- depth of inode B+tree
264 uint32_t freecount {0}; // 0x1C -- # free inodes in AG
265 uint32_t newino {0}; // 0x20 -- last allocated chunk
266 uint32_t dirino {0}; // 0x24 -- unused
267 uint32_t unlinked[64] {}; // 0x28 -- AGI_UNLINKED_BUCKETS hash
268 // V5-only: free inode B+tree root/level (free_root, free_level)
269 uint32_t free_root {0}; // V5 -- finobt root (XFS_SB_FEAT_RO_COMPAT_FINOBT)
270 uint32_t free_level {0}; // V5 -- finobt depth
271};
272
273bool parse_agi(const unsigned char* buf, AGI& out);
274
275// Same as above, but reads V5-only fields (free_root, free_level for FINOBT)
276// when `is_v5` is true. On V4 those bytes don't exist and stay zero.
277bool parse_agi(const unsigned char* buf, bool is_v5, AGI& out);
278
279// -----------------------------------------------------------------------------
280// Inode number conversion helpers
281//
282// XFS inode numbers are 64-bit but really encode (AG#, AG-relative block,
283// in-block offset). The split depends on agblklog and inopblog.
284//
285// ino = (agno << (agblklog + inopblog))
286// | ((agbno) << inopblog)
287// | (agino_offset)
288//
289// So we need agblklog and inopblog from the SB to crack an inode number.
290// -----------------------------------------------------------------------------
291
293 uint32_t agno {0}; // allocation group number
294 uint32_t agbno {0}; // AG-relative block number where inode chunk starts
295 uint32_t offset {0}; // in-block inode offset (0..inopblock-1)
296
297 // Disk byte offset = sb.agblocks * agno * sb.blocksize
298 // + agbno * sb.blocksize
299 // + offset * sb.inodesize
300};
301
302InodeAddress crack_inode(uint64_t ino, const Superblock& sb);
303
304// -----------------------------------------------------------------------------
305// Volume-level handle (lightweight; no I/O happens here)
306//
307// Holds parsed SB plus per-AG headers. Built once at open-time and reused
308// for all subsequent operations. AG headers are read lazily.
309// -----------------------------------------------------------------------------
310
311struct AGContext {
312 bool agf_loaded {false};
313 bool agi_loaded {false};
316};
317
320 uint64_t volume_offset {0}; // disk byte offset where the XFS volume starts
321 std::vector<AGContext> ag {}; // sized to sb.agcount after open
322};
323
324} // namespace xfs
constexpr uint32_t XFS_SB_FEAT_INCOMPAT_BIGTIME
constexpr uint16_t XFS_SB_VERSION_NUMBITS
constexpr uint32_t XFS_ABTB_MAGIC_V4
constexpr uint16_t XFS_SB_VERSION_SHAREDBIT
constexpr uint16_t XFS_SB_VERSION_QUOTABIT
bool parse_agf(const unsigned char *buf, AGF &out)
constexpr uint32_t XFS_AGFL_MAGIC_V5
uint16_t be16(const unsigned char *p)
constexpr uint32_t XFS_AGI_MAGIC_V4
constexpr uint32_t XFS_AGI_MAGIC_V5
constexpr uint16_t XFS_SB_VERSION_ATTRBIT
constexpr uint32_t XFS_SB_FEAT_RO_COMPAT_INOBTCNT
bool parse_superblock(const unsigned char *buf, Superblock &out)
constexpr uint16_t XFS_SB_VERSION_NLINKBIT
constexpr uint32_t XFS_ABTC_MAGIC_V5
constexpr uint32_t XFS_SB_FEAT_INCOMPAT_NEEDSREPAIR
constexpr uint16_t XFS_SB_VERSION_ALIGNBIT
constexpr uint32_t XFS_FIBT_MAGIC_V5
constexpr uint16_t XFS_SB_VERSION_SECTORBIT
constexpr uint32_t XFS_ABTC_MAGIC_V4
constexpr uint16_t XFS_SB_VERSION_DIRV2BIT
constexpr uint16_t XFS_SB_VERSION_5
constexpr uint32_t XFS_AGF_MAGIC_V5
InodeAddress crack_inode(uint64_t ino, const Superblock &sb)
constexpr uint32_t XFS_SB_FEAT_RO_COMPAT_RMAPBT
uint64_t be64(const unsigned char *p)
bool parse_agi(const unsigned char *buf, AGI &out)
constexpr uint32_t XFS_BMAP_MAGIC_V5
constexpr uint32_t XFS_ABTB_MAGIC_V5
constexpr uint16_t XFS_SB_VERSION_EXTFLGBIT
constexpr uint32_t XFS_DINODE_MAGIC
uint32_t be32(const unsigned char *p)
constexpr uint32_t XFS_SB_MAGIC
constexpr uint32_t XFS_FIBT_MAGIC_V4
constexpr uint16_t XFS_SB_VERSION_4
constexpr uint32_t XFS_SB_FEAT_INCOMPAT_SPINODES
constexpr uint32_t XFS_SB_FEAT_RO_COMPAT_FINOBT
constexpr uint32_t XFS_AGF_MAGIC_V4
constexpr uint16_t XFS_SB_VERSION_DALIGNBIT
constexpr uint32_t XFS_IBT_MAGIC_V5
constexpr uint32_t XFS_SB_FEAT_INCOMPAT_META_UUID
constexpr uint32_t XFS_BMAP_MAGIC_V4
constexpr uint32_t XFS_IBT_MAGIC_V4
constexpr uint32_t XFS_SB_FEAT_INCOMPAT_FTYPE
constexpr uint32_t XFS_SB_FEAT_RO_COMPAT_REFLINK
constexpr uint32_t XFS_SB_FEAT_INCOMPAT_NREXT64
constexpr uint16_t XFS_SB_VERSION_LOGV2BIT
constexpr uint16_t XFS_SB_VERSION_BORGBIT
constexpr uint16_t XFS_SB_VERSION_MOREBITSBIT
uint32_t magicnum
uint32_t freeblks
uint32_t flcount
uint32_t roots[3]
uint32_t longest
uint32_t flfirst
uint32_t length
uint32_t btreeblks
uint32_t seqno
uint32_t fllast
uint32_t versionnum
uint32_t levels[3]
uint32_t free_root
uint32_t level
uint32_t length
uint32_t magicnum
uint32_t freecount
uint32_t seqno
uint32_t root
uint32_t unlinked[64]
uint32_t count
uint32_t versionnum
uint32_t newino
uint32_t free_level
uint32_t dirino
uint64_t volume_size_bytes() const
uint32_t bad_features2
uint32_t features_incompat
std::string label() const
unsigned format_version() const
std::array< uint8_t, 16 > meta_uuid
uint32_t features_log_incompat
std::array< char, 12 > fname
uint32_t inoalignmt
std::array< uint8_t, 16 > uuid
uint32_t features_ro_compat
bool has_crc() const
uint16_t logsectsize
bool is_v5() const
bool is_v4() const
bool has_ftype() const
uint16_t versionnum
uint32_t spino_align
uint32_t features_compat
std::vector< AGContext > ag