Skip to content
API Documentation
BastionGuard™
anonymous_namespace{xfs_recovery.cpp} Namespace Reference

Functions

bool read_exact (DiskReader &reader, uint64_t off, void *dst, size_t n)
bool validate_geometry (DiskReader &reader, uint64_t volume_offset, const xfs::Superblock &sb, std::string &err)
void load_ag_headers (DiskReader &reader, uint64_t volume_offset, const xfs::Superblock &sb, uint32_t agno, xfs::AGContext &ag, std::vector< std::string > &logs)
bool read_dinode (DiskReader &reader, const xfs::VolumeContext &vol, uint64_t ino, std::vector< unsigned char > &buf)
bool write_extents_to_path (DiskReader &reader, const xfs::VolumeContext &vol, std::vector< xfs::Extent > extents, uint64_t file_size, const fs::path &out_path, std::string &err)
bool collect_extents (DiskReader &reader, const xfs::VolumeContext &vol, const xfs::DinodeCore &core, const unsigned char *fork_data, uint32_t fork_len, uint32_t max_extents, std::vector< xfs::Extent > &out, std::vector< std::string > &logs, std::string &err)
bool read_extents_data (DiskReader &reader, const xfs::VolumeContext &vol, const std::vector< xfs::Extent > &extents, uint64_t file_size, std::vector< unsigned char > &out, std::string &err)
bool collect_dir_entries_block_or_leaf (DiskReader &reader, const xfs::VolumeContext &vol, const std::vector< xfs::Extent > &extents, bool has_ftype, std::vector< xfs::DirEntry > &out, std::vector< std::string > &logs)

Variables

constexpr uint64_t XFS_DIR2_LEAF_OFFSET_BLOCKS = 32ull * 1024ull * 1024ull * 1024ull / 4096ull

Function Documentation

◆ collect_dir_entries_block_or_leaf()

bool anonymous_namespace{xfs_recovery.cpp}::collect_dir_entries_block_or_leaf ( DiskReader & reader,
const xfs::VolumeContext & vol,
const std::vector< xfs::Extent > & extents,
bool has_ftype,
std::vector< xfs::DirEntry > & out,
std::vector< std::string > & logs )

Definition at line 348 of file xfs_recovery.cpp.

353 {
354 const uint64_t bsize = vol.sb.blocksize;
355 const bool is_v5 = vol.sb.is_v5();
356
357 // Compute the leaf cutoff in this volume's block units. The XFS
358 // address-space convention reserves logical offset 32 GiB for the
359 // leaf/node area; in volume blocks that's (32 GiB / blocksize).
360 const uint64_t leaf_cutoff_blocks = (32ull * 1024ull * 1024ull * 1024ull) / bsize;
361
362 bool any_block_magic_seen = false;
363 bool any_data_magic_seen = false;
364
365 for (const auto& ext : extents) {
366 if (ext.unwritten) continue;
367 // Skip extents in the leaf/freespace/btree area (logical >= cutoff)
368 if (ext.logical_off_blocks >= leaf_cutoff_blocks) continue;
369
370 const uint32_t take_blocks = static_cast<uint32_t>(std::min<uint64_t>(
371 ext.length_blocks,
372 leaf_cutoff_blocks - ext.logical_off_blocks));
373
374 for (uint32_t b = 0; b < take_blocks; ++b) {
375 const uint64_t phys_byte =
376 xfs::fsb_to_disk_offset(ext.physical_block + b, vol);
377 std::vector<unsigned char> blk(bsize, 0);
378 if (!reader.read_at(phys_byte, blk.data(), bsize)) {
379 logs.push_back("XFS dir: read failed at FSB " +
380 std::to_string(ext.physical_block + b));
381 continue;
382 }
383
384 // First, try BLOCK magic (single-block dir); on miss, try DATA.
385 // Most multi-block dirs will be DATA; single-block will be BLOCK.
386 std::vector<xfs::DirEntry> entries;
387 bool parsed = xfs::parse_dir_data_block(blk.data(), bsize,
388 is_v5, has_ftype,
389 /*accept_block=*/true,
390 /*accept_data=*/true,
391 entries);
392 if (!parsed) {
393 // Some implementations have other auxiliary blocks (free
394 // space tracker XD2F/XDF3) inside the data range; skip them.
395 continue;
396 }
397 const uint32_t magic = xfs::be32(blk.data());
398 if (magic == xfs::XFS_DIR2_BLOCK_MAGIC ||
399 magic == xfs::XFS_DIR3_BLOCK_MAGIC) any_block_magic_seen = true;
400 if (magic == xfs::XFS_DIR2_DATA_MAGIC ||
401 magic == xfs::XFS_DIR3_DATA_MAGIC) any_data_magic_seen = true;
402 for (auto& e : entries) out.push_back(std::move(e));
403 }
404 }
405
406 if (!any_block_magic_seen && !any_data_magic_seen) {
407 logs.push_back("XFS dir: no recognised data block magic in extent list");
408 return false;
409 }
410 return true;
411}
bool read_at(uint64_t offset, void *buffer, size_t len)
uint64_t fsb_to_disk_offset(uint64_t fsb, const VolumeContext &vol)
Definition xfs_inode.cpp:95
constexpr uint32_t XFS_DIR2_BLOCK_MAGIC
Definition xfs_dir.hpp:51
constexpr uint32_t XFS_DIR3_BLOCK_MAGIC
Definition xfs_dir.hpp:52
bool parse_dir_data_block(const unsigned char *block_buf, uint32_t block_size, bool is_v5, bool has_ftype, bool accept_block_magic, bool accept_data_magic, std::vector< DirEntry > &out)
Definition xfs_dir.cpp:122
constexpr uint32_t XFS_DIR3_DATA_MAGIC
Definition xfs_dir.hpp:57
uint32_t be32(const unsigned char *p)
constexpr uint32_t XFS_DIR2_DATA_MAGIC
Definition xfs_dir.hpp:56
bool is_v5() const

References xfs::be32(), xfs::Superblock::blocksize, collect_dir_entries_block_or_leaf(), xfs::fsb_to_disk_offset(), xfs::Superblock::is_v5(), xfs::parse_dir_data_block(), DiskReader::read_at(), xfs::VolumeContext::sb, xfs::XFS_DIR2_BLOCK_MAGIC, xfs::XFS_DIR2_DATA_MAGIC, xfs::XFS_DIR3_BLOCK_MAGIC, and xfs::XFS_DIR3_DATA_MAGIC.

Referenced by collect_dir_entries_block_or_leaf().

◆ collect_extents()

bool anonymous_namespace{xfs_recovery.cpp}::collect_extents ( DiskReader & reader,
const xfs::VolumeContext & vol,
const xfs::DinodeCore & core,
const unsigned char * fork_data,
uint32_t fork_len,
uint32_t max_extents,
std::vector< xfs::Extent > & out,
std::vector< std::string > & logs,
std::string & err )

Definition at line 260 of file xfs_recovery.cpp.

266 {
267 out.clear();
269 if (!xfs::decode_extents_inline(fork_data, core.nextents, fork_len, out)) {
270 err = "Corrupted inline extent list (count exceeds fork size)";
271 return false;
272 }
273 return true;
274 }
275 if (core.format == xfs::XFS_DINODE_FMT_BTREE) {
276 // BMBT root sits in the data fork.
277 return xfs::walk_extent_btree_root(reader, vol,
278 fork_data, fork_len,
279 vol.sb.is_v5(),
280 max_extents,
281 out, logs, err);
282 }
283 err = "Unsupported di_format=" + std::to_string(core.format);
284 return false;
285}
constexpr uint8_t XFS_DINODE_FMT_EXTENTS
Definition xfs_inode.hpp:99
bool walk_extent_btree_root(DiskReader &reader, const VolumeContext &vol, const unsigned char *root_buf, uint32_t root_len, bool is_v5_volume, uint32_t max_extents, std::vector< Extent > &out, std::vector< std::string > &logs, std::string &error)
constexpr uint8_t XFS_DINODE_FMT_BTREE
bool decode_extents_inline(const unsigned char *data_fork_buf, uint32_t nextents, uint32_t max_bytes, std::vector< Extent > &out)
uint32_t nextents

References collect_extents(), xfs::decode_extents_inline(), xfs::DinodeCore::format, xfs::Superblock::is_v5(), xfs::DinodeCore::nextents, xfs::VolumeContext::sb, xfs::walk_extent_btree_root(), xfs::XFS_DINODE_FMT_BTREE, and xfs::XFS_DINODE_FMT_EXTENTS.

Referenced by collect_extents().

◆ load_ag_headers()

void anonymous_namespace{xfs_recovery.cpp}::load_ag_headers ( DiskReader & reader,
uint64_t volume_offset,
const xfs::Superblock & sb,
uint32_t agno,
xfs::AGContext & ag,
std::vector< std::string > & logs )

Definition at line 82 of file xfs_recovery.cpp.

84 {
85 const uint64_t ag_byte_offset =
86 volume_offset +
87 static_cast<uint64_t>(agno) * sb.agblocks * sb.blocksize;
88
89 // AGF lives at sector 1 of the AG (i.e. offset = sectsize)
90 const uint64_t agf_off = ag_byte_offset + sb.sectsize;
91 // AGI lives at sector 2 of the AG (offset = 2*sectsize)
92 const uint64_t agi_off = ag_byte_offset + 2u * sb.sectsize;
93
94 // AGF: read one sector (V5 header is 92 bytes plus padding to a sector)
95 {
96 std::vector<unsigned char> buf(sb.sectsize, 0);
97 if (read_exact(reader, agf_off, buf.data(), buf.size())) {
98 xfs::AGF agf;
99 if (xfs::parse_agf(buf.data(), agf)) {
100 ag.agf = agf;
101 ag.agf_loaded = true;
102 } else {
103 std::ostringstream m;
104 m << "AGF magic/version invalid in AG " << agno;
105 logs.push_back(m.str());
106 }
107 } else {
108 std::ostringstream m;
109 m << "Failed to read AGF for AG " << agno
110 << " at offset " << agf_off;
111 logs.push_back(m.str());
112 }
113 }
114
115 // AGI: same idea, one sector. We need is_v5 to read the FINOBT
116 // root/level fields, which live past the V4 AGI footprint.
117 {
118 std::vector<unsigned char> buf(sb.sectsize, 0);
119 if (read_exact(reader, agi_off, buf.data(), buf.size())) {
120 xfs::AGI agi;
121 if (xfs::parse_agi(buf.data(), sb.is_v5(), agi)) {
122 ag.agi = agi;
123 ag.agi_loaded = true;
124 } else {
125 std::ostringstream m;
126 m << "AGI magic/version invalid in AG " << agno;
127 logs.push_back(m.str());
128 }
129 } else {
130 std::ostringstream m;
131 m << "Failed to read AGI for AG " << agno
132 << " at offset " << agi_off;
133 logs.push_back(m.str());
134 }
135 }
136}
bool read_exact(DiskReader &reader, uint64_t off, void *dst, size_t n)
bool parse_agf(const unsigned char *buf, AGF &out)
bool parse_agi(const unsigned char *buf, AGI &out)

References xfs::Superblock::agblocks, xfs::AGContext::agf, xfs::AGContext::agf_loaded, xfs::AGContext::agi, xfs::AGContext::agi_loaded, xfs::Superblock::blocksize, xfs::Superblock::is_v5(), load_ag_headers(), xfs::parse_agf(), xfs::parse_agi(), read_exact(), xfs::Superblock::sectsize, and DiskReader::size().

Referenced by load_ag_headers().

◆ read_dinode()

bool anonymous_namespace{xfs_recovery.cpp}::read_dinode ( DiskReader & reader,
const xfs::VolumeContext & vol,
uint64_t ino,
std::vector< unsigned char > & buf )

Definition at line 146 of file xfs_recovery.cpp.

147 {
148 const xfs::InodeAddress addr = xfs::crack_inode(ino, vol.sb);
149 if (addr.agno >= vol.sb.agcount) return false;
150 const uint64_t disk_off =
151 vol.volume_offset
152 + static_cast<uint64_t>(addr.agno) * vol.sb.agblocks * vol.sb.blocksize
153 + static_cast<uint64_t>(addr.agbno) * vol.sb.blocksize
154 + static_cast<uint64_t>(addr.offset) * vol.sb.inodesize;
155 buf.assign(vol.sb.inodesize, 0);
156 return reader.read_at(disk_off, buf.data(), buf.size());
157}
InodeAddress crack_inode(uint64_t ino, const Superblock &sb)

References xfs::Superblock::agblocks, xfs::InodeAddress::agbno, xfs::Superblock::agcount, xfs::InodeAddress::agno, xfs::Superblock::blocksize, xfs::crack_inode(), xfs::Superblock::inodesize, xfs::InodeAddress::offset, DiskReader::read_at(), read_dinode(), xfs::VolumeContext::sb, and xfs::VolumeContext::volume_offset.

Referenced by read_dinode().

◆ read_exact()

bool anonymous_namespace{xfs_recovery.cpp}::read_exact ( DiskReader & reader,
uint64_t off,
void * dst,
size_t n )

Definition at line 55 of file xfs_recovery.cpp.

55 {
56 return reader.read_at(off, dst, n);
57}

References DiskReader::read_at(), and read_exact().

Referenced by load_ag_headers(), and read_exact().

◆ read_extents_data()

bool anonymous_namespace{xfs_recovery.cpp}::read_extents_data ( DiskReader & reader,
const xfs::VolumeContext & vol,
const std::vector< xfs::Extent > & extents,
uint64_t file_size,
std::vector< unsigned char > & out,
std::string & err )

Definition at line 290 of file xfs_recovery.cpp.

294 {
295 out.assign(static_cast<size_t>(file_size), 0);
296 const uint64_t bsize = vol.sb.blocksize;
297
298 auto sorted = extents;
299 std::sort(sorted.begin(), sorted.end(),
300 [](const xfs::Extent& a, const xfs::Extent& b) {
301 return a.logical_off_blocks < b.logical_off_blocks;
302 });
303
304 for (const auto& ext : sorted) {
305 const uint64_t lo_byte = ext.logical_off_blocks * bsize;
306 if (lo_byte >= file_size) break;
307 const uint64_t span = std::min<uint64_t>(
308 static_cast<uint64_t>(ext.length_blocks) * bsize,
309 file_size - lo_byte);
310 if (ext.unwritten) continue; // already zero-filled
311 const uint64_t phys_off = xfs::fsb_to_disk_offset(ext.physical_block, vol);
312 // Read in block-sized chunks so we don't allocate huge buffers.
313 std::vector<unsigned char> chunk(bsize, 0);
314 uint64_t left = span;
315 uint64_t cur_phys = phys_off;
316 size_t out_pos = static_cast<size_t>(lo_byte);
317 while (left > 0) {
318 if (!reader.read_at(cur_phys, chunk.data(), bsize)) {
319 err = "Read error at physical offset " + std::to_string(cur_phys);
320 return false;
321 }
322 const size_t step = static_cast<size_t>(std::min<uint64_t>(bsize, left));
323 std::memcpy(out.data() + out_pos, chunk.data(), step);
324 out_pos += step;
325 left -= step;
326 cur_phys += bsize;
327 }
328 }
329 return true;
330}

References xfs::Superblock::blocksize, xfs::fsb_to_disk_offset(), DiskReader::read_at(), read_extents_data(), and xfs::VolumeContext::sb.

Referenced by read_extents_data().

◆ validate_geometry()

bool anonymous_namespace{xfs_recovery.cpp}::validate_geometry ( DiskReader & reader,
uint64_t volume_offset,
const xfs::Superblock & sb,
std::string & err )

Definition at line 62 of file xfs_recovery.cpp.

63 {
64 const uint64_t disk_size = reader.size();
65 if (volume_offset >= disk_size) {
66 err = "Volume offset beyond disk end";
67 return false;
68 }
69 const uint64_t fs_size = sb.volume_size_bytes();
70 if (volume_offset + fs_size > disk_size + 64u * 1024) {
71 // A small slack (64 KiB) accounts for some images that report a
72 // FS slightly larger than the partition (rare but seen).
73 err = "Filesystem extends past end of disk";
74 return false;
75 }
76 return true;
77}
uint64_t size() const
uint64_t volume_size_bytes() const

References DiskReader::size(), validate_geometry(), and xfs::Superblock::volume_size_bytes().

Referenced by validate_geometry().

◆ write_extents_to_path()

bool anonymous_namespace{xfs_recovery.cpp}::write_extents_to_path ( DiskReader & reader,
const xfs::VolumeContext & vol,
std::vector< xfs::Extent > extents,
uint64_t file_size,
const fs::path & out_path,
std::string & err )

Definition at line 163 of file xfs_recovery.cpp.

167 {
168 std::ofstream out(out_path, std::ios::binary | std::ios::trunc);
169 if (!out) {
170 err = "Cannot create output file: " + out_path.string();
171 return false;
172 }
173
174 const uint64_t bsize = vol.sb.blocksize;
175 uint64_t bytes_written = 0;
176
177 // Sort by logical offset to handle out-of-order extent lists
178 std::sort(extents.begin(), extents.end(),
179 [](const xfs::Extent& a, const xfs::Extent& b) {
180 return a.logical_off_blocks < b.logical_off_blocks;
181 });
182
183 for (const auto& ext : extents) {
184 const uint64_t logical_byte = ext.logical_off_blocks * bsize;
185 const uint64_t ext_bytes = static_cast<uint64_t>(ext.length_blocks) * bsize;
186
187 // Stop once we've reached file_size (the last extent may overlap past EOF).
188 if (logical_byte >= file_size) break;
189
190 // Pad zeros for any hole between bytes_written and logical_byte
191 if (logical_byte > bytes_written) {
192 const uint64_t pad = std::min(logical_byte, file_size) - bytes_written;
193 std::vector<char> zeros(static_cast<size_t>(std::min<uint64_t>(pad, 64u * 1024)), 0);
194 uint64_t left = pad;
195 while (left > 0) {
196 const size_t step = static_cast<size_t>(std::min<uint64_t>(zeros.size(), left));
197 out.write(zeros.data(), static_cast<std::streamsize>(step));
198 left -= step;
199 }
200 bytes_written = std::min(logical_byte, file_size);
201 }
202
203 // For unwritten extents, write zeros for their length (within file_size).
204 const uint64_t copy_bytes = std::min<uint64_t>(ext_bytes,
205 file_size - bytes_written);
206 if (ext.unwritten) {
207 std::vector<char> zeros(64 * 1024, 0);
208 uint64_t left = copy_bytes;
209 while (left > 0) {
210 const size_t step = static_cast<size_t>(std::min<uint64_t>(zeros.size(), left));
211 out.write(zeros.data(), static_cast<std::streamsize>(step));
212 left -= step;
213 }
214 bytes_written += copy_bytes;
215 continue;
216 }
217
218 // Real extent: read each block and write to output
219 const uint64_t phys_off = xfs::fsb_to_disk_offset(ext.physical_block, vol);
220 std::vector<unsigned char> chunk(bsize, 0);
221 uint64_t left = copy_bytes;
222 uint64_t cur_phys = phys_off;
223 while (left > 0) {
224 if (!reader.read_at(cur_phys, chunk.data(), bsize)) {
225 err = "Read error at physical offset " + std::to_string(cur_phys);
226 return false;
227 }
228 const size_t step = static_cast<size_t>(std::min<uint64_t>(bsize, left));
229 out.write(reinterpret_cast<const char*>(chunk.data()),
230 static_cast<std::streamsize>(step));
231 left -= step;
232 cur_phys += bsize;
233 }
234 bytes_written += copy_bytes;
235
236 if (bytes_written >= file_size) break;
237 }
238
239 // Final tail: pad zeros if file ends in a hole
240 if (bytes_written < file_size) {
241 const uint64_t tail = file_size - bytes_written;
242 std::vector<char> zeros(64 * 1024, 0);
243 uint64_t left = tail;
244 while (left > 0) {
245 const size_t step = static_cast<size_t>(std::min<uint64_t>(zeros.size(), left));
246 out.write(zeros.data(), static_cast<std::streamsize>(step));
247 left -= step;
248 }
249 }
250 return true;
251}

References xfs::Superblock::blocksize, xfs::fsb_to_disk_offset(), DiskReader::read_at(), xfs::VolumeContext::sb, and write_extents_to_path().

Referenced by write_extents_to_path().

Variable Documentation

◆ XFS_DIR2_LEAF_OFFSET_BLOCKS

uint64_t anonymous_namespace{xfs_recovery.cpp}::XFS_DIR2_LEAF_OFFSET_BLOCKS = 32ull * 1024ull * 1024ull * 1024ull / 4096ull
constexpr

Definition at line 346 of file xfs_recovery.cpp.