Skip to content
API Documentation
BastionGuard™
carving Namespace Reference

Namespaces

namespace  anonymous_namespace{carving.cpp}

Classes

struct  CarvingOptions

Functions

bool run_carving (DiskReader &reader, const std::string &output_dir, const CarvingOptions &opts, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)

Function Documentation

◆ run_carving()

bool carving::run_carving ( DiskReader & reader,
const std::string & output_dir,
const CarvingOptions & opts,
std::vector< RecoveredFileRecord > & recovered,
std::vector< std::string > & logs,
std::string & error )

Definition at line 954 of file carving.cpp.

959 {
960 const uint64_t disk_size = reader.size();
961 if (disk_size == 0) {
962 error = "Carving: disk size is zero";
963 return false;
964 }
965 const uint64_t scan_limit = opts.max_bytes_to_scan == 0
966 ? disk_size
967 : std::min<uint64_t>(disk_size,
968 opts.max_bytes_to_scan);
969
970 fs::path out_root = fs::path(output_dir) / "carved";
971 {
972 std::error_code ec;
973 fs::create_directories(out_root, ec);
974 }
975
976 auto formats = build_formats();
977 // Compute the longest magic to determine sliding overlap
978 size_t longest_magic = 0;
979 for (const auto& f : formats) {
980 const size_t needed = f.magic.size() + f.magic_offset_in_file;
981 if (needed > longest_magic) longest_magic = needed;
982 }
983 // Window size: 16 MiB main + overlap of `longest_magic` for boundary
984 constexpr size_t MAIN_WINDOW = 16 * 1024 * 1024;
985 const size_t overlap = longest_magic;
986 std::vector<unsigned char> window(MAIN_WINDOW + overlap);
987
988 size_t carved_count = 0;
989 uint64_t pos = 0;
990 int ident_counter = 0;
991
992 auto peek_more = [&](uint64_t abs_off, unsigned char* dst,
993 size_t want) -> size_t {
994 if (abs_off >= disk_size) return 0;
995 const size_t can = static_cast<size_t>(
996 std::min<uint64_t>(want, disk_size - abs_off));
997 return reader.read_at(abs_off, dst, can) ? can : 0;
998 };
999
1000 logs.push_back(trs("Carving: starting scan up to ") +
1001 std::to_string(scan_limit / (1024 * 1024)) + " MiB");
1002
1003 while (pos < scan_limit) {
1004 const size_t want = static_cast<size_t>(
1005 std::min<uint64_t>(window.size(), scan_limit - pos));
1006 if (!reader.read_at(pos, window.data(), want)) {
1007 logs.push_back(trs("Carving: read failure at offset ") +
1008 std::to_string(pos) + trs(", skipping ahead"));
1009 pos += MAIN_WINDOW;
1010 continue;
1011 }
1012 // Scan only the main portion; the overlap will be re-scanned
1013 // at the start of the next window.
1014 const size_t scan_end = (want > overlap) ? (want - overlap) : want;
1015 // For the final window (we cannot read more) scan everything
1016 const bool last = (pos + want >= scan_limit);
1017 const size_t actual_end = last ? want : scan_end;
1018
1019 for (size_t i = 0; i < actual_end; ++i) {
1020 for (const auto& f : formats) {
1021 const size_t mlen = f.magic.size();
1022 if (i + mlen > want) continue;
1023 if (std::memcmp(window.data() + i, f.magic.data(), mlen) != 0)
1024 continue;
1025 // Magic found. Compute the file's start position.
1026 if (i < f.magic_offset_in_file) continue;
1027 const size_t file_local_start = i - f.magic_offset_in_file;
1028 const uint64_t file_disk_off = pos + file_local_start;
1029
1030 // Pass the rest of the window to the detector
1031 const size_t window_remaining = want - file_local_start;
1032
1033 DetectResult dr = f.detect(
1034 window.data() + file_local_start,
1035 window_remaining, file_disk_off,
1036 opts.max_file_size,
1037 const_cast<PeekMore&>(static_cast<const PeekMore&>(peek_more))
1038 );
1039
1040 if (!dr.ok) continue;
1041 if (dr.size == 0 || dr.size > opts.max_file_size + 1) continue;
1042
1043 // Write the carved file
1044 fs::path out_dir = out_root / f.name;
1045 {
1046 std::error_code ec;
1047 fs::create_directories(out_dir, ec);
1048 }
1049 std::ostringstream nm;
1050 nm << std::hex << file_disk_off << "_" << ident_counter
1051 << "." << f.extension;
1052 ++ident_counter;
1053 fs::path out_path = out_dir / nm.str();
1054
1055 std::ofstream out(out_path, std::ios::binary | std::ios::trunc);
1056 if (!out) continue;
1057 // Write in chunks
1058 constexpr size_t WBUF = 1024 * 1024;
1059 std::vector<unsigned char> wbuf(WBUF);
1060 uint64_t written = 0;
1061 while (written < dr.size) {
1062 const size_t step = static_cast<size_t>(
1063 std::min<uint64_t>(WBUF, dr.size - written));
1064 if (!reader.read_at(file_disk_off + written,
1065 wbuf.data(), step)) break;
1066 out.write(reinterpret_cast<const char*>(wbuf.data()),
1067 static_cast<std::streamsize>(step));
1068 written += step;
1069 }
1070 out.close();
1071
1072 if (written > 0) {
1073 std::string note = "carved-";
1074 note += f.name;
1075 if (dr.size_is_capped) note += "-capped";
1076 recovered.push_back({
1077 out_path.string(), "carving",
1078 ("carved/" + std::string(f.name) + "/" + nm.str()),
1079 written,
1080 dr.size_is_capped ? "partial" : "full",
1081 note});
1082 ++carved_count;
1083 }
1084 // After a successful carve, advance i past the file's
1085 // payload (best-effort; if the user has overlapping
1086 // content we may miss some files but this is much faster
1087 // than re-scanning every byte).
1088 const uint64_t skip_until = file_disk_off + dr.size;
1089 if (skip_until > pos + i) {
1090 const uint64_t skip = skip_until - (pos + i);
1091 if (skip > 0 && skip < want - i) {
1092 i += static_cast<size_t>(skip - 1); // -1 because the for-loop ++
1093 }
1094 }
1095 break; // do not match this offset against more formats
1096 }
1097 }
1098 pos += MAIN_WINDOW;
1099 }
1100
1101 {
1102 std::ostringstream m;
1103 m << trs("Carving: completed. Files recovered: ") << carved_count;
1104 logs.push_back(m.str());
1105 }
1106 return carved_count > 0;
1107}
bool read_at(uint64_t offset, void *buffer, size_t len)
uint64_t size() const
std::string trs(const char *msgid)
Definition i18n.cpp:22
std::function< size_t(uint64_t abs_off, unsigned char *dst, size_t want)> PeekMore
Definition carving.cpp:77
std::vector< CarverFormat > build_formats()
Definition carving.cpp:899

References carving::CarvingOptions::max_bytes_to_scan, carving::CarvingOptions::max_file_size, DiskReader::read_at(), run_carving(), DiskReader::size(), and trs().

Referenced by FilesystemRecovery::recover_supported_filesystems(), and run_carving().