Skip to content

Commit 99e93c0

Browse files
authored
implemented simple file recovery (#8)
1 parent 4745a1e commit 99e93c0

File tree

5 files changed

+47
-9
lines changed

5 files changed

+47
-9
lines changed

Diff for: src/cli.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,12 @@ impl CliParsed {
6161

6262
pub fn parse_and_validate(args: Cli) -> Result<Self, UndeleteError> {
6363
if args.volume.is_none() && args.image.is_none() {
64-
return Err(UndeleteError::ParseError(
64+
return Err(UndeleteError::Parse(
6565
"Either volume or image must be specified".to_string(),
6666
));
6767
}
6868
if args.volume.is_some() && args.image.is_some() {
69-
return Err(UndeleteError::ParseError(
69+
return Err(UndeleteError::Parse(
7070
"Only one of volume or image must be specified".to_string(),
7171
));
7272
}

Diff for: src/errors.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#[derive(Debug)]
22
pub enum UndeleteError {
3-
ParseError(String),
4-
InitializationError(String),
3+
Parse(String),
4+
Initialization(String),
5+
Write(String),
56
}

Diff for: src/main.rs

+23-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::path::PathBuf;
2+
13
use clap::Parser;
24
use cli::Cli;
35
use errors::UndeleteError;
@@ -24,8 +26,27 @@ fn main() -> Result<(), UndeleteError> {
2426
}
2527
};
2628

27-
for entry in parser.get_all_entry_names() {
28-
info!("Entry: {:?}", entry);
29+
let message = match args.dry_run {
30+
true => "Dry run mode enabled, no files will be written to disk",
31+
false => "Dry run mode disabled, files will be written to disk",
32+
};
33+
info!("{}", message);
34+
35+
for entry in parser.get_all_entries().iter().filter(|e| !e.is_allocated) {
36+
let calculated_path = PathBuf::from(&args.output_dir).join(&entry.filename);
37+
info!(
38+
"Found deleted entry '{}' with {}B of data, will write it to {}",
39+
entry.filename,
40+
entry.data.len(),
41+
calculated_path.display()
42+
);
43+
if !args.dry_run {
44+
match std::fs::write(calculated_path.clone(), &entry.data) {
45+
Ok(_) => info!("Successfully wrote '{}' to disk", calculated_path.display()),
46+
Err(e) => return Err(UndeleteError::Write(e.to_string())),
47+
}
48+
}
2949
}
50+
3051
Ok(())
3152
}

Diff for: src/parser_wrapper.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ impl ParserWrapper {
1313
pub fn new(path: PathBuf) -> Result<Self, UndeleteError> {
1414
let parser = match MftParser::from_path(path) {
1515
Ok(p) => p,
16-
Err(e) => return Err(UndeleteError::InitializationError(e.to_string())),
16+
Err(e) => return Err(UndeleteError::Initialization(e.to_string())),
1717
};
1818
Ok(Self { parser })
1919
}
@@ -80,7 +80,7 @@ impl ParserWrapper {
8080
}
8181
}
8282

83-
pub fn get_all_entry_names(&mut self) -> Vec<UndeleteFileEntry> {
83+
pub fn get_all_entries(&mut self) -> Vec<UndeleteFileEntry> {
8484
let found = self
8585
.parser
8686
.iter_entries()
@@ -123,7 +123,7 @@ mod tests {
123123
fn parser_get_entry_names() {
124124
let entries = ParserWrapper::new(PathBuf::from("test_data/MFT_TEST"))
125125
.unwrap()
126-
.get_all_entry_names();
126+
.get_all_entries();
127127

128128
assert!(!entries.is_empty());
129129
}

Diff for: src/undelete_entry.rs

+16
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub struct UndeleteFileEntry {
88
pub filename: String,
99
pub is_allocated: bool,
1010
pub record_number: u64,
11+
pub data: Vec<u8>,
1112
}
1213

1314
impl From<MftEntry> for UndeleteFileEntry {
@@ -23,13 +24,28 @@ impl From<MftEntry> for UndeleteFileEntry {
2324
})
2425
.next();
2526

27+
let data = value
28+
.iter_attributes_matching(Some(vec![MftAttributeType::DATA]))
29+
.filter_map(|attr| match attr {
30+
Ok(attribute) => match attribute.data {
31+
MftAttributeContent::AttrX80(data) => Some(data),
32+
_ => None,
33+
},
34+
Err(_) => None,
35+
})
36+
.next();
37+
2638
UndeleteFileEntry {
2739
filename: match file_attribute {
2840
Some(attr) => attr.name,
2941
None => "".to_string(),
3042
},
3143
is_allocated: value.is_allocated(),
3244
record_number: value.header.record_number,
45+
data: match data {
46+
Some(d) => d.data().to_vec(),
47+
None => vec![],
48+
},
3349
}
3450
}
3551
}

0 commit comments

Comments
 (0)