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

github.com/windirstat/ntfs.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Finck <colin@reactos.org>2021-10-03 17:03:03 +0300
committerColin Finck <colin@reactos.org>2021-10-03 17:03:03 +0300
commit5be9002f7a983df0e99a84fe991a42a9acd1c09c (patch)
tree7c0790cf07fb2f28faff230191e55378f1996a3d /examples
parenteaf86401ce5ee42153b70963fcbd67799926bf32 (diff)
Add ntfs-shell, an example app to demonstrate all features of the crate.
ntfs-shell is a command-line shell to navigate through an NTFS filesystem. It works read-only on image files, loopback devices, and raw partitions. Very useful for debugging the crate, examining the structure of an NTFS filesystem, and extracting data from it.
Diffstat (limited to 'examples')
-rw-r--r--examples/ntfs-shell/main.rs598
-rw-r--r--examples/ntfs-shell/sector_reader.rs130
2 files changed, 728 insertions, 0 deletions
diff --git a/examples/ntfs-shell/main.rs b/examples/ntfs-shell/main.rs
new file mode 100644
index 0000000..06e7376
--- /dev/null
+++ b/examples/ntfs-shell/main.rs
@@ -0,0 +1,598 @@
+// Copyright 2021 Colin Finck <colin@reactos.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+mod sector_reader;
+
+use std::env;
+use std::fs::{File, OpenOptions};
+use std::io;
+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::value::NtfsValue;
+use ntfs::{Ntfs, NtfsAttribute, NtfsAttributeType, NtfsFile, NtfsReadSeek};
+
+use sector_reader::SectorReader;
+
+struct CommandInfo<'n, T>
+where
+ T: Read + Seek,
+{
+ current_directory: NtfsFile<'n>,
+ current_directory_string: String,
+ fs: T,
+}
+
+fn main() -> Result<()> {
+ let args: Vec<String> = env::args().collect();
+ if args.len() != 2 {
+ eprintln!("Usage: ntfs-shell FILESYSTEM");
+ eprintln!();
+ eprintln!("FILESYSTEM can be a path to any NTFS filesystem image.");
+ eprintln!("Under Windows and when run with administrative privileges, FILESYSTEM can also");
+ eprintln!("be the special path \\\\.\\C: to access the filesystem of the C: partition.");
+ bail!("Aborted");
+ }
+
+ let f = File::open(&args[1])?;
+ let sr = SectorReader::new(f, 512)?;
+ 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 mut info = CommandInfo {
+ current_directory,
+ current_directory_string: String::new(),
+ fs,
+ };
+
+ println!("**********************************************************************");
+ println!("ntfs-shell - Demonstration of rust-ntfs");
+ println!("by Colin Finck <colin@reactos.org>");
+ println!("**********************************************************************");
+ println!();
+ println!("Opened \"{}\" read-only.", args[1]);
+ println!();
+
+ loop {
+ print!("ntfs-shell:\\{}> ", info.current_directory_string);
+ io::stdout().flush()?;
+
+ let mut input_string = String::new();
+ io::stdin().read_line(&mut input_string).unwrap();
+ if input_string.is_empty() {
+ // An empty `input_string` without even a newline looks like STDIN was closed.
+ break;
+ }
+
+ let input = input_string.trim();
+ let mid = input.find(' ').unwrap_or(input.len());
+ let (command, arg) = input.split_at(mid);
+ let arg = arg.trim_start();
+
+ let result = match command {
+ "attr" => attr(false, arg, &mut info),
+ "attr_runs" => attr(true, arg, &mut info),
+ "cd" => cd(arg, &mut info),
+ "dir" => dir(&mut info),
+ "exit" | "quit" => break,
+ "fileinfo" => fileinfo(arg, &mut info),
+ "fsinfo" => fsinfo(&mut info),
+ "get" => get(arg, &mut info),
+ "help" => help(arg),
+ "" => continue,
+ _ => Err(anyhow!(
+ "Invalid command \"{}\". Type \"help\" to get a list of all commands.",
+ command
+ )),
+ };
+ if let Err(e) = result {
+ eprintln!("Error: {:?}", e);
+ }
+ }
+
+ Ok(())
+}
+
+fn attr<T>(with_runs: bool, arg: &str, info: &mut CommandInfo<T>) -> Result<()>
+where
+ T: Read + Seek,
+{
+ let ntfs = info.current_directory.ntfs();
+ let file = parse_file_arg(arg, info)?;
+
+ println!("{:=<110}", "");
+ println!(
+ "{:<10} | {:<20} | {:<8} | {:<13} | {:<18} | {:<13} | {}",
+ "INSTANCE", "TYPE", "RESIDENT", "RECORD NUMBER", "START", "LENGTH", "NAME"
+ );
+ println!("{:=<110}", "");
+
+ let attributes = file.attributes_raw();
+ for attribute in attributes {
+ let ty = attribute.ty()?;
+
+ attr_print_attribute(
+ with_runs,
+ &attribute,
+ file.file_record_number(),
+ "● ",
+ " ■ ",
+ )?;
+
+ if ty == NtfsAttributeType::AttributeList {
+ let list = attribute.structured_value::<_, NtfsAttributeList>(&mut info.fs)?;
+ let mut list_iter = list.iter();
+
+ while let Some(entry) = list_iter.next(&mut info.fs) {
+ let entry = entry?;
+
+ let entry_record_number = entry.base_file_reference().file_record_number();
+ if entry_record_number == file.file_record_number() {
+ continue;
+ }
+
+ let entry_file = entry.to_file(ntfs, &mut info.fs)?;
+ let entry_attribute = entry.to_attribute(&entry_file)?;
+
+ attr_print_attribute(
+ with_runs,
+ &entry_attribute,
+ entry_record_number,
+ " ○ ",
+ " □ ",
+ )?;
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn attr_print_attribute<'n>(
+ with_runs: bool,
+ attribute: &NtfsAttribute<'n, '_>,
+ record_number: u64,
+ attribute_prefix: &str,
+ data_run_prefix: &str,
+) -> Result<()> {
+ let instance = format!("{}{}", attribute_prefix, attribute.instance());
+ let ty = attribute.ty()?;
+ let resident = attribute.is_resident();
+ let start = attribute.position();
+ let length = attribute.value_length();
+ let name = attribute.name()?.to_string_lossy();
+
+ println!(
+ "{:<10} | {:<20} | {:<8} | {:#13x} | {:#18x} | {:>13} | \"{}\"",
+ instance, ty, resident, record_number, start, length, name
+ );
+
+ if with_runs {
+ if let NtfsValue::NonResidentAttribute(non_resident_value) = attribute.value()? {
+ for (i, data_run) in non_resident_value.data_runs().enumerate() {
+ let data_run = data_run?;
+ let instance = format!("{}{}", data_run_prefix, i);
+ let start = data_run.data_position().unwrap_or(0);
+ let length = data_run.len();
+
+ println!(
+ "{:<10} | {:<20} | {:<8} | {:>13} | {:#18x} | {:>13} |",
+ instance, "DataRun", "", "", start, length
+ );
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn cd<T>(arg: &str, info: &mut CommandInfo<T>) -> Result<()>
+where
+ T: Read + Seek,
+{
+ if arg.is_empty() {
+ 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)?;
+
+ 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 mut finder = index.finder();
+ let maybe_entry = NtfsFileNameIndex::find(&mut finder, &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!("\"{}\" is not a directory.", arg);
+ return Ok(());
+ }
+
+ info.current_directory = entry.to_file(ntfs, &mut info.fs)?;
+
+ let file_name = info.current_directory.name(&mut info.fs)?;
+ if !info.current_directory_string.is_empty() {
+ info.current_directory_string += "\\";
+ }
+ info.current_directory_string += &file_name.name().to_string_lossy();
+ }
+
+ Ok(())
+}
+
+fn dir<T>(info: &mut CommandInfo<T>) -> Result<()>
+where
+ T: Read + Seek,
+{
+ let index = info.current_directory.directory_index(&mut info.fs)?;
+ let mut iter = index.iter();
+
+ while let Some(entry) = iter.next(&mut info.fs) {
+ let entry = entry?;
+ let file_name = entry
+ .key()
+ .expect("key must exist for a found index entry")?;
+
+ let prefix = if file_name.is_directory() {
+ "<DIR>"
+ } else {
+ ""
+ };
+ println!("{:5} {}", prefix, file_name.name());
+ }
+
+ Ok(())
+}
+
+fn fileinfo<T>(arg: &str, info: &mut CommandInfo<T>) -> Result<()>
+where
+ T: Read + Seek,
+{
+ let file = parse_file_arg(arg, info)?;
+
+ println!("{:=^72}", " FILE RECORD ");
+ println!("{:34}{}", "Allocated Size:", file.allocated_size());
+ println!("{:34}{:#x}", "Byte Position:", file.position());
+ println!("{:34}{}", "Hard-Link Count:", file.hard_link_count());
+ println!("{:34}{}", "Is Directory:", file.is_directory());
+ println!("{:34}{:#x}", "Record Number:", file.file_record_number());
+ println!("{:34}{}", "Sequence Number:", file.sequence_number());
+ println!("{:34}{}", "Used Size:", file.used_size());
+
+ let mut attributes = file.attributes();
+ while let Some(attribute_item) = attributes.next(&mut info.fs) {
+ let attribute_item = attribute_item?;
+ let attribute = attribute_item.to_attribute();
+
+ match attribute.ty() {
+ Ok(NtfsAttributeType::StandardInformation) => fileinfo_std(attribute)?,
+ Ok(NtfsAttributeType::FileName) => fileinfo_filename(info, attribute)?,
+ Ok(NtfsAttributeType::Data) => fileinfo_data(attribute)?,
+ _ => continue,
+ }
+ }
+
+ Ok(())
+}
+
+fn fileinfo_std(attribute: NtfsAttribute) -> Result<()> {
+ println!();
+ println!("{:=^72}", " STANDARD INFORMATION ");
+
+ let info = attribute.resident_structured_value::<NtfsStandardInformation>()?;
+
+ println!("{:34}{:?}", "Attributes:", info.file_attributes());
+
+ let format = "%F %T UTC";
+ let atime = DateTime::<Utc>::from(info.access_time()).format(format);
+ let ctime = DateTime::<Utc>::from(info.creation_time()).format(format);
+ let mtime = DateTime::<Utc>::from(info.modification_time()).format(format);
+ let mmtime = DateTime::<Utc>::from(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
+ .class_id()
+ .map(|x| x.to_string())
+ .unwrap_or_else(|| "<NONE>".to_string());
+ let maximum_versions = info
+ .maximum_versions()
+ .map(|x| x.to_string())
+ .unwrap_or_else(|| "<NONE>".to_string());
+ let owner_id = info
+ .owner_id()
+ .map(|x| x.to_string())
+ .unwrap_or_else(|| "<NONE>".to_string());
+ let quota_charged = info
+ .quota_charged()
+ .map(|x| x.to_string())
+ .unwrap_or_else(|| "<NONE>".to_string());
+ let security_id = info
+ .security_id()
+ .map(|x| x.to_string())
+ .unwrap_or_else(|| "<NONE>".to_string());
+ let usn = info
+ .usn()
+ .map(|x| x.to_string())
+ .unwrap_or_else(|| "<NONE>".to_string());
+ let version = info
+ .version()
+ .map(|x| x.to_string())
+ .unwrap_or_else(|| "<NONE>".to_string());
+ println!("{:34}{}", "Class ID:", class_id);
+ println!("{:34}{}", "Maximum Versions:", maximum_versions);
+ println!("{:34}{}", "Owner ID:", owner_id);
+ println!("{:34}{}", "Quota Charged:", quota_charged);
+ println!("{:34}{}", "Security ID:", security_id);
+ println!("{:34}{}", "USN:", usn);
+ println!("{:34}{}", "Version:", version);
+
+ Ok(())
+}
+
+fn fileinfo_filename<T>(info: &mut CommandInfo<T>, attribute: NtfsAttribute) -> Result<()>
+where
+ T: Read + Seek,
+{
+ println!();
+ println!("{:=^72}", " FILE NAME ");
+
+ let file_name = attribute.structured_value::<_, NtfsFileName>(&mut info.fs)?;
+
+ println!("{:34}\"{}\"", "Name:", file_name.name().to_string_lossy());
+ println!("{:34}{:?}", "Namespace:", file_name.namespace());
+ println!(
+ "{:34}{:#x}",
+ "Parent Directory Record Number:",
+ file_name.parent_directory_reference().file_record_number()
+ );
+
+ Ok(())
+}
+
+fn fileinfo_data(attribute: NtfsAttribute) -> Result<()> {
+ println!();
+ println!("{:=^72}", " DATA STREAM ");
+
+ println!("{:34}\"{}\"", "Name:", attribute.name()?.to_string_lossy());
+ println!("{:34}{}", "Size:", attribute.value_length());
+
+ Ok(())
+}
+
+fn fsinfo<T>(info: &mut CommandInfo<T>) -> Result<()>
+where
+ T: Read + Seek,
+{
+ let ntfs = info.current_directory.ntfs();
+
+ 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 ntfs_version = format!(
+ "{}.{}",
+ volume_info.major_version(),
+ volume_info.minor_version()
+ );
+ 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());
+
+ let volume_name = if let Some(Ok(volume_name)) = ntfs.volume_name(&mut info.fs) {
+ volume_name.name().to_string_lossy()
+ } else {
+ "<NONE>".to_string()
+ };
+ println!("{:20}{}", "Volume Name:", volume_name);
+
+ Ok(())
+}
+
+fn get<T>(arg: &str, info: &mut CommandInfo<T>) -> Result<()>
+where
+ T: Read + Seek,
+{
+ // Extract any specific $DATA stream name from the file.
+ let mid = arg.find(':').unwrap_or(arg.len());
+ let (file_name, data_stream_name) = arg.split_at(mid);
+
+ // Compose the output file name and try to create it.
+ // It must not yet exist, as we don't want to accidentally overwrite things.
+ let output_file_name = if data_stream_name.is_empty() {
+ file_name.to_string()
+ } else {
+ format!("{}_{}", file_name, data_stream_name)
+ };
+ let mut output_file = OpenOptions::new()
+ .write(true)
+ .create_new(true)
+ .open(&output_file_name)
+ .with_context(|| format!("Tried to open \"{}\" for writing", output_file_name))?;
+
+ // Open the desired file and find the $DATA attribute we are looking for.
+ 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 attribute.",
+ data_stream_name
+ );
+ return Ok(());
+ }
+ };
+ let data_item = data_item?;
+ let data_attribute = data_item.to_attribute();
+ let mut data_value = data_attribute.value()?;
+
+ 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(&buf[..bytes_read])?;
+ }
+
+ Ok(())
+}
+
+fn help(arg: &str) -> Result<()> {
+ match arg {
+ "attr" => {
+ println!("Usage: attr FILE");
+ println!();
+ println!("Shows the structure of all NTFS attributes of a single file, not including their data runs.");
+ println!("Try \"attr_runs\" if you are also interested in data run information.");
+ help_file("attr");
+ }
+ "attr_runs" => {
+ println!("Usage: attr_runs FILE");
+ println!();
+ println!("Shows the structure of all NTFS attributes of a single file, including their data runs.");
+ println!("Try \"attr\" if you don't need the data run information.");
+ help_file("attr_runs");
+ }
+ "cd" => {
+ println!("Usage: cd SUBDIRECTORY");
+ println!();
+ println!("Changes the current directory to SUBDIRECTORY.");
+ println!("This implementation of \"cd\" only supports subdirectories of the current directory.");
+ println!("\"cd ..\" moves back into the parent directory.");
+ }
+ "dir" => {
+ println!("Usage: dir");
+ println!();
+ println!("Lists filenames in the current directory (like \"ls\" on UNIX systems).");
+ println!("No additional parameters are supported.");
+ println!("Try \"fileinfo\" to get additional information about a single file.");
+ }
+ "fileinfo" => {
+ println!("Usage: fileinfo FILE");
+ println!();
+ println!("Shows information about a single file (by parsing its NTFS attributes).");
+ help_file("fileinfo");
+ }
+ "get" => {
+ println!("Usage:");
+ println!(" get FILE");
+ println!(" get FILE:STREAM");
+ println!();
+ println!("Copies the data of a single file from the NTFS filesystem to the current directory of your local filesystem.");
+ println!("Optionally, you can append a colon and a data stream name to copy a specific data stream of that file.");
+ println!();
+ println!("This command will fail if the file already exists in the current directory.");
+ help_file("get");
+ }
+ _ => {
+ println!("Available Commands:");
+ println!(" attr - Show structure of NTFS attributes of a particular file");
+ println!(" attr_runs - Show structure of NTFS attributes of a particular file, including data runs");
+ println!(" cd - Change the current directory");
+ println!(" dir - Show files of the current directory");
+ println!(" exit - Quit ntfs-shell");
+ println!(" fileinfo - Show information about a particular file");
+ println!(" fsinfo - Show general filesystem information");
+ println!(" get - Copy a file from the NTFS filesystem");
+ println!(" help - Show this help");
+ println!(" quit - Quit ntfs-shell");
+ println!();
+ println!(
+ "You can also enter \"help COMMAND\" to get additional help about some commands."
+ );
+ }
+ }
+
+ Ok(())
+}
+
+fn help_file(command: &str) {
+ println!();
+ println!("FILE can have one of the following formats:");
+ println!(" ● A name of a file in the current directory.");
+ println!(" Enter the filename as is, including any spaces. Don't put it into additional quotation marks.");
+ println!(" Examples:");
+ println!(" ○ {} ntoskrnl.exe", command);
+ println!(" ○ {} File with spaces.exe", command);
+ println!(" ● A file record number anywhere on the filesystem.");
+ println!(" This is indicated through a leading slash (/). A hexadecimal file record number is indicated via 0x.");
+ println!(" Examples:");
+ println!(" ○ {} /5", command);
+ println!(" ○ {} /0xa299", command);
+}
+
+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!");
+ }
+
+ 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),
+ None => u64::from_str_radix(record_number_arg, 10),
+ };
+
+ if let Ok(record_number) = record_number {
+ let file = 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.directory_index(&mut info.fs)?;
+ let mut finder = index.finder();
+ if let Some(entry) = NtfsFileNameIndex::find(&mut finder, &ntfs, &mut info.fs, arg) {
+ let entry = entry?;
+ let file = entry.to_file(ntfs, &mut info.fs)?;
+ Ok(file)
+ } else {
+ bail!("No such file or directory \"{}\".", arg)
+ }
+ }
+}
diff --git a/examples/ntfs-shell/sector_reader.rs b/examples/ntfs-shell/sector_reader.rs
new file mode 100644
index 0000000..690d3ed
--- /dev/null
+++ b/examples/ntfs-shell/sector_reader.rs
@@ -0,0 +1,130 @@
+// Copyright 2021 Colin Finck <colin@reactos.org>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use std::io;
+use std::io::{Read, Seek, SeekFrom};
+
+/// `SectorReader` encapsulates any reader and only performs read and seek operations on it
+/// on boundaries of the given sector size.
+///
+/// This can be very useful for readers that only accept sector-sized reads (like reading
+/// from a raw partition on Windows).
+/// The sector size must be a power of two.
+///
+/// This reader does not keep any buffer.
+/// You are advised to encapsulate `SectorReader` in a buffered reader, as unbuffered reads of
+/// just a few bytes here and there are highly inefficient.
+pub struct SectorReader<R>
+where
+ R: Read + Seek,
+{
+ /// The inner reader stream.
+ inner: R,
+ /// The sector size set at creation.
+ sector_size: usize,
+ /// The current stream position as requested by the caller through `read` or `seek`.
+ /// The implementation will internally make sure to only read/seek on sector boundaries.
+ stream_position: u64,
+ /// This buffer is only part of the struct as a small performance optimization (keeping it allocated between reads).
+ temp_buf: Vec<u8>,
+}
+
+impl<R> SectorReader<R>
+where
+ R: Read + Seek,
+{
+ pub fn new(inner: R, sector_size: usize) -> io::Result<Self> {
+ if !sector_size.is_power_of_two() {
+ return Err(io::Error::new(
+ io::ErrorKind::Other,
+ "sector_size is not a power of two",
+ ));
+ }
+
+ Ok(Self {
+ inner,
+ sector_size,
+ stream_position: 0,
+ temp_buf: Vec::new(),
+ })
+ }
+
+ fn align_down_to_sector_size(&self, n: u64) -> u64 {
+ n / self.sector_size as u64 * self.sector_size as u64
+ }
+
+ fn align_up_to_sector_size(&self, n: u64) -> u64 {
+ self.align_down_to_sector_size(n) + self.sector_size as u64
+ }
+}
+
+impl<R> Read for SectorReader<R>
+where
+ R: Read + Seek,
+{
+ fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
+ // We can only read from a sector boundary, and `self.stream_position` specifies the position where the
+ // caller thinks we are.
+ // Align down to a sector boundary to determine the position where we really are (see our `seek` implementation).
+ let aligned_position = self.align_down_to_sector_size(self.stream_position);
+
+ // We have to read more bytes now to make up for the alignment difference.
+ // We can also only read in multiples of the sector size, so align up to the next sector boundary.
+ let start = (self.stream_position - aligned_position) as usize;
+ let end = start + buf.len();
+ let aligned_bytes_to_read = self.align_up_to_sector_size(end as u64) as usize;
+
+ // Perform the sector-sized read and copy the actually requested bytes into the given buffer.
+ self.temp_buf.resize(aligned_bytes_to_read, 0);
+ self.inner.read_exact(&mut self.temp_buf)?;
+ buf.copy_from_slice(&self.temp_buf[start..end]);
+
+ // We are done.
+ self.stream_position += buf.len() as u64;
+ Ok(buf.len())
+ }
+}
+
+impl<R> Seek for SectorReader<R>
+where
+ R: Read + Seek,
+{
+ fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
+ let new_pos = match pos {
+ SeekFrom::Start(n) => Some(n),
+ SeekFrom::End(_n) => {
+ // This is unsupported, because it's not safely possible under Windows.
+ // We cannot seek to the end to determine the raw partition size.
+ // Which makes it impossible to set `self.stream_position`.
+ return Err(io::Error::new(
+ io::ErrorKind::Other,
+ "SeekFrom::End is unsupported for SectorReader",
+ ));
+ }
+ SeekFrom::Current(n) => {
+ if n >= 0 {
+ self.stream_position.checked_add(n as u64)
+ } else {
+ self.stream_position.checked_sub(n.wrapping_neg() as u64)
+ }
+ }
+ };
+
+ match new_pos {
+ Some(n) => {
+ // We can only seek on sector boundaries, so align down the requested seek position and seek to that.
+ let aligned_n = self.align_down_to_sector_size(n);
+ self.inner.seek(SeekFrom::Start(aligned_n))?;
+
+ // Make the caller believe that we seeked to the actually requested position.
+ // Our `read` implementation will cover the difference.
+ self.stream_position = n;
+ Ok(self.stream_position)
+ }
+ None => Err(io::Error::new(
+ io::ErrorKind::InvalidInput,
+ "invalid seek to a negative or overflowing position",
+ )),
+ }
+ }
+}