Skip to content
API Documentation
BastionGuard™
xfs_inode.cpp
Go to the documentation of this file.
1// xfs_inode.cpp -- dinode core + extent record decoder.
2//
3// All multi-byte fields on disk are big-endian. Extent records are
4// further bit-packed across two 64-bit words; see the comment block in
5// xfs_inode.hpp for the layout. The decoder is unit-tested -- this is
6// the part that is most sensitive to subtle off-by-one mistakes (the
7// "9 high + 43 low" split of the physical block was the trickiest bit).
8
9#include "xfs_inode.hpp"
10
11#include <algorithm>
12
13namespace xfs {
14
15bool parse_dinode_core(const unsigned char* buf, DinodeCore& out) {
16 if (buf == nullptr) return false;
17 out = {};
18
19 out.magic = be16(buf + 0x00);
20 if (out.magic != XFS_DINODE_MAGIC_BE) return false;
21
22 out.mode = be16(buf + 0x02);
23 out.version = buf[0x04];
24 out.format = buf[0x05];
25
26 // Sanity: version must be 1, 2, or 3
27 if (out.version < 1 || out.version > 3) return false;
28 // Sanity: format must be 0..4 (we only USE 1,2,3 but accept the others)
29 if (out.format > 4) return false;
30
31 out.uid = be32(buf + 0x08);
32 out.gid = be32(buf + 0x0C);
33 out.nlink = be32(buf + 0x10);
34
35 out.size = static_cast<int64_t>(be64(buf + 0x3C));
36 out.nblocks = be64(buf + 0x44);
37
38 out.nextents = be32(buf + 0x50);
39 out.anextents = be16(buf + 0x54);
40 out.forkoff = buf[0x56];
41 out.aformat = buf[0x57];
42 out.flags = be16(buf + 0x5E);
43 out.gen = be32(buf + 0x60);
44
45 if (out.is_v3()) {
46 // V3 extension fields
47 out.flags2 = be64(buf + 0x7C);
48 out.ino = be64(buf + 0x9C);
49 }
50
51 // Sanity: refuse impossibly-large extent counts (corrupt inode).
52 // 1 million extents in a single fork is already absurd.
53 if (out.nextents > 1'000'000) return false;
54
55 return true;
56}
57
58uint32_t dinode_data_fork_length(const DinodeCore& core, uint32_t inode_size) {
59 const uint32_t fork_start = dinode_data_fork_offset(core);
60 if (fork_start >= inode_size) return 0;
61
62 // If forkoff != 0, the attribute fork begins at:
63 // fork_start + forkoff * 8 bytes
64 // ...so the data fork has that many bytes available.
65 if (core.forkoff != 0) {
66 const uint32_t data_len = static_cast<uint32_t>(core.forkoff) * 8u;
67 // Defensive cap: never exceed inode_size - fork_start
68 const uint32_t max_avail = inode_size - fork_start;
69 return std::min(data_len, max_avail);
70 }
71 return inode_size - fork_start;
72}
73
74void decode_extent_record(const unsigned char* buf16, Extent& out) {
75 const uint64_t w0 = be64(buf16 + 0);
76 const uint64_t w1 = be64(buf16 + 8);
77
78 // Bit 63 of w0: flag (unwritten)
79 out.unwritten = ((w0 >> 63) & 1ull) != 0;
80
81 // Bits 62..9 of w0: logical offset (54 bits)
82 const uint64_t logical_mask = (1ull << 54) - 1ull;
83 out.logical_off_blocks = (w0 >> 9) & logical_mask;
84
85 // Bits 8..0 of w0: high 9 bits of physical block
86 const uint64_t phys_high9 = w0 & 0x1FFull;
87 // Bits 63..21 of w1: low 43 bits of physical block
88 const uint64_t phys_low43 = (w1 >> 21) & ((1ull << 43) - 1ull);
89 out.physical_block = (phys_high9 << 43) | phys_low43;
90
91 // Bits 20..0 of w1: length (21 bits)
92 out.length_blocks = static_cast<uint32_t>(w1 & ((1ull << 21) - 1ull));
93}
94
95uint64_t fsb_to_disk_offset(uint64_t fsb, const VolumeContext& vol) {
96 if (vol.sb.agblklog == 0) return vol.volume_offset + fsb * vol.sb.blocksize;
97 const uint64_t mask = (1ull << vol.sb.agblklog) - 1ull;
98 const uint32_t agno = static_cast<uint32_t>(fsb >> vol.sb.agblklog);
99 const uint32_t agbno = static_cast<uint32_t>(fsb & mask);
100 return vol.volume_offset
101 + static_cast<uint64_t>(agno) * vol.sb.agblocks * vol.sb.blocksize
102 + static_cast<uint64_t>(agbno) * vol.sb.blocksize;
103}
104
105bool decode_extents_inline(const unsigned char* data_fork_buf,
106 uint32_t nextents,
107 uint32_t max_bytes,
108 std::vector<Extent>& out) {
109 if (data_fork_buf == nullptr) return false;
110 if (static_cast<uint64_t>(nextents) * 16ull > max_bytes) {
111 return false;
112 }
113 out.clear();
114 out.reserve(nextents);
115 for (uint32_t i = 0; i < nextents; ++i) {
116 Extent e;
117 decode_extent_record(data_fork_buf + i * 16, e);
118 // Sanity: a length of 0 is impossible
119 if (e.length_blocks == 0) {
120 // Be tolerant: skip and continue rather than abort.
121 continue;
122 }
123 out.push_back(e);
124 }
125 return true;
126}
127
128} // namespace xfs
uint16_t be16(const unsigned char *p)
bool parse_dinode_core(const unsigned char *buf, DinodeCore &out)
Definition xfs_inode.cpp:15
void decode_extent_record(const unsigned char *buf16, Extent &out)
Definition xfs_inode.cpp:74
uint64_t fsb_to_disk_offset(uint64_t fsb, const VolumeContext &vol)
Definition xfs_inode.cpp:95
uint32_t dinode_data_fork_offset(const DinodeCore &core)
uint64_t be64(const unsigned char *p)
uint32_t be32(const unsigned char *p)
bool decode_extents_inline(const unsigned char *data_fork_buf, uint32_t nextents, uint32_t max_bytes, std::vector< Extent > &out)
uint32_t dinode_data_fork_length(const DinodeCore &core, uint32_t inode_size)
Definition xfs_inode.cpp:58
constexpr uint16_t XFS_DINODE_MAGIC_BE
uint32_t nextents
bool is_v3() const
uint16_t anextents
uint64_t physical_block
uint64_t logical_off_blocks
uint32_t length_blocks