diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2020-09-15 11:01:15 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2020-09-15 11:11:39 +0200 |
commit | 5544c6cb96aab1c61489bb1c1d45743650c79436 (patch) | |
tree | c42affed36debb6bb9a6d3641a74520c50a636a2 /buffered-reader/src | |
parent | 3a3eb40cebad2f9f9f4d59af2f5b86bb0b7b8a9d (diff) |
buffered-reader: Track file paths and display them in errors.
- Fixes #548.
Diffstat (limited to 'buffered-reader/src')
-rw-r--r-- | buffered-reader/src/file_error.rs | 35 | ||||
-rw-r--r-- | buffered-reader/src/file_generic.rs | 34 | ||||
-rw-r--r-- | buffered-reader/src/file_unix.rs | 47 | ||||
-rw-r--r-- | buffered-reader/src/lib.rs | 3 |
4 files changed, 102 insertions, 17 deletions
diff --git a/buffered-reader/src/file_error.rs b/buffered-reader/src/file_error.rs new file mode 100644 index 00000000..eba444cd --- /dev/null +++ b/buffered-reader/src/file_error.rs @@ -0,0 +1,35 @@ +/// Common error type for file operations. + +use std::error::Error; +use std::fmt; +use std::io; +use std::path::{Path, PathBuf}; + +/// Common error type for file operations. +#[derive(Debug)] +pub(crate) struct FileError { + path: PathBuf, + source: io::Error, +} + +impl FileError { + /// Returns a new `io::Error` backed by a `FileError`. + pub fn new<P: AsRef<Path>>(path: P, source: io::Error) -> io::Error { + io::Error::new(source.kind(), FileError { + path: path.as_ref().into(), + source, + }) + } +} + +impl fmt::Display for FileError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Reading {:?}: {}", self.path.display(), self.source) + } +} + +impl Error for FileError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(&self.source) + } +} diff --git a/buffered-reader/src/file_generic.rs b/buffered-reader/src/file_generic.rs index d1eb20e1..0a073165 100644 --- a/buffered-reader/src/file_generic.rs +++ b/buffered-reader/src/file_generic.rs @@ -1,19 +1,20 @@ use std::fmt; use std::fs; use std::io; -use std::path::Path; +use std::path::{Path, PathBuf}; use super::*; +use crate::file_error::FileError; /// Wraps files. /// /// This is a generic implementation that may be replaced by /// platform-specific versions. -pub struct File<C>(Generic<fs::File, C>); +pub struct File<C>(Generic<fs::File, C>, PathBuf); impl<C> fmt::Display for File<C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "File") + write!(f, "File {:?}", self.1.display()) } } @@ -21,6 +22,7 @@ impl<C> fmt::Debug for File<C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("File") .field(&self.0) + .field(&self.1) .finish() } } @@ -35,14 +37,16 @@ impl File<()> { impl<C> File<C> { /// Like `open()`, but sets a cookie. pub fn with_cookie<P: AsRef<Path>>(path: P, cookie: C) -> io::Result<Self> { - Ok(File(Generic::with_cookie(fs::File::open(path)?, - None, cookie))) + let path = path.as_ref(); + let file = fs::File::open(path).map_err(|e| FileError::new(path, e))?; + Ok(File(Generic::with_cookie(file, None, cookie), path.into())) } } impl<C> io::Read for File<C> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { self.0.read(buf) + .map_err(|e| FileError::new(&self.1, e)) } } @@ -52,11 +56,15 @@ impl<C> BufferedReader<C> for File<C> { } fn data(&mut self, amount: usize) -> io::Result<&[u8]> { + let path = &self.1; self.0.data(amount) + .map_err(|e| FileError::new(path, e)) } fn data_hard(&mut self, amount: usize) -> io::Result<&[u8]> { + let path = &self.1; self.0.data_hard(amount) + .map_err(|e| FileError::new(path, e)) } fn consume(&mut self, amount: usize) -> &[u8] { @@ -64,11 +72,15 @@ impl<C> BufferedReader<C> for File<C> { } fn data_consume(&mut self, amount: usize) -> io::Result<&[u8]> { + let path = &self.1; self.0.data_consume(amount) + .map_err(|e| FileError::new(path, e)) } fn data_consume_hard(&mut self, amount: usize) -> io::Result<&[u8]> { + let path = &self.1; self.0.data_consume_hard(amount) + .map_err(|e| FileError::new(path, e)) } fn get_mut(&mut self) -> Option<&mut dyn BufferedReader<C>> { @@ -96,3 +108,15 @@ impl<C> BufferedReader<C> for File<C> { self.0.cookie_mut() } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn error_contains_path() { + let p = "/i/do/not/exist"; + let e = File::open(p).unwrap_err(); + assert!(e.to_string().contains(p)); + } +} diff --git a/buffered-reader/src/file_unix.rs b/buffered-reader/src/file_unix.rs index 2162f7d1..7bc8b855 100644 --- a/buffered-reader/src/file_unix.rs +++ b/buffered-reader/src/file_unix.rs @@ -10,10 +10,11 @@ use std::fs; use std::io; use std::os::unix::io::AsRawFd; use std::slice; -use std::path::Path; +use std::path::{Path, PathBuf}; use std::ptr; use super::*; +use crate::file_error::FileError; // For small files, the overhead of manipulating the page table is not // worth the gain. This threshold has been chosen so that on my @@ -24,11 +25,11 @@ const MMAP_THRESHOLD: u64 = 16 * 4096; /// /// This implementation tries to mmap the file, falling back to /// just using a generic reader. -pub struct File<'a, C>(Imp<'a, C>); +pub struct File<'a, C>(Imp<'a, C>, PathBuf); impl<'a, C> fmt::Display for File<'a, C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) + write!(f, "{} {:?}", self.0, self.1.display()) } } @@ -36,6 +37,7 @@ impl<'a, C> fmt::Debug for File<'a, C> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("File") .field(&self.0) + .field(&self.1) .finish() } } @@ -100,14 +102,17 @@ impl<'a> File<'a, ()> { impl<'a, C> File<'a, C> { /// Like `open()`, but sets a cookie. pub fn with_cookie<P: AsRef<Path>>(path: P, cookie: C) -> io::Result<Self> { + let path = path.as_ref(); + // As fallback, we use a generic reader. let generic = |file, cookie| { Ok(File( Imp::Generic( - Generic::with_cookie(file, None, cookie)))) + Generic::with_cookie(file, None, cookie)), + path.into())) }; - let file = fs::File::open(path)?; + let file = fs::File::open(path).map_err(|e| FileError::new(path, e))?; // For testing and benchmarking purposes, we use the variable // SEQUOIA_DONT_MMAP to turn off mmapping. @@ -115,7 +120,8 @@ impl<'a, C> File<'a, C> { return generic(file, cookie); } - let length = file.metadata()?.len(); + let length = + file.metadata().map_err(|e| FileError::new(path, e))?.len(); // For small files, the overhead of manipulating the page // table is not worth the gain. @@ -147,7 +153,8 @@ impl<'a, C> File<'a, C> { addr, length, reader: Memory::with_cookie(slice, cookie), - } + }, + path.into(), )) } } @@ -157,7 +164,7 @@ impl<'a, C> io::Read for File<'a, C> { match self.0 { Imp::Generic(ref mut reader) => reader.read(buf), Imp::MMAP { ref mut reader, .. } => reader.read(buf), - } + }.map_err(|e| FileError::new(&self.1, e)) } } @@ -170,17 +177,19 @@ impl<'a, C> BufferedReader<C> for File<'a, C> { } fn data(&mut self, amount: usize) -> io::Result<&[u8]> { + let path = &self.1; match self.0 { Imp::Generic(ref mut reader) => reader.data(amount), Imp::MMAP { ref mut reader, .. } => reader.data(amount), - } + }.map_err(|e| FileError::new(path, e)) } fn data_hard(&mut self, amount: usize) -> io::Result<&[u8]> { + let path = &self.1; match self.0 { Imp::Generic(ref mut reader) => reader.data_hard(amount), Imp::MMAP { ref mut reader, .. } => reader.data_hard(amount), - } + }.map_err(|e| FileError::new(path, e)) } fn consume(&mut self, amount: usize) -> &[u8] { @@ -191,17 +200,19 @@ impl<'a, C> BufferedReader<C> for File<'a, C> { } fn data_consume(&mut self, amount: usize) -> io::Result<&[u8]> { + let path = &self.1; match self.0 { Imp::Generic(ref mut reader) => reader.data_consume(amount), Imp::MMAP { ref mut reader, .. } => reader.data_consume(amount), - } + }.map_err(|e| FileError::new(path, e)) } fn data_consume_hard(&mut self, amount: usize) -> io::Result<&[u8]> { + let path = &self.1; match self.0 { Imp::Generic(ref mut reader) => reader.data_consume_hard(amount), Imp::MMAP { ref mut reader, .. } => reader.data_consume_hard(amount), - } + }.map_err(|e| FileError::new(path, e)) } fn get_mut(&mut self) -> Option<&mut dyn BufferedReader<C>> { @@ -238,3 +249,15 @@ impl<'a, C> BufferedReader<C> for File<'a, C> { } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn error_contains_path() { + let p = "/i/do/not/exist"; + let e = File::open(p).unwrap_err(); + assert!(e.to_string().contains(p)); + } +} diff --git a/buffered-reader/src/lib.rs b/buffered-reader/src/lib.rs index 7c5ec322..32765d74 100644 --- a/buffered-reader/src/lib.rs +++ b/buffered-reader/src/lib.rs @@ -259,6 +259,9 @@ pub use self::decompress_deflate::Zlib; #[cfg(feature = "compression-bzip2")] pub use self::decompress_bzip2::Bzip; +// Common error type for file operations. +mod file_error; + // These are the different File implementations. We // include the modules unconditionally, so that we catch bitrot early. #[allow(dead_code)] |