mirror of
https://gitea.tendokyu.moe/beerpsi/fsdecrypt.git
synced 2026-10-05 13:18:06 +03:00
fix: skip NTFS DOS (8.3) short-name entries when extracting
When a file or directory has a separate 8.3 short name, it appears in the NTFS directory index twice: once under its Win32 long name and once under the Dos short name. The extractor iterated all index entries, so every such item was written a second time under its mangled NAME~1.EXT alias — and short-named directories had their entire subtree re-extracted. Skip index entries in the Dos namespace (the file is still extracted via its Win32 entry). Add unit tests for the skip decision. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
8e3d7a6d90
commit
cd02c09ad0
+46
-3
@@ -7,7 +7,10 @@ use std::{
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use indicatif::{ProgressBar, ProgressStyle};
|
||||
use ntfs::{structured_values::NtfsStandardInformation, Ntfs, NtfsAttributeType, NtfsTime};
|
||||
use ntfs::{
|
||||
structured_values::{NtfsFileNamespace, NtfsStandardInformation},
|
||||
Ntfs, NtfsAttributeType, NtfsTime,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Constants
|
||||
@@ -563,6 +566,17 @@ fn is_ntfs_system_entry(name: &str) -> bool {
|
||||
name.starts_with('$') || name == "." || name == ".." || name == "System Volume Information"
|
||||
}
|
||||
|
||||
/// Whether a directory-index entry should be skipped when extracting.
|
||||
///
|
||||
/// Besides NTFS system metadata, this skips DOS (8.3) short-name aliases: a file
|
||||
/// that has a separate short name appears in the index *twice* — once with its
|
||||
/// Win32 long name and once with the `Dos` short name. Without this, every such
|
||||
/// entry is extracted a second time under its mangled `NAME~1.EXT` name (and
|
||||
/// short-named directories get their whole subtree duplicated).
|
||||
fn skip_index_entry(namespace: NtfsFileNamespace, name: &str) -> bool {
|
||||
namespace == NtfsFileNamespace::Dos || is_ntfs_system_entry(name)
|
||||
}
|
||||
|
||||
fn ntfs_time_to_system_time(t: NtfsTime) -> SystemTime {
|
||||
let nanos = (t.nt_timestamp() - WINDOWS_EPOCH_OFFSET) * 100;
|
||||
SystemTime::UNIX_EPOCH + Duration::from_nanos(nanos)
|
||||
@@ -602,7 +616,7 @@ fn extract_ntfs_dir<T: Read + Seek>(
|
||||
let entry = entry?;
|
||||
let key = entry.key().ok_or_else(|| anyhow!("missing key"))??;
|
||||
let name = key.name().to_string_lossy();
|
||||
if is_ntfs_system_entry(&name) {
|
||||
if skip_index_entry(key.namespace(), &name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -649,7 +663,7 @@ fn calculate_ntfs_size<T: Read + Seek>(
|
||||
while let Some(entry) = iter.next(fs) {
|
||||
let entry = entry?;
|
||||
let key = entry.key().ok_or_else(|| anyhow!("missing key"))??;
|
||||
if is_ntfs_system_entry(&key.name().to_string_lossy().as_ref()) {
|
||||
if skip_index_entry(key.namespace(), key.name().to_string_lossy().as_ref()) {
|
||||
continue;
|
||||
}
|
||||
let file = entry.to_file(ntfs, fs)?;
|
||||
@@ -748,3 +762,32 @@ fn read_be_u32(buf: &[u8], offset: usize) -> u32 {
|
||||
fn read_be_u64(buf: &[u8], offset: usize) -> u64 {
|
||||
u64::from_be_bytes(buf[offset..offset + 8].try_into().unwrap())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::skip_index_entry;
|
||||
use ntfs::structured_values::NtfsFileNamespace;
|
||||
|
||||
#[test]
|
||||
fn skips_dos_short_name_aliases() {
|
||||
// 8.3 aliases duplicate a Win32 entry and must not be extracted again.
|
||||
assert!(skip_index_entry(NtfsFileNamespace::Dos, "OXGETH~1.EXE"));
|
||||
assert!(skip_index_entry(NtfsFileNamespace::Dos, "PROGRA~1"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keeps_long_and_native_names() {
|
||||
assert!(!skip_index_entry(NtfsFileNamespace::Win32, "oxGetHwInfo.exe"));
|
||||
// A name that is its own short name (no separate Dos entry) is kept.
|
||||
assert!(!skip_index_entry(NtfsFileNamespace::Win32AndDos, "game.bat"));
|
||||
assert!(!skip_index_entry(NtfsFileNamespace::Posix, "readme"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn skips_system_entries_regardless_of_namespace() {
|
||||
assert!(skip_index_entry(NtfsFileNamespace::Win32, "$MFT"));
|
||||
assert!(skip_index_entry(NtfsFileNamespace::Win32, "System Volume Information"));
|
||||
assert!(skip_index_entry(NtfsFileNamespace::Win32, "."));
|
||||
assert!(skip_index_entry(NtfsFileNamespace::Win32, ".."));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user