mod sector_reader;
use anyhow::{bail, Context, Result};
use ntfs::indexes::NtfsFileNameIndex;
use ntfs::structured_values::{
NtfsFileName, NtfsFileNamespace,
};
use ntfs::{Ntfs, NtfsFile, NtfsReadSeek};
use sector_reader::SectorReader;
use std::collections::VecDeque;
use std::fs::{File, OpenOptions};
use std::io::{BufReader, Read, Seek, Write};
use std::path::{Component, PathBuf};
use std::{path};
struct CommandInfo<'n, T>
where
T: Read + Seek,
{
current_directory: Vec<NtfsFile<'n>>,
current_directory_string: String,
fs: T,
ntfs: &'n Ntfs,
}
pub fn rawcopy(file_path: &str, save_path: &str) -> Result<()> {
let path = path::PathBuf::from(file_path);
let mut components: VecDeque<_> = path
.components()
.filter_map(|comp| {
if Component::RootDir != comp {
Some(comp.as_os_str().to_str().unwrap())
} else {
None
}
})
.collect();
let prefix = components.pop_front().unwrap();
let file = components.pop_back().unwrap();
let prefix = format!(r"\\.\{prefix}");
let f = File::open(prefix)?;
let sr = SectorReader::new(f, 4096)?;
let mut fs = BufReader::new(sr);
let mut ntfs = Ntfs::new(&mut fs)?;
ntfs.read_upcase_table(&mut fs)?;
let current_directory = vec![ntfs.root_directory(&mut fs)?];
let mut info = CommandInfo {
current_directory,
current_directory_string: String::new(),
fs,
ntfs: &ntfs,
};
for ele in components {
cd(ele, &mut info)?;
}
get(file, save_path, &mut info)?;
Ok(())
}
fn best_file_name<T>(
info: &mut CommandInfo<T>,
file: &NtfsFile,
parent_record_number: u64,
) -> Result<NtfsFileName>
where
T: Read + Seek,
{
let priority = [
Some(NtfsFileNamespace::Win32),
Some(NtfsFileNamespace::Win32AndDos),
None,
];
for match_namespace in priority {
if let Some(file_name) =
file.name(&mut info.fs, match_namespace, Some(parent_record_number))
{
let file_name = file_name?;
return Ok(file_name);
}
}
bail!(
"Found no FileName attribute for File Record {:#x}",
file.file_record_number()
)
}
fn cd<T>(arg: &str, info: &mut CommandInfo<T>) -> Result<()>
where
T: Read + Seek,
{
if arg.is_empty() {
return Ok(());
}
if arg == ".." {
if info.current_directory_string.is_empty() {
return Ok(());
}
info.current_directory.pop();
let new_len = info.current_directory_string.rfind('\\').unwrap_or(0);
info.current_directory_string.truncate(new_len);
} else {
let index = info
.current_directory
.last()
.unwrap()
.directory_index(&mut info.fs)?;
let mut finder = index.finder();
let maybe_entry = NtfsFileNameIndex::find(&mut finder, info.ntfs, &mut info.fs, arg);
if maybe_entry.is_none() {
println!("Cannot find subdirectory \"{arg}\".");
return Ok(());
}
let entry = maybe_entry.unwrap()?;
let file_name = entry
.key()
.expect("key must exist for a found Index Entry")?;
if !file_name.is_directory() {
println!("\"{arg}\" is not a directory.");
return Ok(());
}
let file = entry.to_file(info.ntfs, &mut info.fs)?;
let file_name = best_file_name(
info,
&file,
info.current_directory.last().unwrap().file_record_number(),
)?;
if !info.current_directory_string.is_empty() {
info.current_directory_string += "\\";
}
info.current_directory_string += &file_name.name().to_string_lossy();
info.current_directory.push(file);
}
Ok(())
}
fn get<T>(file: &str, save_path: &str, info: &mut CommandInfo<T>) -> Result<()>
where
T: Read + Seek,
{
let (file_name, data_stream_name) = match file.find(':') {
Some(mid) => (&file[..mid], &file[mid + 1..]),
None => (file, ""),
};
let output_file_name = if data_stream_name.is_empty() {
file_name.to_string()
} else {
format!("{file_name}_{data_stream_name}")
};
let output_file_path = [save_path, &output_file_name].iter().collect::<PathBuf>();
let mut output_file = OpenOptions::new()
.write(true)
.create_new(true)
.open(&output_file_path)
.with_context(|| format!("Tried to open \"{output_file_name}\" for writing"))?;
let file = parse_file_arg(file_name, info)?;
let data_item = match file.data(&mut info.fs, data_stream_name) {
Some(data_item) => data_item,
None => {
println!("The file does not have a \"{data_stream_name}\" $DATA attribute.");
return Ok(());
}
};
let data_item = data_item?;
let data_attribute = data_item.to_attribute()?;
let mut data_value = data_attribute.value(&mut info.fs)?;
println!(
"Saving {} bytes of data in \"{}\"...",
data_value.len(),
output_file_name
);
let mut buf = [0u8; 4096];
loop {
let bytes_read = data_value.read(&mut info.fs, &mut buf)?;
if bytes_read == 0 {
break;
}
output_file.write_all(&buf[..bytes_read])?;
}
println!("Done! save to {}", &output_file_path.to_str().unwrap());
Ok(())
}
#[allow(clippy::from_str_radix_10)]
fn parse_file_arg<'n, T>(arg: &str, info: &mut CommandInfo<'n, T>) -> Result<NtfsFile<'n>>
where
T: Read + Seek,
{
if arg.is_empty() {
bail!("Missing argument!");
}
if let Some(record_number_arg) = arg.strip_prefix('/') {
let record_number = match record_number_arg.strip_prefix("0x") {
Some(hex_record_number_arg) => u64::from_str_radix(hex_record_number_arg, 16),
None => u64::from_str_radix(record_number_arg, 10),
};
if let Ok(record_number) = record_number {
let file = info.ntfs.file(&mut info.fs, record_number)?;
Ok(file)
} else {
bail!(
"Cannot parse record number argument \"{}\"",
record_number_arg
)
}
} else {
let index = info
.current_directory
.last()
.unwrap()
.directory_index(&mut info.fs)?;
let mut finder = index.finder();
if let Some(entry) = NtfsFileNameIndex::find(&mut finder, info.ntfs, &mut info.fs, arg) {
let entry = entry?;
let file = entry.to_file(info.ntfs, &mut info.fs)?;
Ok(file)
} else {
bail!("No such file or directory \"{}\".", arg)
}
}
}