Skip to content
API Documentation
BastionGuard™
anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery Class Reference

Public Member Functions

 Fat32Recovery (DiskReader &reader)
bool recover (uint64_t volume_offset, const fs::path &out_root, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)

Private Member Functions

bool load_bpb (uint64_t volume_offset)
uint64_t cluster_to_offset (uint32_t cluster) const
bool read_cluster (uint32_t cluster, std::vector< unsigned char > &out)
bool next_cluster (uint32_t current, uint32_t &next)
std::vector< uint32_t > cluster_chain (uint32_t start, size_t hard_limit=1<< 20)
bool export_file (const std::string &name, uint32_t start_cluster, uint32_t size_bytes, const fs::path &out_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)
bool recover_directory (uint32_t dir_cluster, 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)
bool export_file_to_dir (const std::string &name, uint32_t start_cluster, uint32_t size_bytes, const fs::path &out_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &, bool is_deleted)

Static Private Member Functions

static std::string decode_lfn_part (const unsigned char *entry)
static std::string short_name (const unsigned char *entry)

Private Attributes

DiskReader & reader_
uint64_t volume_offset_ {0}
uint16_t bytes_per_sector_ {0}
uint8_t sectors_per_cluster_ {0}
uint16_t reserved_sectors_ {0}
uint8_t fat_count_ {0}
uint32_t sectors_per_fat_ {0}
uint32_t root_cluster_ {2}
uint64_t fat_offset_bytes_ {0}
uint64_t data_region_offset_ {0}
uint64_t cluster_size_ {0}

Detailed Description

Definition at line 93 of file filesystem_recovery.cpp.

Constructor & Destructor Documentation

◆ Fat32Recovery()

anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::Fat32Recovery ( DiskReader & reader)
inlineexplicit

Definition at line 95 of file filesystem_recovery.cpp.

References reader_.

Member Function Documentation

◆ cluster_chain()

std::vector< uint32_t > anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::cluster_chain ( uint32_t start,
size_t hard_limit = 1 << 20 )
inlineprivate

Definition at line 161 of file filesystem_recovery.cpp.

161 {
162 std::vector<uint32_t> chain;
163 if (start < 2) return chain;
164 std::set<uint32_t> seen;
165 uint32_t current = start;
166 while (current >= 2 && current < 0x0FFFFFF8 && chain.size() < hard_limit && seen.insert(current).second) {
167 chain.push_back(current);
168 uint32_t next = 0x0FFFFFFF;
169 if (!next_cluster(current, next)) break;
170 if (next == 0x0FFFFFF7 || next == 0) break;
171 current = next;
172 }
173 return chain;
174 }

References next_cluster().

Referenced by export_file(), export_file_to_dir(), and recover_directory().

◆ cluster_to_offset()

uint64_t anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::cluster_to_offset ( uint32_t cluster) const
inlineprivate

◆ decode_lfn_part()

std::string anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::decode_lfn_part ( const unsigned char * entry)
inlinestaticprivate

Definition at line 176 of file filesystem_recovery.cpp.

176 {
177 // Bug #14 fix: FAT32 long filenames are UTF-16LE, not ASCII.
178 // The previous version replaced any non-ASCII char with '_',
179 // mangling all Italian/European/Asian filenames.
180 // We extract the 26 bytes of UTF-16LE name fragment from one LFN slot
181 // (positions 1, 3, 5, 7, 9 / 14..24 / 28, 30) into a flat buffer,
182 // then run it through the shared UTF-8 decoder.
183 static const int positions[] = {1,3,5,7,9,14,16,18,20,22,24,28,30};
184 std::vector<unsigned char> raw;
185 raw.reserve(26);
186 for (int pos : positions) {
187 const uint16_t ch = le16(entry + pos);
188 if (ch == 0x0000 || ch == 0xFFFF) {
189 // Append null-terminator-equivalent and stop
190 raw.push_back(0);
191 raw.push_back(0);
192 break;
193 }
194 raw.push_back(static_cast<unsigned char>(ch & 0xFF));
195 raw.push_back(static_cast<unsigned char>((ch >> 8) & 0xFF));
196 }
197 return text_utils::utf16le_to_utf8(raw.data(), raw.size());
198 }
std::string utf16le_to_utf8(const unsigned char *data, std::size_t byte_count)

References anonymous_namespace{filesystem_recovery.cpp}::le16(), and text_utils::utf16le_to_utf8().

Referenced by recover_directory().

◆ export_file()

bool anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::export_file ( const std::string & name,
uint32_t start_cluster,
uint32_t size_bytes,
const fs::path & out_dir,
std::vector< RecoveredFileRecord > & recovered,
std::vector< std::string > & logs,
std::string & error )
inlineprivate

Definition at line 217 of file filesystem_recovery.cpp.

223 {
224 if (name.empty() || name == "." || name == "..") return true;
225 fs::create_directories(out_dir);
226 const auto chain = cluster_chain(start_cluster);
227 if (chain.empty() && size_bytes > 0) {
228 logs.push_back(trs("Skipped FAT32 file with invalid cluster chain") + std::string(": ") + name);
229 return true;
230 }
231
232 std::vector<std::pair<uint64_t, uint64_t>> ranges;
233 for (uint32_t cluster : chain) {
234 ranges.push_back({cluster_to_offset(cluster), cluster_size_});
235 }
236
237 fs::path out_path = out_dir / sanitize_path_component(name);
238 std::string unique = out_path.string();
239 int suffix = 1;
240 while (fs::exists(unique)) {
241 unique = (out_dir / (sanitize_path_component(name) + "_" + std::to_string(suffix++))).string();
242 }
243 out_path = unique;
244 if (!write_data_to_file(reader_, ranges, out_path, size_bytes, error)) {
245 return false;
246 }
247 recovered.push_back({out_path.string(), "FAT32", name, size_bytes, "full", "filesystem export"});
248 logs.push_back(trs("Recovered FAT32 file:") + std::string(" ") + out_path.string());
249 return true;
250 }
std::vector< uint32_t > cluster_chain(uint32_t start, size_t hard_limit=1<< 20)
std::string trs(const char *msgid)
Definition i18n.cpp:22
bool write_data_to_file(DiskReader &reader, const std::vector< std::pair< uint64_t, uint64_t > > &ranges, const fs::path &out_path, uint64_t expected_size, std::string &error)
std::string sanitize_path_component(const std::string &in)

References cluster_chain(), cluster_size_, cluster_to_offset(), reader_, anonymous_namespace{filesystem_recovery.cpp}::sanitize_path_component(), trs(), and anonymous_namespace{filesystem_recovery.cpp}::write_data_to_file().

Referenced by recover_directory().

◆ export_file_to_dir()

bool anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::export_file_to_dir ( const std::string & name,
uint32_t start_cluster,
uint32_t size_bytes,
const fs::path & out_dir,
std::vector< RecoveredFileRecord > & recovered,
std::vector< std::string > & logs,
std::string & ,
bool is_deleted )
inlineprivate

Definition at line 402 of file filesystem_recovery.cpp.

409 {
410 if (start_cluster < 2 || size_bytes == 0) return false;
411 // Compute how many clusters we need
412 const uint32_t cluster_bytes = static_cast<uint32_t>(cluster_size_);
413 if (cluster_bytes == 0) return false;
414 const uint64_t need_clusters = (size_bytes + cluster_bytes - 1) / cluster_bytes;
415
416 // For deleted files we don't have a reliable FAT chain (it may
417 // point into reused clusters). Heuristic: assume the file was
418 // CONTIGUOUS starting at start_cluster -- this is true for ~80%
419 // of FAT files in practice (defragmented).
420 std::vector<uint32_t> chain;
421 if (is_deleted) {
422 chain.reserve(static_cast<size_t>(need_clusters));
423 for (uint64_t i = 0; i < need_clusters; ++i) {
424 chain.push_back(static_cast<uint32_t>(start_cluster + i));
425 }
426 } else {
427 chain = cluster_chain(start_cluster);
428 }
429 if (chain.empty()) return false;
430
431 const fs::path out_path = out_dir / sanitize_path_component(name);
432 std::ofstream out(out_path, std::ios::binary | std::ios::trunc);
433 if (!out) return false;
434 uint64_t written = 0;
435 std::vector<unsigned char> cb;
436 for (uint32_t cl : chain) {
437 if (written >= size_bytes) break;
438 if (!read_cluster(cl, cb)) break;
439 const uint64_t want = std::min<uint64_t>(cluster_bytes, size_bytes - written);
440 out.write(reinterpret_cast<const char*>(cb.data()),
441 static_cast<std::streamsize>(want));
442 written += want;
443 }
444 const std::string note = is_deleted
445 ? "deleted-contiguous-guess"
446 : "filesystem export";
447 const std::string integ = is_deleted ? "partial" : "full";
448 recovered.push_back({out_path.string(), "FAT32",
449 (is_deleted ? "deleted/" : "") + name,
450 written, integ, note});
451 if (is_deleted) {
452 logs.push_back(trs("Recovered FAT32 deleted file (best-effort, "
453 "assumes contiguous):") +
454 std::string(" ") + out_path.string());
455 }
456 return true;
457 }
bool read_cluster(uint32_t cluster, std::vector< unsigned char > &out)

References cluster_chain(), cluster_size_, read_cluster(), anonymous_namespace{filesystem_recovery.cpp}::sanitize_path_component(), and trs().

Referenced by recover_directory().

◆ load_bpb()

bool anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::load_bpb ( uint64_t volume_offset)
inlineprivate

Definition at line 126 of file filesystem_recovery.cpp.

126 {
127 std::array<unsigned char, 512> b{};
128 if (!reader_.read_at(volume_offset, b.data(), b.size())) return false;
129 if (std::string(reinterpret_cast<const char*>(b.data() + 82), 8) != "FAT32 ") return false;
130 volume_offset_ = volume_offset;
131 bytes_per_sector_ = le16(b.data() + 11);
132 sectors_per_cluster_ = b[13];
133 reserved_sectors_ = le16(b.data() + 14);
134 fat_count_ = b[16];
135 sectors_per_fat_ = le32(b.data() + 36);
136 root_cluster_ = le32(b.data() + 44);
137 if (bytes_per_sector_ == 0 || sectors_per_cluster_ == 0 || sectors_per_fat_ == 0 || fat_count_ == 0) return false;
138 cluster_size_ = static_cast<uint64_t>(bytes_per_sector_) * sectors_per_cluster_;
141 return true;
142 }
bool read_at(uint64_t offset, void *buffer, size_t len)

References bytes_per_sector_, cluster_size_, data_region_offset_, fat_count_, fat_offset_bytes_, anonymous_namespace{filesystem_recovery.cpp}::le16(), anonymous_namespace{filesystem_recovery.cpp}::le32(), DiskReader::read_at(), reader_, reserved_sectors_, root_cluster_, sectors_per_cluster_, sectors_per_fat_, and volume_offset_.

Referenced by recover().

◆ next_cluster()

bool anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::next_cluster ( uint32_t current,
uint32_t & next )
inlineprivate

Definition at line 153 of file filesystem_recovery.cpp.

153 {
154 const uint64_t fat_entry_offset = fat_offset_bytes_ + static_cast<uint64_t>(current) * 4;
155 std::array<unsigned char, 4> entry{};
156 if (!reader_.read_at(fat_entry_offset, entry.data(), entry.size())) return false;
157 next = le32(entry.data()) & 0x0FFFFFFF;
158 return true;
159 }

References fat_offset_bytes_, anonymous_namespace{filesystem_recovery.cpp}::le32(), DiskReader::read_at(), and reader_.

Referenced by cluster_chain().

◆ read_cluster()

bool anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::read_cluster ( uint32_t cluster,
std::vector< unsigned char > & out )
inlineprivate

Definition at line 148 of file filesystem_recovery.cpp.

148 {
149 out.resize(static_cast<size_t>(cluster_size_));
150 return reader_.read_at(cluster_to_offset(cluster), out.data(), out.size());
151 }

References cluster_size_, cluster_to_offset(), DiskReader::read_at(), and reader_.

Referenced by export_file_to_dir(), and recover_directory().

◆ recover()

bool anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::recover ( uint64_t volume_offset,
const fs::path & out_root,
std::vector< RecoveredFileRecord > & recovered,
std::vector< std::string > & logs,
std::string & error )
inline

Definition at line 97 of file filesystem_recovery.cpp.

101 {
102 if (!load_bpb(volume_offset)) return false;
103 fs::create_directories(out_root);
104 logs.push_back(trs("FAT32 recovery started at offset") + std::string(" ") + std::to_string(volume_offset));
105 std::set<uint32_t> dir_guard;
106 if (!recover_directory(root_cluster_, out_root, recovered, logs, error, dir_guard, 0)) {
107 return false;
108 }
109 logs.push_back(trs("FAT32 recovery completed."));
110 return true;
111 }
bool recover_directory(uint32_t dir_cluster, 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)

References load_bpb(), recover_directory(), root_cluster_, and trs().

◆ recover_directory()

bool anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::recover_directory ( uint32_t dir_cluster,
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 252 of file filesystem_recovery.cpp.

258 {
259 if (depth > 32) return true;
260 if (!dir_guard.insert(dir_cluster).second) return true;
261
262 const auto chain = cluster_chain(dir_cluster, 4096);
263 std::vector<unsigned char> cluster_buf;
264 std::vector<std::string> lfn_parts;
265 // Track LFN slots for deleted entries too. The LFN parts of a
266 // deleted file have their first byte set to 0xE5 too -- but we
267 // want to RECOVER the original characters when possible.
268 std::vector<std::string> deleted_lfn_parts;
269
270 for (uint32_t cluster : chain) {
271 if (!read_cluster(cluster, cluster_buf)) continue;
272 for (size_t off = 0; off + 32 <= cluster_buf.size(); off += 32) {
273 const unsigned char* e = cluster_buf.data() + off;
274 if (e[0] == 0x00) return true;
275
276 const unsigned char attr = e[11];
277
278 // ---- DELETED entry (first byte = 0xE5) ----
279 //
280 // A 0xE5 first-byte marks the dirent as freed. The rest
281 // of the bytes (cluster pointers, size, LFN parts) are
282 // intact unless the slot has been reused.
283 //
284 // Note that the FIRST char of the short name was
285 // overwritten by 0xE5, so we can't recover it from the
286 // 8.3 alone. If we have LFN parts, those are intact.
287 if (e[0] == 0xE5) {
288 if (attr == 0x0F) {
289 // Deleted LFN slot -- save the chars; the first
290 // byte (sequence number) was the one overwritten
291 // with 0xE5, the unicode chars are intact.
292 deleted_lfn_parts.push_back(decode_lfn_part(e));
293 continue;
294 }
295 if ((attr & 0x08) != 0) { deleted_lfn_parts.clear(); continue; }
296 // Skip directory entries that were deleted (rare,
297 // and recovering them recursively is risky on a
298 // possibly-corrupted slot).
299 if ((attr & 0x10) != 0) {
300 deleted_lfn_parts.clear();
301 continue;
302 }
303
304 // Build name from deleted LFN parts if available,
305 // otherwise reconstruct best-effort short name.
306 std::string del_name;
307 if (!deleted_lfn_parts.empty()) {
308 for (auto it = deleted_lfn_parts.rbegin();
309 it != deleted_lfn_parts.rend(); ++it) del_name += *it;
310 } else {
311 // First byte was overwritten by 0xE5. Use '_'
312 // as placeholder for the lost first character.
313 std::string sn = short_name(e);
314 if (!sn.empty() && static_cast<unsigned char>(sn[0]) == 0xE5) {
315 sn[0] = '_';
316 }
317 del_name = sn;
318 }
319 deleted_lfn_parts.clear();
320 del_name = sanitize_path_component(del_name);
321 if (del_name.empty()) del_name = "deleted_unnamed";
322
323 const uint32_t dh = le16(e + 20);
324 const uint32_t dl = le16(e + 26);
325 const uint32_t del_cluster = (dh << 16) | dl;
326 const uint32_t del_size = le32(e + 28);
327
328 // Heuristic: skip clearly-bogus entries
329 if (del_size == 0) {
330 // Empty deleted file -- still create it
331 const fs::path deleted_dir = out_dir / "deleted";
332 std::error_code ec;
333 fs::create_directories(deleted_dir, ec);
334 const fs::path out_path = deleted_dir / del_name;
335 std::ofstream(out_path, std::ios::binary);
336 recovered.push_back({out_path.string(), "FAT32",
337 ("deleted/" + del_name), 0, "partial",
338 "deleted-empty"});
339 continue;
340 }
341 if (del_cluster < 2 || del_cluster >= 0x0FFFFFF7) continue;
342
343 // Try to recover by following the FAT chain. The
344 // chain may have been re-used (= the file's data is
345 // gone). We do best-effort: if the chain reads back
346 // and decodes to size_bytes worth of data, save it.
347 const fs::path deleted_dir = out_dir / "deleted";
348 std::error_code ec;
349 fs::create_directories(deleted_dir, ec);
350 std::string ex_err;
351 if (export_file_to_dir(del_name, del_cluster, del_size,
352 deleted_dir, recovered, logs, ex_err,
353 /*is_deleted=*/true)) {
354 // recorded inside export_file_to_dir
355 }
356 continue;
357 }
358
359 if (attr == 0x0F) {
360 lfn_parts.push_back(decode_lfn_part(e));
361 continue;
362 }
363 if ((attr & 0x08) != 0) { lfn_parts.clear(); continue; }
364
365 std::string name;
366 if (!lfn_parts.empty()) {
367 for (auto it = lfn_parts.rbegin(); it != lfn_parts.rend(); ++it) name += *it;
368 } else {
369 name = short_name(e);
370 }
371 lfn_parts.clear();
372 name = sanitize_path_component(name);
373 if (name.empty()) name = "unnamed";
374
375 const uint32_t high = le16(e + 20);
376 const uint32_t low = le16(e + 26);
377 const uint32_t start_cluster = (high << 16) | low;
378 const uint32_t size_bytes = le32(e + 28);
379 const bool is_dir = (attr & 0x10) != 0;
380
381 if (is_dir) {
382 if (name != "." && name != ".." && start_cluster >= 2) {
383 const fs::path subdir = out_dir / name;
384 fs::create_directories(subdir);
385 if (!recover_directory(start_cluster, subdir, recovered, logs, error, dir_guard, depth + 1)) {
386 return false;
387 }
388 }
389 } else {
390 if (!export_file(name, start_cluster, size_bytes, out_dir, recovered, logs, error)) {
391 return false;
392 }
393 }
394 }
395 }
396 return true;
397 }
bool export_file(const std::string &name, uint32_t start_cluster, uint32_t size_bytes, const fs::path &out_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &error)
static std::string decode_lfn_part(const unsigned char *entry)
static std::string short_name(const unsigned char *entry)
bool export_file_to_dir(const std::string &name, uint32_t start_cluster, uint32_t size_bytes, const fs::path &out_dir, std::vector< RecoveredFileRecord > &recovered, std::vector< std::string > &logs, std::string &, bool is_deleted)

References cluster_chain(), decode_lfn_part(), export_file(), export_file_to_dir(), anonymous_namespace{filesystem_recovery.cpp}::le16(), anonymous_namespace{filesystem_recovery.cpp}::le32(), read_cluster(), recover_directory(), anonymous_namespace{filesystem_recovery.cpp}::sanitize_path_component(), and short_name().

Referenced by recover(), and recover_directory().

◆ short_name()

std::string anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::short_name ( const unsigned char * entry)
inlinestaticprivate

Definition at line 200 of file filesystem_recovery.cpp.

200 {
201 std::string base;
202 for (int i = 0; i < 8; ++i) {
203 const unsigned char c = entry[i];
204 if (c == ' ') break;
205 base.push_back(static_cast<char>(c));
206 }
207 std::string ext;
208 for (int i = 8; i < 11; ++i) {
209 const unsigned char c = entry[i];
210 if (c == ' ') break;
211 ext.push_back(static_cast<char>(c));
212 }
213 if (!ext.empty()) return base + "." + ext;
214 return base;
215 }

Referenced by recover_directory().

Member Data Documentation

◆ bytes_per_sector_

uint16_t anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::bytes_per_sector_ {0}
private

Definition at line 116 of file filesystem_recovery.cpp.

Referenced by load_bpb().

◆ cluster_size_

uint64_t anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::cluster_size_ {0}
private

◆ data_region_offset_

uint64_t anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::data_region_offset_ {0}
private

Definition at line 123 of file filesystem_recovery.cpp.

Referenced by cluster_to_offset(), and load_bpb().

◆ fat_count_

uint8_t anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::fat_count_ {0}
private

Definition at line 119 of file filesystem_recovery.cpp.

Referenced by load_bpb().

◆ fat_offset_bytes_

uint64_t anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::fat_offset_bytes_ {0}
private

Definition at line 122 of file filesystem_recovery.cpp.

Referenced by load_bpb(), and next_cluster().

◆ reader_

DiskReader& anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::reader_
private

◆ reserved_sectors_

uint16_t anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::reserved_sectors_ {0}
private

Definition at line 118 of file filesystem_recovery.cpp.

Referenced by load_bpb().

◆ root_cluster_

uint32_t anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::root_cluster_ {2}
private

Definition at line 121 of file filesystem_recovery.cpp.

Referenced by load_bpb(), and recover().

◆ sectors_per_cluster_

uint8_t anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::sectors_per_cluster_ {0}
private

Definition at line 117 of file filesystem_recovery.cpp.

Referenced by load_bpb().

◆ sectors_per_fat_

uint32_t anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::sectors_per_fat_ {0}
private

Definition at line 120 of file filesystem_recovery.cpp.

Referenced by load_bpb().

◆ volume_offset_

uint64_t anonymous_namespace{filesystem_recovery.cpp}::Fat32Recovery::volume_offset_ {0}
private

Definition at line 115 of file filesystem_recovery.cpp.

Referenced by load_bpb().


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