From 2edaaff8bdda1ae6f0858f7b87ce3ab2f2f97c47 Mon Sep 17 00:00:00 2001 From: Colin Finck Date: Sun, 3 Oct 2021 19:48:24 +0200 Subject: Make `NtfsFile::name` useful by matching on namespace and parent. An NTFS file often has more than one FileName attribute and the caller is likely interested in a specific one. ntfs-shell is changed accordingly to prefer the long filenames. --- examples/ntfs-shell/main.rs | 135 +++++++++++++++++++++++++++++--------------- src/file.rs | 48 +++++++++++----- 2 files changed, 125 insertions(+), 58 deletions(-) diff --git a/examples/ntfs-shell/main.rs b/examples/ntfs-shell/main.rs index 06e7376..988f675 100644 --- a/examples/ntfs-shell/main.rs +++ b/examples/ntfs-shell/main.rs @@ -11,7 +11,9 @@ use std::io::{BufReader, Read, Seek, Write}; use anyhow::{anyhow, bail, Context, Result}; use chrono::{DateTime, Utc}; use ntfs::indexes::NtfsFileNameIndex; -use ntfs::structured_values::{NtfsAttributeList, NtfsFileName, NtfsStandardInformation}; +use ntfs::structured_values::{ + NtfsAttributeList, NtfsFileName, NtfsFileNamespace, NtfsStandardInformation, +}; use ntfs::value::NtfsValue; use ntfs::{Ntfs, NtfsAttribute, NtfsAttributeType, NtfsFile, NtfsReadSeek}; @@ -21,9 +23,10 @@ struct CommandInfo<'n, T> where T: Read + Seek, { - current_directory: NtfsFile<'n>, + current_directory: Vec>, current_directory_string: String, fs: T, + ntfs: &'n Ntfs, } fn main() -> Result<()> { @@ -42,12 +45,13 @@ fn main() -> Result<()> { let mut fs = BufReader::new(sr); let mut ntfs = Ntfs::new(&mut fs)?; ntfs.read_upcase_table(&mut fs)?; - let current_directory = ntfs.root_directory(&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, }; println!("**********************************************************************"); @@ -102,7 +106,6 @@ fn attr(with_runs: bool, arg: &str, info: &mut CommandInfo) -> Result<()> where T: Read + Seek, { - let ntfs = info.current_directory.ntfs(); let file = parse_file_arg(arg, info)?; println!("{:=<110}", ""); @@ -136,7 +139,7 @@ where continue; } - let entry_file = entry.to_file(ntfs, &mut info.fs)?; + let entry_file = entry.to_file(info.ntfs, &mut info.fs)?; let entry_attribute = entry.to_attribute(&entry_file)?; attr_print_attribute( @@ -191,6 +194,38 @@ fn attr_print_attribute<'n>( Ok(()) } +fn best_file_name( + info: &mut CommandInfo, + file: &NtfsFile, + parent_record_number: u64, +) -> Result +where + T: Read + Seek, +{ + // Try to find a long filename (Win32) first. + // If we don't find one, the file may only have a single short name (Win32AndDos). + // If we don't find one either, go with any namespace. It may still be a Dos or Posix name then. + 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(arg: &str, info: &mut CommandInfo) -> Result<()> where T: Read + Seek, @@ -199,23 +234,23 @@ where return Ok(()); } - let ntfs = info.current_directory.ntfs(); - if arg == ".." { if info.current_directory_string.is_empty() { return Ok(()); } - let file_name = info.current_directory.name(&mut info.fs)?; - let parent_ref = file_name.parent_directory_reference(); - info.current_directory = parent_ref.to_file(ntfs, &mut info.fs)?; + 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.directory_index(&mut info.fs)?; + let index = info + .current_directory + .last() + .unwrap() + .directory_index(&mut info.fs)?; let mut finder = index.finder(); - let maybe_entry = NtfsFileNameIndex::find(&mut finder, &ntfs, &mut info.fs, arg); + let maybe_entry = NtfsFileNameIndex::find(&mut finder, info.ntfs, &mut info.fs, arg); if maybe_entry.is_none() { println!("Cannot find subdirectory \"{}\".", arg); @@ -232,13 +267,18 @@ where return Ok(()); } - info.current_directory = entry.to_file(ntfs, &mut info.fs)?; - - let file_name = info.current_directory.name(&mut info.fs)?; + 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(()) @@ -248,7 +288,11 @@ fn dir(info: &mut CommandInfo) -> Result<()> where T: Read + Seek, { - let index = info.current_directory.directory_index(&mut info.fs)?; + let index = info + .current_directory + .last() + .unwrap() + .directory_index(&mut info.fs)?; let mut iter = index.iter(); while let Some(entry) = iter.next(&mut info.fs) { @@ -303,46 +347,46 @@ fn fileinfo_std(attribute: NtfsAttribute) -> Result<()> { println!(); println!("{:=^72}", " STANDARD INFORMATION "); - let info = attribute.resident_structured_value::()?; + let std_info = attribute.resident_structured_value::()?; - println!("{:34}{:?}", "Attributes:", info.file_attributes()); + println!("{:34}{:?}", "Attributes:", std_info.file_attributes()); let format = "%F %T UTC"; - let atime = DateTime::::from(info.access_time()).format(format); - let ctime = DateTime::::from(info.creation_time()).format(format); - let mtime = DateTime::::from(info.modification_time()).format(format); - let mmtime = DateTime::::from(info.mft_record_modification_time()).format(format); + let atime = DateTime::::from(std_info.access_time()).format(format); + let ctime = DateTime::::from(std_info.creation_time()).format(format); + let mtime = DateTime::::from(std_info.modification_time()).format(format); + let mmtime = DateTime::::from(std_info.mft_record_modification_time()).format(format); println!("{:34}{}", "Access Time:", atime); println!("{:34}{}", "Creation Time:", ctime); println!("{:34}{}", "Modification Time:", mtime); println!("{:34}{}", "MFT Record Modification Time:", mmtime); // NTFS 3.x extended information - let class_id = info + let class_id = std_info .class_id() .map(|x| x.to_string()) .unwrap_or_else(|| "".to_string()); - let maximum_versions = info + let maximum_versions = std_info .maximum_versions() .map(|x| x.to_string()) .unwrap_or_else(|| "".to_string()); - let owner_id = info + let owner_id = std_info .owner_id() .map(|x| x.to_string()) .unwrap_or_else(|| "".to_string()); - let quota_charged = info + let quota_charged = std_info .quota_charged() .map(|x| x.to_string()) .unwrap_or_else(|| "".to_string()); - let security_id = info + let security_id = std_info .security_id() .map(|x| x.to_string()) .unwrap_or_else(|| "".to_string()); - let usn = info + let usn = std_info .usn() .map(|x| x.to_string()) .unwrap_or_else(|| "".to_string()); - let version = info + let version = std_info .version() .map(|x| x.to_string()) .unwrap_or_else(|| "".to_string()); @@ -391,13 +435,11 @@ fn fsinfo(info: &mut CommandInfo) -> Result<()> where T: Read + Seek, { - let ntfs = info.current_directory.ntfs(); + println!("{:20}{}", "Cluster Size:", info.ntfs.cluster_size()); + println!("{:20}{}", "File Record Size:", info.ntfs.file_record_size()); + println!("{:20}{:#x}", "MFT Byte Position:", info.ntfs.mft_position()); - println!("{:20}{}", "Cluster Size:", ntfs.cluster_size()); - println!("{:20}{}", "File Record Size:", ntfs.file_record_size()); - println!("{:20}{:#x}", "MFT Byte Position:", ntfs.mft_position()); - - let volume_info = ntfs.volume_info(&mut info.fs)?; + let volume_info = info.ntfs.volume_info(&mut info.fs)?; let ntfs_version = format!( "{}.{}", volume_info.major_version(), @@ -405,11 +447,11 @@ where ); println!("{:20}{}", "NTFS Version:", ntfs_version); - println!("{:20}{}", "Sector Size:", ntfs.sector_size()); - println!("{:20}{}", "Serial Number:", ntfs.serial_number()); - println!("{:20}{}", "Size:", ntfs.size()); + println!("{:20}{}", "Sector Size:", info.ntfs.sector_size()); + println!("{:20}{}", "Serial Number:", info.ntfs.serial_number()); + println!("{:20}{}", "Size:", info.ntfs.size()); - let volume_name = if let Some(Ok(volume_name)) = ntfs.volume_name(&mut info.fs) { + let volume_name = if let Some(Ok(volume_name)) = info.ntfs.volume_name(&mut info.fs) { volume_name.name().to_string_lossy() } else { "".to_string() @@ -567,8 +609,6 @@ where bail!("Missing argument!"); } - let ntfs = info.current_directory.ntfs(); - 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), @@ -576,7 +616,7 @@ where }; if let Ok(record_number) = record_number { - let file = ntfs.file(&mut info.fs, record_number)?; + let file = info.ntfs.file(&mut info.fs, record_number)?; Ok(file) } else { bail!( @@ -585,11 +625,16 @@ where ) } } else { - let index = info.current_directory.directory_index(&mut info.fs)?; + 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, &ntfs, &mut info.fs, arg) { + + if let Some(entry) = NtfsFileNameIndex::find(&mut finder, info.ntfs, &mut info.fs, arg) { let entry = entry?; - let file = entry.to_file(ntfs, &mut info.fs)?; + let file = entry.to_file(info.ntfs, &mut info.fs)?; Ok(file) } else { bail!("No such file or directory \"{}\".", arg) diff --git a/src/file.rs b/src/file.rs index fbece7e..378671e 100644 --- a/src/file.rs +++ b/src/file.rs @@ -9,7 +9,7 @@ use crate::indexes::NtfsFileNameIndex; use crate::ntfs::Ntfs; use crate::record::{Record, RecordHeader}; use crate::structured_values::{ - NtfsFileName, NtfsIndexRoot, NtfsStandardInformation, + NtfsFileName, NtfsFileNamespace, NtfsIndexRoot, NtfsStandardInformation, NtfsStructuredValueFromResidentAttributeValue, }; use binread::io::{Read, Seek, SeekFrom}; @@ -207,7 +207,7 @@ impl<'n> NtfsFile<'n> { } /// Finds a resident attribute of a specific type, optionally with a specific name, and returns its structured value. - /// Returns `NtfsError::AttributeNotFound` if no such resident attribute could be found. + /// Returns [`NtfsError::AttributeNotFound`] if no such resident attribute could be found. /// /// The attribute type is given through the passed structured value type parameter. pub(crate) fn find_resident_attribute_structured_value<'f, S>( @@ -258,7 +258,7 @@ impl<'n> NtfsFile<'n> { /// Convenience function to get the $STANDARD_INFORMATION attribute of this file /// (see [`NtfsStandardInformation`]). /// - /// This internally calls [`NtfsFile::attributes`] to iterate through the file's + /// This internally calls [`NtfsFile::attributes_raw`] to iterate through the file's /// attributes and pick up the first $STANDARD_INFORMATION attribute. pub fn info(&self) -> Result { self.find_resident_attribute_structured_value::(None) @@ -268,32 +268,54 @@ impl<'n> NtfsFile<'n> { self.flags().contains(NtfsFileFlags::IS_DIRECTORY) } - /// Convenience function to get the $FILE_NAME attribute of this file (see [`NtfsFileName`]). + /// Convenience function to get a $FILE_NAME attribute of this file (see [`NtfsFileName`]). + /// + /// A file may have multiple $FILE_NAME attributes for each [`NtfsFileNamespace`]. + /// Files with hard links have further $FILE_NAME attributes for each directory they are in. + /// You may optionally filter for a namespace and parent directory via the parameters. /// /// This internally calls [`NtfsFile::attributes`] to iterate through the file's - /// attributes and pick up the first $FILE_NAME attribute. - pub fn name(&self, fs: &mut T) -> Result + /// attributes and pick up the first matching $FILE_NAME attribute. + pub fn name( + &self, + fs: &mut T, + match_namespace: Option, + match_parent_record_number: Option, + ) -> Option> where T: Read + Seek, { let mut iter = self.attributes(); while let Some(item) = iter.next(fs) { - let item = item?; + let item = iter_try!(item); let attribute = item.to_attribute(); - let ty = attribute.ty()?; + let ty = iter_try!(attribute.ty()); if ty != NtfsAttributeType::FileName { continue; } - return attribute.structured_value::<_, NtfsFileName>(fs); + let file_name = iter_try!(attribute.structured_value::<_, NtfsFileName>(fs)); + + if let Some(namespace) = match_namespace { + if file_name.namespace() != namespace { + continue; + } + } + + if let Some(parent_record_number) = match_parent_record_number { + if file_name.parent_directory_reference().file_record_number() + != parent_record_number + { + continue; + } + } + + return Some(Ok(file_name)); } - Err(NtfsError::AttributeNotFound { - position: self.position(), - ty: NtfsAttributeType::FileName, - }) + None } /// Returns the [`Ntfs`] object associated to this file. -- cgit v1.2.3