Skip to content
API Documentation
BastionGuard™
btrfs_recovery.cpp
Go to the documentation of this file.
1#include "btrfs_recovery.hpp"
2#include "i18n.hpp"
3#include "text_utils.hpp"
4
5#include <algorithm>
6#include <array>
7#include <cstdint>
8#include <filesystem>
9#include <fstream>
10#include <iomanip>
11#include <map>
12#include <optional>
13#include <set>
14#include <sstream>
15#include <string>
16#include <vector>
17
18#ifdef HAVE_ZLIB
19#include <zlib.h>
20#endif
21#ifdef HAVE_ZSTD
22#include <zstd.h>
23#endif
24#ifdef HAVE_LZO
25#include <lzo/lzo1x.h>
26#endif
27
28namespace fs = std::filesystem;
29
30namespace {
31constexpr uint8_t BTRFS_INODE_ITEM_KEY = 1;
32constexpr uint8_t BTRFS_INODE_REF_KEY = 12;
33constexpr uint8_t BTRFS_DIR_ITEM_KEY = 84;
34constexpr uint8_t BTRFS_DIR_INDEX_KEY = 96;
35constexpr uint8_t BTRFS_EXTENT_DATA_KEY = 108;
36constexpr uint8_t BTRFS_ROOT_ITEM_KEY = 132;
37constexpr uint8_t BTRFS_ROOT_BACKREF_KEY = 144;
38constexpr uint8_t BTRFS_ROOT_REF_KEY = 156;
39constexpr uint8_t BTRFS_CHUNK_ITEM_KEY = 228;
40
41constexpr uint8_t BTRFS_FILE_EXTENT_INLINE = 0;
42constexpr uint8_t BTRFS_FILE_EXTENT_REG = 1;
43constexpr uint8_t BTRFS_FILE_EXTENT_PREALLOC = 2;
44
45constexpr uint64_t BTRFS_BLOCK_GROUP_RAID0 = 1ull << 3;
46constexpr uint64_t BTRFS_BLOCK_GROUP_RAID1 = 1ull << 4;
47constexpr uint64_t BTRFS_BLOCK_GROUP_DUP = 1ull << 5;
48constexpr uint64_t BTRFS_BLOCK_GROUP_RAID10 = 1ull << 6;
49constexpr uint64_t BTRFS_BLOCK_GROUP_RAID5 = 1ull << 7;
50constexpr uint64_t BTRFS_BLOCK_GROUP_RAID6 = 1ull << 8;
51
52uint16_t le16(const unsigned char* p) {
53 return static_cast<uint16_t>(p[0]) | (static_cast<uint16_t>(p[1]) << 8);
54}
55
56uint32_t le32(const unsigned char* p) {
57 return static_cast<uint32_t>(p[0]) |
58 (static_cast<uint32_t>(p[1]) << 8) |
59 (static_cast<uint32_t>(p[2]) << 16) |
60 (static_cast<uint32_t>(p[3]) << 24);
61}
62
63uint64_t le64(const unsigned char* p) {
64 return static_cast<uint64_t>(p[0]) |
65 (static_cast<uint64_t>(p[1]) << 8) |
66 (static_cast<uint64_t>(p[2]) << 16) |
67 (static_cast<uint64_t>(p[3]) << 24) |
68 (static_cast<uint64_t>(p[4]) << 32) |
69 (static_cast<uint64_t>(p[5]) << 40) |
70 (static_cast<uint64_t>(p[6]) << 48) |
71 (static_cast<uint64_t>(p[7]) << 56);
72}
73
74std::string sanitize(const std::string& in) {
75 // Bug #15 fix: btrfs filenames are stored as UTF-8 byte sequences. The
76 // previous ASCII-only sanitizer turned every accented character into '_'.
77 // We reuse the shared helper which preserves valid UTF-8 multibyte runs.
78 auto out = text_utils::sanitize_component(in);
79 if (out == "unnamed") out = "item";
80 return out;
81}
82
83std::string to_hex(uint64_t v) {
84 std::ostringstream oss;
85 oss << "0x" << std::hex << std::uppercase << v;
86 return oss.str();
87}
88
89struct BtrfsKey {
90 uint64_t objectid {0};
91 uint8_t type {0};
92 uint64_t offset {0};
93};
94
95struct BtrfsItem {
97 uint32_t data_offset {0};
98 uint32_t data_size {0};
99};
100
103 uint64_t blockptr {0};
104 uint64_t generation {0};
105};
106
108 uint64_t physical {0};
109 uint64_t devid {0};
110};
111
113 uint64_t logical {0};
114 uint64_t length {0};
115 uint64_t stripe_length {0};
116 uint64_t type {0};
117 uint16_t num_stripes {0};
118 std::vector<ChunkStripe> stripes;
119};
120
121struct RootEntry {
122 uint64_t bytenr {0};
123 uint8_t level {0};
124 std::string name;
125};
126
127struct InodeInfo {
128 uint64_t size {0};
129 uint32_t mode {0};
130};
131
132struct DirEdge {
133 uint64_t parent {0};
134 uint64_t child {0};
135 std::string name;
136};
137
139 uint64_t file_offset {0};
140 uint64_t ram_bytes {0};
141 uint8_t compression {0};
142 uint8_t type {0};
143 uint64_t disk_bytenr {0};
144 uint64_t disk_num_bytes {0};
145 uint64_t extent_offset {0};
146 uint64_t num_bytes {0};
147 std::vector<unsigned char> inline_data;
148};
149
151 std::map<uint64_t, InodeInfo> inodes;
152 std::map<uint64_t, std::vector<FileExtentInfo>> extents;
153 std::map<uint64_t, DirEdge> parent_edge;
154};
155
156BtrfsKey parse_key(const unsigned char* p) {
157 return {le64(p), p[8], le64(p + 9)};
158}
159
160// Btrfs compression on-disk format notes (per fs/btrfs/{zlib,lzo,zstd}.c):
161//
162// ZLIB: the entire input is a SINGLE zlib stream (with its own
163// deflate dictionary); since each Btrfs extent is at most
164// 128 KiB of uncompressed data ("BTRFS_COMPRESSION_BLOCKSIZE"),
165// the stream is small and uncompress() works directly. No
166// chunk framing.
167//
168// LZO: chunked format with explicit framing:
169// __le32 total_compressed_len (whole payload)
170// per chunk:
171// __le32 chunk_compressed_len
172// <chunk_compressed_len bytes of LZO1X data>
173// Each chunk decompresses to up to one page (4 KiB) of output.
174// Padding may bring chunks to PAGE_SIZE boundaries.
175//
176// ZSTD: chunked similarly to LZO:
177// __le32 chunk_compressed_len
178// <chunk_compressed_len bytes of zstd frame>
179// Each chunk decompresses to up to BTRFS_COMPRESSION_BLOCKSIZE
180// (128 KiB) of output.
181
182bool decompress_payload(const std::vector<unsigned char>& in,
183 uint8_t compression,
184 size_t expected_size,
185 std::vector<unsigned char>& out) {
186 if (compression == 0) {
187 out = in;
188 if (out.size() > expected_size) out.resize(expected_size);
189 return true;
190 }
191#ifdef HAVE_ZLIB
192 if (compression == 1) {
193 // Btrfs zlib: single zlib stream per extent (max 128 KiB output).
194 out.resize(expected_size);
195 uLongf dest_len = static_cast<uLongf>(out.size());
196 int rc = ::uncompress(out.data(), &dest_len, in.data(), static_cast<uLong>(in.size()));
197 if (rc != Z_OK) return false;
198 out.resize(static_cast<size_t>(dest_len));
199 return true;
200 }
201#endif
202#ifdef HAVE_LZO
203 if (compression == 2) {
204 // Btrfs LZO chunked decoder.
205 constexpr size_t kBtrfsLzoPageSize = 4096;
206 if (in.size() < 4) return false;
207 const uint32_t total_len = le32(in.data());
208 if (total_len > in.size()) return false;
209 // Chunks start at offset 4 (after the total length header).
210 // The kernel writer pads chunks so that no chunk header straddles
211 // a 4 KiB page boundary in the COMPRESSED stream. We mirror that:
212 // when the next chunk header would straddle a page, we skip to
213 // the next page.
214 size_t pos = 4;
215 out.clear();
216 out.reserve(expected_size);
217 while (pos + 4 <= total_len && out.size() < expected_size) {
218 // Page-boundary skip: if a 4-byte chunk header would not fit
219 // in the remaining bytes of the current page, advance to the
220 // next page.
221 const size_t page_off = pos % kBtrfsLzoPageSize;
222 if (page_off + 4 > kBtrfsLzoPageSize) {
223 pos += kBtrfsLzoPageSize - page_off;
224 if (pos + 4 > total_len) break;
225 }
226 const uint32_t chunk_len = le32(in.data() + pos);
227 pos += 4;
228 if (chunk_len == 0 || chunk_len > total_len - pos) return false;
229
230 std::vector<unsigned char> chunk_out(kBtrfsLzoPageSize);
231 lzo_uint dest_len = static_cast<lzo_uint>(chunk_out.size());
232 const int rc = lzo1x_decompress_safe(
233 in.data() + pos, static_cast<lzo_uint>(chunk_len),
234 chunk_out.data(), &dest_len, nullptr);
235 if (rc != LZO_E_OK) return false;
236 chunk_out.resize(static_cast<size_t>(dest_len));
237 out.insert(out.end(), chunk_out.begin(), chunk_out.end());
238 pos += chunk_len;
239 }
240 if (out.size() > expected_size) out.resize(expected_size);
241 return true;
242 }
243#endif
244#ifdef HAVE_ZSTD
245 if (compression == 3) {
246 // Btrfs zstd chunked decoder.
247 // Format: per-chunk: __le32 chunk_len + <chunk_len bytes zstd frame>.
248 // Each chunk decompresses to up to 128 KiB.
249 constexpr size_t kBtrfsZstdMaxChunkOutput = 128 * 1024;
250 size_t pos = 0;
251 out.clear();
252 out.reserve(expected_size);
253 while (pos + 4 <= in.size() && out.size() < expected_size) {
254 const uint32_t chunk_len = le32(in.data() + pos);
255 pos += 4;
256 if (chunk_len == 0 || chunk_len > in.size() - pos) return false;
257
258 std::vector<unsigned char> chunk_out(kBtrfsZstdMaxChunkOutput);
259 const size_t rc = ZSTD_decompress(
260 chunk_out.data(), chunk_out.size(),
261 in.data() + pos, chunk_len);
262 if (ZSTD_isError(rc)) return false;
263 chunk_out.resize(rc);
264 out.insert(out.end(), chunk_out.begin(), chunk_out.end());
265 pos += chunk_len;
266 }
267 if (out.size() > expected_size) out.resize(expected_size);
268 return true;
269 }
270#endif
271 return false;
272}
273
274uint8_t gf_mul_byte(uint8_t a, uint8_t b) {
275 uint8_t p = 0;
276 for (int i = 0; i < 8; ++i) {
277 if (b & 1u) p ^= a;
278 const bool hi = (a & 0x80u) != 0;
279 a = static_cast<uint8_t>(a << 1u);
280 if (hi) a ^= 0x1du;
281 b = static_cast<uint8_t>(b >> 1u);
282 }
283 return p;
284}
285
286uint8_t gf_pow2(size_t exp) {
287 uint8_t v = 1;
288 for (size_t i = 0; i < exp; ++i) v = gf_mul_byte(v, 2);
289 return v;
290}
291
292uint8_t gf_inv_byte(uint8_t a) {
293 if (a == 0) return 0;
294 for (int i = 1; i < 256; ++i) {
295 if (gf_mul_byte(a, static_cast<uint8_t>(i)) == 1u) return static_cast<uint8_t>(i);
296 }
297 return 0;
298}
299
300
301uint32_t crc32c_update(uint32_t crc, const unsigned char* data, size_t len) {
302 static uint32_t table[256]{};
303 static bool init = false;
304 if (!init) {
305 for (uint32_t i = 0; i < 256; ++i) {
306 uint32_t r = i;
307 for (int j = 0; j < 8; ++j) r = (r >> 1) ^ (0x82F63B78u & static_cast<uint32_t>(-(static_cast<int32_t>(r & 1u))));
308 table[i] = r;
309 }
310 init = true;
311 }
312 crc = ~crc;
313 for (size_t i = 0; i < len; ++i) crc = table[(crc ^ data[i]) & 0xFFu] ^ (crc >> 8);
314 return ~crc;
315}
316
317bool validate_tree_block_checksum(const std::vector<unsigned char>& block) {
318 if (block.size() < 0x100) return false;
319 const uint32_t stored = le32(block.data());
320 const uint32_t calc = crc32c_update(0, block.data() + 0x20, block.size() - 0x20);
321 return stored == calc;
322}
323
324bool validate_tree_block_header(const std::vector<unsigned char>& block,
325 uint64_t logical_bytenr,
326 uint32_t nodesize,
327 const std::array<unsigned char, 16>* fsid = nullptr,
328 uint64_t max_generation = 0) {
329 if (block.size() < 101) return false;
330 if (!validate_tree_block_checksum(block)) return false;
331 if (le64(block.data() + 48) != logical_bytenr) return false;
332 if (fsid != nullptr && !std::equal(fsid->begin(), fsid->end(), block.data() + 32)) return false;
333 const uint64_t generation = le64(block.data() + 80);
334 if (generation == 0) return false;
335 if (max_generation != 0 && generation > max_generation + 1024) return false;
336 const uint32_t nritems = le32(block.data() + 96);
337 const uint8_t level = block[100];
338 if (level > 8) return false;
339 const size_t header_size = 101;
340 const size_t item_size = (level == 0) ? 25 : 33;
341 const uint64_t need = static_cast<uint64_t>(header_size) + static_cast<uint64_t>(nritems) * item_size;
342 if (need > nodesize) return false;
343 return true;
344}
345
346} // namespace
347
349public:
350 explicit BtrfsRecoveryImpl(DiskReader& reader) : reader_(reader) {}
351
352 bool recover(uint64_t volume_offset,
353 const std::string& output_dir,
354 std::vector<RecoveredFileRecord>& recovered,
355 std::vector<std::string>& logs,
356 std::string& error) {
357 std::array<unsigned char, 0x2000> sb{};
358 if (!reader_.read_at(volume_offset + 0x10000, sb.data(), sb.size())) {
359 error = trs("Failed to read Btrfs superblock");
360 return false;
361 }
362 if (std::string(reinterpret_cast<const char*>(sb.data() + 0x40), 8) != "_BHRfS_M") {
363 error = trs("Invalid Btrfs superblock magic");
364 return false;
365 }
366
367 volume_offset_ = volume_offset;
368 std::copy(sb.begin() + 0x20, sb.begin() + 0x20 + 16, fsid_.begin());
369 root_tree_bytenr_ = le64(sb.data() + 0x50);
370 chunk_root_bytenr_ = le64(sb.data() + 0x58);
371 const uint64_t log_root = le64(sb.data() + 0x68);
372 const uint64_t total_bytes = le64(sb.data() + 0x70);
373 const uint64_t bytes_used = le64(sb.data() + 0x78);
374 root_dir_objectid_ = le64(sb.data() + 0x80);
375 nodesize_ = le32(sb.data() + 0x94);
376 const uint32_t sectorsize = le32(sb.data() + 0x90);
377 const uint32_t stripesize = le32(sb.data() + 0x9C);
378 const uint32_t sys_chunk_array_size = le32(sb.data() + 0xA0);
379 const uint64_t generation = le64(sb.data() + 0x10);
380 super_generation_ = generation;
381 if (nodesize_ == 0 || nodesize_ > 16u * 1024u * 1024u) {
382 error = trs("Invalid Btrfs nodesize");
383 return false;
384 }
385
386 out_root_ = fs::path(output_dir) / "filesystem_recovered" / "btrfs" / ("offset_" + std::to_string(volume_offset));
387 fs::create_directories(out_root_ / "trees");
388 fs::create_directories(out_root_ / "files");
389
390 parse_system_chunk_array(sb.data() + 0x32B, sys_chunk_array_size);
391 if (chunk_map_.empty()) {
392 error = trs("Unable to parse Btrfs system chunk array");
393 return false;
394 }
395
396 std::ofstream meta(out_root_ / "btrfs_metadata.txt", std::ios::binary);
397 if (!meta) {
398 error = trs("Unable to create output file") + std::string(": ") + (out_root_ / "btrfs_metadata.txt").string();
399 return false;
400 }
401 meta << "Btrfs recovery metadata\n";
402 meta << "volume_offset=" << volume_offset << "\n";
403 meta << "root=" << root_tree_bytenr_ << " (" << to_hex(root_tree_bytenr_) << ")\n";
404 meta << "chunk_root=" << chunk_root_bytenr_ << " (" << to_hex(chunk_root_bytenr_) << ")\n";
405 meta << "log_root=" << log_root << " (" << to_hex(log_root) << ")\n";
406 meta << "generation=" << generation << "\n";
407 meta << "total_bytes=" << total_bytes << "\n";
408 meta << "bytes_used=" << bytes_used << "\n";
409 meta << "sectorsize=" << sectorsize << "\n";
410 meta << "nodesize=" << nodesize_ << "\n";
411 meta << "stripesize=" << stripesize << "\n";
412 meta << "root_dir_objectid=" << root_dir_objectid_ << "\n";
413 meta << "chunk_mappings=" << chunk_map_.size() << "\n";
414 meta.close();
415
416 if (!load_chunk_tree(logs)) {
417 logs.push_back(trs("Btrfs chunk tree load was partial; continuing with system chunk mappings only."));
418 }
419 if (!load_root_tree(logs, error)) return false;
420
421 size_t files_exported = 0;
422 size_t compressed_exported = 0;
423 size_t compressed_skipped = 0;
424 for (const auto& [root_id, root] : roots_) {
425 FsRootState state;
426 if (!collect_fs_root(root_id, root, state, logs)) continue;
427 files_exported += export_fs_root(root_id, root, state, recovered, compressed_exported, compressed_skipped, logs);
428 }
429
430 recovered.push_back({(out_root_ / "btrfs_metadata.txt").string(), "Btrfs", "/", 0, "metadata", "tree metadata"});
431 logs.push_back(trs("Btrfs file recovery completed at offset") + std::string(" ") + std::to_string(volume_offset));
432 logs.push_back(trs("Btrfs chunk mappings loaded") + std::string(": ") + std::to_string(chunk_map_.size()));
433 logs.push_back(trs("Btrfs filesystem roots discovered") + std::string(": ") + std::to_string(roots_.size()));
434 logs.push_back(trs("Btrfs files exported") + std::string(": ") + std::to_string(files_exported));
435 logs.push_back(trs("Btrfs compressed extents exported") + std::string(": ") + std::to_string(compressed_exported));
436 logs.push_back(trs("Btrfs compressed extents skipped") + std::string(": ") + std::to_string(compressed_skipped));
437 return true;
438 }
439
440private:
442 uint64_t volume_offset_ {0};
443 uint64_t root_tree_bytenr_ {0};
444 uint64_t chunk_root_bytenr_ {0};
445 uint64_t root_dir_objectid_ {256};
446 uint32_t nodesize_ {0};
447 uint64_t super_generation_ {0};
448 std::array<unsigned char, 16> fsid_{};
449 fs::path out_root_;
450 std::vector<ChunkMapEntry> chunk_map_;
451 std::map<uint64_t, RootEntry> roots_;
452 std::set<uint64_t> visited_tree_blocks_;
453
454 void parse_system_chunk_array(const unsigned char* data, uint32_t size) {
455 uint32_t pos = 0;
456 while (pos + 17 + 48 <= size) {
457 BtrfsKey key = parse_key(data + pos);
458 pos += 17;
459 if (key.type != BTRFS_CHUNK_ITEM_KEY) break;
460 const uint64_t length = le64(data + pos + 0);
461 const uint64_t stripe_len = le64(data + pos + 16);
462 const uint64_t type = le64(data + pos + 24);
463 const uint16_t num_stripes = le16(data + pos + 40);
464 const size_t chunk_size = 48 + static_cast<size_t>(num_stripes) * 48;
465 if (pos + chunk_size > size || num_stripes == 0) break;
466 std::vector<ChunkStripe> stripes;
467 stripes.reserve(num_stripes);
468 for (uint16_t i = 0; i < num_stripes; ++i) {
469 const unsigned char* sp = data + pos + 48 + static_cast<size_t>(i) * 48;
470 stripes.push_back({le64(sp + 8), le64(sp + 0)});
471 }
472 chunk_map_.push_back({key.offset, length, stripe_len, type, num_stripes, std::move(stripes)});
473 pos += static_cast<uint32_t>(chunk_size);
474 }
476 }
477
478 const ChunkMapEntry* find_chunk_entry(uint64_t logical) const {
479 for (const auto& entry : chunk_map_) {
480 if (logical >= entry.logical && logical < entry.logical + entry.length) return &entry;
481 }
482 return nullptr;
483 }
484
485 bool validate_raid6_candidate(const std::vector<std::vector<unsigned char>>& bufs,
486 const std::vector<bool>& ok,
487 const std::vector<size_t>& data_cols,
488 const std::map<size_t, size_t>& data_index,
489 size_t p_col,
490 size_t q_col,
491 const unsigned char* candidate,
492 size_t candidate_col,
493 size_t chunk) {
494 if (chunk == 0) return true;
495 if (ok[p_col]) {
496 for (size_t j = 0; j < chunk; ++j) {
497 uint8_t parity = 0;
498 for (size_t c : data_cols) {
499 const uint8_t v = (c == candidate_col) ? candidate[j] : bufs[c][j];
500 parity ^= v;
501 }
502 if (parity != bufs[p_col][j]) return false;
503 }
504 }
505 if (ok[q_col]) {
506 for (size_t j = 0; j < chunk; ++j) {
507 uint8_t parity = 0;
508 for (size_t c : data_cols) {
509 const uint8_t v = (c == candidate_col) ? candidate[j] : bufs[c][j];
510 parity ^= gf_mul_byte(v, gf_pow2(data_index.at(c)));
511 }
512 if (parity != bufs[q_col][j]) return false;
513 }
514 }
515 return true;
516 }
517
519 uint64_t logical,
520 unsigned char* out,
521 size_t chunk) {
522 if (entry.stripes.empty() || entry.stripe_length == 0 || chunk == 0) return false;
523 const uint64_t inner = logical - entry.logical;
524 const uint64_t stripe_len = entry.stripe_length;
525 const uint64_t stripe_inner = inner % stripe_len;
526
527 auto read_piece = [&](size_t stripe_index, std::vector<unsigned char>& buf) -> bool {
528 if (stripe_index >= entry.stripes.size()) return false;
529 buf.resize(chunk);
530 const uint64_t stripe_unit = (entry.type & BTRFS_BLOCK_GROUP_RAID5) ?
531 ((inner / stripe_len) / (entry.stripes.size() - 1)) :
532 ((inner / stripe_len) / (entry.stripes.size() - 2));
533 const uint64_t physical = volume_offset_ + entry.stripes[stripe_index].physical + stripe_unit * stripe_len + stripe_inner;
534 return reader_.read_at(physical, buf.data(), chunk);
535 };
536
537 if ((entry.type & BTRFS_BLOCK_GROUP_RAID5) != 0 && entry.stripes.size() >= 2) {
538 const size_t n = entry.stripes.size();
539 const uint64_t data_stripes = n - 1;
540 const uint64_t stripe_unit = inner / stripe_len;
541 const uint64_t row = stripe_unit / data_stripes;
542 const size_t col_in_row = static_cast<size_t>(stripe_unit % data_stripes);
543 const size_t parity_col = static_cast<size_t>((n - 1 - (row % n)) % n);
544 size_t data_col = 0;
545 std::vector<size_t> data_cols;
546 for (size_t phys_col = 0, logical_col = 0; phys_col < n; ++phys_col) {
547 if (phys_col == parity_col) continue;
548 data_cols.push_back(phys_col);
549 if (logical_col == col_in_row) data_col = phys_col;
550 ++logical_col;
551 }
552 std::vector<std::vector<unsigned char>> bufs(n);
553 std::vector<bool> ok(n, false);
554 for (size_t i = 0; i < n; ++i) ok[i] = read_piece(i, bufs[i]);
555 if (ok[data_col]) {
556 std::copy(bufs[data_col].begin(), bufs[data_col].end(), out);
557 return true;
558 }
559 if (!ok[parity_col]) return false;
560 std::copy(bufs[parity_col].begin(), bufs[parity_col].end(), out);
561 for (size_t c : data_cols) {
562 if (c == data_col) continue;
563 if (!ok[c]) return false;
564 for (size_t j = 0; j < chunk; ++j) out[j] ^= bufs[c][j];
565 }
566 return true;
567 }
568
569 if ((entry.type & BTRFS_BLOCK_GROUP_RAID6) != 0 && entry.stripes.size() >= 3) {
570 const size_t n = entry.stripes.size();
571 const uint64_t data_stripes = n - 2;
572 const uint64_t stripe_unit = inner / stripe_len;
573 const uint64_t row = stripe_unit / data_stripes;
574 const size_t col_in_row = static_cast<size_t>(stripe_unit % data_stripes);
575 const size_t p_col = static_cast<size_t>((n - 1 - (row % n)) % n);
576 const size_t q_col = static_cast<size_t>((n - 2 - (row % n) + n) % n);
577 size_t data_col = 0;
578 std::vector<size_t> data_cols;
579 std::map<size_t, size_t> data_index;
580 for (size_t phys_col = 0, logical_col = 0; phys_col < n; ++phys_col) {
581 if (phys_col == p_col || phys_col == q_col) continue;
582 data_cols.push_back(phys_col);
583 data_index[phys_col] = logical_col;
584 if (logical_col == col_in_row) data_col = phys_col;
585 ++logical_col;
586 }
587 std::vector<std::vector<unsigned char>> bufs(n);
588 std::vector<bool> ok(n, false);
589 for (size_t i = 0; i < n; ++i) ok[i] = read_piece(i, bufs[i]);
590 if (ok[data_col]) {
591 std::copy(bufs[data_col].begin(), bufs[data_col].end(), out);
592 return true;
593 }
594 std::vector<size_t> missing;
595 for (size_t i = 0; i < n; ++i) if (!ok[i]) missing.push_back(i);
596 if (missing.empty()) return false;
597 if (missing.size() == 1) {
598 if (!ok[p_col]) return false;
599 std::copy(bufs[p_col].begin(), bufs[p_col].end(), out);
600 for (size_t c : data_cols) {
601 if (c == data_col) continue;
602 if (!ok[c]) return false;
603 for (size_t j = 0; j < chunk; ++j) out[j] ^= bufs[c][j];
604 }
605 return true;
606 }
607 if (missing.size() > 2 || !ok[q_col]) return false;
608 const size_t other = (missing[0] == data_col) ? missing[1] : missing[0];
609 if (other == q_col) {
610 if (!ok[p_col]) return false;
611 std::copy(bufs[p_col].begin(), bufs[p_col].end(), out);
612 for (size_t c : data_cols) {
613 if (c == data_col) continue;
614 if (!ok[c]) return false;
615 for (size_t j = 0; j < chunk; ++j) out[j] ^= bufs[c][j];
616 }
617 return true;
618 }
619 if (other == p_col) {
620 const uint8_t a = gf_pow2(data_index[data_col]);
621 const uint8_t inv = gf_inv_byte(a);
622 if (inv == 0) return false;
623 for (size_t j = 0; j < chunk; ++j) {
624 uint8_t acc = bufs[q_col][j];
625 for (size_t c : data_cols) {
626 if (c == data_col) continue;
627 if (!ok[c]) return false;
628 acc ^= gf_mul_byte(bufs[c][j], gf_pow2(data_index[c]));
629 }
630 out[j] = gf_mul_byte(acc, inv);
631 }
632 return validate_raid6_candidate(bufs, ok, data_cols, data_index, p_col, q_col, out, data_col, chunk);
633 }
634 if (!ok[p_col]) return false;
635 if (data_index.find(other) == data_index.end()) return false;
636 const uint8_t a = gf_pow2(data_index[data_col]);
637 const uint8_t b = gf_pow2(data_index[other]);
638 const uint8_t denom = static_cast<uint8_t>(a ^ b);
639 const uint8_t denom_inv = gf_inv_byte(denom);
640 if (denom_inv == 0) return false;
641 for (size_t j = 0; j < chunk; ++j) {
642 uint8_t s1 = bufs[p_col][j];
643 uint8_t s2 = bufs[q_col][j];
644 for (size_t c : data_cols) {
645 if (c == data_col || c == other) continue;
646 if (!ok[c]) return false;
647 s1 ^= bufs[c][j];
648 s2 ^= gf_mul_byte(bufs[c][j], gf_pow2(data_index[c]));
649 }
650 out[j] = gf_mul_byte(static_cast<uint8_t>(s2 ^ gf_mul_byte(b, s1)), denom_inv);
651 }
652 return validate_raid6_candidate(bufs, ok, data_cols, data_index, p_col, q_col, out, data_col, chunk);
653 }
654 return false;
655 }
656
658 std::sort(chunk_map_.begin(), chunk_map_.end(), [](const ChunkMapEntry& a, const ChunkMapEntry& b) {
659 return a.logical < b.logical;
660 });
661 }
662
663 bool logical_to_physical(uint64_t logical, std::vector<uint64_t>& physical_candidates, uint64_t& available) const {
664 physical_candidates.clear();
665 for (const auto& entry : chunk_map_) {
666 if (logical < entry.logical || logical >= entry.logical + entry.length) continue;
667 const uint64_t inner = logical - entry.logical;
668 const uint64_t stripe_len = entry.stripe_length == 0 ? entry.length : entry.stripe_length;
669 const uint64_t stripe_inner = stripe_len == 0 ? 0 : (inner % stripe_len);
670 available = std::min<uint64_t>(entry.length - inner, stripe_len == 0 ? entry.length - inner : stripe_len - stripe_inner);
671
672 auto add_candidate = [&](size_t idx, uint64_t offset_inside) {
673 if (idx >= entry.stripes.size()) return;
674 physical_candidates.push_back(volume_offset_ + entry.stripes[idx].physical + offset_inside);
675 };
676
677 if ((entry.type & BTRFS_BLOCK_GROUP_RAID0) != 0 && !entry.stripes.empty() && stripe_len > 0) {
678 const uint64_t stripe_unit = inner / stripe_len;
679 const size_t stripe_index = static_cast<size_t>(stripe_unit % entry.stripes.size());
680 const uint64_t stripe_round = stripe_unit / entry.stripes.size();
681 add_candidate(stripe_index, stripe_round * stripe_len + stripe_inner);
682 return !physical_candidates.empty();
683 }
684
685 if ((entry.type & BTRFS_BLOCK_GROUP_RAID10) != 0 && !entry.stripes.empty() && stripe_len > 0) {
686 const uint64_t stripe_unit = inner / stripe_len;
687 const size_t stripe_index = static_cast<size_t>(stripe_unit % entry.stripes.size());
688 const uint64_t stripe_round = stripe_unit / entry.stripes.size();
689 add_candidate(stripe_index, stripe_round * stripe_len + stripe_inner);
690 for (size_t i = 0; i < entry.stripes.size(); ++i) {
691 if (i != stripe_index) add_candidate(i, stripe_round * stripe_len + stripe_inner);
692 }
693 return !physical_candidates.empty();
694 }
695
696 if (((entry.type & BTRFS_BLOCK_GROUP_RAID1) != 0) ||
697 ((entry.type & BTRFS_BLOCK_GROUP_DUP) != 0)) {
698 for (size_t i = 0; i < entry.stripes.size(); ++i) add_candidate(i, inner);
699 return !physical_candidates.empty();
700 }
701
702 if ((entry.type & BTRFS_BLOCK_GROUP_RAID5) != 0 && entry.stripes.size() >= 2 && stripe_len > 0) {
703 const size_t n = entry.stripes.size();
704 const uint64_t data_stripes = n - 1;
705 const uint64_t stripe_unit = inner / stripe_len;
706 const uint64_t row = stripe_unit / data_stripes;
707 const size_t col_in_row = static_cast<size_t>(stripe_unit % data_stripes);
708 const size_t parity_col = static_cast<size_t>((n - 1 - (row % n)) % n);
709 size_t data_col = 0;
710 for (size_t phys_col = 0, logical_col = 0; phys_col < n; ++phys_col) {
711 if (phys_col == parity_col) continue;
712 if (logical_col == col_in_row) {
713 data_col = phys_col;
714 break;
715 }
716 ++logical_col;
717 }
718 add_candidate(data_col, row * stripe_len + stripe_inner);
719 for (size_t i = 0; i < n; ++i) {
720 if (i != data_col && i != parity_col) add_candidate(i, row * stripe_len + stripe_inner);
721 }
722 return !physical_candidates.empty();
723 }
724
725 if ((entry.type & BTRFS_BLOCK_GROUP_RAID6) != 0 && entry.stripes.size() >= 3 && stripe_len > 0) {
726 const size_t n = entry.stripes.size();
727 const uint64_t data_stripes = n - 2;
728 const uint64_t stripe_unit = inner / stripe_len;
729 const uint64_t row = stripe_unit / data_stripes;
730 const size_t col_in_row = static_cast<size_t>(stripe_unit % data_stripes);
731 const size_t p_col = static_cast<size_t>((n - 1 - (row % n)) % n);
732 const size_t q_col = static_cast<size_t>((n - 2 - (row % n) + n) % n);
733 size_t data_col = 0;
734 for (size_t phys_col = 0, logical_col = 0; phys_col < n; ++phys_col) {
735 if (phys_col == p_col || phys_col == q_col) continue;
736 if (logical_col == col_in_row) {
737 data_col = phys_col;
738 break;
739 }
740 ++logical_col;
741 }
742 add_candidate(data_col, row * stripe_len + stripe_inner);
743 for (size_t i = 0; i < n; ++i) {
744 if (i != data_col && i != p_col && i != q_col) add_candidate(i, row * stripe_len + stripe_inner);
745 }
746 return !physical_candidates.empty();
747 }
748
749 if (!entry.stripes.empty()) {
750 add_candidate(0, inner);
751 return !physical_candidates.empty();
752 }
753 return false;
754 }
755 return false;
756 }
757
758 bool read_logical(uint64_t logical, void* buffer, size_t len) {
759 unsigned char* out = static_cast<unsigned char*>(buffer);
760 uint64_t cur = logical;
761 size_t remaining = len;
762 while (remaining > 0) {
763 std::vector<uint64_t> physical_candidates;
764 uint64_t available = 0;
765 if (!logical_to_physical(cur, physical_candidates, available) || physical_candidates.empty()) return false;
766 const size_t chunk = static_cast<size_t>(std::min<uint64_t>(remaining, available));
767 bool ok = false;
768 for (uint64_t physical : physical_candidates) {
769 if (reader_.read_at(physical, out, chunk)) {
770 ok = true;
771 break;
772 }
773 }
774 if (!ok) {
775 const ChunkMapEntry* entry = find_chunk_entry(cur);
776 if (entry == nullptr || !reconstruct_raid56_read(*entry, cur, out, chunk)) return false;
777 }
778 cur += chunk;
779 out += chunk;
780 remaining -= chunk;
781 }
782 return true;
783 }
784
785 bool read_tree_block(uint64_t logical_bytenr, std::vector<unsigned char>& block) {
786 block.resize(nodesize_);
787 if (!read_logical(logical_bytenr, block.data(), block.size())) return false;
788 if (validate_tree_block_header(block, logical_bytenr, nodesize_, &fsid_, super_generation_)) return true;
789
790 const ChunkMapEntry* entry = find_chunk_entry(logical_bytenr);
791 if (entry == nullptr) return false;
792 const bool is_raid56 = ((entry->type & BTRFS_BLOCK_GROUP_RAID5) != 0) ||
793 ((entry->type & BTRFS_BLOCK_GROUP_RAID6) != 0);
794 if (!is_raid56) return false;
795 std::vector<unsigned char> rebuilt(nodesize_);
796 if (!reconstruct_raid56_read(*entry, logical_bytenr, rebuilt.data(), rebuilt.size())) return false;
797 if (!validate_tree_block_header(rebuilt, logical_bytenr, nodesize_, &fsid_, super_generation_)) return false;
798 block.swap(rebuilt);
799 return true;
800 }
801
802 bool load_chunk_tree(std::vector<std::string>& logs) {
803 size_t before = chunk_map_.size();
804 walk_tree(chunk_root_bytenr_, [&](uint64_t, uint64_t, const std::vector<unsigned char>& block, const BtrfsItem& item) {
805 if (item.key.type != BTRFS_CHUNK_ITEM_KEY) return;
806 if (item.data_offset + item.data_size > block.size() || item.data_size < 48) return;
807 const unsigned char* p = block.data() + item.data_offset;
808 const uint64_t length = le64(p + 0);
809 const uint64_t stripe_len = le64(p + 16);
810 const uint64_t type = le64(p + 24);
811 const uint16_t num_stripes = le16(p + 40);
812 if (num_stripes == 0 || item.data_size < 48u + static_cast<uint32_t>(num_stripes) * 48u) return;
813 std::vector<ChunkStripe> stripes;
814 stripes.reserve(num_stripes);
815 for (uint16_t i = 0; i < num_stripes; ++i) {
816 const unsigned char* sp = p + 48 + static_cast<size_t>(i) * 48;
817 stripes.push_back({le64(sp + 8), le64(sp + 0)});
818 }
819 chunk_map_.push_back({item.key.offset, length, stripe_len, type, num_stripes, std::move(stripes)});
820 });
822 logs.push_back(trs("Btrfs chunk tree parsed") + std::string(": ") + std::to_string(chunk_map_.size() - before) + " new mappings");
823 return !chunk_map_.empty();
824 }
825
826 bool load_root_tree(std::vector<std::string>& logs, std::string& error) {
827 if (root_tree_bytenr_ == 0) {
828 error = trs("Invalid Btrfs root tree");
829 return false;
830 }
831 walk_tree(root_tree_bytenr_, [&](uint64_t, uint64_t, const std::vector<unsigned char>& block, const BtrfsItem& item) {
832 if (item.key.type == BTRFS_ROOT_ITEM_KEY) {
833 if (item.data_offset + item.data_size > block.size() || item.data_size < 375) return;
834 const unsigned char* p = block.data() + item.data_offset;
835 const uint64_t root_bytenr = le64(p + 176);
836 const uint8_t level = p[265];
837 if (item.key.objectid >= root_dir_objectid_ && root_bytenr != 0) {
838 roots_[item.key.objectid] = {root_bytenr, level, "subvolume_" + std::to_string(item.key.objectid)};
839 }
840 } else if (item.key.type == BTRFS_ROOT_REF_KEY || item.key.type == BTRFS_ROOT_BACKREF_KEY) {
841 if (item.data_offset + item.data_size > block.size() || item.data_size < 18) return;
842 const unsigned char* p = block.data() + item.data_offset;
843 const uint16_t name_len = le16(p + 16);
844 if (18u + name_len > item.data_size) return;
845 std::string name(reinterpret_cast<const char*>(p + 18), reinterpret_cast<const char*>(p + 18 + name_len));
846 auto it = roots_.find(item.key.offset);
847 if (it != roots_.end() && !name.empty()) it->second.name = sanitize(name);
848 }
849 });
850 logs.push_back(trs("Btrfs root tree parsed") + std::string(": ") + std::to_string(roots_.size()) + " filesystem roots");
851 return true;
852 }
853
854 template <typename LeafFn>
855 void walk_tree(uint64_t logical_bytenr, LeafFn&& leaf_fn) {
856 visited_tree_blocks_.clear();
857 walk_tree_internal(logical_bytenr, leaf_fn);
858 }
859
860 template <typename LeafFn>
861 void walk_tree_internal(uint64_t logical_bytenr, LeafFn& leaf_fn) {
862 if (logical_bytenr == 0 || !visited_tree_blocks_.insert(logical_bytenr).second) return;
863 std::vector<unsigned char> block;
864 if (!read_tree_block(logical_bytenr, block) || block.size() < 101) return;
865 const uint64_t owner = le64(block.data() + 88);
866 const uint32_t nritems = le32(block.data() + 96);
867 const uint8_t level = block[100];
868 if (level == 0) {
869 const size_t header_size = 101;
870 const size_t item_size = 25;
871 for (uint32_t i = 0; i < nritems; ++i) {
872 const size_t off = header_size + static_cast<size_t>(i) * item_size;
873 if (off + item_size > block.size()) break;
874 BtrfsItem item;
875 item.key = parse_key(block.data() + off);
876 item.data_offset = le32(block.data() + off + 17);
877 item.data_size = le32(block.data() + off + 21);
878 leaf_fn(logical_bytenr, owner, block, item);
879 }
880 return;
881 }
882 const size_t header_size = 101;
883 const size_t ptr_size = 33;
884 for (uint32_t i = 0; i < nritems; ++i) {
885 const size_t off = header_size + static_cast<size_t>(i) * ptr_size;
886 if (off + ptr_size > block.size()) break;
887 BtrfsKeyPtr ptr;
888 ptr.key = parse_key(block.data() + off);
889 ptr.blockptr = le64(block.data() + off + 17);
890 ptr.generation = le64(block.data() + off + 25);
891 walk_tree_internal(ptr.blockptr, leaf_fn);
892 }
893 }
894
895 bool collect_fs_root(uint64_t root_id,
896 const RootEntry& root,
897 FsRootState& state,
898 std::vector<std::string>& logs) {
899 (void)root_id;
900 if (root.bytenr == 0) return false;
901 walk_tree(root.bytenr, [&](uint64_t, uint64_t, const std::vector<unsigned char>& block, const BtrfsItem& item) {
902 if (item.data_offset + item.data_size > block.size()) return;
903 const unsigned char* p = block.data() + item.data_offset;
904 if (item.key.type == BTRFS_INODE_ITEM_KEY) {
905 if (item.data_size < 56) return;
906 state.inodes[item.key.objectid] = {le64(p + 16), le32(p + 52)};
907 } else if (item.key.type == BTRFS_DIR_ITEM_KEY || item.key.type == BTRFS_DIR_INDEX_KEY) {
908 size_t pos = 0;
909 while (pos + 30 <= item.data_size) {
910 const uint64_t child = le64(p + pos + 0);
911 const uint16_t data_len = le16(p + pos + 25);
912 const uint16_t name_len = le16(p + pos + 27);
913 if (pos + 30 + name_len + data_len > item.data_size) break;
914 std::string name(reinterpret_cast<const char*>(p + pos + 30), reinterpret_cast<const char*>(p + pos + 30 + name_len));
915 if (!name.empty() && name != "." && name != "..") {
916 state.parent_edge.try_emplace(child, DirEdge{item.key.objectid, child, sanitize(name)});
917 }
918 pos += 30 + name_len + data_len;
919 }
920 } else if (item.key.type == BTRFS_EXTENT_DATA_KEY) {
921 if (item.data_size < 21) return;
922 FileExtentInfo fe;
923 fe.file_offset = item.key.offset;
924 fe.ram_bytes = le64(p + 8);
925 fe.compression = p[16];
926 fe.type = p[20];
927 if (fe.type == BTRFS_FILE_EXTENT_INLINE) {
928 fe.inline_data.assign(p + 21, p + item.data_size);
929 } else if (fe.type == BTRFS_FILE_EXTENT_REG || fe.type == BTRFS_FILE_EXTENT_PREALLOC) {
930 if (item.data_size < 53) return;
931 fe.disk_bytenr = le64(p + 21);
932 fe.disk_num_bytes = le64(p + 29);
933 fe.extent_offset = le64(p + 37);
934 fe.num_bytes = le64(p + 45);
935 }
936 state.extents[item.key.objectid].push_back(std::move(fe));
937 }
938 });
939 logs.push_back(trs("Btrfs fs root parsed") + std::string(": ") + root.name);
940 return true;
941 }
942
943 fs::path inode_relative_path(uint64_t inode, const FsRootState& state) const {
944 std::vector<std::string> parts;
945 std::set<uint64_t> seen;
946 uint64_t cur = inode;
947 while (seen.insert(cur).second) {
948 auto it = state.parent_edge.find(cur);
949 if (it == state.parent_edge.end()) break;
950 parts.push_back(it->second.name);
951 cur = it->second.parent;
952 if (cur == root_dir_objectid_) break;
953 }
954 fs::path out;
955 for (auto it = parts.rbegin(); it != parts.rend(); ++it) out /= *it;
956 if (out.empty()) out = fs::path("orphaned") / ("inode_" + std::to_string(inode));
957 return out;
958 }
959
960 bool read_logical_bytes(uint64_t logical, uint64_t length, std::vector<unsigned char>& out) {
961 out.resize(static_cast<size_t>(length));
962 return read_logical(logical, out.data(), out.size());
963 }
964
965 bool render_extent(const FileExtentInfo& extent,
966 std::vector<unsigned char>& out,
967 bool& compressed_ok,
968 bool& compressed_skipped) {
969 compressed_ok = false;
970 compressed_skipped = false;
971 if (extent.type == BTRFS_FILE_EXTENT_INLINE) {
972 if (extent.compression == 0) {
973 out = extent.inline_data;
974 return true;
975 }
976 if (decompress_payload(extent.inline_data, extent.compression, static_cast<size_t>(extent.ram_bytes), out)) {
977 compressed_ok = true;
978 return true;
979 }
980 compressed_skipped = true;
981 return false;
982 }
983 if (extent.type != BTRFS_FILE_EXTENT_REG && extent.type != BTRFS_FILE_EXTENT_PREALLOC) return false;
984 if (extent.disk_bytenr == 0) {
985 out.assign(static_cast<size_t>(extent.num_bytes), 0);
986 return true;
987 }
988 std::vector<unsigned char> raw;
989 if (!read_logical_bytes(extent.disk_bytenr, extent.disk_num_bytes, raw)) return false;
990 if (extent.compression == 0) {
991 if (extent.extent_offset > raw.size()) return false;
992 const uint64_t avail = std::min<uint64_t>(extent.num_bytes, raw.size() - extent.extent_offset);
993 out.assign(raw.begin() + static_cast<long>(extent.extent_offset),
994 raw.begin() + static_cast<long>(extent.extent_offset + avail));
995 if (out.size() < extent.num_bytes) out.resize(static_cast<size_t>(extent.num_bytes), 0);
996 return true;
997 }
998 std::vector<unsigned char> decomp;
999 if (!decompress_payload(raw, extent.compression, static_cast<size_t>(extent.ram_bytes), decomp)) {
1000 compressed_skipped = true;
1001 return false;
1002 }
1003 compressed_ok = true;
1004 if (extent.extent_offset > decomp.size()) return false;
1005 const uint64_t avail = std::min<uint64_t>(extent.num_bytes, decomp.size() - extent.extent_offset);
1006 out.assign(decomp.begin() + static_cast<long>(extent.extent_offset),
1007 decomp.begin() + static_cast<long>(extent.extent_offset + avail));
1008 if (out.size() < extent.num_bytes) out.resize(static_cast<size_t>(extent.num_bytes), 0);
1009 return true;
1010 }
1011
1012 size_t export_fs_root(uint64_t root_id,
1013 const RootEntry& root,
1014 const FsRootState& state,
1015 std::vector<RecoveredFileRecord>& recovered,
1016 size_t& compressed_exported,
1017 size_t& compressed_skipped,
1018 std::vector<std::string>& logs) {
1019 size_t count = 0;
1020 fs::path root_out = out_root_ / "files" / sanitize(root.name.empty() ? ("subvolume_" + std::to_string(root_id)) : root.name);
1021 fs::create_directories(root_out);
1022 for (const auto& [inode, ino] : state.inodes) {
1023 const uint32_t type_bits = ino.mode & 0170000u;
1024 if (type_bits == 0040000u) continue;
1025 fs::path rel = inode_relative_path(inode, state);
1026 fs::path final = root_out / rel;
1027 fs::create_directories(final.parent_path());
1028 std::vector<unsigned char> file_data;
1029 auto ext_it = state.extents.find(inode);
1030 if (ext_it != state.extents.end()) {
1031 auto extents = ext_it->second;
1032 std::sort(extents.begin(), extents.end(), [](const FileExtentInfo& a, const FileExtentInfo& b) {
1033 return a.file_offset < b.file_offset;
1034 });
1035 uint64_t written = 0;
1036 for (const auto& ext : extents) {
1037 if (ext.file_offset > written) file_data.resize(static_cast<size_t>(ext.file_offset), 0);
1038 std::vector<unsigned char> part;
1039 bool c_ok = false;
1040 bool c_skip = false;
1041 if (!render_extent(ext, part, c_ok, c_skip)) {
1042 if (c_skip) ++compressed_skipped;
1043 part.assign(static_cast<size_t>(ext.num_bytes), 0);
1044 } else if (c_ok) {
1045 ++compressed_exported;
1046 }
1047 if (file_data.size() < ext.file_offset) file_data.resize(static_cast<size_t>(ext.file_offset), 0);
1048 if (file_data.size() == ext.file_offset) {
1049 file_data.insert(file_data.end(), part.begin(), part.end());
1050 } else {
1051 if (file_data.size() < ext.file_offset + part.size()) file_data.resize(static_cast<size_t>(ext.file_offset + part.size()), 0);
1052 std::copy(part.begin(), part.end(), file_data.begin() + static_cast<long>(ext.file_offset));
1053 }
1054 written = std::max<uint64_t>(written, ext.file_offset + part.size());
1055 }
1056 }
1057 if (file_data.size() > ino.size) file_data.resize(static_cast<size_t>(ino.size));
1058 if (file_data.size() < ino.size) file_data.resize(static_cast<size_t>(ino.size), 0);
1059
1060 std::ofstream out(final, std::ios::binary);
1061 if (!out) {
1062 logs.push_back(trs("Btrfs export failed") + std::string(": ") + final.string());
1063 continue;
1064 }
1065 if (!file_data.empty()) out.write(reinterpret_cast<const char*>(file_data.data()), static_cast<std::streamsize>(file_data.size()));
1066 if (!out) {
1067 logs.push_back(trs("Btrfs export failed") + std::string(": ") + final.string());
1068 continue;
1069 }
1070 recovered.push_back({final.string(), "Btrfs", final.lexically_relative(out_root_ / "files").string(), ino.size, "full", "filesystem export"});
1071 ++count;
1072 }
1073 return count;
1074 }
1075};
1076
1078
1079bool BtrfsRecovery::recover(uint64_t volume_offset,
1080 const std::string& output_dir,
1081 std::vector<RecoveredFileRecord>& recovered,
1082 std::vector<std::string>& logs,
1083 std::string& error) {
1085 return impl.recover(volume_offset, output_dir, recovered, logs, error);
1086}
std::map< uint64_t, RootEntry > roots_
bool recover(uint64_t volume_offset, const std::string &output_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)
std::set< uint64_t > visited_tree_blocks_
void walk_tree(uint64_t logical_bytenr, LeafFn &&leaf_fn)
bool load_chunk_tree(std::vector< std::string > &logs)
void walk_tree_internal(uint64_t logical_bytenr, LeafFn &leaf_fn)
bool logical_to_physical(uint64_t logical, std::vector< uint64_t > &physical_candidates, uint64_t &available) const
BtrfsRecoveryImpl(DiskReader &reader)
std::vector< ChunkMapEntry > chunk_map_
std::array< unsigned char, 16 > fsid_
bool render_extent(const FileExtentInfo &extent, std::vector< unsigned char > &out, bool &compressed_ok, bool &compressed_skipped)
bool read_logical(uint64_t logical, void *buffer, size_t len)
bool collect_fs_root(uint64_t root_id, const RootEntry &root, FsRootState &state, std::vector< std::string > &logs)
fs::path inode_relative_path(uint64_t inode, const FsRootState &state) const
bool read_tree_block(uint64_t logical_bytenr, std::vector< unsigned char > &block)
bool load_root_tree(std::vector< std::string > &logs, std::string &error)
bool validate_raid6_candidate(const std::vector< std::vector< unsigned char > > &bufs, const std::vector< bool > &ok, const std::vector< size_t > &data_cols, const std::map< size_t, size_t > &data_index, size_t p_col, size_t q_col, const unsigned char *candidate, size_t candidate_col, size_t chunk)
bool read_logical_bytes(uint64_t logical, uint64_t length, std::vector< unsigned char > &out)
const ChunkMapEntry * find_chunk_entry(uint64_t logical) const
void parse_system_chunk_array(const unsigned char *data, uint32_t size)
size_t export_fs_root(uint64_t root_id, const RootEntry &root, const FsRootState &state, std::vector< RecoveredFileRecord > &recovered, size_t &compressed_exported, size_t &compressed_skipped, std::vector< std::string > &logs)
bool reconstruct_raid56_read(const ChunkMapEntry &entry, uint64_t logical, unsigned char *out, size_t chunk)
bool recover(uint64_t volume_offset, const std::string &output_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)
BtrfsRecovery(DiskReader &reader)
DiskReader & reader_
bool read_at(uint64_t offset, void *buffer, size_t len)
std::string trs(const char *msgid)
Definition i18n.cpp:22
bool decompress_payload(const std::vector< unsigned char > &in, uint8_t compression, size_t expected_size, std::vector< unsigned char > &out)
bool validate_tree_block_checksum(const std::vector< unsigned char > &block)
uint32_t le32(const unsigned char *p)
uint32_t crc32c_update(uint32_t crc, const unsigned char *data, size_t len)
std::string sanitize(const std::string &in)
bool validate_tree_block_header(const std::vector< unsigned char > &block, uint64_t logical_bytenr, uint32_t nodesize, const std::array< unsigned char, 16 > *fsid=nullptr, uint64_t max_generation=0)
BtrfsKey parse_key(const unsigned char *p)
uint8_t gf_mul_byte(uint8_t a, uint8_t b)
uint64_t le64(const unsigned char *p)
uint16_t le16(const unsigned char *p)
std::string sanitize_component(const std::string &in)
std::map< uint64_t, std::vector< FileExtentInfo > > extents