overhaul error messages

Inspired by the poor error message here:
https://github.com/scottlamb/moonfire-nvr/issues/107#issuecomment-777587727

*   print the friendlier Display version of the error rather than Debug.
    Eg, "EROFS: Read-only filesystem" rather than "Sys(EROFS)". Do this
    everywhere: on command exit, on syncer retries, and on stream
    retries.
*   print the most immediate problem and additional lines for each
    cause.
*   print the backtrace or an advertisement for RUST_BACKTRACE=1 if it's
    unavailable.
*   also mention RUST_BACKTRACE=1 in the troubleshooting guide.
*   add context in various places, including pathnames. There are surely
    many places more it'd be helpful, but this is a start.
*   allow subcommands to return failure without an Error.
    In particular, "moonfire-nvr check" does its own error printing
    because it wants to print all the errors it finds. Printing "see
    earlier errors" with a meaningless stack trace seems like it'd just
    confuse. But I also want to get rid of the misleading "Success" at
    the end and 0 return to the OS.
This commit is contained in:
Scott Lamb
2021-02-11 10:45:56 -08:00
parent ff1615a0b4
commit 9a5957d5ef
19 changed files with 101 additions and 55 deletions

View File

@@ -47,13 +47,16 @@ pub struct Options {
pub compare_lens: bool,
}
pub fn run(conn: &rusqlite::Connection, opts: &Options) -> Result<(), Error> {
pub fn run(conn: &rusqlite::Connection, opts: &Options) -> Result<i32, Error> {
let mut printed_error = false;
// Compare schemas.
{
let mut expected = rusqlite::Connection::open_in_memory()?;
db::init(&mut expected)?;
if let Some(diffs) = compare::get_diffs("actual", conn, "expected", &expected)? {
println!("{}", &diffs);
error!("Schema is not as expected:\n{}", &diffs);
printed_error = true;
} else {
println!("Schema is as expected.");
}
@@ -88,7 +91,8 @@ pub fn run(conn: &rusqlite::Connection, opts: &Options) -> Result<(), Error> {
}
// Open the directory (checking its metadata) and hold it open (for the lock).
let dir = dir::SampleFileDir::open(&dir_path, &meta)?;
let dir = dir::SampleFileDir::open(&dir_path, &meta)
.map_err(|e| e.context(format!("unable to open dir {}", dir_path)))?;
let mut streams = read_dir(&dir, opts)?;
let mut rows = garbage_stmt.query(params![dir_id])?;
while let Some(row) = rows.next()? {
@@ -113,7 +117,7 @@ pub fn run(conn: &rusqlite::Connection, opts: &Options) -> Result<(), Error> {
None => Stream::default(),
Some(d) => d.remove(&stream_id).unwrap_or_else(Stream::default),
};
compare_stream(conn, stream_id, opts, stream)?;
printed_error |= compare_stream(conn, stream_id, opts, stream)?;
}
}
@@ -125,12 +129,13 @@ pub fn run(conn: &rusqlite::Connection, opts: &Options) -> Result<(), Error> {
if r.recording_row.is_some() || r.playback_row.is_some() ||
r.integrity_row || !r.garbage_row {
error!("dir {} recording {} for unknown stream: {:#?}", dir_id, id, r);
printed_error = true;
}
}
}
}
Ok(())
Ok(if printed_error { 1 } else { 0 })
}
#[derive(Debug, Eq, PartialEq)]
@@ -217,9 +222,10 @@ fn read_dir(d: &dir::SampleFileDir, opts: &Options) -> Result<Dir, Error> {
/// Looks through a known stream for errors.
fn compare_stream(conn: &rusqlite::Connection, stream_id: i32, opts: &Options,
mut stream: Stream) -> Result<(), Error> {
mut stream: Stream) -> Result<bool, Error> {
let start = CompositeId::new(stream_id, 0);
let end = CompositeId::new(stream_id, i32::max_value());
let mut printed_error = false;
// recording row.
{
@@ -271,6 +277,7 @@ fn compare_stream(conn: &rusqlite::Connection, stream_id: i32, opts: &Options,
Ok(s) => s,
Err(e) => {
error!("id {} has bad video_index: {}", id, e);
printed_error = true;
continue;
},
};
@@ -307,6 +314,7 @@ fn compare_stream(conn: &rusqlite::Connection, stream_id: i32, opts: &Options,
if !recording.garbage_row || recording.playback_row.is_some() ||
recording.integrity_row {
error!("Missing recording row for {}: {:#?}", id, recording);
printed_error = true;
}
continue;
},
@@ -315,17 +323,25 @@ fn compare_stream(conn: &rusqlite::Connection, stream_id: i32, opts: &Options,
Some(ref p) => {
if r != p {
error!("Recording {} summary doesn't match video_index: {:#?}", id, recording);
printed_error = true;
}
},
None => error!("Recording {} missing playback row: {:#?}", id, recording),
None => {
error!("Recording {} missing playback row: {:#?}", id, recording);
printed_error = true;
},
}
match recording.file {
Some(len) => if opts.compare_lens && r.bytes != len {
error!("Recording {} length mismatch: {:#?}", id, recording);
printed_error = true;
},
None => {
error!("Recording {} missing file: {:#?}", id, recording);
printed_error = true;
},
None => error!("Recording {} missing file: {:#?}", id, recording),
}
}
Ok(())
Ok(printed_error)
}

View File

@@ -1213,7 +1213,8 @@ impl LockedDatabase {
open.id = o.id;
open.uuid.extend_from_slice(&o.uuid.as_bytes()[..]);
}
let d = dir::SampleFileDir::open(&dir.path, &meta)?;
let d = dir::SampleFileDir::open(&dir.path, &meta)
.map_err(|e| e.context(format!("Failed to open dir {}", dir.path)))?;
if self.open.is_none() { // read-only mode; it's already fully opened.
dir.dir = Some(d);
} else { // read-write mode; there are more steps to do.

View File

@@ -151,7 +151,7 @@ pub(crate) fn read_meta(dir: &Fd) -> Result<schema::DirMeta, Error> {
}
let data = &data[pos..pos+len as usize];
let mut s = protobuf::CodedInputStream::from_bytes(&data);
meta.merge_from(&mut s).map_err(|e| e.context("Unable to parse metadata proto: {}"))?;
meta.merge_from(&mut s).map_err(|e| e.context("Unable to parse metadata proto"))?;
Ok(meta)
}
@@ -164,17 +164,18 @@ pub(crate) fn write_meta(dirfd: RawFd, meta: &schema::DirMeta) -> Result<(), Err
}
data.resize(FIXED_DIR_META_LEN, 0); // pad to required length.
let mut f = crate::fs::openat(dirfd, cstr!("meta"), OFlag::O_CREAT | OFlag::O_WRONLY,
Mode::S_IRUSR | Mode::S_IWUSR)?;
let stat = f.metadata()?;
Mode::S_IRUSR | Mode::S_IWUSR)
.map_err(|e| e.context("Unable to open meta file"))?;
let stat = f.metadata().map_err(|e| e.context("Unable to stat meta file"))?;
if stat.len() == 0 {
// Need to sync not only the data but also the file metadata and dirent.
f.write_all(&data)?;
f.sync_all()?;
nix::unistd::fsync(dirfd)?;
f.write_all(&data).map_err(|e| e.context("Unable to write to meta file"))?;
f.sync_all().map_err(|e| e.context("Unable to sync meta file"))?;
nix::unistd::fsync(dirfd).map_err(|e| e.context("Unable to sync dir"))?;
} else if stat.len() == FIXED_DIR_META_LEN as u64 {
// Just syncing the data will suffice; existing metadata and dirent are fine.
f.write_all(&data)?;
f.sync_data()?;
f.write_all(&data).map_err(|e| e.context("Unable to write to meta file"))?;
f.sync_data().map_err(|e| e.context("Unable to sync meta file"))?;
} else {
bail!("Existing meta file is {}-byte; expected {}", stat.len(), FIXED_DIR_META_LEN);
}
@@ -194,8 +195,8 @@ impl SampleFileDir {
FlockArg::LockExclusiveNonblock
} else {
FlockArg::LockSharedNonblock
})?;
let dir_meta = read_meta(&s.fd)?;
}).map_err(|e| e.context(format!("unable to lock dir {}", path)))?;
let dir_meta = read_meta(&s.fd).map_err(|e| e.context("unable to read meta file"))?;
if !SampleFileDir::consistent(db_meta, &dir_meta) {
let serialized =
db_meta.write_length_delimited_to_bytes().expect("proto3->vec is infallible");
@@ -230,7 +231,8 @@ impl SampleFileDir {
pub(crate) fn create(path: &str, db_meta: &schema::DirMeta)
-> Result<Arc<SampleFileDir>, Error> {
let s = SampleFileDir::open_self(path, true)?;
s.fd.lock(FlockArg::LockExclusiveNonblock)?;
s.fd.lock(FlockArg::LockExclusiveNonblock)
.map_err(|e| e.context(format!("unable to lock dir {}", path)))?;
let old_meta = read_meta(&s.fd)?;
// Verify metadata. We only care that it hasn't been completely opened.
@@ -265,8 +267,7 @@ impl SampleFileDir {
}
fn open_self(path: &str, create: bool) -> Result<Arc<SampleFileDir>, Error> {
let fd = Fd::open(path, create)
.map_err(|e| format_err!("unable to open sample file dir {}: {}", path, e))?;
let fd = Fd::open(path, create)?;
Ok(Arc::new(SampleFileDir {
fd,
}))

View File

@@ -146,7 +146,8 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
}
let dir = dir::Fd::open(path, false)?;
dir.lock(FlockArg::LockExclusiveNonblock)?;
dir.lock(FlockArg::LockExclusiveNonblock)
.map_err(|e| e.context(format!("unable to lock dir {}", path)))?;
let mut need_sync = maybe_upgrade_meta(&dir, &db_meta)?;
if maybe_cleanup_garbage_uuids(&dir)? {