Skip to content
API Documentation
BastionGuard™
xfs_format.cpp
Go to the documentation of this file.
1// xfs_format.cpp -- on-disk parsers for XFS structures.
2//
3// All parsers are pure functions: they read from a byte buffer of the
4// expected size and fill in the destination struct. They do NOT perform
5// I/O. They DO perform basic sanity checks and return false on malformed
6// data (e.g. wrong magic, impossible field combinations).
7//
8// CRC validation is intentionally skipped here; for read-only recovery
9// from possibly-damaged disks we want to extract data even when CRCs are
10// bad. A separate function can verify CRCs later if the UI wants to flag
11// degraded reads.
12
13#include "xfs_format.hpp"
14
15#include <algorithm>
16
17namespace xfs {
18
19bool parse_superblock(const unsigned char* buf, Superblock& out) {
20 if (buf == nullptr) return false;
21
22 out.magicnum = be32(buf + 0x00);
23 if (out.magicnum != XFS_SB_MAGIC) {
24 return false;
25 }
26
27 out.blocksize = be32(buf + 0x04);
28 out.dblocks = be64(buf + 0x08);
29 out.rblocks = be64(buf + 0x10);
30 out.rextents = be64(buf + 0x18);
31 std::copy(buf + 0x20, buf + 0x30, out.uuid.begin());
32 out.logstart = be64(buf + 0x30);
33 out.rootino = be64(buf + 0x38);
34 out.rbmino = be64(buf + 0x40);
35 out.rsumino = be64(buf + 0x48);
36 out.rextsize = be32(buf + 0x50);
37 out.agblocks = be32(buf + 0x54);
38 out.agcount = be32(buf + 0x58);
39 out.rbmblocks = be32(buf + 0x5C);
40 out.logblocks = be32(buf + 0x60);
41 out.versionnum = be16(buf + 0x64);
42 out.sectsize = be16(buf + 0x66);
43 out.inodesize = be16(buf + 0x68);
44 out.inopblock = be16(buf + 0x6A);
45 std::copy(buf + 0x6C, buf + 0x78, out.fname.begin());
46 out.blocklog = buf[0x78];
47 out.sectlog = buf[0x79];
48 out.inodelog = buf[0x7A];
49 out.inopblog = buf[0x7B];
50 out.agblklog = buf[0x7C];
51 out.rextslog = buf[0x7D];
52 out.inprogress = buf[0x7E];
53 out.imax_pct = buf[0x7F];
54 out.icount = be64(buf + 0x80);
55 out.ifree = be64(buf + 0x88);
56 out.fdblocks = be64(buf + 0x90);
57 out.frextents = be64(buf + 0x98);
58 out.uquotino = be64(buf + 0xA0);
59 out.gquotino = be64(buf + 0xA8);
60 out.qflags = be16(buf + 0xB0);
61 out.flags = buf[0xB2];
62 out.shared_vn = buf[0xB3];
63 out.inoalignmt = be32(buf + 0xB4);
64 out.unit = be32(buf + 0xB8);
65 out.width = be32(buf + 0xBC);
66 out.dirblklog = buf[0xC0];
67 out.logsectlog = buf[0xC1];
68 out.logsectsize= be16(buf + 0xC2);
69 out.logsunit = be32(buf + 0xC4);
70 out.features2 = be32(buf + 0xC8);
71
72 // V5-only fields. Read them anyway (cheap), but they're only meaningful
73 // if format_version()==5. The V4 superblock zeroes these bytes (mkfs
74 // wipes the whole sector before writing).
75 out.bad_features2 = be32(buf + 0xCC);
76 out.features_compat = be32(buf + 0xD0);
77 out.features_ro_compat = be32(buf + 0xD4);
78 out.features_incompat = be32(buf + 0xD8);
79 out.features_log_incompat = be32(buf + 0xDC);
80 out.crc = be32(buf + 0xE0);
81 out.spino_align = be32(buf + 0xE4);
82 out.pquotino = be64(buf + 0xE8);
83 out.lsn = be64(buf + 0xF0);
84 std::copy(buf + 0xF8, buf + 0x108, out.meta_uuid.begin());
85
86 // ---- Sanity checks ----
87
88 // blocksize must be a power of 2 in [512, 65536]
89 if (out.blocksize < 512 || out.blocksize > 65536 ||
90 (out.blocksize & (out.blocksize - 1)) != 0) {
91 return false;
92 }
93 // sectsize must be a power of 2 in [256, 32768] and <= blocksize
94 if (out.sectsize < 256 || out.sectsize > 32768 ||
95 (out.sectsize & (out.sectsize - 1)) != 0 ||
96 out.sectsize > out.blocksize) {
97 return false;
98 }
99 // inodesize must be a power of 2 in [256, 2048] and <= blocksize
100 if (out.inodesize < 256 || out.inodesize > 2048 ||
101 (out.inodesize & (out.inodesize - 1)) != 0 ||
102 out.inodesize > out.blocksize) {
103 return false;
104 }
105 // log2 fields must agree with the explicit values
106 if ((1u << out.blocklog) != out.blocksize ||
107 (1u << out.sectlog) != out.sectsize ||
108 (1u << out.inodelog) != out.inodesize) {
109 return false;
110 }
111 if (out.inopblock == 0 || (1u << out.inopblog) != out.inopblock) {
112 return false;
113 }
114 // agblocks/agcount must be plausible
115 if (out.agblocks == 0 || out.agcount == 0) {
116 return false;
117 }
118 // dblocks must be at least one AG worth
119 if (out.dblocks < out.agblocks) {
120 return false;
121 }
122 // version must be 4 or 5
123 const unsigned ver = out.format_version();
124 if (ver != XFS_SB_VERSION_4 && ver != XFS_SB_VERSION_5) {
125 return false;
126 }
127 return true;
128}
129
130bool parse_agf(const unsigned char* buf, AGF& out) {
131 if (buf == nullptr) return false;
132
133 out.magicnum = be32(buf + 0x00);
134 if (out.magicnum != XFS_AGF_MAGIC_V4) { // V4/V5 share the same ASCII "XAGF"
135 return false;
136 }
137 out.versionnum = be32(buf + 0x04);
138 out.seqno = be32(buf + 0x08);
139 out.length = be32(buf + 0x0C);
140 out.roots[0] = be32(buf + 0x10);
141 out.roots[1] = be32(buf + 0x14);
142 out.roots[2] = be32(buf + 0x18); // V5 rmap root; on V4 this is reserved/zero
143 out.levels[0] = be32(buf + 0x1C);
144 out.levels[1] = be32(buf + 0x20);
145 out.levels[2] = be32(buf + 0x24);
146 out.flfirst = be32(buf + 0x28);
147 out.fllast = be32(buf + 0x2C);
148 out.flcount = be32(buf + 0x30);
149 out.freeblks = be32(buf + 0x34);
150 out.longest = be32(buf + 0x38);
151 out.btreeblks = be32(buf + 0x3C);
152
153 if (out.versionnum == 0 || out.length == 0) return false;
154 if (out.levels[0] > 16 || out.levels[1] > 16) return false; // sanity
155 return true;
156}
157
158bool parse_agi(const unsigned char* buf, AGI& out) {
159 return parse_agi(buf, /*is_v5=*/false, out);
160}
161
162bool parse_agi(const unsigned char* buf, bool is_v5, AGI& out) {
163 if (buf == nullptr) return false;
164
165 out.magicnum = be32(buf + 0x00);
166 if (out.magicnum != XFS_AGI_MAGIC_V4) { // same magic on V4/V5
167 return false;
168 }
169 out.versionnum = be32(buf + 0x04);
170 out.seqno = be32(buf + 0x08);
171 out.length = be32(buf + 0x0C);
172 out.count = be32(buf + 0x10);
173 out.root = be32(buf + 0x14);
174 out.level = be32(buf + 0x18);
175 out.freecount = be32(buf + 0x1C);
176 out.newino = be32(buf + 0x20);
177 out.dirino = be32(buf + 0x24);
178 for (int i = 0; i < 64; ++i) {
179 out.unlinked[i] = be32(buf + 0x28 + i * 4);
180 }
181
182 // V5-only fields: agi_free_root / agi_free_level (FINOBT roots).
183 //
184 // V5 AGI layout after unlinked[64] (offset 0x128):
185 // 0x128 uuid_t agi_uuid (16 bytes)
186 // 0x138 __be32 agi_crc
187 // 0x13C __be32 agi_pad32
188 // 0x140 __be64 agi_lsn
189 // 0x148 __be32 agi_free_root
190 // 0x14C __be32 agi_free_level
191 //
192 // On V4 these bytes are not present (AGI is 296 bytes), so we leave
193 // free_root/free_level at zero unless is_v5 is true.
194 if (is_v5) {
195 out.free_root = be32(buf + 0x148);
196 out.free_level = be32(buf + 0x14C);
197 } else {
198 out.free_root = 0;
199 out.free_level = 0;
200 }
201
202 if (out.versionnum == 0 || out.length == 0) return false;
203 if (out.level > 16) return false;
204 if (out.free_level > 16) return false;
205 return true;
206}
207
208InodeAddress crack_inode(uint64_t ino, const Superblock& sb) {
209 InodeAddress a;
210 const unsigned agno_shift = sb.agblklog + sb.inopblog;
211 const unsigned agbno_shift = sb.inopblog;
212 const uint64_t inopblock_mask = static_cast<uint64_t>(sb.inopblock) - 1;
213
214 a.agno = static_cast<uint32_t>(ino >> agno_shift);
215 a.agbno = static_cast<uint32_t>((ino >> agbno_shift) & ((1u << sb.agblklog) - 1));
216 a.offset = static_cast<uint32_t>(ino & inopblock_mask);
217 return a;
218}
219
220} // namespace xfs
bool parse_agf(const unsigned char *buf, AGF &out)
uint16_t be16(const unsigned char *p)
constexpr uint32_t XFS_AGI_MAGIC_V4
bool parse_superblock(const unsigned char *buf, Superblock &out)
constexpr uint16_t XFS_SB_VERSION_5
InodeAddress crack_inode(uint64_t ino, const Superblock &sb)
uint64_t be64(const unsigned char *p)
bool parse_agi(const unsigned char *buf, AGI &out)
uint32_t be32(const unsigned char *p)
constexpr uint32_t XFS_SB_MAGIC
constexpr uint16_t XFS_SB_VERSION_4
constexpr uint32_t XFS_AGF_MAGIC_V4
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
uint32_t bad_features2
uint32_t features_incompat
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
uint16_t logsectsize
uint16_t versionnum
uint32_t spino_align
uint32_t features_compat