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

@@ -6,8 +6,8 @@
//!
//! See `guide/schema.md` for more information.
use crate::db;
use failure::{bail, Error};
use crate::db::{self, EXPECTED_VERSION};
use base::{bail, Error};
use nix::NixPath;
use rusqlite::params;
use std::ffi::CStr;
@@ -60,14 +60,16 @@ fn upgrade(args: &Args, target_ver: i32, conn: &mut rusqlite::Connection) -> Res
{
assert_eq!(upgraders.len(), db::EXPECTED_VERSION as usize);
let old_ver = conn.query_row("select max(id) from version", params![], |row| row.get(0))?;
if old_ver > db::EXPECTED_VERSION {
if old_ver > EXPECTED_VERSION {
bail!(
"Database is at version {}, later than expected {}",
old_ver,
db::EXPECTED_VERSION
FailedPrecondition,
msg("database is at version {old_ver}, later than expected {EXPECTED_VERSION}"),
);
} else if old_ver < 0 {
bail!("Database is at negative version {}!", old_ver);
bail!(
FailedPrecondition,
msg("Database is at negative version {old_ver}!")
);
}
info!(
"Upgrading database from version {} to version {}...",
@@ -95,7 +97,7 @@ pub fn run(args: &Args, conn: &mut rusqlite::Connection) -> Result<(), Error> {
db::check_sqlite_version()?;
db::set_integrity_pragmas(conn)?;
set_journal_mode(conn, args.preset_journal)?;
upgrade(args, db::EXPECTED_VERSION, conn)?;
upgrade(args, EXPECTED_VERSION, conn)?;
// As in "moonfire-nvr init": try for page_size=16384 and wal for the reasons explained there.
//
@@ -154,7 +156,7 @@ mod tests {
use super::*;
use crate::compare;
use crate::testutil;
use failure::ResultExt;
use base::err;
use fnv::FnvHashMap;
const BAD_ANAMORPHIC_VIDEO_SAMPLE_ENTRY: &[u8] = b"\x00\x00\x00\x84\x61\x76\x63\x31\x00\x00\
@@ -209,7 +211,7 @@ mod tests {
let tmpdir = tempfile::Builder::new()
.prefix("moonfire-nvr-test")
.tempdir()?;
//let path = tmpdir.path().to_str().ok_or_else(|| format_err!("invalid UTF-8"))?.to_owned();
//let path = tmpdir.path().to_str().ok_or_else(|| err!("invalid UTF-8"))?.to_owned();
let mut upgraded = new_conn()?;
upgraded.execute_batch(include_str!("v0.sql"))?;
upgraded.execute_batch(
@@ -291,7 +293,7 @@ mod tests {
*ver,
&mut upgraded,
)
.context(format!("upgrading to version {ver}"))?;
.map_err(|e| err!(e, msg("upgrade to version {ver} failed")))?;
if let Some(f) = fresh_sql {
compare(&upgraded, *ver, f)?;
}

View File

@@ -5,7 +5,7 @@
/// Upgrades a version 0 schema to a version 1 schema.
use crate::db;
use crate::recording;
use failure::Error;
use base::Error;
use rusqlite::{named_params, params};
use std::collections::HashMap;
use tracing::warn;

View File

@@ -5,7 +5,7 @@
/// Upgrades a version 1 schema to a version 2 schema.
use crate::dir;
use crate::schema::DirMeta;
use failure::{bail, format_err, Error};
use base::{bail, Error};
use nix::fcntl::{FlockArg, OFlag};
use nix::sys::stat::Mode;
use rusqlite::{named_params, params};
@@ -13,9 +13,12 @@ use std::os::unix::io::AsRawFd;
use uuid::Uuid;
pub fn run(args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error> {
let sample_file_path = args.sample_file_dir.ok_or_else(|| {
format_err!("--sample-file-dir required when upgrading from schema version 1 to 2.")
})?;
let Some(sample_file_path) = args.sample_file_dir else {
bail!(
InvalidArgument,
msg("--sample-file-dir required when upgrading from schema version 1 to 2."),
);
};
let mut d = nix::dir::Dir::open(
sample_file_path,
@@ -101,12 +104,12 @@ pub fn run(args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
}
dir::write_meta(d.as_raw_fd(), &meta)?;
let sample_file_path = sample_file_path.to_str().ok_or_else(|| {
format_err!(
"sample file dir {} is not a valid string",
sample_file_path.display()
)
})?;
let Some(sample_file_path) = sample_file_path.to_str() else {
bail!(
InvalidArgument,
msg("sample file dir {} is not a valid string", sample_file_path.display()),
);
};
tx.execute(
r#"
insert into sample_file_dir (path, uuid, last_complete_open_id)
@@ -317,15 +320,24 @@ fn verify_dir_contents(
};
let s = match f.to_str() {
Ok(s) => s,
Err(_) => bail!("unexpected file {:?} in {:?}", f, sample_file_path),
Err(_) => bail!(
FailedPrecondition,
msg("unexpected file {f:?} in {sample_file_path:?}")
),
};
let uuid = match Uuid::parse_str(s) {
Ok(u) => u,
Err(_) => bail!("unexpected file {:?} in {:?}", f, sample_file_path),
Err(_) => bail!(
FailedPrecondition,
msg("unexpected file {f:?} in {sample_file_path:?}")
),
};
if s != uuid.as_hyphenated().to_string() {
// non-canonical form.
bail!("unexpected file {:?} in {:?}", f, sample_file_path);
bail!(
FailedPrecondition,
msg("unexpected file {f:?} in {sample_file_path:?}")
);
}
files.insert(uuid);
}
@@ -338,9 +350,12 @@ fn verify_dir_contents(
let uuid: crate::db::SqlUuid = row.get(0)?;
if !files.remove(&uuid.0) {
bail!(
"{} is missing from dir {}!",
uuid.0,
sample_file_path.display()
FailedPrecondition,
msg(
"{} is missing from dir {}!",
uuid.0,
sample_file_path.display()
),
);
}
}
@@ -367,10 +382,13 @@ fn verify_dir_contents(
if !files.is_empty() {
bail!(
"{} unexpected sample file uuids in dir {}: {:?}!",
files.len(),
sample_file_path.display(),
files
FailedPrecondition,
msg(
"{} unexpected sample file uuids in dir {}: {:?}!",
files.len(),
sample_file_path.display(),
files,
),
);
}
Ok(())
@@ -413,7 +431,7 @@ fn fix_video_sample_entry(tx: &rusqlite::Transaction) -> Result<(), Error> {
fn rfc6381_codec_from_sample_entry(sample_entry: &[u8]) -> Result<String, Error> {
if sample_entry.len() < 99 || &sample_entry[4..8] != b"avc1" || &sample_entry[90..94] != b"avcC"
{
bail!("not a valid AVCSampleEntry");
bail!(InvalidArgument, msg("not a valid AVCSampleEntry"));
}
let profile_idc = sample_entry[103];
let constraint_flags_byte = sample_entry[104];

View File

@@ -8,9 +8,8 @@
use crate::db::{self, SqlUuid};
use crate::dir;
use crate::schema;
use failure::Error;
use base::Error;
use rusqlite::params;
use std::convert::TryFrom;
use std::os::unix::io::AsRawFd;
use std::path::PathBuf;
use std::sync::Arc;
@@ -50,7 +49,7 @@ fn open_sample_file_dir(tx: &rusqlite::Transaction) -> Result<Arc<dir::SampleFil
open.id = o_id as u32;
open.uuid.extend_from_slice(&o_uuid.0.as_bytes()[..]);
}
let p = PathBuf::try_from(p)?;
let p = PathBuf::from(p);
dir::SampleFileDir::open(&p, &meta)
}

View File

@@ -3,7 +3,7 @@
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception.
/// Upgrades a version 3 schema to a version 4 schema.
use failure::Error;
use base::Error;
pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error> {
// These create statements match the schema.sql when version 4 was the latest.

View File

@@ -8,8 +8,8 @@
/// Otherwise, verify they are consistent with the database then upgrade them.
use crate::db::SqlUuid;
use crate::{dir, schema};
use base::{bail, err, Error};
use cstr::cstr;
use failure::{bail, Error, Fail};
use nix::fcntl::{FlockArg, OFlag};
use nix::sys::stat::Mode;
use protobuf::Message;
@@ -34,15 +34,17 @@ fn maybe_upgrade_meta(dir: &dir::Fd, db_meta: &schema::DirMeta) -> Result<bool,
let mut s = protobuf::CodedInputStream::from_bytes(&data);
let mut dir_meta = schema::DirMeta::new();
dir_meta
.merge_from(&mut s)
.map_err(|e| e.context("Unable to parse metadata proto: {}"))?;
dir_meta.merge_from(&mut s).map_err(|e| {
err!(
FailedPrecondition,
msg("unable to parse metadata proto"),
source(e)
)
})?;
if let Err(e) = dir::SampleFileDir::check_consistent(db_meta, &dir_meta) {
bail!(
"Inconsistent db_meta={:?} dir_meta={:?}: {}",
&db_meta,
&dir_meta,
e
FailedPrecondition,
msg("inconsistent db_meta={db_meta:?} dir_meta={dir_meta:?}: {e}"),
);
}
let mut f = crate::fs::openat(
@@ -56,9 +58,12 @@ fn maybe_upgrade_meta(dir: &dir::Fd, db_meta: &schema::DirMeta) -> Result<bool,
.expect("proto3->vec is infallible");
if data.len() > FIXED_DIR_META_LEN {
bail!(
"Length-delimited DirMeta message requires {} bytes, over limit of {}",
data.len(),
FIXED_DIR_META_LEN
Internal,
msg(
"length-delimited DirMeta message requires {} bytes, over limit of {}",
data.len(),
FIXED_DIR_META_LEN,
),
);
}
data.resize(FIXED_DIR_META_LEN, 0); // pad to required length.
@@ -144,12 +149,12 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
o.uuid.extend_from_slice(&uuid.0.as_bytes()[..]);
}
(None, None) => {}
_ => bail!("open table missing id"),
_ => bail!(Internal, msg("open table missing id")),
}
let dir = dir::Fd::open(path, false)?;
dir.lock(FlockArg::LockExclusiveNonblock)
.map_err(|e| e.context(format!("unable to lock dir {path}")))?;
.map_err(|e| err!(e, msg("unable to lock dir {path}")))?;
let mut need_sync = maybe_upgrade_meta(&dir, &db_meta)?;
if maybe_cleanup_garbage_uuids(&dir)? {

View File

@@ -2,9 +2,9 @@
// 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::{bail, err, Error};
/// Upgrades a version 4 schema to a version 5 schema.
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use failure::{bail, format_err, Error, ResultExt};
use h264_reader::avcc::AvcDecoderConfigurationRecord;
use rusqlite::{named_params, params};
use std::convert::{TryFrom, TryInto};
@@ -29,22 +29,31 @@ fn default_pixel_aspect_ratio(width: u16, height: u16) -> (u16, u16) {
fn parse(data: &[u8]) -> Result<AvcDecoderConfigurationRecord, Error> {
if data.len() < 94 || &data[4..8] != b"avc1" || &data[90..94] != b"avcC" {
bail!("data of len {} doesn't have an avcC", data.len());
bail!(
DataLoss,
msg("data of len {} doesn't have an avcC", data.len())
);
}
let avcc_len = BigEndian::read_u32(&data[86..90]);
if avcc_len < 8 {
// length and type.
bail!("invalid avcc len {}", avcc_len);
bail!(DataLoss, msg("invalid avcc len {avcc_len}"));
}
let end_pos = 86 + usize::try_from(avcc_len)?;
if end_pos != data.len() {
let end_pos = usize::try_from(avcc_len)
.ok()
.and_then(|l| l.checked_add(86));
if end_pos != Some(data.len()) {
bail!(
"expected avcC to be end of extradata; there are {} more bytes.",
data.len() - end_pos
DataLoss,
msg(
"avcC end pos {:?} and total data len {} should match",
end_pos,
data.len(),
),
);
}
AvcDecoderConfigurationRecord::try_from(&data[94..end_pos])
.map_err(|e| format_err!("Bad AvcDecoderConfigurationRecord: {:?}", e))
AvcDecoderConfigurationRecord::try_from(&data[94..])
.map_err(|e| err!(DataLoss, msg("Bad AvcDecoderConfigurationRecord: {:?}", e)))
}
pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error> {
@@ -100,24 +109,37 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
let mut rows = stmt.query(params![])?;
while let Some(row) = rows.next()? {
let id: i32 = row.get(0)?;
let width: u16 = row.get::<_, i32>(1)?.try_into()?;
let height: u16 = row.get::<_, i32>(2)?.try_into()?;
let rfc6381_codec: &str = row.get_ref(3)?.as_str()?;
let width: u16 = row
.get::<_, i32>(1)?
.try_into()
.map_err(|_| err!(OutOfRange))?;
let height: u16 = row
.get::<_, i32>(2)?
.try_into()
.map_err(|_| err!(OutOfRange))?;
let rfc6381_codec: &str = row
.get_ref(3)?
.as_str()
.map_err(|_| err!(InvalidArgument))?;
let mut data: Vec<u8> = row.get(4)?;
let avcc = parse(&data)?;
if avcc.num_of_sequence_parameter_sets() != 1 {
bail!("Multiple SPSs!");
bail!(Unimplemented, msg("multiple SPSs!"));
}
let ctx = avcc.create_context().map_err(|e| {
format_err!(
"Can't load SPS+PPS for video_sample_entry_id {}: {:?}",
id,
e
err!(
Unknown,
msg("can't load SPS+PPS for video_sample_entry_id {id}: {e:?}"),
)
})?;
let sps = ctx
.sps_by_id(h264_reader::nal::pps::ParamSetId::from_u32(0).unwrap())
.ok_or_else(|| format_err!("No SPS 0 for video_sample_entry_id {}", id))?;
.ok_or_else(|| {
err!(
Unimplemented,
msg("no SPS 0 for video_sample_entry_id {id}")
)
})?;
let pasp = sps
.vui_parameters
.as_ref()
@@ -129,7 +151,10 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
data.write_u32::<BigEndian>(pasp.0.into())?;
data.write_u32::<BigEndian>(pasp.1.into())?;
let len = data.len();
BigEndian::write_u32(&mut data[0..4], u32::try_from(len)?);
BigEndian::write_u32(
&mut data[0..4],
u32::try_from(len).map_err(|_| err!(OutOfRange))?,
);
}
insert.execute(named_params! {
@@ -268,7 +293,7 @@ pub fn run(_args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
":video_sync_samples": video_sync_samples,
":video_sample_entry_id": video_sample_entry_id,
})
.with_context(|_| format!("Unable to insert composite_id {composite_id}"))?;
.map_err(|e| err!(e, msg("unable to insert composite_id {composite_id}")))?;
cum_duration_90k += i64::from(wall_duration_90k);
cum_runs += if run_offset == 0 { 1 } else { 0 };
}

View File

@@ -3,7 +3,7 @@
// SPDX-License-Identifier: GPL-v3.0-or-later WITH GPL-3.0-linking-exception
/// Upgrades a version 6 schema to a version 7 schema.
use failure::{format_err, Error, ResultExt};
use base::{err, Error};
use fnv::FnvHashMap;
use rusqlite::{named_params, params};
use std::{convert::TryFrom, path::PathBuf};
@@ -28,7 +28,13 @@ fn copy_meta(tx: &rusqlite::Transaction) -> Result<(), Error> {
let config = GlobalConfig {
max_signal_changes: max_signal_changes
.map(|s| {
u32::try_from(s).map_err(|_| format_err!("max_signal_changes out of range"))
u32::try_from(s).map_err(|e| {
err!(
OutOfRange,
msg("max_signal_changes out of range"),
source(e)
)
})
})
.transpose()?,
..Default::default()
@@ -57,7 +63,7 @@ fn copy_sample_file_dir(tx: &rusqlite::Transaction) -> Result<(), Error> {
let path: String = row.get(2)?;
let uuid: SqlUuid = row.get(1)?;
let config = SampleFileDirConfig {
path: PathBuf::try_from(path)?,
path: PathBuf::from(path),
..Default::default()
};
let last_complete_open_id: Option<i64> = row.get(3)?;
@@ -107,7 +113,10 @@ fn copy_users(tx: &rusqlite::Transaction) -> Result<(), Error> {
let permissions: Vec<u8> = row.get(7)?;
let config = UserConfig {
disabled: (flags & 1) != 0,
unix_uid: unix_uid.map(u64::try_from).transpose()?,
unix_uid: unix_uid
.map(u64::try_from)
.transpose()
.map_err(|_| err!(OutOfRange, msg("bad unix_uid")))?,
..Default::default()
};
insert.execute(named_params! {
@@ -134,7 +143,8 @@ fn copy_signal_types(tx: &rusqlite::Transaction) -> Result<(), Error> {
let type_ = types_
.entry(type_uuid.0)
.or_insert_with(SignalTypeConfig::default);
let value = u8::try_from(value).map_err(|_| format_err!("bad signal type value"))?;
let value =
u8::try_from(value).map_err(|_| err!(OutOfRange, msg("bad signal type value")))?;
let value_config = type_.values.entry(value).or_insert_with(Default::default);
if let Some(n) = name {
value_config.name = n;
@@ -163,7 +173,8 @@ fn copy_signals(tx: &rusqlite::Transaction) -> Result<(), Error> {
let mut rows = stmt.query(params![])?;
while let Some(row) = rows.next()? {
let id: i32 = row.get(0)?;
let id = u32::try_from(id)?;
let id =
u32::try_from(id).map_err(|e| err!(OutOfRange, msg("bad signal id"), source(e)))?;
let source_uuid: SqlUuid = row.get(1)?;
let type_uuid: SqlUuid = row.get(2)?;
let short_name: String = row.get(3)?;
@@ -187,7 +198,8 @@ fn copy_signals(tx: &rusqlite::Transaction) -> Result<(), Error> {
let mut rows = stmt.query(params![])?;
while let Some(row) = rows.next()? {
let signal_id: i32 = row.get(0)?;
let signal_id = u32::try_from(signal_id)?;
let signal_id = u32::try_from(signal_id)
.map_err(|e| err!(OutOfRange, msg("bad signal_id"), source(e)))?;
let camera_id: i32 = row.get(1)?;
let type_: i32 = row.get(2)?;
let signal = signals.get_mut(&signal_id).unwrap();
@@ -261,7 +273,13 @@ fn copy_cameras(tx: &rusqlite::Transaction) -> Result<(), Error> {
.filter(|h| !h.is_empty())
.map(|h| Url::parse(&format!("http://{h}/")))
.transpose()
.with_context(|_| "bad onvif_host")?,
.map_err(|e| {
err!(
InvalidArgument,
msg("bad onvif_host for camera id {id}"),
source(e)
)
})?,
username: username.take().unwrap_or_default(),
password: password.take().unwrap_or_default(),
..Default::default()
@@ -324,7 +342,13 @@ fn copy_streams(tx: &rusqlite::Transaction) -> Result<(), Error> {
""
})
.to_owned(),
url: Some(Url::parse(&rtsp_url)?),
url: Some(Url::parse(&rtsp_url).map_err(|e| {
err!(
InvalidArgument,
msg("bad rtsp_url for stream id {id}"),
source(e)
)
})?),
retain_bytes,
flush_if_sec,
..Default::default()