Skip to content
API Documentation
BastionGuard™
xfs_inode.hpp
Go to the documentation of this file.
1// xfs_inode.hpp -- XFS dinode parsing.
2//
3// A "dinode" is XFS's on-disk inode structure. Its size is fixed per
4// filesystem (typically 256, 512, 1024, or 2048 bytes -- sb.inodesize)
5// and it lives at a fixed disk byte offset that the IBT walker has
6// already given us as `AllocatedInode.disk_offset`.
7//
8// Structure of a dinode:
9//
10// [ core (~100 bytes) ]
11// [ data fork content ] -- format depends on di_format
12// [ attribute fork ] -- optional, located at di_forkoff*8 from end of core
13//
14// Core (V3, the modern V5 format):
15//
16// offset size field
17// 0x00 2 di_magic "IN" (0x494E) big-endian
18// 0x02 2 di_mode POSIX mode (file type + permissions)
19// 0x04 1 di_version 1, 2, or 3 (V3 = V5 inode)
20// 0x05 1 di_format 1=local, 2=extents, 3=btree, 4=uuid (legacy)
21// 0x06 2 di_onlink (legacy V1 only; pad on V2/V3)
22// 0x08 4 di_uid
23// 0x0C 4 di_gid
24// 0x10 4 di_nlink V2/V3
25// 0x14 2 di_projid_lo
26// 0x16 2 di_projid_hi
27// 0x18 8 di_pad[] (8 bytes pad)
28// 0x20 2 di_flushiter bumped at write
29// 0x22 2 reserved
30// 0x24 8 di_atime sec(4) + nsec(4)
31// 0x2C 8 di_mtime
32// 0x34 8 di_ctime
33// 0x3C 8 di_size (logical file size in bytes)
34// 0x44 8 di_nblocks
35// 0x4C 4 di_extsize
36// 0x50 4 di_nextents (number of data-fork extents)
37// 0x54 2 di_anextents (number of attribute-fork extents)
38// 0x56 1 di_forkoff (in 8-byte units; 0 = no attribute fork)
39// 0x57 1 di_aformat
40// 0x58 4 di_dmevmask
41// 0x5C 2 di_dmstate
42// 0x5E 2 di_flags
43// 0x60 4 di_gen
44// 0x64 4 di_next_unlinked -- last byte of V1/V2 core
45//
46// V3-specific extension (only if di_version == 3):
47// 0x68 4 di_crc CRC32 of the dinode
48// 0x6C 8 di_changecount
49// 0x74 8 di_lsn
50// 0x7C 8 di_flags2
51// 0x84 4 di_cowextsize
52// 0x88 12 di_pad2[]
53// 0x94 8 di_crtime (creation time)
54// 0x9C 8 di_ino (inode number, redundant copy)
55// 0xA4 16 di_uuid (volume uuid copy)
56//
57// V3 core ends at offset 0xB4 = 180 bytes. The data fork starts there.
58// V2/V1 core ends at offset 0x68 = 104 bytes.
59//
60// EXTENT RECORDS (di_format=2)
61// ----------------------------
62// An extent record is exactly 16 bytes and is bit-packed across two
63// 64-bit big-endian words. Layout (bit 0 = MSB):
64//
65// word0[63] flag (0 = normal extent, 1 = unwritten preallocated)
66// word0[62..9] logical offset (54 bits) -- in filesystem blocks
67// word0[8..0] upper 9 bits of physical block (out of 52)
68// word1[63..21] lower 43 bits of physical block (so 9+43 = 52 total)
69// word1[20..0] length in blocks (21 bits)
70//
71// Decoding requires careful big-endian handling and shift counts. We
72// expose `decode_extent_record(bytes16, out_extent)` and unit-test it.
73//
74// Inline data:
75// For di_format == 1 (local), the data fork is just bytes -- e.g. for a
76// shortform directory, those are the SF directory entries; for a small
77// symlink, those are the link target string; for a tiny file with
78// inline data... actually XFS does NOT inline file data the way ext4
79// does. di_format=1 for a regular file is essentially never seen in
80// practice (XFS allocates an extent even for empty files). What is
81// commonly inlined:
82// - Shortform symlinks (mode S_IFLNK + di_format=1)
83// - Shortform directories (mode S_IFDIR + di_format=1)
84// We handle both.
85
86#pragma once
87
88#include "xfs_format.hpp"
89
90#include <cstdint>
91#include <string>
92#include <vector>
93
94namespace xfs {
95
96// di_format values
97constexpr uint8_t XFS_DINODE_FMT_DEV = 0; // character/block device
98constexpr uint8_t XFS_DINODE_FMT_LOCAL = 1; // inline (shortform dir, symlink)
99constexpr uint8_t XFS_DINODE_FMT_EXTENTS = 2; // extent list
100constexpr uint8_t XFS_DINODE_FMT_BTREE = 3; // extent B+tree (large/fragmented)
101constexpr uint8_t XFS_DINODE_FMT_UUID = 4; // legacy
102
103// POSIX mode bits we actually look at
104constexpr uint16_t MODE_TYPE_MASK = 0xF000;
105constexpr uint16_t MODE_IFREG = 0x8000;
106constexpr uint16_t MODE_IFDIR = 0x4000;
107constexpr uint16_t MODE_IFLNK = 0xA000;
108constexpr uint16_t MODE_IFCHR = 0x2000;
109constexpr uint16_t MODE_IFBLK = 0x6000;
110constexpr uint16_t MODE_IFIFO = 0x1000;
111constexpr uint16_t MODE_IFSOCK = 0xC000;
112
113constexpr uint16_t XFS_DINODE_MAGIC_BE = 0x494E; // "IN"
114
116 uint16_t magic {0};
117 uint16_t mode {0};
118 uint8_t version {0};
119 uint8_t format {0};
120 uint32_t uid {0};
121 uint32_t gid {0};
122 uint32_t nlink {0};
123 int64_t size {0};
124 uint64_t nblocks {0};
125 uint32_t nextents {0};
126 uint16_t anextents {0};
127 uint8_t forkoff {0}; // attribute fork offset, in 8-byte units
128 uint8_t aformat {0};
129 uint16_t flags {0};
130 uint32_t gen {0};
131
132 // V3 only -- zero on V1/V2
133 uint64_t ino {0};
134 uint64_t flags2 {0};
135
136 // ---- helpers ----
137 bool is_v3() const { return version == 3; }
138 bool is_dir() const { return (mode & MODE_TYPE_MASK) == MODE_IFDIR; }
139 bool is_reg() const { return (mode & MODE_TYPE_MASK) == MODE_IFREG; }
140 bool is_symlink() const { return (mode & MODE_TYPE_MASK) == MODE_IFLNK; }
141 bool is_special() const {
142 const uint16_t t = mode & MODE_TYPE_MASK;
143 return t == MODE_IFCHR || t == MODE_IFBLK ||
144 t == MODE_IFIFO || t == MODE_IFSOCK;
145 }
146};
147
148// Parse the core portion of a dinode from a raw buffer (must be at least
149// inode_size bytes). Returns false on bad magic / impossible values.
150bool parse_dinode_core(const unsigned char* buf, DinodeCore& out);
151
152// Returns the byte offset within the inode where the data fork begins.
153// V3 inode: 176 (0xB0). V1/V2 inode: 100 (0x64).
154inline uint32_t dinode_data_fork_offset(const DinodeCore& core) {
155 return core.is_v3() ? 176u : 100u;
156}
157
158// Returns the maximum byte length of the data fork (limited by attribute
159// fork position when present, otherwise inode_size).
160//
161// `inode_size` is sb.inodesize (e.g. 256/512/1024/2048).
162uint32_t dinode_data_fork_length(const DinodeCore& core, uint32_t inode_size);
163
164// =============================================================================
165// Extent records (bmbt records)
166// =============================================================================
167
168struct Extent {
169 uint64_t logical_off_blocks {0}; // in filesystem blocks
170 uint64_t physical_block {0}; // global filesystem block number
171 uint32_t length_blocks {0}; // length in blocks
172 bool unwritten {false};
173};
174
175// Decode one 16-byte BMBT extent record. The encoding is bit-packed
176// across two 64-bit big-endian words (see header banner above).
177void decode_extent_record(const unsigned char* buf16, Extent& out);
178
179// Convert a "filesystem block number" (FSB) -- which encodes both AG
180// number and AG-relative block in the high/low halves -- to an absolute
181// disk byte offset. This is the formula:
182//
183// agno = fsb >> sb.agblklog
184// agbno = fsb & ((1 << sb.agblklog) - 1)
185// disk_offset = volume_offset + agno*agblocks*blocksize + agbno*blocksize
186uint64_t fsb_to_disk_offset(uint64_t fsb, const VolumeContext& vol);
187
188// Decode all extents in a data fork that has di_format=2.
189// `data_fork_buf` points to the first extent record; `nextents` from
190// the dinode core says how many to read; `max_bytes` is the max safe
191// read length (data_fork_length).
192//
193// Returns false if `nextents * 16 > max_bytes` (corrupt inode).
194bool decode_extents_inline(const unsigned char* data_fork_buf,
195 uint32_t nextents,
196 uint32_t max_bytes,
197 std::vector<Extent>& out);
198
199} // namespace xfs
constexpr uint8_t XFS_DINODE_FMT_EXTENTS
Definition xfs_inode.hpp:99
constexpr uint16_t MODE_IFSOCK
constexpr uint8_t XFS_DINODE_FMT_LOCAL
Definition xfs_inode.hpp:98
constexpr uint16_t MODE_IFREG
constexpr uint16_t MODE_IFIFO
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)
constexpr uint8_t XFS_DINODE_FMT_BTREE
constexpr uint8_t XFS_DINODE_FMT_UUID
constexpr uint16_t MODE_IFCHR
constexpr uint16_t MODE_IFBLK
bool decode_extents_inline(const unsigned char *data_fork_buf, uint32_t nextents, uint32_t max_bytes, std::vector< Extent > &out)
constexpr uint8_t XFS_DINODE_FMT_DEV
Definition xfs_inode.hpp:97
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
constexpr uint16_t MODE_IFDIR
constexpr uint16_t MODE_TYPE_MASK
constexpr uint16_t MODE_IFLNK
uint32_t nextents
bool is_symlink() const
bool is_dir() const
bool is_special() const
bool is_reg() const
bool is_v3() const
uint16_t anextents
uint64_t physical_block
uint64_t logical_off_blocks
uint32_t length_blocks