Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/windirstat/mft.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOmer BenAmram <omerbenamram@gmail.com>2020-01-06 23:12:42 +0300
committerGitHub <noreply@github.com>2020-01-06 23:12:42 +0300
commit6f8256e8f4fbc1f11951577712d1595ffaabad44 (patch)
tree36e4f85209ca27b447f188a660e98731d1cb96b8
parent8fc54c4abcdad8ccca1459dd9557996512d49557 (diff)
parent678d4df9663d4bfdd119e0cbf35f556b4414dec3 (diff)
Merge pull request #35 from omerbenamram/housekeeping
Housekeeping
-rw-r--r--CHANGELOG.md7
-rw-r--r--Cargo.toml54
-rw-r--r--src/attribute/header.rs12
-rw-r--r--src/attribute/raw.rs5
-rw-r--r--src/attribute/x10.rs11
-rw-r--r--src/attribute/x20.rs7
-rw-r--r--src/attribute/x30.rs19
-rw-r--r--src/attribute/x40.rs11
-rw-r--r--src/attribute/x80.rs7
-rw-r--r--src/bin/mft_dump.rs103
-rw-r--r--src/csv.rs21
-rw-r--r--src/entry.rs58
-rw-r--r--src/err.rs62
-rw-r--r--src/mft.rs7
-rw-r--r--src/utils.rs3
-rw-r--r--tests/fixtures.rs4
-rw-r--r--tests/test_cli_interactive.rs149
17 files changed, 256 insertions, 284 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 42b4e09..1fb0869 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [0.5.0] - 2020-01-06
+
+### Changed
+- Bumped dependencies.
+- `mft_dump` is now an optional features so consumers of the library can enjoy faster compilation time.
+- Changed error handling to `anyhow` + `thiserror` from `snafu`.
+
## [0.4.4] - 2019-06-19
### Fixed
diff --git a/Cargo.toml b/Cargo.toml
index 7c0070a..79463e9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,48 +11,54 @@ authors = ["Omer Ben-Amram <omerbenamram@gmail.com>"]
edition = "2018"
[dependencies]
-log = {version = "^0.4", features=["release_max_level_debug"]}
-clap = "2.33.0"
-encoding = "0.2.33"
-byteorder = "1.3.1"
-bitflags = "1.0.4"
-serde = {version = "1.0.91", features = ["derive"]}
-serde_json = "1.0.39"
-csv = "1.0.7"
-snafu = {version="0.5.0", features = ["backtraces", "rust_1_30"]}
+log = { version = "0.4", features = ["release_max_level_debug"] }
+encoding = "0.2"
+byteorder = "1.3"
+bitflags = "1.2"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = "1.0"
+csv = "1.1"
+thiserror = "1.0"
num-traits = "0.2"
num-derive = "0.3"
-winstructs = "0.2.0"
+winstructs = "0.3.0"
lru = "0.4.3"
-itertools = "0.8.0"
-rand = "0.7.0"
+itertools = "0.8"
+rand = "0.7"
# `mft_dump` dependencies
-simplelog = "0.7.1"
-dialoguer = "0.4.0"
-indoc = "0.3"
+clap = {version = "2.33.0", optional = true}
+anyhow = {version = "1.0", optional = true}
+simplelog = {version = "0.7.4", optional = true}
+dialoguer = {version = "0.5.0", optional = true}
+indoc = {version = "0.3.4", optional = true}
+
+[features]
+default = ["mft_dump"]
+mft_dump = ["anyhow", "simplelog", "dialoguer", "indoc", "clap"]
[dependencies.chrono]
-version = "0.4.6"
+version = "0.4.10"
features = ["serde"]
[dev-dependencies]
-criterion = "0.3"
-skeptic = "0.13"
-assert_cmd = "0.12"
-predicates = "1"
-env_logger = "0.7.0"
-tempfile = "3"
+criterion = "0.3.0"
+skeptic = "0.13.4"
+assert_cmd = "0.12.0"
+predicates = "1.0.2"
+env_logger = "0.7.1"
+tempfile = "3.1.0"
# rexpect relies on unix process semantics, but it's only used for process interaction tests.
[target.'cfg(not(target_os = "windows"))'.dev-dependencies]
-rexpect = "0.3"
+rexpect = "0.3.0"
[build-dependencies]
-skeptic = "0.13"
+skeptic = "0.13.4"
[[bin]]
name = "mft_dump"
+required-features = ["mft_dump"]
[[bench]]
name = "benchmark"
diff --git a/src/attribute/header.rs b/src/attribute/header.rs
index df19697..ac7cf44 100644
--- a/src/attribute/header.rs
+++ b/src/attribute/header.rs
@@ -1,5 +1,5 @@
use crate::attribute::{AttributeDataFlags, MftAttributeType};
-use crate::err::{self, Result};
+use crate::err::{Error, Result};
use crate::utils::read_utf16_string;
use crate::ReadSeek;
@@ -54,10 +54,9 @@ impl MftAttributeHeader {
let type_code = match MftAttributeType::from_u32(type_code_value) {
Some(attribute_type) => attribute_type,
None => {
- return err::UnknownAttributeType {
+ return Err(Error::UnknownAttributeType {
attribute_type: type_code_value,
- }
- .fail()
+ })
}
};
@@ -81,11 +80,10 @@ impl MftAttributeHeader {
0 => ResidentialHeader::Resident(ResidentHeader::from_stream(stream)?),
1 => ResidentialHeader::NonResident(NonResidentHeader::from_stream(stream)?),
_ => {
- return err::UnhandledResidentFlag {
+ return Err(Error::UnhandledResidentFlag {
flag: resident_flag,
offset: stream.tell()?,
- }
- .fail();
+ })
}
};
diff --git a/src/attribute/raw.rs b/src/attribute/raw.rs
index 8285ae5..2482455 100644
--- a/src/attribute/raw.rs
+++ b/src/attribute/raw.rs
@@ -1,8 +1,7 @@
use crate::attribute::MftAttributeType;
-use crate::err::{self, Result};
+use crate::err::Result;
use crate::{utils, ReadSeek};
use serde::{ser, Serialize};
-use snafu::ResultExt;
/// Placeholder attribute for currently unparsed attributes.
#[derive(Serialize, Clone, Debug)]
@@ -20,7 +19,7 @@ impl RawAttribute {
) -> Result<Self> {
let mut data = vec![0_u8; data_size];
- stream.read_exact(&mut data).context(err::IoError)?;
+ stream.read_exact(&mut data)?;
Ok(RawAttribute {
attribute_type,
diff --git a/src/attribute/x10.rs b/src/attribute/x10.rs
index ce7a610..cb7bd2c 100644
--- a/src/attribute/x10.rs
+++ b/src/attribute/x10.rs
@@ -1,12 +1,11 @@
use crate::attribute::FileAttributeFlags;
-use crate::err::{self, Result};
+use crate::err::{Error, Result};
use crate::ReadSeek;
use byteorder::{LittleEndian, ReadBytesExt};
use chrono::{DateTime, Utc};
use log::trace;
use serde::Serialize;
-use snafu::ResultExt;
use winstructs::timestamp::WinTimestamp;
#[derive(Serialize, Debug, Clone)]
@@ -62,16 +61,16 @@ impl StandardInfoAttr {
pub fn from_reader<S: ReadSeek>(reader: &mut S) -> Result<StandardInfoAttr> {
trace!("Offset {}: StandardInfoAttr", reader.tell()?);
let created = WinTimestamp::from_reader(reader)
- .context(err::FailedToReadWindowsTime)?
+ .map_err(Error::failed_to_read_windows_time)?
.to_datetime();
let modified = WinTimestamp::from_reader(reader)
- .context(err::FailedToReadWindowsTime)?
+ .map_err(Error::failed_to_read_windows_time)?
.to_datetime();
let mft_modified = WinTimestamp::from_reader(reader)
- .context(err::FailedToReadWindowsTime)?
+ .map_err(Error::failed_to_read_windows_time)?
.to_datetime();
let accessed = WinTimestamp::from_reader(reader)
- .context(err::FailedToReadWindowsTime)?
+ .map_err(Error::failed_to_read_windows_time)?
.to_datetime();
Ok(StandardInfoAttr {
diff --git a/src/attribute/x20.rs b/src/attribute/x20.rs
index 13425ba..8437823 100644
--- a/src/attribute/x20.rs
+++ b/src/attribute/x20.rs
@@ -1,4 +1,4 @@
-use crate::err::{self, Result};
+use crate::err::{Error, Result};
use crate::ReadSeek;
use log::trace;
@@ -8,7 +8,6 @@ use encoding::{DecoderTrap, Encoding};
use serde::Serialize;
-use snafu::ResultExt;
use std::io::SeekFrom;
use winstructs::ntfs::mft_reference::MftReference;
@@ -34,7 +33,7 @@ impl AttributeListAttr {
let name_offset = stream.read_u8()?;
let first_vcn = stream.read_u64::<LittleEndian>()?;
let base_reference =
- MftReference::from_reader(stream).context(err::FailedToReadMftReference)?;
+ MftReference::from_reader(stream).map_err(Error::failed_to_read_mft_reference)?;
let attribute_id = stream.read_u16::<LittleEndian>()?;
let name = if name_length > 0 {
@@ -45,7 +44,7 @@ impl AttributeListAttr {
match UTF_16LE.decode(&name_buffer, DecoderTrap::Ignore) {
Ok(s) => s,
- Err(_e) => return err::InvalidFilename {}.fail(),
+ Err(_e) => return Err(Error::InvalidFilename {}),
}
} else {
String::new()
diff --git a/src/attribute/x30.rs b/src/attribute/x30.rs
index 8a0d0f4..22aa8ec 100644
--- a/src/attribute/x30.rs
+++ b/src/attribute/x30.rs
@@ -1,8 +1,7 @@
use crate::attribute::FileAttributeFlags;
-use crate::err::{self, Result};
+use crate::err::{Error, Result};
use crate::ReadSeek;
use log::trace;
-use snafu::OptionExt;
use byteorder::{LittleEndian, ReadBytesExt};
use encoding::all::UTF_16LE;
@@ -12,7 +11,6 @@ use chrono::{DateTime, Utc};
use num_traits::FromPrimitive;
use serde::Serialize;
-use snafu::ResultExt;
use winstructs::ntfs::mft_reference::MftReference;
use winstructs::timestamp::WinTimestamp;
@@ -77,18 +75,19 @@ impl FileNameAttr {
/// ```
pub fn from_stream<S: ReadSeek>(stream: &mut S) -> Result<FileNameAttr> {
trace!("Offset {}: FilenameAttr", stream.tell()?);
- let parent = MftReference::from_reader(stream).context(err::FailedToReadMftReference)?;
+ let parent =
+ MftReference::from_reader(stream).map_err(Error::failed_to_read_mft_reference)?;
let created = WinTimestamp::from_reader(stream)
- .context(err::FailedToReadWindowsTime)?
+ .map_err(Error::failed_to_read_windows_time)?
.to_datetime();
let modified = WinTimestamp::from_reader(stream)
- .context(err::FailedToReadWindowsTime)?
+ .map_err(Error::failed_to_read_windows_time)?
.to_datetime();
let mft_modified = WinTimestamp::from_reader(stream)
- .context(err::FailedToReadWindowsTime)?
+ .map_err(Error::failed_to_read_windows_time)?
.to_datetime();
let accessed = WinTimestamp::from_reader(stream)
- .context(err::FailedToReadWindowsTime)?
+ .map_err(Error::failed_to_read_windows_time)?
.to_datetime();
let logical_size = stream.read_u64::<LittleEndian>()?;
@@ -98,14 +97,14 @@ impl FileNameAttr {
let name_length = stream.read_u8()?;
let namespace = stream.read_u8()?;
let namespace =
- FileNamespace::from_u8(namespace).context(err::UnknownNamespace { namespace })?;
+ FileNamespace::from_u8(namespace).ok_or(Error::UnknownNamespace { namespace })?;
let mut name_buffer = vec![0; (name_length as usize * 2) as usize];
stream.read_exact(&mut name_buffer)?;
let name = match UTF_16LE.decode(&name_buffer, DecoderTrap::Ignore) {
Ok(s) => s,
- Err(_e) => return err::InvalidFilename {}.fail(),
+ Err(_e) => return Err(Error::InvalidFilename {}),
};
Ok(FileNameAttr {
diff --git a/src/attribute/x40.rs b/src/attribute/x40.rs
index beea829..2526c82 100644
--- a/src/attribute/x40.rs
+++ b/src/attribute/x40.rs
@@ -1,7 +1,6 @@
-use crate::err::{self, Result};
+use crate::err::{Error, Result};
use crate::ReadSeek;
use serde::Serialize;
-use snafu::ResultExt;
use winstructs::guid::Guid;
/// $Data Attribute
@@ -20,11 +19,11 @@ pub struct ObjectIdAttr {
impl ObjectIdAttr {
/// Data size should be either 16 or 64
pub fn from_stream<S: ReadSeek>(stream: &mut S, data_size: usize) -> Result<ObjectIdAttr> {
- let object_id = Guid::from_stream(stream).context(err::FailedToReadGuid)?;
+ let object_id = Guid::from_reader(stream).map_err(Error::failed_to_read_guid)?;
let (birth_volume_id, birth_object_id, domain_id) = if data_size == 64 {
- let g1 = Guid::from_stream(stream).context(err::FailedToReadGuid)?;
- let g2 = Guid::from_stream(stream).context(err::FailedToReadGuid)?;
- let g3 = Guid::from_stream(stream).context(err::FailedToReadGuid)?;
+ let g1 = Guid::from_reader(stream).map_err(Error::failed_to_read_guid)?;
+ let g2 = Guid::from_reader(stream).map_err(Error::failed_to_read_guid)?;
+ let g3 = Guid::from_reader(stream).map_err(Error::failed_to_read_guid)?;
(Some(g1), Some(g2), Some(g3))
} else {
(None, None, None)
diff --git a/src/attribute/x80.rs b/src/attribute/x80.rs
index f194a0e..81918c7 100644
--- a/src/attribute/x80.rs
+++ b/src/attribute/x80.rs
@@ -1,7 +1,6 @@
-use crate::err::{self, Result};
+use crate::err::Result;
use crate::{utils, ReadSeek};
use serde::ser;
-use snafu::ResultExt;
/// $Data Attribute
#[derive(Clone, Debug)]
@@ -11,7 +10,7 @@ impl DataAttr {
pub fn from_stream<S: ReadSeek>(stream: &mut S, data_size: usize) -> Result<DataAttr> {
let mut data = vec![0_u8; data_size];
- stream.read_exact(&mut data).context(err::IoError)?;
+ stream.read_exact(&mut data)?;
Ok(DataAttr(data))
}
@@ -26,6 +25,6 @@ impl ser::Serialize for DataAttr {
where
S: ser::Serializer,
{
- serializer.serialize_str(&utils::to_hex_string(&self.0).to_string())
+ serializer.serialize_str(&utils::to_hex_string(&self.0))
}
}
diff --git a/src/bin/mft_dump.rs b/src/bin/mft_dump.rs
index 75f4a8e..6655524 100644
--- a/src/bin/mft_dump.rs
+++ b/src/bin/mft_dump.rs
@@ -9,12 +9,10 @@ use mft::{MftEntry, ReadSeek};
use dialoguer::Confirmation;
use mft::csv::FlatMftEntryWithName;
-use snafu::ErrorCompat;
-use std::error::Error;
+use anyhow::{anyhow, Context, Error, Result};
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
-use std::process::exit;
use mft::entry::ZERO_HEADER;
use std::fmt::Write as FmtWrite;
@@ -22,13 +20,6 @@ use std::ops::RangeInclusive;
use std::str::FromStr;
use std::{fs, io, path};
-/// Simple error macro for use inside of internal errors in `MftDump`
-macro_rules! err {
- ($($tt:tt)*) => { Err(Box::<dyn std::error::Error>::from(format!($($tt)*))) }
-}
-
-type StdErr = Box<dyn std::error::Error>;
-
#[derive(Debug, PartialOrd, PartialEq)]
enum OutputFormat {
JSON,
@@ -56,19 +47,19 @@ impl Ranges {
}
impl FromStr for Ranges {
- type Err = StdErr;
+ type Err = Error;
- fn from_str(s: &str) -> Result<Self, Self::Err> {
+ fn from_str(s: &str) -> Result<Self> {
let mut ranges = vec![];
for x in s.split(',') {
// range
if x.contains('-') {
let range: Vec<&str> = x.split('-').collect();
if range.len() != 2 {
- return err!(
+ return Err(anyhow!(
"Failed to parse ranges: Range should contain exactly one `-`, found {}",
x
- );
+ ));
}
ranges.push(range[0].parse()?..=range[1].parse()?);
@@ -144,26 +135,27 @@ struct MftDump {
verbosity_level: Option<Level>,
output_format: OutputFormat,
ranges: Option<Ranges>,
- backtraces: bool,
}
impl MftDump {
- pub fn from_cli_matches(matches: &ArgMatches) -> Result<Self, StdErr> {
+ pub fn from_cli_matches(matches: &ArgMatches) -> Result<Self> {
let output_format =
OutputFormat::from_str(matches.value_of("output-format").unwrap_or_default())
.expect("Validated with clap default values");
- let backtraces = matches.is_present("backtraces");
+ if matches.is_present("backtraces") {
+ std::env::set_var("RUST_LIB_BACKTRACE", "1");
+ }
let output: Option<Box<dyn Write>> = if let Some(path) = matches.value_of("output-target") {
match Self::create_output_file(path, !matches.is_present("no-confirm-overwrite")) {
Ok(f) => Some(Box::new(f)),
Err(e) => {
- return err!(
+ return Err(anyhow!(
"An error occurred while creating output file at `{}` - `{}`",
path,
e
- );
+ ));
}
}
} else {
@@ -201,16 +193,18 @@ impl MftDump {
verbosity_level,
output_format,
ranges,
- backtraces,
})
}
- fn create_output_dir(path: impl AsRef<Path>) -> Result<(), StdErr> {
+ fn create_output_dir(path: impl AsRef<Path>) -> Result<()> {
let p = path.as_ref();
if p.exists() {
if !p.is_dir() {
- return err!("There is a file at {}, refusing to overwrite", p.display());
+ return Err(anyhow!(
+ "There is a file at {}, refusing to overwrite",
+ p.display()
+ ));
}
// p exists and is a directory, it's ok to add files.
} else {
@@ -221,17 +215,14 @@ impl MftDump {
}
/// If `prompt` is passed, will display a confirmation prompt before overwriting files.
- fn create_output_file(
- path: impl AsRef<Path>,
- prompt: bool,
- ) -> Result<File, Box<dyn std::error::Error>> {
+ fn create_output_file(path: impl AsRef<Path>, prompt: bool) -> Result<File> {
let p = path.as_ref();
if p.is_dir() {
- return err!(
+ return Err(anyhow!(
"There is a directory at {}, refusing to overwrite",
p.display()
- );
+ ));
}
if p.exists() {
@@ -245,11 +236,11 @@ impl MftDump {
.interact()
{
Ok(true) => Ok(File::create(p)?),
- Ok(false) => err!("Cancelled"),
- Err(e) => err!(
+ Ok(false) => Err(anyhow!("Cancelled")),
+ Err(e) => Err(anyhow!(
"Failed to write confirmation prompt to term caused by\n{}",
e
- ),
+ )),
}
} else {
Ok(File::create(p)?)
@@ -267,25 +258,16 @@ impl MftDump {
Ok(File::create(p)?)
}
}
- None => err!("Output file cannot be root."),
+ None => Err(anyhow!("Output file cannot be root.")),
}
}
}
/// Main entry point for `EvtxDump`
- pub fn run(&mut self) -> Result<(), StdErr> {
+ pub fn run(&mut self) -> Result<()> {
self.try_to_initialize_logging();
- let mut parser = match MftParser::from_path(&self.filepath) {
- Ok(parser) => parser,
- Err(e) => {
- return err!(
- "Failed to open file {}.\n\tcaused by: {}",
- self.filepath.display(),
- &e
- )
- }
- };
+ let mut parser = MftParser::from_path(&self.filepath)?;
// Since the JSON parser can do away with a &mut Write, but the csv parser needs ownership
// of `Write`, we eagerly create the csv writer here, moving the Box<Write> out from
@@ -320,12 +302,6 @@ impl MftDump {
},
Err(error) => {
eprintln!("{}", error);
-
- if self.backtraces {
- if let Some(bt) = error.backtrace() {
- eprintln!("{}", bt);
- }
- }
continue;
}
};
@@ -371,11 +347,11 @@ impl MftDump {
);
if PathBuf::from(&data_stream_path).exists() {
- return err!(
+ return Err(anyhow!(
"Tried to override an existing stream {} already exists!\
This is a bug, please report to github!",
data_stream_path
- );
+ ));
}
let mut f = File::create(&data_stream_path)?;
@@ -407,12 +383,12 @@ impl MftDump {
io::stderr(),
) {
Ok(_) => {}
- Err(e) => eprintln!("Failed to initialize logging: {}", e.description()),
+ Err(e) => eprintln!("Failed to initialize logging: {}", e),
};
}
}
- pub fn print_json_entry(&mut self, entry: &MftEntry) -> Result<(), Box<dyn std::error::Error>> {
+ pub fn print_json_entry(&mut self, entry: &MftEntry) -> Result<()> {
let out = self
.output
.as_mut()
@@ -435,7 +411,7 @@ impl MftDump {
entry: &MftEntry,
parser: &mut MftParser<impl ReadSeek>,
writer: &mut csv::Writer<W>,
- ) -> Result<(), Box<dyn std::error::Error>> {
+ ) -> Result<()> {
let flat_entry = FlatMftEntryWithName::from_entry(&entry, parser);
writer.serialize(flat_entry)?;
@@ -470,7 +446,7 @@ pub fn sanitized(component: &str) -> String {
buf
}
-fn main() {
+fn main() -> Result<()> {
let matches = App::new("MFT Parser")
.version(env!("CARGO_PKG_VERSION"))
.author("Omer B. <omerbenamram@gmail.com>")
@@ -534,19 +510,8 @@ fn main() {
.help("If set, a backtrace will be printed with some errors if available"))
.get_matches();
- let mut app = match MftDump::from_cli_matches(&matches) {
- Ok(app) => app,
- Err(e) => {
- eprintln!("An error occurred while setting up the app: {}", &e);
- exit(1);
- }
- };
+ let mut app = MftDump::from_cli_matches(&matches).context("Failed setting up the app")?;
+ app.run().context("A runtime error has occurred")?;
- match app.run() {
- Ok(()) => {}
- Err(e) => {
- eprintln!("A runtime error has occurred: {}", &e);
- exit(1);
- }
- };
+ Ok(())
}
diff --git a/src/csv.rs b/src/csv.rs
index fbe1b65..3420b6e 100644
--- a/src/csv.rs
+++ b/src/csv.rs
@@ -92,10 +92,7 @@ impl FlatMftEntryWithName {
let has_ads = entry_attributes
.iter()
- .any(|a| {
- a.header.type_code == MftAttributeType::DATA && a.header.name.len() > 0
- });
-
+ .any(|a| a.header.type_code == MftAttributeType::DATA && !a.header.name.is_empty());
FlatMftEntryWithName {
entry_id: entry.header.record_number,
@@ -111,14 +108,14 @@ impl FlatMftEntryWithName {
is_a_directory: entry.is_dir(),
is_deleted: !entry.header.flags.contains(EntryFlags::ALLOCATED),
has_alternate_data_streams: has_ads,
- standard_info_flags: standard_info.as_ref().and_then(|i| Some(i.file_flags)),
- standard_info_last_modified: standard_info.as_ref().and_then(|i| Some(i.modified)),
- standard_info_last_access: standard_info.as_ref().and_then(|i| Some(i.accessed)),
- standard_info_created: standard_info.as_ref().and_then(|i| Some(i.created)),
- file_name_flags: file_name.as_ref().and_then(|i| Some(i.flags)),
- file_name_last_modified: file_name.as_ref().and_then(|i| Some(i.modified)),
- file_name_last_access: file_name.as_ref().and_then(|i| Some(i.accessed)),
- file_name_created: file_name.as_ref().and_then(|i| Some(i.created)),
+ standard_info_flags: standard_info.as_ref().map(|i| i.file_flags),
+ standard_info_last_modified: standard_info.as_ref().map(|i| i.modified),
+ standard_info_last_access: standard_info.as_ref().map(|i| i.accessed),
+ standard_info_created: standard_info.as_ref().map(|i| i.created),
+ file_name_flags: file_name.as_ref().map(|i| i.flags),
+ file_name_last_modified: file_name.as_ref().map(|i| i.modified),
+ file_name_last_access: file_name.as_ref().map(|i| i.accessed),
+ file_name_created: file_name.as_ref().map(|i| i.created),
file_size,
full_path: parser
.get_full_path_for_entry(entry)
diff --git a/src/entry.rs b/src/entry.rs
index f1b2a63..1ce8f35 100644
--- a/src/entry.rs
+++ b/src/entry.rs
@@ -1,8 +1,7 @@
-use crate::err::{self, Result};
+use crate::err::{Error, Result};
use crate::impl_serialize_for_bitflags;
use log::trace;
-use snafu::{ensure, ResultExt};
use winstructs::ntfs::mft_reference::MftReference;
@@ -22,9 +21,9 @@ use std::io::{Cursor, Seek};
const SEQUENCE_NUMBER_STRIDE: usize = 512;
-pub const ZERO_HEADER: &'static [u8; 4] = b"\x00\x00\x00\x00";
-pub const BAAD_HEADER: &'static [u8; 4] = b"BAAD";
-pub const FILE_HEADER: &'static [u8; 4] = b"FILE";
+pub const ZERO_HEADER: &[u8; 4] = b"\x00\x00\x00\x00";
+pub const BAAD_HEADER: &[u8; 4] = b"BAAD";
+pub const FILE_HEADER: &[u8; 4] = b"FILE";
#[derive(Debug, Clone)]
pub struct MftEntry {
@@ -98,12 +97,11 @@ impl EntryHeader {
let header_is_valid = [FILE_HEADER, BAAD_HEADER, ZERO_HEADER].contains(&&signature);
- ensure!(
- header_is_valid,
- err::InvalidEntrySignature {
- bad_sig: signature.to_vec()
- }
- );
+ if !header_is_valid {
+ return Err(Error::InvalidEntrySignature {
+ bad_sig: signature.to_vec(),
+ });
+ }
if signature == *ZERO_HEADER {
return Ok(Self::zero());
@@ -120,7 +118,7 @@ impl EntryHeader {
let entry_size_allocated = reader.read_u32::<LittleEndian>()?;
let base_reference =
- MftReference::from_reader(reader).context(err::FailedToReadMftReference)?;
+ MftReference::from_reader(reader).map_err(Error::failed_to_read_mft_reference)?;
let first_attribute_id = reader.read_u16::<LittleEndian>()?;
@@ -187,8 +185,8 @@ impl MftEntry {
})
}
- /// Initializes an MFT Entry from a buffer but skips checking and fixing the
- /// fixup array. This will throw InvalidEntrySignature error if the entry header
+ /// Initializes an MFT Entry from a buffer but skips checking and fixing the
+ /// fixup array. This will throw InvalidEntrySignature error if the entry header
/// is not valid.
pub fn from_buffer_skip_fixup(buffer: Vec<u8>, entry_number: u64) -> Result<MftEntry> {
let mut cursor = Cursor::new(&buffer);
@@ -196,12 +194,11 @@ impl MftEntry {
let entry_header = EntryHeader::from_reader(&mut cursor, entry_number)?;
trace!("Number of sectors: {:#?}", entry_header);
- ensure!(
- entry_header.is_valid(),
- err::InvalidEntrySignature {
- bad_sig: entry_header.signature.to_vec()
- }
- );
+ if !entry_header.is_valid() {
+ return Err(Error::InvalidEntrySignature {
+ bad_sig: entry_header.signature.to_vec(),
+ });
+ }
Ok(MftEntry {
header: entry_header,
@@ -239,7 +236,6 @@ impl MftEntry {
/// https://docs.microsoft.com/en-us/windows/desktop/devnotes/multi-sector-header
/// **Note**: The fixup will be written at the end of each 512-byte stride,
/// even if the device has more (or less) than 512 bytes per sector.
- #[must_use]
fn apply_fixups(header: &EntryHeader, buffer: &mut [u8]) -> Result<()> {
let number_of_fixups = u32::from(header.usa_size - 1);
trace!("Number of fixups: {}", number_of_fixups);
@@ -265,14 +261,13 @@ impl MftEntry {
let end_of_sector_bytes =
&mut buffer[end_of_sector_bytes_start_offset..end_of_sector_bytes_end_offset];
- ensure!(
- end_of_sector_bytes == update_sequence,
- err::FailedToApplyFixup {
+ if end_of_sector_bytes != update_sequence {
+ return Err(Error::FailedToApplyFixup {
stride_number,
end_of_sector_bytes: end_of_sector_bytes.to_vec(),
- fixup_bytes: fixup_bytes.to_vec()
- }
- );
+ fixup_bytes: fixup_bytes.to_vec(),
+ });
+ }
end_of_sector_bytes.copy_from_slice(&fixup_bytes);
}
@@ -309,12 +304,9 @@ impl MftEntry {
return None;
}
- match cursor.seek(SeekFrom::Start(offset)).context(err::IoError) {
- Ok(_) => {}
- Err(e) => {
- exhausted = true;
- return Some(Err(e.into()));
- }
+ if let Err(e) = cursor.seek(SeekFrom::Start(offset)) {
+ exhausted = true;
+ return Some(Err(e.into()));
};
let header = MftAttributeHeader::from_stream(&mut cursor);
diff --git a/src/err.rs b/src/err.rs
index 78076fb..6073830 100644
--- a/src/err.rs
+++ b/src/err.rs
@@ -1,61 +1,71 @@
-use snafu::{Backtrace, Snafu};
-use std::path::PathBuf;
-use std::{io, result};
+use std::path::{Path, PathBuf};
+use thiserror::Error;
-pub type Result<T> = result::Result<T, Error>;
+pub type Result<T> = ::std::result::Result<T, Error>;
-#[derive(Debug, Snafu)]
-#[snafu(visibility(pub(crate)))]
+#[derive(Debug, Error)]
pub enum Error {
- #[snafu(display("An I/O error has occurred: {}", source))]
+ #[error("An I/O error has occurred")]
IoError {
+ #[from]
source: std::io::Error,
- backtrace: Backtrace,
},
- #[snafu(display("Failed to open file {}: {}", path.display(), source))]
+ #[error("Failed to open file {}", path.display())]
FailedToOpenFile {
path: PathBuf,
source: std::io::Error,
},
- #[snafu(display("Error while decoding name in filename attribute"))]
+ #[error("Error while decoding name in filename attribute")]
InvalidFilename,
- #[snafu(display(
+ #[error(
"Bad signature: {:x?}, expected one of [b\"FILE\", b\"BAAD\", b\"0000\"]",
bad_sig
- ))]
+ )]
InvalidEntrySignature { bad_sig: Vec<u8> },
- #[snafu(display("Unknown `AttributeType`: {:04X}", attribute_type))]
+ #[error("Unknown `AttributeType`: {:04X}", attribute_type)]
UnknownAttributeType { attribute_type: u32 },
- #[snafu(display("Unknown filename namespace {}", namespace))]
+ #[error("Unknown filename namespace {}", namespace)]
UnknownNamespace { namespace: u8 },
- #[snafu(display("Unhandled resident flag: {} (offset: {})", flag, offset))]
+ #[error("Unhandled resident flag: {} (offset: {})", flag, offset)]
UnhandledResidentFlag { flag: u8, offset: u64 },
- #[snafu(display(
+ #[error(
"Fixup bytes do not match bytes at end of stride {} {:x?}: {:x?}",
stride_number,
end_of_sector_bytes,
fixup_bytes
- ))]
+ )]
FailedToApplyFixup {
stride_number: usize,
end_of_sector_bytes: Vec<u8>,
fixup_bytes: Vec<u8>,
},
- #[snafu(display("Failed to read MftReference: `{}`", source))]
+ #[error("Failed to read MftReference")]
FailedToReadMftReference { source: winstructs::err::Error },
- #[snafu(display("Failed to read WindowsTime: `{}`", source))]
+ #[error("Failed to read WindowsTime")]
FailedToReadWindowsTime { source: winstructs::err::Error },
- #[snafu(display("Failed to read GUID: `{}`", source))]
+ #[error("Failed to read GUID")]
FailedToReadGuid { source: winstructs::err::Error },
- #[snafu(display("An unexpected error has occurred: {}", detail))]
+ #[error("An unexpected error has occurred: {}", detail)]
Any { detail: String },
}
-impl From<io::Error> for Error {
- fn from(err: io::Error) -> Self {
- Error::IoError {
- source: err,
- backtrace: Backtrace::new(),
+impl Error {
+ pub fn failed_to_read_windows_time(source: winstructs::err::Error) -> Error {
+ Error::FailedToReadWindowsTime { source }
+ }
+
+ pub fn failed_to_read_mft_reference(source: winstructs::err::Error) -> Error {
+ Error::FailedToReadMftReference { source }
+ }
+
+ pub fn failed_to_read_guid(source: winstructs::err::Error) -> Error {
+ Error::FailedToReadGuid { source }
+ }
+
+ pub fn failed_to_open_file(path: impl AsRef<Path>, source: std::io::Error) -> Error {
+ Error::FailedToOpenFile {
+ path: path.as_ref().to_path_buf(),
+ source,
}
}
}
diff --git a/src/mft.rs b/src/mft.rs
index 9d78543..4cb2c91 100644
--- a/src/mft.rs
+++ b/src/mft.rs
@@ -1,9 +1,8 @@
use crate::entry::MftEntry;
-use crate::err::{self, Result};
+use crate::err::{Error, Result};
use crate::{EntryHeader, ReadSeek};
use log::{debug, trace};
-use snafu::ResultExt;
use lru::LruCache;
use std::fs::{self, File};
@@ -25,7 +24,7 @@ impl MftParser<BufReader<File>> {
pub fn from_path(filename: impl AsRef<Path>) -> Result<Self> {
let f = filename.as_ref();
- let mft_fh = File::open(f).context(err::FailedToOpenFile { path: f.to_owned() })?;
+ let mft_fh = File::open(f).map_err(|e| Error::failed_to_open_file(f, e))?;
let size = fs::metadata(f)?.len();
Self::from_read_seek(BufReader::with_capacity(4096, mft_fh), Some(size))
@@ -181,6 +180,8 @@ mod tests {
count += 1;
}
}
+
+ assert!(count > 0)
}
#[test]
diff --git a/src/utils.rs b/src/utils.rs
index 25470e7..559c70c 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -43,7 +43,8 @@ pub fn read_utf16_string<T: ReadSeek>(stream: &mut T, len: Option<usize>) -> io:
},
}
- decode_utf16(buffer.into_iter())
+ // We need to stop if we see a NUL byte, even if asked for more bytes.
+ decode_utf16(buffer.into_iter().take_while(|&byte| byte != 0x00))
.map(|r| r.map_err(|_e| io::Error::from(io::ErrorKind::InvalidData)))
.collect()
}
diff --git a/tests/fixtures.rs b/tests/fixtures.rs
index 17dfed6..bc831b3 100644
--- a/tests/fixtures.rs
+++ b/tests/fixtures.rs
@@ -1,9 +1,9 @@
#![allow(dead_code)]
use std::path::PathBuf;
-use std::sync::{Once, ONCE_INIT};
+use std::sync::Once;
-static LOGGER_INIT: Once = ONCE_INIT;
+static LOGGER_INIT: Once = Once::new();
// Rust runs the tests concurrently, so unless we synchronize logging access
// it will crash when attempting to run `cargo test` with some logging facilities.
diff --git a/tests/test_cli_interactive.rs b/tests/test_cli_interactive.rs
index 6667ac0..41c179a 100644
--- a/tests/test_cli_interactive.rs
+++ b/tests/test_cli_interactive.rs
@@ -2,81 +2,82 @@
// since they use `rexpect`, which internally uses quirky fork semantics to open a pty.
// They will fail if tried to be executed concurrently any other CLI test.
-mod fixtures;
-
-use fixtures::*;
-
-use std::fs::File;
-use std::io::{Read, Write};
-use tempfile::tempdir;
-
-use assert_cmd::cargo::cargo_bin;
#[cfg(not(target_os = "windows"))]
-use rexpect::spawn;
-
-// It should behave the same on windows, but interactive testing relies on unix pty internals.
-#[test]
-#[cfg(not(target_os = "windows"))]
-fn test_it_confirms_before_overwriting_a_file() {
- let d = tempdir().unwrap();
- let f = d.as_ref().join("test.out");
-
- let mut file = File::create(&f).unwrap();
- file.write_all(b"I'm a file!").unwrap();
-
- let sample = mft_sample();
-
- let cmd_string = format!(
- "{bin} -f {output_file} {sample}",
- bin = cargo_bin("mft_dump").display(),
- output_file = f.to_string_lossy(),
- sample = sample.to_str().unwrap()
- );
-
- let mut p = spawn(&cmd_string, Some(20000)).unwrap();
- p.exp_regex(r#"Are you sure you want to override.*"#)
- .unwrap();
- p.send_line("y").unwrap();
- p.exp_eof().unwrap();
-
- let mut expected = vec![];
-
- File::open(&f).unwrap().read_to_end(&mut expected).unwrap();
- assert!(
- !expected.len() > 100,
- "Expected output to be printed to file"
- )
-}
+mod fixtures;
-#[test]
#[cfg(not(target_os = "windows"))]
-fn test_it_confirms_before_overwriting_a_file_and_quits() {
- let d = tempdir().unwrap();
- let f = d.as_ref().join("test.out");
-
- let mut file = File::create(&f).unwrap();
- file.write_all(b"I'm a file!").unwrap();
-
- let sample = mft_sample();
-
- let cmd_string = format!(
- "{bin} -f {output_file} {sample}",
- bin = cargo_bin("mft_dump").display(),
- output_file = f.to_string_lossy(),
- sample = sample.to_str().unwrap()
- );
-
- let mut p = spawn(&cmd_string, Some(20000)).unwrap();
- p.exp_regex(r#"Are you sure you want to override.*"#)
- .unwrap();
- p.send_line("n").unwrap();
- p.exp_eof().unwrap();
-
- let mut expected = vec![];
-
- File::open(&f).unwrap().read_to_end(&mut expected).unwrap();
- assert!(
- !expected.len() > 100,
- "Expected output to be printed to file"
- )
+mod cli_tests {
+ use super::fixtures::*;
+
+ use std::fs::File;
+ use std::io::{Read, Write};
+ use tempfile::tempdir;
+
+ use assert_cmd::cargo::cargo_bin;
+ use rexpect::spawn;
+
+ // It should behave the same on windows, but interactive testing relies on unix pty internals.
+ #[test]
+ fn test_it_confirms_before_overwriting_a_file() {
+ let d = tempdir().unwrap();
+ let f = d.as_ref().join("test.out");
+
+ let mut file = File::create(&f).unwrap();
+ file.write_all(b"I'm a file!").unwrap();
+
+ let sample = mft_sample();
+
+ let cmd_string = format!(
+ "{bin} -f {output_file} {sample}",
+ bin = cargo_bin("mft_dump").display(),
+ output_file = f.to_string_lossy(),
+ sample = sample.to_str().unwrap()
+ );
+
+ let mut p = spawn(&cmd_string, Some(20000)).unwrap();
+ p.exp_regex(r#"Are you sure you want to override.*"#)
+ .unwrap();
+ p.send_line("y").unwrap();
+ p.exp_eof().unwrap();
+
+ let mut expected = vec![];
+
+ File::open(&f).unwrap().read_to_end(&mut expected).unwrap();
+ assert!(
+ !expected.len() > 100,
+ "Expected output to be printed to file"
+ )
+ }
+
+ #[test]
+ fn test_it_confirms_before_overwriting_a_file_and_quits() {
+ let d = tempdir().unwrap();
+ let f = d.as_ref().join("test.out");
+
+ let mut file = File::create(&f).unwrap();
+ file.write_all(b"I'm a file!").unwrap();
+
+ let sample = mft_sample();
+
+ let cmd_string = format!(
+ "{bin} -f {output_file} {sample}",
+ bin = cargo_bin("mft_dump").display(),
+ output_file = f.to_string_lossy(),
+ sample = sample.to_str().unwrap()
+ );
+
+ let mut p = spawn(&cmd_string, Some(20000)).unwrap();
+ p.exp_regex(r#"Are you sure you want to override.*"#)
+ .unwrap();
+ p.send_line("n").unwrap();
+ p.exp_eof().unwrap();
+
+ let mut expected = vec![];
+
+ File::open(&f).unwrap().read_to_end(&mut expected).unwrap();
+ assert!(
+ !expected.len() > 100,
+ "Expected output to be printed to file"
+ )
+ }
}