Skip to content
API Documentation
BastionGuard™
anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl Class Reference

Public Member Functions

 Ext4RecoveryImpl (DiskReader &reader)
bool recover (uint64_t volume_offset, const std::string &output_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)

Private Member Functions

bool load_superblock (std::string &error)
uint64_t block_to_offset (uint64_t block) const
bool read_fs_block (uint64_t block_no, unsigned char *buffer, size_t len)
bool read_volume_bytes_replayed (uint64_t volume_rel_offset, unsigned char *buffer, size_t len)
void build_journal_replay_map (std::vector< std::string > &logs)
bool read_group_desc (uint32_t group, std::vector< unsigned char > &out)
bool inode_marked_allocated (uint32_t inode_no)
bool read_inode (uint32_t inode_no, Ext4Inode &inode, std::string &error)
uint64_t inode_size_bytes (const Ext4Inode &inode) const
bool inode_is_dir (const Ext4Inode &inode) const
bool inode_is_reg (const Ext4Inode &inode) const
bool inode_uses_extents (const Ext4Inode &inode) const
bool find_inline_xattr_system_data (const Ext4Inode &inode, std::vector< unsigned char > &out) const
bool collect_extents_from_node (uint64_t node_phys_block, std::vector< ExtentRef > &extents, std::string &error)
bool inode_extents (const Ext4Inode &inode, std::vector< ExtentRef > &extents, std::string &error)
bool read_u32_block (uint64_t block_no, std::vector< uint32_t > &out, std::string &error)
bool inode_block_map_classic (const Ext4Inode &inode, std::vector< uint64_t > &blocks, std::string &error)
bool inode_ranges (const Ext4Inode &inode, std::vector< ExportRange > &ranges, std::string &error)
bool export_file (uint32_t inode_no, const std::string &name, const Ext4Inode &inode, const fs::path &out_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)
void scan_journal_dirents (const std::vector< unsigned char > &block, uint32_t target_block, uint32_t sequence, std::vector< JournalDirentCandidate > &out)
void export_journal (const fs::path &out_root, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs)
bool recover_directory (uint32_t inode_no, const fs::path &out_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error, std::set< uint32_t > &dir_guard, int depth)

Static Private Member Functions

static bool looks_like_printable_name (const unsigned char *p, size_t len)
static std::string ext4_file_type_name (uint8_t t)

Private Attributes

DiskReader & reader_
uint64_t volume_offset_ {0}
uint64_t block_size_ {0}
uint32_t blocks_per_group_ {0}
uint32_t inodes_per_group_ {0}
uint16_t inode_size_ {0}
uint64_t desc_table_offset_ {0}
uint16_t desc_size_ {32}
uint32_t first_data_block_ {0}
uint64_t total_inodes_ {0}
uint32_t journal_inode_ {0}
std::map< uint64_t, std::pair< uint32_t, std::vector< unsigned char > > > journal_replay_blocks_
std::set< uint64_t > journal_revoked_blocks_
size_t committed_transactions_ {0}
size_t revoke_records_ {0}

Detailed Description

Definition at line 155 of file ext4_recovery.cpp.

Constructor & Destructor Documentation

◆ Ext4RecoveryImpl()

anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::Ext4RecoveryImpl ( DiskReader & reader)
inlineexplicit

Definition at line 157 of file ext4_recovery.cpp.

References reader_.

Member Function Documentation

◆ block_to_offset()

uint64_t anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::block_to_offset ( uint64_t block) const
inlineprivate

◆ build_journal_replay_map()

void anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::build_journal_replay_map ( std::vector< std::string > & logs)
inlineprivate

Definition at line 267 of file ext4_recovery.cpp.

267 {
271 revoke_records_ = 0;
272 if (journal_inode_ == 0) return;
273 Ext4Inode journal_inode{};
274 std::string local_error;
275 if (!read_inode(journal_inode_, journal_inode, local_error)) return;
276 std::vector<ExportRange> ranges;
277 if (!inode_ranges(journal_inode, ranges, local_error)) return;
278 std::vector<unsigned char> block(static_cast<size_t>(block_size_));
279 uint64_t remaining_total = inode_size_bytes(journal_inode);
280 std::vector<std::pair<uint32_t, uint32_t>> pending_tags;
281 std::map<uint32_t, std::vector<unsigned char>> pending_data;
282 std::set<uint32_t> pending_revokes;
283 uint32_t pending_sequence = 0;
284 bool transaction_open = false;
285
286 auto apply_transaction = [&]() {
287 if (!transaction_open) return;
288 for (uint32_t blk : pending_revokes) {
289 journal_revoked_blocks_.insert(blk);
290 journal_replay_blocks_.erase(blk);
291 }
292 for (const auto& [target_block, payload] : pending_data) {
293 if (pending_revokes.find(target_block) != pending_revokes.end()) continue;
294 auto& slot = journal_replay_blocks_[target_block];
295 if (slot.second.empty() || pending_sequence >= slot.first) {
296 slot.first = pending_sequence;
297 slot.second = payload;
298 }
299 journal_revoked_blocks_.erase(target_block);
300 }
301 if (!pending_data.empty() || !pending_revokes.empty()) ++committed_transactions_;
302 revoke_records_ += pending_revokes.size();
303 pending_tags.clear();
304 pending_data.clear();
305 pending_revokes.clear();
306 transaction_open = false;
307 pending_sequence = 0;
308 };
309
310 for (const auto& r : ranges) {
311 const uint64_t offset = r.offset;
312 const uint64_t length = r.length;
313 uint64_t cursor = offset;
314 uint64_t remaining = std::min<uint64_t>(length, remaining_total);
315 while (remaining >= block_size_) {
316 const uint64_t fs_block = (cursor - volume_offset_) / block_size_;
317 if (!reader_.read_at(block_to_offset(fs_block), block.data(), block.size())) break;
318 const uint32_t magic = be32(block.data());
319 if (magic == 0xC03B3998u) {
320 const uint32_t type = be32(block.data() + 4);
321 const uint32_t sequence = be32(block.data() + 8);
322 if (type == 1u) {
323 if (transaction_open && sequence != pending_sequence && (!pending_data.empty() || !pending_revokes.empty())) {
324 apply_transaction();
325 }
326 if (!transaction_open) {
327 pending_data.clear();
328 pending_revokes.clear();
329 }
330 pending_tags.clear();
331 pending_sequence = sequence;
332 transaction_open = true;
333 size_t pos = 12;
334 while (pos + 8 <= block.size()) {
335 const uint32_t target_block = be32(block.data() + pos + 0);
336 const uint32_t flags = be32(block.data() + pos + 4);
337 if (target_block == 0) break;
338 pending_tags.push_back({target_block, flags});
339 pos += 8;
340 if ((flags & 0x8u) != 0) break;
341 }
342 } else if (type == 2u) {
343 if (transaction_open && sequence == pending_sequence) apply_transaction();
344 } else if (type == 5u) {
345 if (!transaction_open || sequence != pending_sequence) {
346 pending_data.clear();
347 pending_revokes.clear();
348 pending_tags.clear();
349 pending_sequence = sequence;
350 transaction_open = true;
351 }
352 for (size_t pos = 12; pos + 4 <= block.size(); pos += 4) {
353 const uint32_t revoked_block = be32(block.data() + pos);
354 if (revoked_block == 0) break;
355 pending_revokes.insert(revoked_block);
356 }
357 }
358 } else if (transaction_open && !pending_tags.empty()) {
359 const auto [target_block, _flags] = pending_tags.front();
360 pending_tags.erase(pending_tags.begin());
361 pending_data[target_block] = block;
362 }
363 cursor += block_size_;
364 remaining -= block_size_;
365 remaining_total = remaining_total >= block_size_ ? remaining_total - block_size_ : 0;
366 if (remaining_total < block_size_) break;
367 }
368 if (remaining_total < block_size_) break;
369 }
370 if (!journal_replay_blocks_.empty()) logs.push_back(trs("ext4 journal replay overlay blocks") + std::string(": ") + std::to_string(journal_replay_blocks_.size()));
371 if (committed_transactions_ > 0) logs.push_back(trs("ext4 committed journal transactions") + std::string(": ") + std::to_string(committed_transactions_));
372 if (revoke_records_ > 0) logs.push_back(trs("ext4 journal revoke records") + std::string(": ") + std::to_string(revoke_records_));
373 }
bool read_at(uint64_t offset, void *buffer, size_t len)
bool read_inode(uint32_t inode_no, Ext4Inode &inode, std::string &error)
bool inode_ranges(const Ext4Inode &inode, std::vector< ExportRange > &ranges, std::string &error)
std::map< uint64_t, std::pair< uint32_t, std::vector< unsigned char > > > journal_replay_blocks_
uint64_t inode_size_bytes(const Ext4Inode &inode) const
std::string trs(const char *msgid)
Definition i18n.cpp:22
uint32_t be32(const unsigned char *p)

References anonymous_namespace{ext4_recovery.cpp}::be32(), block_size_, block_to_offset(), committed_transactions_, inode_ranges(), inode_size_bytes(), journal_inode_, journal_replay_blocks_, journal_revoked_blocks_, DiskReader::read_at(), read_inode(), reader_, revoke_records_, trs(), and volume_offset_.

Referenced by recover().

◆ collect_extents_from_node()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::collect_extents_from_node ( uint64_t node_phys_block,
std::vector< ExtentRef > & extents,
std::string & error )
inlineprivate

Definition at line 547 of file ext4_recovery.cpp.

549 {
550 std::vector<unsigned char> block(static_cast<size_t>(block_size_));
551 if (!read_fs_block(node_phys_block, block.data(), block.size())) {
552 error = trs("Failed to read ext4 extent node");
553 return false;
554 }
555 const uint16_t magic = le16(block.data() + 0);
556 const uint16_t entries = le16(block.data() + 2);
557 const uint16_t node_depth = le16(block.data() + 6);
558 if (magic != 0xF30A) {
559 error = trs("Invalid ext4 extent header");
560 return false;
561 }
562 const unsigned char* p = block.data() + 12;
563 if (node_depth == 0) {
564 for (uint16_t i = 0; i < entries; ++i, p += 12) {
565 const uint32_t ee_block = le32(p + 0);
566 const uint16_t ee_len = le16(p + 4);
567 const uint16_t ee_start_hi = le16(p + 6);
568 const uint32_t ee_start_lo = le32(p + 8);
569 const bool uninit = (ee_len & 0x8000u) != 0;
570 const uint32_t count = uninit ? (ee_len & 0x7FFFu) : ee_len;
571 if (count == 0) continue;
572 ExtentRef ref;
573 ref.physical_block = (static_cast<uint64_t>(ee_start_hi) << 32) | ee_start_lo;
574 ref.logical_block = ee_block;
575 ref.block_count = count;
576 ref.uninitialized = uninit;
577 extents.push_back(ref);
578 }
579 return true;
580 }
581 for (uint16_t i = 0; i < entries; ++i, p += 12) {
582 const uint32_t ei_leaf_lo = le32(p + 4);
583 const uint16_t ei_leaf_hi = le16(p + 8);
584 const uint64_t child = (static_cast<uint64_t>(ei_leaf_hi) << 32) | ei_leaf_lo;
585 if (!collect_extents_from_node(child, extents, error)) return false;
586 }
587 return true;
588 }
bool read_fs_block(uint64_t block_no, unsigned char *buffer, size_t len)
bool collect_extents_from_node(uint64_t node_phys_block, std::vector< ExtentRef > &extents, std::string &error)
uint16_t le16(const unsigned char *p)
uint32_t le32(const unsigned char *p)

References anonymous_namespace{ext4_recovery.cpp}::ExtentRef::block_count, block_size_, collect_extents_from_node(), anonymous_namespace{ext4_recovery.cpp}::le16(), anonymous_namespace{ext4_recovery.cpp}::le32(), anonymous_namespace{ext4_recovery.cpp}::ExtentRef::logical_block, anonymous_namespace{ext4_recovery.cpp}::ExtentRef::physical_block, read_fs_block(), trs(), and anonymous_namespace{ext4_recovery.cpp}::ExtentRef::uninitialized.

Referenced by collect_extents_from_node(), and inode_extents().

◆ export_file()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::export_file ( uint32_t inode_no,
const std::string & name,
const Ext4Inode & inode,
const fs::path & out_dir,
std::vector< RecoveredFileRecord > & recovered,
std::vector< std::string > & logs,
std::string & error )
inlineprivate

Definition at line 752 of file ext4_recovery.cpp.

758 {
759 if (!inode_is_reg(inode)) return true;
760 const uint64_t size_bytes = inode_size_bytes(inode);
761 if (size_bytes == 0) {
762 fs::create_directories(out_dir);
763 fs::path out_path = out_dir / sanitize_path_component(name);
764 std::ofstream(out_path, std::ios::binary);
765 recovered.push_back({out_path.string(), "ext4", name, 0, "empty", "zero-length file"});
766 return true;
767 }
768
769 // ext4 inline_data: when EXT4_INLINE_DATA_FL (0x10000000) is set,
770 // the file content lives in i_block[60] for the first 60 bytes
771 // (and optionally in an xattr 'system.data' for the overflow
772 // beyond 60 bytes).
773 constexpr uint32_t EXT4_INLINE_DATA_FL = 0x10000000u;
774 if (inode.flags & EXT4_INLINE_DATA_FL) {
775 fs::create_directories(out_dir);
776 fs::path out_path = out_dir / sanitize_path_component(name);
777 std::string unique = out_path.string();
778 int suffix = 1;
779 while (fs::exists(unique)) {
780 unique = (out_dir / (sanitize_path_component(name) + "_" +
781 std::to_string(suffix++))).string();
782 }
783 out_path = unique;
784
785 constexpr uint64_t inline_iblock_bytes = 60; // bytes in i_block[]
786
787 std::ofstream out(out_path, std::ios::binary | std::ios::trunc);
788 if (!out) {
789 error = trs("Cannot create ext4 file");
790 return false;
791 }
792
793 // First 60 bytes from i_block[]
794 const uint64_t first_chunk = std::min<uint64_t>(size_bytes, inline_iblock_bytes);
795 out.write(reinterpret_cast<const char*>(inode.block.data()),
796 static_cast<std::streamsize>(first_chunk));
797 uint64_t written = first_chunk;
798
799 // Beyond 60 bytes: look up 'system.data' xattr
800 if (size_bytes > inline_iblock_bytes) {
801 std::vector<unsigned char> xattr_data;
802 if (find_inline_xattr_system_data(inode, xattr_data)) {
803 const uint64_t need = size_bytes - inline_iblock_bytes;
804 const uint64_t can = std::min<uint64_t>(need, xattr_data.size());
805 out.write(reinterpret_cast<const char*>(xattr_data.data()),
806 static_cast<std::streamsize>(can));
807 written += can;
808 if (written == size_bytes) {
809 recovered.push_back({out_path.string(), "ext4", name,
810 written, "full",
811 "inline-data+xattr-overflow"});
812 logs.push_back(trs("Recovered ext4 inline-data file with "
813 "xattr overflow:") +
814 std::string(" ") + out_path.string());
815 } else {
816 logs.push_back(trs("ext4 inline_data file truncated "
817 "(xattr 'system.data' too short):") +
818 std::string(" ") + name);
819 recovered.push_back({out_path.string(), "ext4", name,
820 written, "partial",
821 "inline-data+xattr-partial"});
822 }
823 } else {
824 logs.push_back(trs("ext4 inline_data file truncated to 60 bytes "
825 "(xattr 'system.data' not found):") +
826 std::string(" ") + name);
827 recovered.push_back({out_path.string(), "ext4", name,
828 written, "partial", "inline-data-60B"});
829 }
830 } else {
831 recovered.push_back({out_path.string(), "ext4", name,
832 written, "full", "inline-data"});
833 logs.push_back(trs("Recovered ext4 inline-data file:") +
834 std::string(" ") + out_path.string());
835 }
836 return true;
837 }
838
839 std::vector<ExportRange> ranges;
840 if (!inode_ranges(inode, ranges, error)) {
841 logs.push_back(trs("Skipped ext4 file without supported data mapping") + std::string(": inode ") + std::to_string(inode_no));
842 error.clear();
843 return true;
844 }
845
846 fs::create_directories(out_dir);
847 fs::path out_path = out_dir / sanitize_path_component(name);
848 std::string unique = out_path.string();
849 int suffix = 1;
850 while (fs::exists(unique)) {
851 unique = (out_dir / (sanitize_path_component(name) + "_" + std::to_string(suffix++))).string();
852 }
853 out_path = unique;
854
855 if (!write_ranges_to_file([&](uint64_t offset, unsigned char* buffer, size_t len) {
856 return read_volume_bytes_replayed(offset - volume_offset_, buffer, len);
857 }, ranges, out_path, size_bytes, error)) return false;
858 recovered.push_back({out_path.string(), "ext4", name, size_bytes, "full", "filesystem export"});
859 logs.push_back(trs("Recovered ext4 file:") + std::string(" ") + out_path.string());
860 return true;
861 }
bool read_volume_bytes_replayed(uint64_t volume_rel_offset, unsigned char *buffer, size_t len)
bool find_inline_xattr_system_data(const Ext4Inode &inode, std::vector< unsigned char > &out) const
bool write_ranges_to_file(ReadFn &&read_fn, const std::vector< ExportRange > &ranges, const fs::path &out_path, uint64_t expected_size, std::string &error)
std::string sanitize_path_component(const std::string &in)

References anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::block, find_inline_xattr_system_data(), anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::flags, inode_is_reg(), inode_ranges(), inode_size_bytes(), read_volume_bytes_replayed(), anonymous_namespace{ext4_recovery.cpp}::sanitize_path_component(), trs(), volume_offset_, and anonymous_namespace{ext4_recovery.cpp}::write_ranges_to_file().

Referenced by export_journal(), and recover_directory().

◆ export_journal()

void anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::export_journal ( const fs::path & out_root,
std::vector< RecoveredFileRecord > & recovered,
std::vector< std::string > & logs )
inlineprivate

Definition at line 911 of file ext4_recovery.cpp.

913 {
914 if (journal_inode_ == 0) return;
915
916 Ext4Inode journal_inode{};
917 std::string local_error;
918 if (!read_inode(journal_inode_, journal_inode, local_error)) {
919 logs.push_back(trs("ext4 journal inode could not be read"));
920 return;
921 }
922
923 std::vector<ExportRange> ranges;
924 if (!inode_ranges(journal_inode, ranges, local_error)) {
925 logs.push_back(trs("ext4 journal mapping unsupported or unreadable"));
926 return;
927 }
928
929 const fs::path journal_root = out_root / "journal";
930 fs::create_directories(journal_root);
931 const uint64_t journal_size = inode_size_bytes(journal_inode);
932 const fs::path journal_copy = journal_root / "journal_copy.bin";
933 if (!write_ranges_to_file([&](uint64_t offset, unsigned char* buffer, size_t len) {
934 return reader_.read_at(offset, buffer, len);
935 }, ranges, journal_copy, journal_size, local_error)) {
936 logs.push_back(trs("ext4 journal export failed") + std::string(": ") + local_error);
937 return;
938 }
939 recovered.push_back({journal_copy.string(), "ext4", "journal/journal_copy.bin", journal_size, "metadata", "journal copy"});
940
941 std::ofstream info(journal_root / "journal_info.txt", std::ios::binary);
942 if (!info) return;
943
944 std::map<uint32_t, size_t> type_counts;
945 std::vector<unsigned char> block(static_cast<size_t>(block_size_));
946 uint64_t remaining_total = journal_size;
947 size_t metadata_blocks_exported = 0;
948 size_t payload_blocks_exported = 0;
949 const fs::path replay_dir = journal_root / "replayed_metadata";
950 fs::create_directories(replay_dir);
951
952 std::vector<std::pair<uint32_t, uint32_t>> pending_tags;
953 std::vector<JournalDirentCandidate> journal_dirents;
954 uint32_t pending_sequence = 0;
955 uint64_t block_index = 0;
956 for (const auto& r : ranges) {
957 const uint64_t offset = r.offset;
958 const uint64_t length = r.length;
959 uint64_t cursor = offset;
960 uint64_t remaining = std::min<uint64_t>(length, remaining_total);
961 while (remaining >= block_size_) {
962 if (!reader_.read_at(cursor, block.data(), block.size())) break;
963 const uint32_t magic = be32(block.data());
964 if (magic == 0xC03B3998u) {
965 const uint32_t type = be32(block.data() + 4);
966 const uint32_t sequence = be32(block.data() + 8);
967 type_counts[type]++;
968 info << "block=" << block_index << " type=" << type << " sequence=" << sequence << "\n";
969 pending_tags.clear();
970 pending_sequence = sequence;
971 if (type == 1u) {
972 size_t pos = 12;
973 while (pos + 8 <= block.size()) {
974 const uint32_t target_block = be32(block.data() + pos + 0);
975 const uint32_t flags = be32(block.data() + pos + 4);
976 if (target_block == 0) break;
977 pending_tags.push_back({target_block, flags});
978 info << "descriptor[" << block_index << "].tag_target=" << target_block
979 << " flags=" << flags << "\n";
980 pos += 8;
981 if ((flags & 0x8u) != 0) break;
982 }
983 } else if (type == 5u) {
984 const fs::path block_file = replay_dir / ("journal_block_" + std::to_string(block_index) + "_revoke.bin");
985 std::ofstream bout(block_file, std::ios::binary);
986 if (bout) {
987 bout.write(reinterpret_cast<const char*>(block.data()), static_cast<std::streamsize>(block.size()));
988 ++metadata_blocks_exported;
989 }
990 for (size_t pos = 12; pos + 4 <= block.size(); pos += 4) {
991 const uint32_t revoked_block = be32(block.data() + pos);
992 if (revoked_block == 0) break;
993 info << "revoke[" << block_index << "].target_block=" << revoked_block << " sequence=" << sequence << "\n";
994 }
995 }
996 } else if (!pending_tags.empty()) {
997 const auto [target_block, flags] = pending_tags.front();
998 pending_tags.erase(pending_tags.begin());
999 const fs::path block_file = replay_dir /
1000 ("journal_payload_" + std::to_string(block_index) +
1001 "_target_" + std::to_string(target_block) +
1002 "_seq_" + std::to_string(pending_sequence) + ".bin");
1003 std::ofstream bout(block_file, std::ios::binary);
1004 if (bout) {
1005 bout.write(reinterpret_cast<const char*>(block.data()), static_cast<std::streamsize>(block.size()));
1006 ++payload_blocks_exported;
1007 }
1008 info << "payload[" << block_index << "].target_block=" << target_block
1009 << " flags=" << flags << " sequence=" << pending_sequence << "\n";
1010 scan_journal_dirents(block, target_block, pending_sequence, journal_dirents);
1011 }
1012 cursor += block_size_;
1013 remaining -= block_size_;
1014 remaining_total = remaining_total >= block_size_ ? remaining_total - block_size_ : 0;
1015 ++block_index;
1016 if (remaining_total < block_size_) break;
1017 }
1018 if (remaining_total < block_size_) break;
1019 }
1020
1021 info << "journal_inode=" << journal_inode_ << "\n";
1022 info << "journal_size=" << journal_size << "\n";
1023 for (const auto& [type, count] : type_counts) {
1024 info << "type_" << type << "=" << count << "\n";
1025 }
1026 info << "metadata_blocks_exported=" << metadata_blocks_exported << "\n";
1027 info << "payload_blocks_exported=" << payload_blocks_exported << "\n";
1028 info << "replay_overlay_blocks=" << journal_replay_blocks_.size() << "\n";
1029 if (!journal_dirents.empty()) {
1030 std::ofstream dirent_out(journal_root / "recovered_dirents.txt", std::ios::binary);
1031 std::set<uint32_t> exported_candidate_inodes;
1032 const fs::path candidate_dir = journal_root / "candidate_files";
1033 fs::create_directories(candidate_dir);
1034 for (const auto& d : journal_dirents) {
1035 dirent_out << "inode=" << d.inode << " type=" << ext4_file_type_name(d.file_type)
1036 << " name=" << d.name << " target_block=" << d.target_block
1037 << " sequence=" << d.sequence << "\n";
1038 if (d.file_type == 1 && exported_candidate_inodes.insert(d.inode).second) {
1039 if (!inode_marked_allocated(d.inode)) continue;
1040 Ext4Inode cand{};
1041 std::string cand_error;
1042 if (read_inode(d.inode, cand, cand_error) && inode_is_reg(cand)) {
1043 export_file(d.inode, sanitize_path_component(d.name), cand, candidate_dir, recovered, logs, cand_error);
1044 }
1045 }
1046 }
1047 info << "dirent_candidates=" << journal_dirents.size() << "\n";
1048 } else {
1049 info << "dirent_candidates=0\n";
1050 }
1051 logs.push_back(trs("ext4 journal exported") + std::string(": ") + journal_copy.string());
1052 }
void scan_journal_dirents(const std::vector< unsigned char > &block, uint32_t target_block, uint32_t sequence, std::vector< JournalDirentCandidate > &out)
bool export_file(uint32_t inode_no, const std::string &name, const Ext4Inode &inode, const fs::path &out_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)

References anonymous_namespace{ext4_recovery.cpp}::be32(), block_size_, export_file(), ext4_file_type_name(), inode_is_reg(), inode_marked_allocated(), inode_ranges(), inode_size_bytes(), journal_inode_, journal_replay_blocks_, DiskReader::read_at(), read_inode(), reader_, anonymous_namespace{ext4_recovery.cpp}::sanitize_path_component(), scan_journal_dirents(), trs(), and anonymous_namespace{ext4_recovery.cpp}::write_ranges_to_file().

Referenced by recover().

◆ ext4_file_type_name()

std::string anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::ext4_file_type_name ( uint8_t t)
inlinestaticprivate

Definition at line 874 of file ext4_recovery.cpp.

874 {
875 switch (t) {
876 case 1: return "file";
877 case 2: return "dir";
878 case 3: return "char";
879 case 4: return "block";
880 case 5: return "fifo";
881 case 6: return "socket";
882 case 7: return "symlink";
883 default: return "unknown";
884 }
885 }

Referenced by export_journal().

◆ find_inline_xattr_system_data()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::find_inline_xattr_system_data ( const Ext4Inode & inode,
std::vector< unsigned char > & out ) const
inlineprivate

Definition at line 500 of file ext4_recovery.cpp.

501 {
502 out.clear();
503 if (inode.extra_bytes.empty()) return false;
504 const size_t xattr_start_in_extra = inode.extra_isize;
505 if (xattr_start_in_extra + 4 > inode.extra_bytes.size()) return false;
506 const unsigned char* xattr_area = inode.extra_bytes.data() + xattr_start_in_extra;
507 const size_t xattr_area_len = inode.extra_bytes.size() - xattr_start_in_extra;
508 // Magic check: 0xEA020000 little-endian = bytes EA 02 00 00
509 if (xattr_area[0] != 0xEA || xattr_area[1] != 0x02 ||
510 xattr_area[2] != 0x00 || xattr_area[3] != 0x00) return false;
511 // Entries start after the 4-byte magic
512 size_t entry_off = 4;
513 while (entry_off + 16 <= xattr_area_len) {
514 const unsigned char* e = xattr_area + entry_off;
515 const uint8_t e_name_len = e[0];
516 const uint8_t e_name_index = e[1];
517 const uint16_t e_value_offs = le16(e + 2);
518 const uint32_t e_value_inum = le32(e + 4);
519 const uint32_t e_value_size = le32(e + 8);
520 // Zero-name terminator
521 if (e_name_len == 0 && e_name_index == 0 &&
522 e_value_offs == 0 && e_value_inum == 0 &&
523 e_value_size == 0) break;
524 // Name padded to 4-byte boundary
525 const size_t name_padded = (e_name_len + 3u) & ~3u;
526 if (entry_off + 16 + name_padded > xattr_area_len) break;
527 const char* name = reinterpret_cast<const char*>(e + 16);
528 // Match "system.data": index 7, name "data"
529 const bool is_system_data =
530 e_name_index == 7 && e_name_len == 4 &&
531 std::string(name, 4) == "data";
532 if (is_system_data && e_value_inum == 0) {
533 // Value is at e_value_offs from the START of the xattr
534 // area (= where the magic is)
535 if (e_value_offs + e_value_size <= xattr_area_len) {
536 out.assign(xattr_area + e_value_offs,
537 xattr_area + e_value_offs + e_value_size);
538 return true;
539 }
540 return false;
541 }
542 entry_off += 16 + name_padded;
543 }
544 return false;
545 }

References anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::extra_bytes, anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::extra_isize, anonymous_namespace{ext4_recovery.cpp}::le16(), and anonymous_namespace{ext4_recovery.cpp}::le32().

Referenced by export_file().

◆ inode_block_map_classic()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::inode_block_map_classic ( const Ext4Inode & inode,
std::vector< uint64_t > & blocks,
std::string & error )
inlineprivate

Definition at line 642 of file ext4_recovery.cpp.

642 {
643 blocks.clear();
644 const uint64_t size_bytes = inode_size_bytes(inode);
645 const uint64_t needed_blocks = (size_bytes + block_size_ - 1) / block_size_;
646 if (needed_blocks == 0) return true;
647
648 std::array<uint32_t, 15> ptrs{};
649 for (size_t i = 0; i < ptrs.size(); ++i) ptrs[i] = le32(inode.block.data() + i * 4);
650
651 for (size_t i = 0; i < 12 && blocks.size() < needed_blocks; ++i) {
652 if (ptrs[i] != 0) blocks.push_back(ptrs[i]);
653 }
654
655 std::vector<uint32_t> level1;
656 std::vector<uint32_t> level2;
657 std::vector<uint32_t> level3;
658
659 auto append_ptrs = [&](const std::vector<uint32_t>& src) {
660 for (uint32_t block : src) {
661 if (blocks.size() >= needed_blocks) break;
662 if (block != 0) blocks.push_back(block);
663 }
664 };
665
666 if (blocks.size() < needed_blocks && ptrs[12] != 0) {
667 if (!read_u32_block(ptrs[12], level1, error)) return false;
668 append_ptrs(level1);
669 }
670 if (blocks.size() < needed_blocks && ptrs[13] != 0) {
671 if (!read_u32_block(ptrs[13], level1, error)) return false;
672 for (uint32_t indirect : level1) {
673 if (indirect == 0 || blocks.size() >= needed_blocks) continue;
674 if (!read_u32_block(indirect, level2, error)) return false;
675 append_ptrs(level2);
676 }
677 }
678 if (blocks.size() < needed_blocks && ptrs[14] != 0) {
679 if (!read_u32_block(ptrs[14], level1, error)) return false;
680 for (uint32_t doubly : level1) {
681 if (doubly == 0 || blocks.size() >= needed_blocks) continue;
682 if (!read_u32_block(doubly, level2, error)) return false;
683 for (uint32_t indirect : level2) {
684 if (indirect == 0 || blocks.size() >= needed_blocks) continue;
685 if (!read_u32_block(indirect, level3, error)) return false;
686 append_ptrs(level3);
687 }
688 }
689 }
690
691 if (blocks.size() > needed_blocks) blocks.resize(static_cast<size_t>(needed_blocks));
692 return true;
693 }
bool read_u32_block(uint64_t block_no, std::vector< uint32_t > &out, std::string &error)

References anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::block, block_size_, inode_size_bytes(), anonymous_namespace{ext4_recovery.cpp}::le32(), and read_u32_block().

Referenced by inode_ranges().

◆ inode_extents()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::inode_extents ( const Ext4Inode & inode,
std::vector< ExtentRef > & extents,
std::string & error )
inlineprivate

Definition at line 590 of file ext4_recovery.cpp.

590 {
591 if (!inode_uses_extents(inode)) {
592 error = trs("ext4 inode does not use extents");
593 return false;
594 }
595 const unsigned char* p = inode.block.data();
596 const uint16_t magic = le16(p + 0);
597 const uint16_t entries = le16(p + 2);
598 const uint16_t depth = le16(p + 6);
599 if (magic != 0xF30A) {
600 error = trs("Invalid ext4 inode extent header");
601 return false;
602 }
603 p += 12;
604 if (depth == 0) {
605 for (uint16_t i = 0; i < entries; ++i, p += 12) {
606 const uint32_t ee_block = le32(p + 0);
607 const uint16_t ee_len = le16(p + 4);
608 const uint16_t ee_start_hi = le16(p + 6);
609 const uint32_t ee_start_lo = le32(p + 8);
610 const bool uninit = (ee_len & 0x8000u) != 0;
611 const uint32_t count = uninit ? (ee_len & 0x7FFFu) : ee_len;
612 if (count == 0) continue;
613 ExtentRef ref;
614 ref.physical_block = (static_cast<uint64_t>(ee_start_hi) << 32) | ee_start_lo;
615 ref.logical_block = ee_block;
616 ref.block_count = count;
617 ref.uninitialized = uninit;
618 extents.push_back(ref);
619 }
620 return true;
621 }
622 for (uint16_t i = 0; i < entries; ++i, p += 12) {
623 const uint32_t ei_leaf_lo = le32(p + 4);
624 const uint16_t ei_leaf_hi = le16(p + 8);
625 const uint64_t child = (static_cast<uint64_t>(ei_leaf_hi) << 32) | ei_leaf_lo;
626 if (!collect_extents_from_node(child, extents, error)) return false;
627 }
628 return true;
629 }

References anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::block, anonymous_namespace{ext4_recovery.cpp}::ExtentRef::block_count, collect_extents_from_node(), inode_uses_extents(), anonymous_namespace{ext4_recovery.cpp}::le16(), anonymous_namespace{ext4_recovery.cpp}::le32(), anonymous_namespace{ext4_recovery.cpp}::ExtentRef::logical_block, anonymous_namespace{ext4_recovery.cpp}::ExtentRef::physical_block, trs(), and anonymous_namespace{ext4_recovery.cpp}::ExtentRef::uninitialized.

Referenced by inode_ranges().

◆ inode_is_dir()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::inode_is_dir ( const Ext4Inode & inode) const
inlineprivate

Definition at line 474 of file ext4_recovery.cpp.

474{ return (inode.mode & 0xF000) == 0x4000; }

References anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::mode.

Referenced by recover_directory().

◆ inode_is_reg()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::inode_is_reg ( const Ext4Inode & inode) const
inlineprivate

Definition at line 475 of file ext4_recovery.cpp.

475{ return (inode.mode & 0xF000) == 0x8000; }

References anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::mode.

Referenced by export_file(), and export_journal().

◆ inode_marked_allocated()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::inode_marked_allocated ( uint32_t inode_no)
inlineprivate

Definition at line 381 of file ext4_recovery.cpp.

381 {
382 if (inode_no == 0 || inode_no > total_inodes_) return false;
383 const uint32_t group = (inode_no - 1) / inodes_per_group_;
384 const uint32_t index = (inode_no - 1) % inodes_per_group_;
385 std::vector<unsigned char> gd;
386 if (!read_group_desc(group, gd) || gd.size() < 12) return false;
387 uint64_t inode_bitmap_block = le32(gd.data() + 4);
388 if (desc_size_ >= 64 && gd.size() >= 40) {
389 inode_bitmap_block |= static_cast<uint64_t>(le32(gd.data() + 0x24)) << 32;
390 }
391 if (inode_bitmap_block == 0) return false;
392 std::vector<unsigned char> bitmap(static_cast<size_t>(block_size_));
393 if (!read_fs_block(inode_bitmap_block, bitmap.data(), bitmap.size())) return false;
394 const uint32_t byte_index = index / 8;
395 const uint32_t bit_index = index % 8;
396 if (byte_index >= bitmap.size()) return false;
397 return (bitmap[byte_index] & static_cast<unsigned char>(1u << bit_index)) != 0;
398 }
bool read_group_desc(uint32_t group, std::vector< unsigned char > &out)

References block_size_, desc_size_, inodes_per_group_, anonymous_namespace{ext4_recovery.cpp}::le32(), read_fs_block(), read_group_desc(), and total_inodes_.

Referenced by export_journal(), and read_inode().

◆ inode_ranges()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::inode_ranges ( const Ext4Inode & inode,
std::vector< ExportRange > & ranges,
std::string & error )
inlineprivate

Definition at line 695 of file ext4_recovery.cpp.

697 {
698 ranges.clear();
699 if (inode_uses_extents(inode)) {
700 std::vector<ExtentRef> extents;
701 if (!inode_extents(inode, extents, error)) return false;
702 std::sort(extents.begin(), extents.end(),
703 [](const auto& a, const auto& b){
704 return a.logical_block < b.logical_block;
705 });
706 // Walk extents in logical-block order. Fill any GAP between
707 // consecutive extents (= sparse hole) with a zero-fill range
708 // sized exactly hole_blocks * block_size_. Mark
709 // UNINITIALIZED extents as zero-fill (their physical content
710 // is undefined; the kernel returns zeros).
711 uint64_t logical_cursor = 0;
712 for (const auto& e : extents) {
713 if (e.logical_block > logical_cursor) {
714 // Sparse hole between previous extent and this one
715 const uint64_t hole_blocks = e.logical_block - logical_cursor;
716 ExportRange zr;
717 zr.length = hole_blocks * block_size_;
718 zr.zero_fill = true;
719 ranges.push_back(zr);
720 }
721 ExportRange r;
722 r.length = static_cast<uint64_t>(e.block_count) * block_size_;
723 if (e.uninitialized) {
724 r.zero_fill = true;
725 } else {
726 r.offset = block_to_offset(e.physical_block);
727 r.zero_fill = false;
728 }
729 ranges.push_back(r);
730 logical_cursor = e.logical_block + e.block_count;
731 }
732 return true;
733 }
734
735 std::vector<uint64_t> blocks;
736 if (!inode_block_map_classic(inode, blocks, error)) return false;
737 // For classic block map: a zero entry means a sparse hole.
738 for (uint64_t block : blocks) {
739 ExportRange r;
740 r.length = block_size_;
741 if (block == 0) {
742 r.zero_fill = true;
743 } else {
744 r.offset = block_to_offset(block);
745 r.zero_fill = false;
746 }
747 ranges.push_back(r);
748 }
749 return true;
750 }
bool inode_extents(const Ext4Inode &inode, std::vector< ExtentRef > &extents, std::string &error)
bool inode_block_map_classic(const Ext4Inode &inode, std::vector< uint64_t > &blocks, std::string &error)

References block_size_, block_to_offset(), inode_block_map_classic(), inode_extents(), inode_uses_extents(), anonymous_namespace{ext4_recovery.cpp}::ExportRange::length, and anonymous_namespace{ext4_recovery.cpp}::ExportRange::zero_fill.

Referenced by build_journal_replay_map(), export_file(), export_journal(), and recover_directory().

◆ inode_size_bytes()

uint64_t anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::inode_size_bytes ( const Ext4Inode & inode) const
inlineprivate

Definition at line 465 of file ext4_recovery.cpp.

465 {
466 // Bug #12 fix: size_high (i_size_high) is valid for any file/dir when the
467 // huge_file feature is on. The previous code restricted it to regular
468 // files only (mode == 0x8000), which gave wrong sizes for large dirs and
469 // for symlinks/special files >4GB on filesystems with that feature.
470 const uint64_t high = static_cast<uint64_t>(inode.size_high) << 32;
471 return high | inode.size_lo;
472 }

References anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::size_high, and anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::size_lo.

Referenced by build_journal_replay_map(), export_file(), export_journal(), and inode_block_map_classic().

◆ inode_uses_extents()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::inode_uses_extents ( const Ext4Inode & inode) const
inlineprivate

Definition at line 476 of file ext4_recovery.cpp.

476{ return (inode.flags & 0x00080000u) != 0; }

References anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::flags.

Referenced by inode_extents(), and inode_ranges().

◆ load_superblock()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::load_superblock ( std::string & error)
inlineprivate

Definition at line 201 of file ext4_recovery.cpp.

201 {
202 std::array<unsigned char, 1024> super{};
203 if (!reader_.read_at(volume_offset_ + 1024, super.data(), super.size())) {
204 error = trs("Failed to read ext4 superblock");
205 return false;
206 }
207 if (le16(super.data() + 0x38) != 0xEF53) {
208 error = trs("Invalid ext4 superblock magic");
209 return false;
210 }
211
212 const uint32_t log_block_size = le32(super.data() + 0x18);
213 block_size_ = 1024ull << log_block_size;
214 first_data_block_ = le32(super.data() + 0x14);
215 blocks_per_group_ = le32(super.data() + 0x20);
216 inodes_per_group_ = le32(super.data() + 0x28);
217 inode_size_ = le16(super.data() + 0x58);
218 desc_size_ = le16(super.data() + 0xFE);
219 total_inodes_ = le32(super.data() + 0x00);
220 journal_inode_ = le32(super.data() + 0xE0);
221
222 if (inode_size_ < 128) inode_size_ = 128;
223 if (desc_size_ < 32) desc_size_ = 32;
224 if (blocks_per_group_ == 0 || inodes_per_group_ == 0 || block_size_ < 1024) {
225 error = trs("Unsupported ext4 geometry");
226 return false;
227 }
228
229 const uint64_t desc_block = (block_size_ == 1024) ? 2 : 1;
231 return true;
232 }

References block_size_, blocks_per_group_, desc_size_, desc_table_offset_, first_data_block_, inode_size_, inodes_per_group_, journal_inode_, anonymous_namespace{ext4_recovery.cpp}::le16(), anonymous_namespace{ext4_recovery.cpp}::le32(), DiskReader::read_at(), reader_, total_inodes_, trs(), and volume_offset_.

Referenced by recover().

◆ looks_like_printable_name()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::looks_like_printable_name ( const unsigned char * p,
size_t len )
inlinestaticprivate

Definition at line 864 of file ext4_recovery.cpp.

864 {
865 if (len == 0 || len > 255) return false;
866 for (size_t i = 0; i < len; ++i) {
867 unsigned char c = p[i];
868 if (c == 0) return false;
869 if (c < 32 || c == '/' || c == '\\') return false;
870 }
871 return true;
872 }

Referenced by scan_journal_dirents().

◆ read_fs_block()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::read_fs_block ( uint64_t block_no,
unsigned char * buffer,
size_t len )
inlineprivate

Definition at line 238 of file ext4_recovery.cpp.

238 {
239 if (len > block_size_) return false;
240 if (journal_revoked_blocks_.find(block_no) == journal_revoked_blocks_.end()) {
241 auto it = journal_replay_blocks_.find(block_no);
242 if (it != journal_replay_blocks_.end() && it->second.second.size() >= len) {
243 std::copy_n(it->second.second.data(), len, buffer);
244 return true;
245 }
246 }
247 return reader_.read_at(block_to_offset(block_no), buffer, len);
248 }

References block_size_, block_to_offset(), journal_replay_blocks_, journal_revoked_blocks_, DiskReader::read_at(), and reader_.

Referenced by collect_extents_from_node(), inode_marked_allocated(), read_u32_block(), and read_volume_bytes_replayed().

◆ read_group_desc()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::read_group_desc ( uint32_t group,
std::vector< unsigned char > & out )
inlineprivate

Definition at line 375 of file ext4_recovery.cpp.

375 {
376 out.resize(desc_size_);
377 const uint64_t rel = desc_table_offset_ - volume_offset_ + static_cast<uint64_t>(group) * desc_size_;
378 return read_volume_bytes_replayed(rel, out.data(), out.size());
379 }

References desc_size_, desc_table_offset_, read_volume_bytes_replayed(), and volume_offset_.

Referenced by inode_marked_allocated(), and read_inode().

◆ read_inode()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::read_inode ( uint32_t inode_no,
Ext4Inode & inode,
std::string & error )
inlineprivate

Definition at line 400 of file ext4_recovery.cpp.

400 {
401 if (inode_no == 0 || inode_no > total_inodes_) {
402 error = trs("Invalid ext4 inode number");
403 return false;
404 }
405 const uint32_t group = (inode_no - 1) / inodes_per_group_;
406 const uint32_t index = (inode_no - 1) % inodes_per_group_;
407 std::vector<unsigned char> gd;
408 if (!read_group_desc(group, gd)) {
409 error = trs("Failed to read ext4 group descriptor");
410 return false;
411 }
412 uint64_t inode_table_block = le32(gd.data() + 8);
413 if (desc_size_ >= 64) {
414 inode_table_block |= static_cast<uint64_t>(le32(gd.data() + 40)) << 32;
415 }
416 if (inode_table_block == 0) {
417 error = trs("Invalid ext4 inode table pointer");
418 return false;
419 }
420 const uint64_t inode_rel_offset = inode_table_block * block_size_ + static_cast<uint64_t>(index) * inode_size_;
421 std::vector<unsigned char> raw(inode_size_);
422 if (!read_volume_bytes_replayed(inode_rel_offset, raw.data(), raw.size())) {
423 error = trs("Failed to read ext4 inode");
424 return false;
425 }
426 inode.mode = le16(raw.data() + 0x0);
427 inode.uid = le16(raw.data() + 0x2);
428 inode.size_lo = le32(raw.data() + 0x4);
429 inode.atime = le32(raw.data() + 0x8);
430 inode.ctime = le32(raw.data() + 0xC);
431 inode.mtime = le32(raw.data() + 0x10);
432 inode.dtime = le32(raw.data() + 0x14);
433 inode.gid = le16(raw.data() + 0x18);
434 inode.links_count = le16(raw.data() + 0x1A);
435 inode.blocks_lo = le32(raw.data() + 0x1C);
436 inode.flags = le32(raw.data() + 0x20);
437 std::copy(raw.begin() + 0x28, raw.begin() + 0x28 + 60, inode.block.begin());
438 inode.generation = le32(raw.data() + 0x64);
439 inode.file_acl_lo = le32(raw.data() + 0x68);
440 inode.size_high = le32(raw.data() + 0x6C);
441 // i_extra_isize at offset 0x80: tells us how much of the
442 // post-128 area is reserved for extended inode core (vs
443 // available for in-inode xattrs).
444 if (inode_size_ >= 0x82) {
445 inode.extra_isize = le16(raw.data() + 0x80);
446 // Sanity-bound to inode_size - 128 - 4 (we need at least the
447 // xattr magic to fit)
448 if (inode.extra_isize > inode_size_ - 132) {
449 inode.extra_isize = 0;
450 }
451 }
452 // Save the post-128 region for xattr parsing
453 if (inode_size_ > 128) {
454 inode.extra_bytes.assign(raw.begin() + 128, raw.end());
455 }
456 // Bug #19 fix: track allocation status as a flag instead of rejecting.
457 // Recovery of DELETED files MUST read unallocated inodes (dtime != 0).
458 // The previous code aborted on exactly that case, making deleted-file
459 // recovery silently impossible on ext4. Now we report the status and
460 // let the caller decide.
461 inode.allocated = inode_marked_allocated(inode_no);
462 return true;
463 }

References anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::allocated, anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::atime, anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::block, block_size_, anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::blocks_lo, anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::ctime, desc_size_, anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::dtime, anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::extra_bytes, anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::extra_isize, anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::file_acl_lo, anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::flags, anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::generation, anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::gid, inode_marked_allocated(), inode_size_, inodes_per_group_, anonymous_namespace{ext4_recovery.cpp}::le16(), anonymous_namespace{ext4_recovery.cpp}::le32(), anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::links_count, anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::mode, anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::mtime, read_group_desc(), read_volume_bytes_replayed(), anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::size_high, anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::size_lo, total_inodes_, trs(), and anonymous_namespace{ext4_recovery.cpp}::Ext4Inode::uid.

Referenced by build_journal_replay_map(), export_journal(), and recover_directory().

◆ read_u32_block()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::read_u32_block ( uint64_t block_no,
std::vector< uint32_t > & out,
std::string & error )
inlineprivate

Definition at line 631 of file ext4_recovery.cpp.

631 {
632 std::vector<unsigned char> raw(static_cast<size_t>(block_size_));
633 if (!read_fs_block(block_no, raw.data(), raw.size())) {
634 error = trs("Failed to read ext4 indirect block");
635 return false;
636 }
637 out.resize(raw.size() / 4);
638 for (size_t i = 0; i < out.size(); ++i) out[i] = le32(raw.data() + i * 4);
639 return true;
640 }

References block_size_, anonymous_namespace{ext4_recovery.cpp}::le32(), read_fs_block(), and trs().

Referenced by inode_block_map_classic().

◆ read_volume_bytes_replayed()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::read_volume_bytes_replayed ( uint64_t volume_rel_offset,
unsigned char * buffer,
size_t len )
inlineprivate

Definition at line 250 of file ext4_recovery.cpp.

250 {
251 if (len == 0) return true;
252 uint64_t pos = volume_rel_offset;
253 size_t done = 0;
254 while (done < len) {
255 const uint64_t block_no = pos / block_size_;
256 const size_t within = static_cast<size_t>(pos % block_size_);
257 const size_t take = std::min<size_t>(len - done, static_cast<size_t>(block_size_ - within));
258 std::vector<unsigned char> tmp(static_cast<size_t>(block_size_));
259 if (!read_fs_block(block_no, tmp.data(), tmp.size())) return false;
260 std::copy_n(tmp.data() + within, take, buffer + done);
261 pos += take;
262 done += take;
263 }
264 return true;
265 }

References block_size_, and read_fs_block().

Referenced by export_file(), read_group_desc(), read_inode(), and recover_directory().

◆ recover()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::recover ( uint64_t volume_offset,
const std::string & output_dir,
std::vector< RecoveredFileRecord > & recovered,
std::vector< std::string > & logs,
std::string & error )
inline

Definition at line 159 of file ext4_recovery.cpp.

163 {
164 volume_offset_ = volume_offset;
165 if (!load_superblock(error)) return false;
166
167 fs::path out_root = fs::path(output_dir) / "filesystem_recovered" / "ext4" / ("offset_" + std::to_string(volume_offset));
168 fs::create_directories(out_root);
169
170 logs.push_back(trs("ext4 recovery started at offset") + std::string(" ") + std::to_string(volume_offset));
172 logs.push_back(trs("ext4 block size") + std::string(": ") + std::to_string(block_size_) + ", inode size: " + std::to_string(inode_size_));
173
174 std::set<uint32_t> dir_guard;
175 if (!recover_directory(2, out_root, recovered, logs, error, dir_guard, 0)) {
176 return false;
177 }
178
179 export_journal(out_root, recovered, logs);
180 logs.push_back(trs("ext4 recovery completed."));
181 return true;
182 }
bool recover_directory(uint32_t inode_no, const fs::path &out_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error, std::set< uint32_t > &dir_guard, int depth)
void build_journal_replay_map(std::vector< std::string > &logs)
void export_journal(const fs::path &out_root, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs)

References block_size_, build_journal_replay_map(), export_journal(), inode_size_, load_superblock(), recover_directory(), trs(), and volume_offset_.

◆ recover_directory()

bool anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::recover_directory ( uint32_t inode_no,
const fs::path & out_dir,
std::vector< RecoveredFileRecord > & recovered,
std::vector< std::string > & logs,
std::string & error,
std::set< uint32_t > & dir_guard,
int depth )
inlineprivate

Definition at line 1054 of file ext4_recovery.cpp.

1060 {
1061 if (depth > 64) return true;
1062 if (!dir_guard.insert(inode_no).second) return true;
1063
1064 Ext4Inode inode;
1065 if (!read_inode(inode_no, inode, error)) return false;
1066 if (!inode_is_dir(inode)) return true;
1067
1068 std::vector<ExportRange> ranges;
1069 if (!inode_ranges(inode, ranges, error)) {
1070 logs.push_back(trs("Skipped ext4 directory without supported data mapping") + std::string(": inode ") + std::to_string(inode_no));
1071 error.clear();
1072 return true;
1073 }
1074
1075 std::vector<unsigned char> block(static_cast<size_t>(block_size_));
1076 for (const auto& r : ranges) {
1077 const uint64_t offset = r.offset;
1078 const uint64_t length = r.length;
1079 uint64_t cursor = offset;
1080 uint64_t remaining = length;
1081 while (remaining >= block_size_) {
1082 if (!read_volume_bytes_replayed(cursor - volume_offset_, block.data(), block.size())) break;
1083 size_t pos = 0;
1084 while (pos + 8 <= block.size()) {
1085 const uint32_t entry_inode = le32(block.data() + pos + 0);
1086 const uint16_t rec_len = le16(block.data() + pos + 4);
1087 const uint8_t name_len = block[pos + 6];
1088 const uint8_t file_type = block[pos + 7];
1089 if (rec_len < 8 || pos + rec_len > block.size()) break;
1090 if (entry_inode == 0) { pos += rec_len; continue; }
1091 std::string name(reinterpret_cast<const char*>(block.data() + pos + 8),
1092 reinterpret_cast<const char*>(block.data() + pos + 8 + std::min<uint16_t>(name_len, rec_len - 8)));
1093 name = sanitize_path_component(name);
1094 if (name.empty() || name == "." || name == "..") {
1095 pos += rec_len;
1096 continue;
1097 }
1098 if (file_type == 2) {
1099 fs::path subdir = out_dir / name;
1100 fs::create_directories(subdir);
1101 if (!recover_directory(entry_inode, subdir, recovered, logs, error, dir_guard, depth + 1)) return false;
1102 } else if (file_type == 1 || file_type == 7 || file_type == 0) {
1103 Ext4Inode child;
1104 if (read_inode(entry_inode, child, error)) {
1105 if (!export_file(entry_inode, name, child, out_dir, recovered, logs, error)) return false;
1106 } else {
1107 error.clear();
1108 }
1109 }
1110 pos += rec_len;
1111 }
1112 cursor += block_size_;
1113 remaining -= block_size_;
1114 }
1115 }
1116 return true;
1117 }

References block_size_, export_file(), inode_is_dir(), inode_ranges(), anonymous_namespace{ext4_recovery.cpp}::le16(), anonymous_namespace{ext4_recovery.cpp}::le32(), read_inode(), read_volume_bytes_replayed(), recover_directory(), anonymous_namespace{ext4_recovery.cpp}::sanitize_path_component(), trs(), and volume_offset_.

Referenced by recover(), and recover_directory().

◆ scan_journal_dirents()

void anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::scan_journal_dirents ( const std::vector< unsigned char > & block,
uint32_t target_block,
uint32_t sequence,
std::vector< JournalDirentCandidate > & out )
inlineprivate

Definition at line 887 of file ext4_recovery.cpp.

890 {
891 size_t pos = 0;
892 while (pos + 8 <= block.size()) {
893 const uint32_t inode = le32(block.data() + pos + 0);
894 const uint16_t rec_len = le16(block.data() + pos + 4);
895 const uint8_t name_len = block[pos + 6];
896 const uint8_t file_type = block[pos + 7];
897 if (rec_len < 8 || rec_len > block.size() - pos) {
898 ++pos;
899 continue;
900 }
901 if (inode != 0 && name_len > 0 && 8u + name_len <= rec_len && looks_like_printable_name(block.data() + pos + 8, name_len)) {
902 std::string name(reinterpret_cast<const char*>(block.data() + pos + 8), name_len);
903 if (name != "." && name != "..") {
904 out.push_back({inode, rec_len, file_type, name, target_block, sequence});
905 }
906 }
907 pos += rec_len;
908 }
909 }
static bool looks_like_printable_name(const unsigned char *p, size_t len)

References anonymous_namespace{ext4_recovery.cpp}::le16(), anonymous_namespace{ext4_recovery.cpp}::le32(), and looks_like_printable_name().

Referenced by export_journal().

Member Data Documentation

◆ block_size_

◆ blocks_per_group_

uint32_t anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::blocks_per_group_ {0}
private

Definition at line 188 of file ext4_recovery.cpp.

Referenced by load_superblock().

◆ committed_transactions_

size_t anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::committed_transactions_ {0}
private

Definition at line 198 of file ext4_recovery.cpp.

Referenced by build_journal_replay_map().

◆ desc_size_

uint16_t anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::desc_size_ {32}
private

◆ desc_table_offset_

uint64_t anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::desc_table_offset_ {0}
private

Definition at line 191 of file ext4_recovery.cpp.

Referenced by load_superblock(), and read_group_desc().

◆ first_data_block_

uint32_t anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::first_data_block_ {0}
private

Definition at line 193 of file ext4_recovery.cpp.

Referenced by load_superblock().

◆ inode_size_

uint16_t anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::inode_size_ {0}
private

Definition at line 190 of file ext4_recovery.cpp.

Referenced by load_superblock(), read_inode(), and recover().

◆ inodes_per_group_

uint32_t anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::inodes_per_group_ {0}
private

Definition at line 189 of file ext4_recovery.cpp.

Referenced by inode_marked_allocated(), load_superblock(), and read_inode().

◆ journal_inode_

uint32_t anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::journal_inode_ {0}
private

Definition at line 195 of file ext4_recovery.cpp.

Referenced by build_journal_replay_map(), export_journal(), and load_superblock().

◆ journal_replay_blocks_

std::map<uint64_t, std::pair<uint32_t, std::vector<unsigned char> > > anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::journal_replay_blocks_
private

Definition at line 196 of file ext4_recovery.cpp.

Referenced by build_journal_replay_map(), export_journal(), and read_fs_block().

◆ journal_revoked_blocks_

std::set<uint64_t> anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::journal_revoked_blocks_
private

Definition at line 197 of file ext4_recovery.cpp.

Referenced by build_journal_replay_map(), and read_fs_block().

◆ reader_

DiskReader& anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::reader_
private

◆ revoke_records_

size_t anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::revoke_records_ {0}
private

Definition at line 199 of file ext4_recovery.cpp.

Referenced by build_journal_replay_map().

◆ total_inodes_

uint64_t anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::total_inodes_ {0}
private

Definition at line 194 of file ext4_recovery.cpp.

Referenced by inode_marked_allocated(), load_superblock(), and read_inode().

◆ volume_offset_

uint64_t anonymous_namespace{ext4_recovery.cpp}::Ext4RecoveryImpl::volume_offset_ {0}
private

The documentation for this class was generated from the following file: