28namespace fs = std::filesystem;
52uint16_t
le16(
const unsigned char* p) {
53 return static_cast<uint16_t
>(p[0]) | (
static_cast<uint16_t
>(p[1]) << 8);
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);
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);
79 if (out ==
"unnamed") out =
"item";
84 std::ostringstream oss;
85 oss <<
"0x" << std::hex << std::uppercase << v;
152 std::map<uint64_t, std::vector<FileExtentInfo>>
extents;
157 return {
le64(p), p[8],
le64(p + 9)};
184 size_t expected_size,
185 std::vector<unsigned char>& out) {
186 if (compression == 0) {
188 if (out.size() > expected_size) out.resize(expected_size);
192 if (compression == 1) {
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));
203 if (compression == 2) {
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;
216 out.reserve(expected_size);
217 while (pos + 4 <= total_len && out.size() < expected_size) {
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;
226 const uint32_t chunk_len =
le32(in.data() + pos);
228 if (chunk_len == 0 || chunk_len > total_len - pos)
return false;
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());
240 if (out.size() > expected_size) out.resize(expected_size);
245 if (compression == 3) {
249 constexpr size_t kBtrfsZstdMaxChunkOutput = 128 * 1024;
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);
256 if (chunk_len == 0 || chunk_len > in.size() - pos)
return false;
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());
267 if (out.size() > expected_size) out.resize(expected_size);
276 for (
int i = 0; i < 8; ++i) {
278 const bool hi = (a & 0x80u) != 0;
279 a =
static_cast<uint8_t
>(a << 1u);
281 b =
static_cast<uint8_t
>(b >> 1u);
288 for (
size_t i = 0; i < exp; ++i) v =
gf_mul_byte(v, 2);
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);
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;
305 for (uint32_t i = 0; i < 256; ++i) {
307 for (
int j = 0; j < 8; ++j) r = (r >> 1) ^ (0x82F63B78u &
static_cast<uint32_t
>(-(
static_cast<int32_t
>(r & 1u))));
313 for (
size_t i = 0; i < len; ++i) crc = table[(crc ^ data[i]) & 0xFFu] ^ (crc >> 8);
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;
325 uint64_t logical_bytenr,
327 const std::array<unsigned char, 16>* fsid =
nullptr,
328 uint64_t max_generation = 0) {
329 if (block.size() < 101)
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;
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");
362 if (std::string(
reinterpret_cast<const char*
>(sb.data() + 0x40), 8) !=
"_BHRfS_M") {
363 error =
trs(
"Invalid Btrfs superblock magic");
368 std::copy(sb.begin() + 0x20, sb.begin() + 0x20 + 16,
fsid_.begin());
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);
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);
382 error =
trs(
"Invalid Btrfs nodesize");
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");
392 error =
trs(
"Unable to parse Btrfs system chunk array");
396 std::ofstream meta(
out_root_ /
"btrfs_metadata.txt", std::ios::binary);
398 error =
trs(
"Unable to create output file") + std::string(
": ") + (
out_root_ /
"btrfs_metadata.txt").
string();
401 meta <<
"Btrfs recovery metadata\n";
402 meta <<
"volume_offset=" << volume_offset <<
"\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";
413 meta <<
"chunk_mappings=" <<
chunk_map_.size() <<
"\n";
417 logs.push_back(
trs(
"Btrfs chunk tree load was partial; continuing with system chunk mappings only."));
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_) {
427 files_exported +=
export_fs_root(root_id, root, state, recovered, compressed_exported, compressed_skipped, logs);
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));
448 std::array<unsigned char, 16>
fsid_{};
456 while (pos + 17 + 48 <= size) {
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)});
472 chunk_map_.push_back({key.
offset, length, stripe_len, type, num_stripes, std::move(stripes)});
473 pos +=
static_cast<uint32_t
>(chunk_size);
480 if (logical >= entry.logical && logical < entry.logical + entry.length)
return &entry;
486 const std::vector<bool>& ok,
487 const std::vector<size_t>& data_cols,
488 const std::map<size_t, size_t>& data_index,
491 const unsigned char* candidate,
492 size_t candidate_col,
494 if (chunk == 0)
return true;
496 for (
size_t j = 0; j < chunk; ++j) {
498 for (
size_t c : data_cols) {
499 const uint8_t v = (c == candidate_col) ? candidate[j] : bufs[c][j];
502 if (parity != bufs[p_col][j])
return false;
506 for (
size_t j = 0; j < chunk; ++j) {
508 for (
size_t c : data_cols) {
509 const uint8_t v = (c == candidate_col) ? candidate[j] : bufs[c][j];
512 if (parity != bufs[q_col][j])
return false;
523 const uint64_t inner = logical - entry.
logical;
525 const uint64_t stripe_inner = inner % stripe_len;
527 auto read_piece = [&](
size_t stripe_index, std::vector<unsigned char>& buf) ->
bool {
528 if (stripe_index >= entry.
stripes.size())
return false;
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;
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);
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;
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]);
556 std::copy(bufs[data_col].begin(), bufs[data_col].end(), out);
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];
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);
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;
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]);
591 std::copy(bufs[data_col].begin(), bufs[data_col].end(), out);
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];
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];
619 if (other == p_col) {
620 const uint8_t a =
gf_pow2(data_index[data_col]);
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;
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);
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;
659 return a.logical < b.logical;
663 bool logical_to_physical(uint64_t logical, std::vector<uint64_t>& physical_candidates, uint64_t& available)
const {
664 physical_candidates.clear();
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);
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);
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();
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);
693 return !physical_candidates.empty();
698 for (
size_t i = 0; i < entry.stripes.size(); ++i) add_candidate(i, inner);
699 return !physical_candidates.empty();
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);
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) {
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);
722 return !physical_candidates.empty();
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);
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) {
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);
746 return !physical_candidates.empty();
749 if (!entry.stripes.empty()) {
750 add_candidate(0, inner);
751 return !physical_candidates.empty();
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));
768 for (uint64_t physical : physical_candidates) {
787 if (!
read_logical(logical_bytenr, block.data(), block.size()))
return false;
791 if (entry ==
nullptr)
return false;
794 if (!is_raid56)
return false;
795 std::vector<unsigned char> rebuilt(
nodesize_);
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)});
819 chunk_map_.push_back({item.
key.
offset, length, stripe_len, type, num_stripes, std::move(stripes)});
822 logs.push_back(
trs(
"Btrfs chunk tree parsed") + std::string(
": ") + std::to_string(
chunk_map_.size() - before) +
" new mappings");
828 error =
trs(
"Invalid Btrfs root tree");
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];
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));
847 if (it !=
roots_.end() && !name.empty()) it->second.name =
sanitize(name);
850 logs.push_back(
trs(
"Btrfs root tree parsed") + std::string(
": ") + std::to_string(
roots_.size()) +
" filesystem roots");
854 template <
typename LeafFn>
855 void walk_tree(uint64_t logical_bytenr, LeafFn&& leaf_fn) {
860 template <
typename LeafFn>
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];
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;
878 leaf_fn(logical_bytenr, owner, block, item);
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;
898 std::vector<std::string>& logs) {
900 if (root.
bytenr == 0)
return false;
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)};
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)});
918 pos += 30 + name_len + data_len;
921 if (item.data_size < 21) return;
923 fe.file_offset = item.key.offset;
924 fe.ram_bytes = le64(p + 8);
925 fe.compression = p[16];
927 if (fe.type == BTRFS_FILE_EXTENT_INLINE) {
928 fe.inline_data.assign(p + 21, p + item.data_size);
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);
936 state.extents[item.key.objectid].push_back(std::move(fe));
939 logs.push_back(
trs(
"Btrfs fs root parsed") + std::string(
": ") + root.name);
944 std::vector<std::string> parts;
945 std::set<uint64_t> seen;
946 uint64_t cur = inode;
947 while (seen.insert(cur).second) {
950 parts.push_back(it->second.name);
951 cur = it->second.parent;
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));
961 out.resize(
static_cast<size_t>(length));
966 std::vector<unsigned char>& out,
968 bool& compressed_skipped) {
969 compressed_ok =
false;
970 compressed_skipped =
false;
977 compressed_ok =
true;
980 compressed_skipped =
true;
985 out.assign(
static_cast<size_t>(extent.
num_bytes), 0);
988 std::vector<unsigned char> raw;
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);
998 std::vector<unsigned char> decomp;
1000 compressed_skipped =
true;
1003 compressed_ok =
true;
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);
1015 std::vector<RecoveredFileRecord>& recovered,
1016 size_t& compressed_exported,
1017 size_t& compressed_skipped,
1018 std::vector<std::string>& logs) {
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;
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;
1033 return a.file_offset < b.file_offset;
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;
1040 bool c_skip =
false;
1042 if (c_skip) ++compressed_skipped;
1043 part.assign(
static_cast<size_t>(ext.num_bytes), 0);
1045 ++compressed_exported;
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());
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));
1054 written = std::max<uint64_t>(written, ext.file_offset + part.size());
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);
1060 std::ofstream out(
final, std::ios::binary);
1062 logs.push_back(
trs(
"Btrfs export failed") + std::string(
": ") +
final.
string());
1065 if (!file_data.empty()) out.write(
reinterpret_cast<const char*
>(file_data.data()),
static_cast<std::streamsize
>(file_data.size()));
1067 logs.push_back(
trs(
"Btrfs export failed") + std::string(
": ") +
final.
string());
1070 recovered.push_back({
final.string(),
"Btrfs",
final.lexically_relative(
out_root_ /
"files").string(), ino.size,
"full",
"filesystem export"});
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);
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)
uint64_t root_dir_objectid_
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_
uint64_t chunk_root_bytenr_
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
uint64_t root_tree_bytenr_
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)
uint64_t super_generation_
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)
bool read_at(uint64_t offset, void *buffer, size_t len)
std::string trs(const char *msgid)
bool decompress_payload(const std::vector< unsigned char > &in, uint8_t compression, size_t expected_size, std::vector< unsigned char > &out)
uint8_t gf_pow2(size_t exp)
constexpr uint8_t BTRFS_FILE_EXTENT_INLINE
constexpr uint8_t BTRFS_FILE_EXTENT_REG
constexpr uint8_t BTRFS_ROOT_ITEM_KEY
bool validate_tree_block_checksum(const std::vector< unsigned char > &block)
uint32_t le32(const unsigned char *p)
constexpr uint64_t BTRFS_BLOCK_GROUP_RAID5
constexpr uint8_t BTRFS_INODE_REF_KEY
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)
constexpr uint8_t BTRFS_ROOT_REF_KEY
uint8_t gf_inv_byte(uint8_t a)
constexpr uint8_t BTRFS_DIR_INDEX_KEY
uint8_t gf_mul_byte(uint8_t a, uint8_t b)
uint64_t le64(const unsigned char *p)
constexpr uint8_t BTRFS_FILE_EXTENT_PREALLOC
constexpr uint8_t BTRFS_INODE_ITEM_KEY
constexpr uint64_t BTRFS_BLOCK_GROUP_RAID1
constexpr uint8_t BTRFS_CHUNK_ITEM_KEY
constexpr uint64_t BTRFS_BLOCK_GROUP_RAID6
constexpr uint64_t BTRFS_BLOCK_GROUP_DUP
constexpr uint64_t BTRFS_BLOCK_GROUP_RAID0
constexpr uint8_t BTRFS_ROOT_BACKREF_KEY
constexpr uint8_t BTRFS_EXTENT_DATA_KEY
std::string to_hex(uint64_t v)
uint16_t le16(const unsigned char *p)
constexpr uint8_t BTRFS_DIR_ITEM_KEY
constexpr uint64_t BTRFS_BLOCK_GROUP_RAID10
std::string sanitize_component(const std::string &in)
std::vector< ChunkStripe > stripes
std::vector< unsigned char > inline_data
std::map< uint64_t, DirEdge > parent_edge
std::map< uint64_t, InodeInfo > inodes
std::map< uint64_t, std::vector< FileExtentInfo > > extents