summaryrefslogtreecommitdiffstats
path: root/buffered-reader
diff options
context:
space:
mode:
authorJustus Winter <justus@sequoia-pgp.org>2020-09-15 11:01:15 +0200
committerJustus Winter <justus@sequoia-pgp.org>2020-09-15 11:11:39 +0200
commit5544c6cb96aab1c61489bb1c1d45743650c79436 (patch)
treec42affed36debb6bb9a6d3641a74520c50a636a2 /buffered-reader
parent3a3eb40cebad2f9f9f4d59af2f5b86bb0b7b8a9d (diff)
buffered-reader: Track file paths and display them in errors.
- Fixes #548.
Diffstat (limited to 'buffered-reader')
-rw-r--r--buffered-reader/src/file_error.rs35
-rw-r--r--buffered-reader/src/file_generic.rs34
-rw-r--r--buffered-reader/src/file_unix.rs47
-rw-r--r--buffered-reader/src/lib.rs3
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)]