massive error overhaul

* fully stop using ancient `failure` crate in favor of own error type
* set an `ErrorKind` on everything
This commit is contained in:
Scott Lamb
2023-07-09 22:04:17 -07:00
parent 6a5b751bd6
commit 64ca096ff3
54 changed files with 1493 additions and 1108 deletions

View File

@@ -4,9 +4,9 @@
//! Subcommand to check the database and sample file dir for errors.
use base::Error;
use bpaf::Bpaf;
use db::check;
use failure::Error;
use std::path::PathBuf;
/// Checks database integrity (like fsck).

View File

@@ -4,11 +4,11 @@
use crate::stream::{self, Opener};
use base::strutil::{decode_size, encode_size};
use base::{bail, err, Error};
use cursive::traits::{Finder, Nameable, Resizable, Scrollable};
use cursive::views::{self, Dialog, ViewRef};
use cursive::Cursive;
use db::writer;
use failure::{bail, format_err, Error, ResultExt};
use itertools::Itertools;
use std::collections::BTreeMap;
use std::str::FromStr;
@@ -123,21 +123,31 @@ fn parse_url(
if raw.is_empty() {
return Ok(None);
}
let url = url::Url::parse(raw)
.with_context(|_| format!("can't parse {} {:?} as URL", field_name, &raw))?;
let url = url::Url::parse(raw).map_err(|_| {
err!(
InvalidArgument,
msg("can't parse {field_name} {raw:?} as URL")
)
})?;
if !allowed_schemes.iter().any(|scheme| *scheme == url.scheme()) {
bail!(
"Unexpected scheme in {} {:?}; should be one of: {}",
field_name,
url.as_str(),
allowed_schemes.iter().join(", ")
InvalidArgument,
msg(
"unexpected scheme in {} {:?}; should be one of: {}",
field_name,
url.as_str(),
allowed_schemes.iter().join(", "),
),
);
}
if !url.username().is_empty() || url.password().is_some() {
bail!(
"Unexpected credentials in {} {:?}; use the username and password fields instead",
field_name,
url.as_str()
InvalidArgument,
msg(
"unexpected credentials in {} {:?}; use the username and password fields instead",
field_name,
url.as_str(),
),
);
}
Ok(Some(url))
@@ -166,8 +176,8 @@ fn press_edit(siv: &mut Cursive, db: &Arc<db::Database>, id: Option<i32>) {
let type_ = db::StreamType::from_index(i).unwrap();
if stream.record && (stream.url.is_empty() || stream.sample_file_dir_id.is_none()) {
bail!(
"Can't record {} stream without RTSP URL and sample file directory",
type_.as_str()
InvalidArgument,
msg("can't record {type_} stream without RTSP URL and sample file directory"),
);
}
let stream_change = &mut change.streams[i];
@@ -184,9 +194,9 @@ fn press_edit(siv: &mut Cursive, db: &Arc<db::Database>, id: Option<i32>) {
0
} else {
stream.flush_if_sec.parse().map_err(|_| {
format_err!(
"flush_if_sec for {} must be a non-negative integer",
type_.as_str()
err!(
InvalidArgument,
msg("flush_if_sec for {type_} must be a non-negative integer"),
)
})?
};

View File

@@ -3,12 +3,12 @@
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
use base::strutil::{decode_size, encode_size};
use base::Error;
use cursive::traits::{Nameable, Resizable};
use cursive::view::Scrollable;
use cursive::Cursive;
use cursive::{views, With};
use db::writer;
use failure::Error;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::path::Path;

View File

@@ -8,10 +8,10 @@
//! configuration will likely be almost entirely done through a web-based UI.
use base::clock;
use base::Error;
use bpaf::Bpaf;
use cursive::views;
use cursive::Cursive;
use failure::Error;
use std::path::PathBuf;
use std::sync::Arc;

View File

@@ -2,8 +2,8 @@
// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
use base::Error;
use bpaf::Bpaf;
use failure::Error;
use std::path::PathBuf;
use tracing::info;

View File

@@ -5,9 +5,9 @@
//! Subcommand to login a user (without requiring a password).
use base::clock::{self, Clocks};
use base::{bail, err, Error};
use bpaf::Bpaf;
use db::auth::SessionFlag;
use failure::{format_err, Error};
use std::io::Write as _;
use std::os::unix::fs::OpenOptionsExt as _;
use std::path::PathBuf;
@@ -69,9 +69,9 @@ pub fn run(args: Args) -> Result<i32, Error> {
let (_db_dir, conn) = super::open_conn(&args.db_dir, super::OpenMode::ReadWrite)?;
let db = std::sync::Arc::new(db::Database::new(clocks, conn, true).unwrap());
let mut l = db.lock();
let u = l
.get_user(&args.username)
.ok_or_else(|| format_err!("no such user {:?}", &args.username))?;
let Some(u) = l.get_user(&args.username) else {
bail!(NotFound, msg("no such user {:?}", &args.username));
};
let permissions = args
.permissions
.map(db::Permissions::from)
@@ -101,13 +101,13 @@ pub fn run(args: Args) -> Result<i32, Error> {
let d = args
.domain
.as_ref()
.ok_or_else(|| format_err!("--curl-cookie-jar requires --domain"))?;
.ok_or_else(|| err!(InvalidArgument, msg("--curl-cookie-jar requires --domain")))?;
let mut f = std::fs::OpenOptions::new()
.write(true)
.create_new(true)
.mode(0o600)
.open(p)
.map_err(|e| format_err!("Unable to open {}: {}", p.display(), e))?;
.map_err(|e| err!(e, msg("unable to open {}", p.display())))?;
write!(
&mut f,
"# Netscape HTTP Cookie File\n\

View File

@@ -2,8 +2,8 @@
// Copyright (C) 2016 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
use base::{err, Error};
use db::dir;
use failure::{Error, Fail};
use nix::fcntl::FlockArg;
use std::path::Path;
use tracing::info;
@@ -28,16 +28,19 @@ enum OpenMode {
/// The returned `dir::Fd` holds the lock and should be kept open as long as the `Connection` is.
fn open_dir(db_dir: &Path, mode: OpenMode) -> Result<dir::Fd, Error> {
let dir = dir::Fd::open(db_dir, mode == OpenMode::Create).map_err(|e| {
e.context(if mode == OpenMode::Create {
format!("unable to create db dir {}", db_dir.display())
if mode == OpenMode::Create {
err!(e, msg("unable to create db dir {}", db_dir.display()))
} else if e == nix::Error::ENOENT {
format!(
"db dir {} not found; try running moonfire-nvr init",
db_dir.display()
err!(
NotFound,
msg(
"db dir {} not found; try running moonfire-nvr init",
db_dir.display(),
),
)
} else {
format!("unable to open db dir {}", db_dir.display())
})
err!(e, msg("unable to open db dir {}", db_dir.display()))
}
})?;
let ro = mode == OpenMode::ReadOnly;
dir.lock(if ro {
@@ -46,11 +49,14 @@ fn open_dir(db_dir: &Path, mode: OpenMode) -> Result<dir::Fd, Error> {
FlockArg::LockExclusiveNonblock
})
.map_err(|e| {
e.context(format!(
"unable to get {} lock on db dir {} ",
if ro { "shared" } else { "exclusive" },
db_dir.display()
))
err!(
e,
msg(
"unable to get {} lock on db dir {} ",
if ro { "shared" } else { "exclusive" },
db_dir.display(),
),
)
})?;
Ok(dir)
}

View File

@@ -6,9 +6,10 @@ use crate::streamer;
use crate::web;
use crate::web::accept::Listener;
use base::clock;
use base::err;
use base::{bail, Error};
use bpaf::Bpaf;
use db::{dir, writer};
use failure::{bail, Error, ResultExt};
use fnv::FnvHashMap;
use hyper::service::{make_service_fn, service_fn};
use retina::client::SessionGroup;
@@ -76,7 +77,10 @@ fn resolve_zone() -> Result<String, Error> {
}
if p != LOCALTIME_PATH {
bail!("Unable to resolve env TZ={} to a timezone.", &tz);
bail!(
FailedPrecondition,
msg("unable to resolve env TZ={tz} to a timezone")
);
}
}
@@ -86,21 +90,23 @@ fn resolve_zone() -> Result<String, Error> {
Ok(localtime_dest) => {
let localtime_dest = match localtime_dest.to_str() {
Some(d) => d,
None => bail!("{} symlink destination is invalid UTF-8", LOCALTIME_PATH),
None => bail!(
FailedPrecondition,
msg("{LOCALTIME_PATH} symlink destination is invalid UTF-8")
),
};
if let Some(p) = zoneinfo_name(localtime_dest) {
return Ok(p.to_owned());
}
bail!(
"Unable to resolve {} symlink destination {} to a timezone.",
LOCALTIME_PATH,
&localtime_dest
FailedPrecondition,
msg("unable to resolve {LOCALTIME_PATH} symlink destination {localtime_dest} to a timezone"),
);
}
Err(e) => {
use ::std::io::ErrorKind;
if e.kind() != ErrorKind::NotFound && e.kind() != ErrorKind::InvalidInput {
bail!("Unable to read {} symlink: {}", LOCALTIME_PATH, e);
bail!(e, msg("unable to read {LOCALTIME_PATH} symlink"));
}
}
};
@@ -110,10 +116,8 @@ fn resolve_zone() -> Result<String, Error> {
Ok(z) => Ok(z.trim().to_owned()),
Err(e) => {
bail!(
"Unable to resolve timezone from TZ env, {}, or {}. Last error: {}",
LOCALTIME_PATH,
TIMEZONE_PATH,
e
e,
msg("unable to resolve timezone from TZ env, {LOCALTIME_PATH}, or {TIMEZONE_PATH}"),
);
}
}
@@ -127,15 +131,18 @@ struct Syncer {
fn read_config(path: &Path) -> Result<ConfigFile, Error> {
let config = std::fs::read(path)?;
let config = toml::from_slice(&config)?;
let config = toml::from_slice(&config).map_err(|e| err!(InvalidArgument, source(e)))?;
Ok(config)
}
pub fn run(args: Args) -> Result<i32, Error> {
let config = read_config(&args.config).with_context(|_| {
format!(
"Unable to load config file {}. See documentation in ref/config.md.",
&args.config.display()
let config = read_config(&args.config).map_err(|e| {
err!(
e,
msg(
"unable to load config file {}; see documentation in ref/config.md",
&args.config.display(),
),
)
})?;
@@ -180,8 +187,8 @@ async fn async_run(read_only: bool, config: &ConfigFile) -> Result<i32, Error> {
}
tokio::select! {
_ = int.recv() => bail!("immediate shutdown due to second signal (SIGINT)"),
_ = term.recv() => bail!("immediate shutdown due to second singal (SIGTERM)"),
_ = int.recv() => bail!(Cancelled, msg("immediate shutdown due to second signal (SIGINT)")),
_ = term.recv() => bail!(Cancelled, msg("immediate shutdown due to second singal (SIGTERM)")),
result = &mut inner => result,
}
}
@@ -213,17 +220,16 @@ fn make_listener(addr: &config::AddressConfig) -> Result<Listener, Error> {
config::AddressConfig::Ipv6(a) => (*a).into(),
config::AddressConfig::Unix(p) => {
prepare_unix_socket(p);
return Ok(Listener::Unix(
tokio::net::UnixListener::bind(p)
.with_context(|_| format!("unable bind Unix socket {}", p.display()))?,
));
return Ok(Listener::Unix(tokio::net::UnixListener::bind(p).map_err(
|e| err!(e, msg("unable bind Unix socket {}", p.display())),
)?));
}
};
// Go through std::net::TcpListener to avoid needing async. That's there for DNS resolution,
// but it's unnecessary when starting from a SocketAddr.
let listener = std::net::TcpListener::bind(sa)
.with_context(|_| format!("unable to bind TCP socket {}", &sa))?;
.map_err(|e| err!(e, msg("unable to bind TCP socket {sa}")))?;
listener.set_nonblocking(true)?;
Ok(Listener::Tcp(tokio::net::TcpListener::from_std(listener)?))
}
@@ -419,13 +425,16 @@ async fn inner(
}
}
})
.await?;
.await
.map_err(|e| err!(Unknown, source(e)))?;
db.lock().clear_watches();
info!("Waiting for HTTP requests to finish.");
for h in web_handles {
h.await??;
h.await
.map_err(|e| err!(Unknown, source(e)))?
.map_err(|e| err!(Unknown, source(e)))?;
}
info!("Waiting for TEARDOWN requests to complete.");

View File

@@ -5,8 +5,8 @@
//! Subcommand to run a SQLite shell.
use super::OpenMode;
use base::Error;
use bpaf::Bpaf;
use failure::Error;
use std::ffi::OsString;
use std::os::unix::process::CommandExt;
use std::path::PathBuf;

View File

@@ -2,8 +2,8 @@
// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
use base::Error;
use bpaf::Bpaf;
use failure::Error;
/// Translates between integer and human-readable timestamps.
#[derive(Bpaf, Debug)]

View File

@@ -2,11 +2,11 @@
// Copyright (C) 2020 The Moonfire NVR Authors; see AUTHORS and LICENSE.txt.
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
use bpaf::Bpaf;
/// Upgrades the database schema.
///
/// See `guide/schema.md` for more information.
use failure::Error;
use base::Error;
use bpaf::Bpaf;
/// Upgrades to the latest database schema.
#[derive(Bpaf, Debug)]