Skip to content
API Documentation
BastionGuard™
xfs::anonymous_namespace{xfs_btree.cpp} Namespace Reference

Classes

struct  LocalBmbtRec

Functions

uint32_t ibt_maxrecs (uint32_t blocksize, uint32_t header_size, uint32_t level)
uint64_t ag_block_to_disk_offset (const VolumeContext &vol, uint32_t agno, uint32_t agbno)
bool walk_ibt_block (DiskReader &reader, const VolumeContext &vol, uint32_t agno, uint32_t agbno, uint32_t expected_level, uint32_t magic_v4, uint32_t magic_v5, const char *tree_label, const InobtRecordCallback &on_record, std::set< uint32_t > &visited, size_t &nodes_visited, std::vector< std::string > &logs, std::string &error, bool &callback_stopped)
void decode_bmbt_record_local (const unsigned char *buf16, LocalBmbtRec &r)
uint64_t fsb_to_byte_offset_local (uint64_t fsb, const VolumeContext &vol)
void push_extent (std::vector< Extent > &out, const LocalBmbtRec &r)
bool walk_bmbt_block (DiskReader &reader, const VolumeContext &vol, uint64_t child_fsb, uint32_t expected_level, bool is_v5, std::vector< Extent > &out, std::vector< std::string > &logs, uint32_t max_extents, std::set< uint64_t > &visited, size_t &nodes_visited)

Variables

constexpr uint32_t IBT_REC_SIZE = 16
constexpr uint32_t IBT_KEY_SIZE = 4
constexpr uint32_t IBT_PTR_SIZE = 4
constexpr size_t MAX_NODES_PER_AG = 1'000'000

Function Documentation

◆ ag_block_to_disk_offset()

uint64_t xfs::anonymous_namespace{xfs_btree.cpp}::ag_block_to_disk_offset ( const VolumeContext & vol,
uint32_t agno,
uint32_t agbno )

Definition at line 59 of file xfs_btree.cpp.

60 {
61 return vol.volume_offset
62 + static_cast<uint64_t>(agno) * vol.sb.agblocks * vol.sb.blocksize
63 + static_cast<uint64_t>(agbno) * vol.sb.blocksize;
64}

References ag_block_to_disk_offset(), xfs::Superblock::agblocks, xfs::Superblock::blocksize, xfs::VolumeContext::sb, and xfs::VolumeContext::volume_offset.

Referenced by ag_block_to_disk_offset(), and walk_ibt_block().

◆ decode_bmbt_record_local()

void xfs::anonymous_namespace{xfs_btree.cpp}::decode_bmbt_record_local ( const unsigned char * buf16,
LocalBmbtRec & r )

Definition at line 517 of file xfs_btree.cpp.

517 {
518 const uint64_t w0 = be64(buf16 + 0);
519 const uint64_t w1 = be64(buf16 + 8);
520 r.unwritten = ((w0 >> 63) & 1ull) != 0;
521 r.logical_off_blocks = (w0 >> 9) & ((1ull << 54) - 1ull);
522 const uint64_t hi9 = w0 & 0x1FFull;
523 const uint64_t lo43 = (w1 >> 21) & ((1ull << 43) - 1ull);
524 r.physical_block = (hi9 << 43) | lo43;
525 r.length_blocks = static_cast<uint32_t>(w1 & ((1ull << 21) - 1ull));
526}
uint64_t be64(const unsigned char *p)

References xfs::be64(), and decode_bmbt_record_local().

Referenced by decode_bmbt_record_local(), and walk_bmbt_block().

◆ fsb_to_byte_offset_local()

uint64_t xfs::anonymous_namespace{xfs_btree.cpp}::fsb_to_byte_offset_local ( uint64_t fsb,
const VolumeContext & vol )

Definition at line 528 of file xfs_btree.cpp.

528 {
529 if (vol.sb.agblklog == 0) return vol.volume_offset + fsb * vol.sb.blocksize;
530 const uint64_t mask = (1ull << vol.sb.agblklog) - 1ull;
531 const uint32_t agno = static_cast<uint32_t>(fsb >> vol.sb.agblklog);
532 const uint32_t agbno = static_cast<uint32_t>(fsb & mask);
533 return vol.volume_offset
534 + static_cast<uint64_t>(agno) * vol.sb.agblocks * vol.sb.blocksize
535 + static_cast<uint64_t>(agbno) * vol.sb.blocksize;
536}

References xfs::Superblock::agblklog, xfs::Superblock::agblocks, xfs::Superblock::blocksize, fsb_to_byte_offset_local(), xfs::VolumeContext::sb, and xfs::VolumeContext::volume_offset.

Referenced by fsb_to_byte_offset_local(), and walk_bmbt_block().

◆ ibt_maxrecs()

uint32_t xfs::anonymous_namespace{xfs_btree.cpp}::ibt_maxrecs ( uint32_t blocksize,
uint32_t header_size,
uint32_t level )

Definition at line 52 of file xfs_btree.cpp.

52 {
53 const uint32_t avail = blocksize - header_size;
54 return (level == 0) ? (avail / IBT_REC_SIZE)
55 : (avail / (IBT_KEY_SIZE + IBT_PTR_SIZE));
56}

References IBT_KEY_SIZE, ibt_maxrecs(), IBT_PTR_SIZE, and IBT_REC_SIZE.

Referenced by ibt_maxrecs(), and walk_ibt_block().

◆ push_extent()

void xfs::anonymous_namespace{xfs_btree.cpp}::push_extent ( std::vector< Extent > & out,
const LocalBmbtRec & r )

Definition at line 565 of file xfs_btree.cpp.

565 {
566 Extent e;
567 e.logical_off_blocks = r.logical_off_blocks;
568 e.physical_block = r.physical_block;
569 e.length_blocks = r.length_blocks;
570 e.unwritten = r.unwritten;
571 out.push_back(e);
572}
uint64_t physical_block
uint64_t logical_off_blocks
uint32_t length_blocks

References xfs::Extent::length_blocks, xfs::Extent::logical_off_blocks, xfs::Extent::physical_block, push_extent(), and xfs::Extent::unwritten.

Referenced by push_extent(), and walk_bmbt_block().

◆ walk_bmbt_block()

bool xfs::anonymous_namespace{xfs_btree.cpp}::walk_bmbt_block ( DiskReader & reader,
const VolumeContext & vol,
uint64_t child_fsb,
uint32_t expected_level,
bool is_v5,
std::vector< Extent > & out,
std::vector< std::string > & logs,
uint32_t max_extents,
std::set< uint64_t > & visited,
size_t & nodes_visited )

Definition at line 577 of file xfs_btree.cpp.

584 {
585 if (out.size() >= max_extents) return true;
586 if (child_fsb == NULLFSBLOCK || child_fsb == 0) return true;
587 if (++nodes_visited > 1'000'000) {
588 logs.push_back("XFS BMBT: traversal cap reached (1M nodes), stopping");
589 return false;
590 }
591 if (!visited.insert(child_fsb).second) {
592 logs.push_back("XFS BMBT: cycle at FSB " + std::to_string(child_fsb)
593 + " (skipped)");
594 return true;
595 }
596
597 const uint64_t off = fsb_to_byte_offset_local(child_fsb, vol);
598 std::vector<unsigned char> blk(vol.sb.blocksize, 0);
599 if (!reader.read_at(off, blk.data(), blk.size())) {
600 logs.push_back("XFS BMBT: read failed at FSB " + std::to_string(child_fsb));
601 return true;
602 }
603
604 BtreeLongHeader hdr;
605 if (!parse_btree_long_header(blk.data(),
608 hdr)) {
609 logs.push_back("XFS BMBT: bad magic at FSB " + std::to_string(child_fsb));
610 return true;
611 }
612 if (hdr.level != expected_level) {
613 logs.push_back("XFS BMBT: level mismatch at FSB "
614 + std::to_string(child_fsb)
615 + " (got " + std::to_string(hdr.level)
616 + ", expected " + std::to_string(expected_level) + ")");
617 // Continue anyway -- damaged trees can still yield records
618 }
619
620 const uint32_t hsize = btree_long_header_size(hdr.is_v5_header);
621
622 if (hdr.level == 0) {
623 // Leaf: numrecs records of 16 bytes each starting at hsize
624 const uint32_t maxrecs_leaf =
625 (vol.sb.blocksize - hsize) / BMBT_REC_SIZE;
626 const uint32_t n =
627 std::min<uint32_t>(hdr.numrecs, maxrecs_leaf);
628 for (uint32_t i = 0; i < n; ++i) {
629 if (out.size() >= max_extents) return true;
631 decode_bmbt_record_local(blk.data() + hsize + i * BMBT_REC_SIZE, r);
632 if (r.length_blocks == 0) continue; // tolerate
633 push_extent(out, r);
634 }
635 } else {
636 // Internal: numrecs keys followed by maxrecs_internal keys (XFS
637 // reserves space for max). Pointers start at hsize + maxrecs *
638 // KEY_SIZE.
639 const uint32_t key_ptr_pair =
641 const uint32_t maxrecs_internal =
642 (vol.sb.blocksize - hsize) / key_ptr_pair;
643 const uint32_t n =
644 std::min<uint32_t>(hdr.numrecs, maxrecs_internal);
645 const uint32_t ptrs_offset =
646 hsize + maxrecs_internal * BMBT_KEY_SIZE_LONG;
647 for (uint32_t i = 0; i < n; ++i) {
648 const uint32_t poff = ptrs_offset + i * BMBT_PTR_SIZE_LONG;
649 if (poff + BMBT_PTR_SIZE_LONG > vol.sb.blocksize) break;
650 const uint64_t cf = be64(blk.data() + poff);
651 if (!walk_bmbt_block(reader, vol, cf, hdr.level - 1, is_v5,
652 out, logs, max_extents, visited,
653 nodes_visited)) {
654 return false;
655 }
656 }
657 }
658 return true;
659}
bool read_at(uint64_t offset, void *buffer, size_t len)
void push_extent(std::vector< Extent > &out, const LocalBmbtRec &r)
uint64_t fsb_to_byte_offset_local(uint64_t fsb, const VolumeContext &vol)
void decode_bmbt_record_local(const unsigned char *buf16, LocalBmbtRec &r)
bool walk_bmbt_block(DiskReader &reader, const VolumeContext &vol, uint64_t child_fsb, uint32_t expected_level, bool is_v5, std::vector< Extent > &out, std::vector< std::string > &logs, uint32_t max_extents, std::set< uint64_t > &visited, size_t &nodes_visited)
bool parse_btree_long_header(const unsigned char *buf, uint32_t expected_magic_v4, uint32_t expected_magic_v5, BtreeLongHeader &out)
uint32_t btree_long_header_size(bool is_v5)
constexpr uint32_t XFS_BMBT_MAGIC_V5
constexpr uint32_t BMBT_PTR_SIZE_LONG
constexpr uint32_t XFS_BMBT_MAGIC_V4
constexpr uint32_t BMBT_KEY_SIZE_LONG
constexpr uint32_t BMBT_REC_SIZE
constexpr uint64_t NULLFSBLOCK
Definition xfs_btree.hpp:86

References xfs::be64(), xfs::Superblock::blocksize, xfs::BMBT_KEY_SIZE_LONG, xfs::BMBT_PTR_SIZE_LONG, xfs::BMBT_REC_SIZE, xfs::btree_long_header_size(), decode_bmbt_record_local(), fsb_to_byte_offset_local(), xfs::BtreeLongHeader::is_v5_header, xfs::BtreeLongHeader::level, xfs::NULLFSBLOCK, xfs::BtreeLongHeader::numrecs, xfs::parse_btree_long_header(), push_extent(), DiskReader::read_at(), xfs::VolumeContext::sb, walk_bmbt_block(), xfs::XFS_BMBT_MAGIC_V4, and xfs::XFS_BMBT_MAGIC_V5.

Referenced by walk_bmbt_block().

◆ walk_ibt_block()

bool xfs::anonymous_namespace{xfs_btree.cpp}::walk_ibt_block ( DiskReader & reader,
const VolumeContext & vol,
uint32_t agno,
uint32_t agbno,
uint32_t expected_level,
uint32_t magic_v4,
uint32_t magic_v5,
const char * tree_label,
const InobtRecordCallback & on_record,
std::set< uint32_t > & visited,
size_t & nodes_visited,
std::vector< std::string > & logs,
std::string & error,
bool & callback_stopped )

Definition at line 134 of file xfs_btree.cpp.

147 {
148 if (callback_stopped) return true;
149
150 if (agbno == 0 || agbno == NULLAGBLOCK || agbno >= vol.sb.agblocks) {
151 std::ostringstream m;
152 m << "XFS " << tree_label << " (AG " << agno << "): invalid child pointer " << agbno;
153 logs.push_back(m.str());
154 return true; // not fatal: skip this branch
155 }
156
157 if (++nodes_visited > MAX_NODES_PER_AG) {
158 error = std::string("XFS ") + tree_label
159 + ": traversal exceeded safety cap (possible loop)";
160 return false;
161 }
162
163 if (!visited.insert(agbno).second) {
164 std::ostringstream m;
165 m << "XFS " << tree_label << " (AG " << agno
166 << "): cycle detected at block " << agbno << ", skipping";
167 logs.push_back(m.str());
168 return true;
169 }
170
171 const uint64_t blk_off = ag_block_to_disk_offset(vol, agno, agbno);
172 std::vector<unsigned char> block(vol.sb.blocksize, 0);
173 if (!reader.read_at(blk_off, block.data(), block.size())) {
174 std::ostringstream m;
175 m << "XFS " << tree_label << " (AG " << agno
176 << "): read failed for block " << agbno;
177 logs.push_back(m.str());
178 return true; // not fatal -- just lose this branch
179 }
180
181 BtreeNodeHeader hdr;
182 if (!parse_btree_header(block.data(), magic_v4, magic_v5, hdr)) {
183 std::ostringstream m;
184 m << "XFS " << tree_label << " (AG " << agno
185 << "): bad magic at block " << agbno;
186 logs.push_back(m.str());
187 return true;
188 }
189 if (hdr.level != expected_level) {
190 std::ostringstream m;
191 m << "XFS " << tree_label << " (AG " << agno
192 << "): block " << agbno << " has level " << hdr.level
193 << ", expected " << expected_level;
194 logs.push_back(m.str());
195 // Don't abort -- some damaged trees can still yield records
196 }
197
198 const uint32_t header_size = btree_header_size(hdr.is_v5_header);
199 const bool is_v5 = vol.sb.is_v5();
200 const bool is_v5_sparse =
202
203 if (hdr.level == 0) {
204 // ----------- LEAF -----------
205 const uint32_t maxrecs = ibt_maxrecs(vol.sb.blocksize, header_size, 0);
206 if (hdr.numrecs > maxrecs) {
207 std::ostringstream m;
208 m << "XFS " << tree_label << " (AG " << agno
209 << "): leaf at block " << agbno
210 << " reports numrecs=" << hdr.numrecs
211 << " > maxrecs=" << maxrecs << ", clamping";
212 logs.push_back(m.str());
213 }
214 const uint32_t n = std::min<uint32_t>(hdr.numrecs, maxrecs);
215 for (uint32_t i = 0; i < n; ++i) {
216 const unsigned char* recp =
217 block.data() + header_size + i * IBT_REC_SIZE;
218 InobtRec rec;
219 parse_inobt_record(recp, is_v5_sparse, rec);
220 if (!on_record(rec, agno)) {
221 callback_stopped = true;
222 return true;
223 }
224 }
225 } else {
226 // ----------- INTERNAL -----------
227 const uint32_t maxrecs = ibt_maxrecs(vol.sb.blocksize, header_size,
228 hdr.level);
229 if (hdr.numrecs > maxrecs) {
230 std::ostringstream m;
231 m << "XFS " << tree_label << " (AG " << agno
232 << "): internal at block " << agbno
233 << " reports numrecs=" << hdr.numrecs
234 << " > maxrecs=" << maxrecs << ", clamping";
235 logs.push_back(m.str());
236 }
237 const uint32_t n = std::min<uint32_t>(hdr.numrecs, maxrecs);
238
239 // Pointers start AFTER the maxrecs key array (not numrecs!)
240 const uint32_t ptrs_offset = header_size + maxrecs * IBT_KEY_SIZE;
241
242 for (uint32_t i = 0; i < n; ++i) {
243 const uint32_t ptr_off = ptrs_offset + i * IBT_PTR_SIZE;
244 if (ptr_off + IBT_PTR_SIZE > vol.sb.blocksize) break;
245 const uint32_t child = be32(block.data() + ptr_off);
246 if (!walk_ibt_block(reader, vol, agno, child,
247 hdr.level - 1,
248 magic_v4, magic_v5, tree_label,
249 on_record, visited, nodes_visited,
250 logs, error, callback_stopped)) {
251 return false;
252 }
253 if (callback_stopped) return true;
254 }
255 }
256 return true;
257}
uint64_t ag_block_to_disk_offset(const VolumeContext &vol, uint32_t agno, uint32_t agbno)
Definition xfs_btree.cpp:59
bool walk_ibt_block(DiskReader &reader, const VolumeContext &vol, uint32_t agno, uint32_t agbno, uint32_t expected_level, uint32_t magic_v4, uint32_t magic_v5, const char *tree_label, const InobtRecordCallback &on_record, std::set< uint32_t > &visited, size_t &nodes_visited, std::vector< std::string > &logs, std::string &error, bool &callback_stopped)
uint32_t ibt_maxrecs(uint32_t blocksize, uint32_t header_size, uint32_t level)
Definition xfs_btree.cpp:52
constexpr uint32_t NULLAGBLOCK
Definition xfs_btree.hpp:85
bool parse_btree_header(const unsigned char *buf, uint32_t expected_magic_v4, uint32_t expected_magic_v5, BtreeNodeHeader &out)
Definition xfs_btree.cpp:72
uint32_t btree_header_size(bool is_v5)
void parse_inobt_record(const unsigned char *buf, bool is_v5_sparse, InobtRec &out)
uint32_t be32(const unsigned char *p)
constexpr uint32_t XFS_SB_FEAT_INCOMPAT_SPINODES
uint32_t features_incompat
bool is_v5() const

References ag_block_to_disk_offset(), xfs::Superblock::agblocks, xfs::be32(), xfs::Superblock::blocksize, xfs::btree_header_size(), xfs::Superblock::features_incompat, IBT_KEY_SIZE, ibt_maxrecs(), IBT_PTR_SIZE, IBT_REC_SIZE, xfs::Superblock::is_v5(), xfs::BtreeNodeHeader::is_v5_header, xfs::BtreeNodeHeader::level, MAX_NODES_PER_AG, xfs::NULLAGBLOCK, xfs::BtreeNodeHeader::numrecs, xfs::parse_btree_header(), xfs::parse_inobt_record(), DiskReader::read_at(), xfs::VolumeContext::sb, walk_ibt_block(), and xfs::XFS_SB_FEAT_INCOMPAT_SPINODES.

Referenced by walk_ibt_block().

Variable Documentation

◆ IBT_KEY_SIZE

uint32_t xfs::anonymous_namespace{xfs_btree.cpp}::IBT_KEY_SIZE = 4
constexpr

Definition at line 46 of file xfs_btree.cpp.

Referenced by ibt_maxrecs(), and walk_ibt_block().

◆ IBT_PTR_SIZE

uint32_t xfs::anonymous_namespace{xfs_btree.cpp}::IBT_PTR_SIZE = 4
constexpr

Definition at line 47 of file xfs_btree.cpp.

Referenced by ibt_maxrecs(), and walk_ibt_block().

◆ IBT_REC_SIZE

uint32_t xfs::anonymous_namespace{xfs_btree.cpp}::IBT_REC_SIZE = 16
constexpr

Definition at line 45 of file xfs_btree.cpp.

Referenced by ibt_maxrecs(), and walk_ibt_block().

◆ MAX_NODES_PER_AG

size_t xfs::anonymous_namespace{xfs_btree.cpp}::MAX_NODES_PER_AG = 1'000'000
constexpr

Definition at line 68 of file xfs_btree.cpp.

Referenced by walk_ibt_block().