mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-11-20 09:56:07 -05:00
Merge branch 'master' into new-schema
This commit is contained in:
@@ -31,7 +31,6 @@ odds = { version = "0.3.1", features = ["std-vec"] }
|
||||
parking_lot = { version = "0.9", features = [] }
|
||||
prettydiff = "0.3.1"
|
||||
protobuf = { git = "https://github.com/stepancheg/rust-protobuf" }
|
||||
regex = "1.0"
|
||||
ring = "0.14.6"
|
||||
rusqlite = "0.21.0"
|
||||
smallvec = "1.0"
|
||||
|
||||
27
db/auth.rs
27
db/auth.rs
@@ -42,6 +42,7 @@ use rusqlite::{Connection, Transaction, params};
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::net::IpAddr;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
|
||||
lazy_static! {
|
||||
@@ -57,7 +58,7 @@ pub(crate) fn set_test_config() {
|
||||
Arc::new(libpasta::Config::with_primitive(libpasta::primitives::Bcrypt::new(2)));
|
||||
}
|
||||
|
||||
enum UserFlags {
|
||||
enum UserFlag {
|
||||
Disabled = 1,
|
||||
}
|
||||
|
||||
@@ -91,7 +92,7 @@ impl User {
|
||||
}
|
||||
|
||||
pub fn has_password(&self) -> bool { self.password_hash.is_some() }
|
||||
fn disabled(&self) -> bool { (self.flags & UserFlags::Disabled as i32) != 0 }
|
||||
fn disabled(&self) -> bool { (self.flags & UserFlag::Disabled as i32) != 0 }
|
||||
}
|
||||
|
||||
/// A change to a user.
|
||||
@@ -132,7 +133,7 @@ impl UserChange {
|
||||
}
|
||||
|
||||
pub fn disable(&mut self) {
|
||||
self.flags |= UserFlags::Disabled as i32;
|
||||
self.flags |= UserFlag::Disabled as i32;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,13 +195,29 @@ impl rusqlite::types::FromSql for FromSqlIpAddr {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum SessionFlags {
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
#[repr(i32)]
|
||||
pub enum SessionFlag {
|
||||
HttpOnly = 1,
|
||||
Secure = 2,
|
||||
SameSite = 4,
|
||||
SameSiteStrict = 8,
|
||||
}
|
||||
|
||||
impl FromStr for SessionFlag {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"http-only" => Ok(Self::HttpOnly),
|
||||
"secure" => Ok(Self::Secure),
|
||||
"same-site" => Ok(Self::SameSite),
|
||||
"same-site-strict" => Ok(Self::SameSiteStrict),
|
||||
_ => bail!("No such session flag {:?}", s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub enum RevocationReason {
|
||||
LoggedOut = 1,
|
||||
@@ -210,7 +227,7 @@ pub enum RevocationReason {
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Session {
|
||||
user_id: i32,
|
||||
flags: i32, // bitmask of SessionFlags enum values
|
||||
flags: i32, // bitmask of SessionFlag enum values
|
||||
domain: Option<Vec<u8>>,
|
||||
description: Option<String>,
|
||||
seed: Seed,
|
||||
|
||||
10
db/dir.rs
10
db/dir.rs
@@ -41,7 +41,7 @@ use log::warn;
|
||||
use protobuf::Message;
|
||||
use nix::{NixPath, fcntl::{FlockArg, OFlag}, sys::stat::Mode};
|
||||
use nix::sys::statvfs::Statvfs;
|
||||
use std::ffi::{CStr, CString};
|
||||
use std::ffi::CStr;
|
||||
use std::fs;
|
||||
use std::io::{Read, Write};
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
@@ -104,16 +104,14 @@ impl Drop for Fd {
|
||||
|
||||
impl Fd {
|
||||
/// Opens the given path as a directory.
|
||||
pub fn open(path: &str, mkdir: bool) -> Result<Fd, nix::Error> {
|
||||
let cstring = CString::new(path).map_err(|_| nix::Error::InvalidPath)?;
|
||||
pub fn open<P: ?Sized + NixPath>(path: &P, mkdir: bool) -> Result<Fd, nix::Error> {
|
||||
if mkdir {
|
||||
match nix::unistd::mkdir(cstring.as_c_str(), nix::sys::stat::Mode::S_IRWXU) {
|
||||
match nix::unistd::mkdir(path, nix::sys::stat::Mode::S_IRWXU) {
|
||||
Ok(()) | Err(nix::Error::Sys(nix::errno::Errno::EEXIST)) => {},
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
let fd = nix::fcntl::open(cstring.as_c_str(), OFlag::O_DIRECTORY | OFlag::O_RDONLY,
|
||||
Mode::empty())?;
|
||||
let fd = nix::fcntl::open(path, OFlag::O_DIRECTORY | OFlag::O_RDONLY, Mode::empty())?;
|
||||
Ok(Fd(fd))
|
||||
}
|
||||
|
||||
|
||||
238
db/recording.rs
238
db/recording.rs
@@ -30,199 +30,17 @@
|
||||
|
||||
use crate::coding::{append_varint32, decode_varint32, unzigzag32, zigzag32};
|
||||
use crate::db;
|
||||
use failure::{Error, bail, format_err};
|
||||
use lazy_static::lazy_static;
|
||||
use failure::{Error, bail};
|
||||
use log::trace;
|
||||
use regex::Regex;
|
||||
use std::ops;
|
||||
use std::fmt;
|
||||
use std::ops::Range;
|
||||
use std::str::FromStr;
|
||||
use time;
|
||||
|
||||
pub const TIME_UNITS_PER_SEC: i64 = 90000;
|
||||
pub use base::time::TIME_UNITS_PER_SEC;
|
||||
|
||||
pub const DESIRED_RECORDING_DURATION: i64 = 60 * TIME_UNITS_PER_SEC;
|
||||
pub const MAX_RECORDING_DURATION: i64 = 5 * 60 * TIME_UNITS_PER_SEC;
|
||||
|
||||
/// A time specified as 90,000ths of a second since 1970-01-01 00:00:00 UTC.
|
||||
#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct Time(pub i64);
|
||||
|
||||
impl Time {
|
||||
pub fn new(tm: time::Timespec) -> Self {
|
||||
Time(tm.sec * TIME_UNITS_PER_SEC + tm.nsec as i64 * TIME_UNITS_PER_SEC / 1_000_000_000)
|
||||
}
|
||||
|
||||
pub const fn min_value() -> Self { Time(i64::min_value()) }
|
||||
pub const fn max_value() -> Self { Time(i64::max_value()) }
|
||||
|
||||
/// Parses a time as either 90,000ths of a second since epoch or a RFC 3339-like string.
|
||||
///
|
||||
/// The former is 90,000ths of a second since 1970-01-01T00:00:00 UTC, excluding leap seconds.
|
||||
///
|
||||
/// The latter is a string such as `2006-01-02T15:04:05`, followed by an optional 90,000ths of
|
||||
/// a second such as `:00001`, followed by an optional time zone offset such as `Z` or
|
||||
/// `-07:00`. A missing fraction is assumed to be 0. A missing time zone offset implies the
|
||||
/// local time zone.
|
||||
pub fn parse(s: &str) -> Result<Self, Error> {
|
||||
lazy_static! {
|
||||
static ref RE: Regex = Regex::new(r#"(?x)
|
||||
^
|
||||
([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})
|
||||
(?::([0-9]{5}))?
|
||||
(Z|[+-]([0-9]{2}):([0-9]{2}))?
|
||||
$"#).unwrap();
|
||||
}
|
||||
|
||||
// First try parsing as 90,000ths of a second since epoch.
|
||||
match i64::from_str(s) {
|
||||
Ok(i) => return Ok(Time(i)),
|
||||
Err(_) => {},
|
||||
}
|
||||
|
||||
// If that failed, parse as a time string or bust.
|
||||
let c = RE.captures(s).ok_or_else(|| format_err!("unparseable time {:?}", s))?;
|
||||
let mut tm = time::Tm{
|
||||
tm_sec: i32::from_str(c.get(6).unwrap().as_str()).unwrap(),
|
||||
tm_min: i32::from_str(c.get(5).unwrap().as_str()).unwrap(),
|
||||
tm_hour: i32::from_str(c.get(4).unwrap().as_str()).unwrap(),
|
||||
tm_mday: i32::from_str(c.get(3).unwrap().as_str()).unwrap(),
|
||||
tm_mon: i32::from_str(c.get(2).unwrap().as_str()).unwrap(),
|
||||
tm_year: i32::from_str(c.get(1).unwrap().as_str()).unwrap(),
|
||||
tm_wday: 0,
|
||||
tm_yday: 0,
|
||||
tm_isdst: -1,
|
||||
tm_utcoff: 0,
|
||||
tm_nsec: 0,
|
||||
};
|
||||
if tm.tm_mon == 0 {
|
||||
bail!("time {:?} has month 0", s);
|
||||
}
|
||||
tm.tm_mon -= 1;
|
||||
if tm.tm_year < 1900 {
|
||||
bail!("time {:?} has year before 1900", s);
|
||||
}
|
||||
tm.tm_year -= 1900;
|
||||
|
||||
// The time crate doesn't use tm_utcoff properly; it just calls timegm() if tm_utcoff == 0,
|
||||
// mktime() otherwise. If a zone is specified, use the timegm path and a manual offset.
|
||||
// If no zone is specified, use the tm_utcoff path. This is pretty lame, but follow the
|
||||
// chrono crate's lead and just use 0 or 1 to choose between these functions.
|
||||
let sec = if let Some(zone) = c.get(8) {
|
||||
tm.to_timespec().sec + if zone.as_str() == "Z" {
|
||||
0
|
||||
} else {
|
||||
let off = i64::from_str(c.get(9).unwrap().as_str()).unwrap() * 3600 +
|
||||
i64::from_str(c.get(10).unwrap().as_str()).unwrap() * 60;
|
||||
if zone.as_str().as_bytes()[0] == b'-' { off } else { -off }
|
||||
}
|
||||
} else {
|
||||
tm.tm_utcoff = 1;
|
||||
tm.to_timespec().sec
|
||||
};
|
||||
let fraction = if let Some(f) = c.get(7) { i64::from_str(f.as_str()).unwrap() } else { 0 };
|
||||
Ok(Time(sec * TIME_UNITS_PER_SEC + fraction))
|
||||
}
|
||||
|
||||
/// Convert to unix seconds by floor method (rounding down).
|
||||
pub fn unix_seconds(&self) -> i64 { self.0 / TIME_UNITS_PER_SEC }
|
||||
}
|
||||
|
||||
impl ops::Sub for Time {
|
||||
type Output = Duration;
|
||||
fn sub(self, rhs: Time) -> Duration { Duration(self.0 - rhs.0) }
|
||||
}
|
||||
|
||||
impl ops::AddAssign<Duration> for Time {
|
||||
fn add_assign(&mut self, rhs: Duration) { self.0 += rhs.0 }
|
||||
}
|
||||
|
||||
impl ops::Add<Duration> for Time {
|
||||
type Output = Time;
|
||||
fn add(self, rhs: Duration) -> Time { Time(self.0 + rhs.0) }
|
||||
}
|
||||
|
||||
impl ops::Sub<Duration> for Time {
|
||||
type Output = Time;
|
||||
fn sub(self, rhs: Duration) -> Time { Time(self.0 - rhs.0) }
|
||||
}
|
||||
|
||||
impl fmt::Debug for Time {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// Write both the raw and display forms.
|
||||
write!(f, "{} /* {} */", self.0, self)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Time {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let tm = time::at(time::Timespec{sec: self.0 / TIME_UNITS_PER_SEC, nsec: 0});
|
||||
let zone_minutes = tm.tm_utcoff.abs() / 60;
|
||||
write!(f, "{}:{:05}{}{:02}:{:02}", tm.strftime("%FT%T").or_else(|_| Err(fmt::Error))?,
|
||||
self.0 % TIME_UNITS_PER_SEC,
|
||||
if tm.tm_utcoff > 0 { '+' } else { '-' }, zone_minutes / 60, zone_minutes % 60)
|
||||
}
|
||||
}
|
||||
|
||||
/// A duration specified in 1/90,000ths of a second.
|
||||
/// Durations are typically non-negative, but a `db::CameraDayValue::duration` may be negative.
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
|
||||
pub struct Duration(pub i64);
|
||||
|
||||
impl Duration {
|
||||
pub fn to_tm_duration(&self) -> time::Duration {
|
||||
time::Duration::nanoseconds(self.0 * 100000 / 9)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Duration {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut seconds = self.0 / TIME_UNITS_PER_SEC;
|
||||
const MINUTE_IN_SECONDS: i64 = 60;
|
||||
const HOUR_IN_SECONDS: i64 = 60 * MINUTE_IN_SECONDS;
|
||||
const DAY_IN_SECONDS: i64 = 24 * HOUR_IN_SECONDS;
|
||||
let days = seconds / DAY_IN_SECONDS;
|
||||
seconds %= DAY_IN_SECONDS;
|
||||
let hours = seconds / HOUR_IN_SECONDS;
|
||||
seconds %= HOUR_IN_SECONDS;
|
||||
let minutes = seconds / MINUTE_IN_SECONDS;
|
||||
seconds %= MINUTE_IN_SECONDS;
|
||||
let mut have_written = if days > 0 {
|
||||
write!(f, "{} day{}", days, if days == 1 { "" } else { "s" })?;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if hours > 0 {
|
||||
write!(f, "{}{} hour{}", if have_written { " " } else { "" },
|
||||
hours, if hours == 1 { "" } else { "s" })?;
|
||||
have_written = true;
|
||||
}
|
||||
if minutes > 0 {
|
||||
write!(f, "{}{} minute{}", if have_written { " " } else { "" },
|
||||
minutes, if minutes == 1 { "" } else { "s" })?;
|
||||
have_written = true;
|
||||
}
|
||||
if seconds > 0 || !have_written {
|
||||
write!(f, "{}{} second{}", if have_written { " " } else { "" },
|
||||
seconds, if seconds == 1 { "" } else { "s" })?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add for Duration {
|
||||
type Output = Duration;
|
||||
fn add(self, rhs: Duration) -> Duration { Duration(self.0 + rhs.0) }
|
||||
}
|
||||
|
||||
impl ops::AddAssign for Duration {
|
||||
fn add_assign(&mut self, rhs: Duration) { self.0 += rhs.0 }
|
||||
}
|
||||
|
||||
impl ops::SubAssign for Duration {
|
||||
fn sub_assign(&mut self, rhs: Duration) { self.0 -= rhs.0 }
|
||||
}
|
||||
pub use base::time::Time;
|
||||
pub use base::time::Duration;
|
||||
|
||||
/// An iterator through a sample index.
|
||||
/// Initially invalid; call `next()` before each read.
|
||||
@@ -533,52 +351,6 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::testutil::{self, TestDb};
|
||||
|
||||
#[test]
|
||||
fn test_parse_time() {
|
||||
testutil::init();
|
||||
let tests = &[
|
||||
("2006-01-02T15:04:05-07:00", 102261550050000),
|
||||
("2006-01-02T15:04:05:00001-07:00", 102261550050001),
|
||||
("2006-01-02T15:04:05-08:00", 102261874050000),
|
||||
("2006-01-02T15:04:05", 102261874050000), // implied -08:00
|
||||
("2006-01-02T15:04:05:00001", 102261874050001), // implied -08:00
|
||||
("2006-01-02T15:04:05-00:00", 102259282050000),
|
||||
("2006-01-02T15:04:05Z", 102259282050000),
|
||||
("102261550050000", 102261550050000),
|
||||
];
|
||||
for test in tests {
|
||||
assert_eq!(test.1, Time::parse(test.0).unwrap().0, "parsing {}", test.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_format_time() {
|
||||
testutil::init();
|
||||
assert_eq!("2006-01-02T15:04:05:00000-08:00", format!("{}", Time(102261874050000)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_duration() {
|
||||
testutil::init();
|
||||
let tests = &[
|
||||
// (output, seconds)
|
||||
("0 seconds", 0),
|
||||
("1 second", 1),
|
||||
("1 minute", 60),
|
||||
("1 minute 1 second", 61),
|
||||
("2 minutes", 120),
|
||||
("1 hour", 3600),
|
||||
("1 hour 1 minute", 3660),
|
||||
("2 hours", 7200),
|
||||
("1 day", 86400),
|
||||
("1 day 1 hour", 86400 + 3600),
|
||||
("2 days", 2 * 86400),
|
||||
];
|
||||
for test in tests {
|
||||
assert_eq!(test.0, format!("{}", Duration(test.1 * TIME_UNITS_PER_SEC)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Tests encoding the example from design/schema.md.
|
||||
#[test]
|
||||
fn test_encode_example() {
|
||||
|
||||
@@ -53,9 +53,9 @@ const UPGRADE_NOTES: &'static str =
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Args<'a> {
|
||||
pub flag_sample_file_dir: Option<&'a str>,
|
||||
pub flag_preset_journal: &'a str,
|
||||
pub flag_no_vacuum: bool,
|
||||
pub sample_file_dir: Option<&'a std::path::Path>,
|
||||
pub preset_journal: &'a str,
|
||||
pub no_vacuum: bool,
|
||||
}
|
||||
|
||||
fn set_journal_mode(conn: &rusqlite::Connection, requested: &str) -> Result<(), Error> {
|
||||
@@ -88,7 +88,7 @@ fn upgrade(args: &Args, target_ver: i32, conn: &mut rusqlite::Connection) -> Res
|
||||
bail!("Database is at negative version {}!", old_ver);
|
||||
}
|
||||
info!("Upgrading database from version {} to version {}...", old_ver, target_ver);
|
||||
set_journal_mode(&conn, args.flag_preset_journal)?;
|
||||
set_journal_mode(&conn, args.preset_journal)?;
|
||||
for ver in old_ver .. target_ver {
|
||||
info!("...from version {} to version {}", ver, ver + 1);
|
||||
let tx = conn.transaction()?;
|
||||
@@ -120,7 +120,7 @@ pub fn run(args: &Args, conn: &mut rusqlite::Connection) -> Result<(), Error> {
|
||||
// WAL is the preferred journal mode for normal operation; it reduces the number of syncs
|
||||
// without compromising safety.
|
||||
set_journal_mode(&conn, "wal")?;
|
||||
if !args.flag_no_vacuum {
|
||||
if !args.no_vacuum {
|
||||
info!("...vacuuming database after upgrade.");
|
||||
conn.execute_batch(r#"
|
||||
pragma page_size = 16384;
|
||||
@@ -159,7 +159,7 @@ impl NixPath for UuidPath {
|
||||
mod tests {
|
||||
use crate::compare;
|
||||
use crate::testutil;
|
||||
use failure::{ResultExt, format_err};
|
||||
use failure::ResultExt;
|
||||
use fnv::FnvHashMap;
|
||||
use super::*;
|
||||
|
||||
@@ -209,7 +209,7 @@ mod tests {
|
||||
fn upgrade_and_compare() -> Result<(), Error> {
|
||||
testutil::init();
|
||||
let tmpdir = tempdir::TempDir::new("moonfire-nvr-test")?;
|
||||
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(|| format_err!("invalid UTF-8"))?.to_owned();
|
||||
let mut upgraded = new_conn()?;
|
||||
upgraded.execute_batch(include_str!("v0.sql"))?;
|
||||
upgraded.execute_batch(r#"
|
||||
@@ -252,9 +252,9 @@ mod tests {
|
||||
(5, Some(include_str!("v5.sql"))),
|
||||
(6, Some(include_str!("../schema.sql")))] {
|
||||
upgrade(&Args {
|
||||
flag_sample_file_dir: Some(&path),
|
||||
flag_preset_journal: "delete",
|
||||
flag_no_vacuum: false,
|
||||
sample_file_dir: Some(&tmpdir.path()),
|
||||
preset_journal: "delete",
|
||||
no_vacuum: false,
|
||||
}, *ver, &mut upgraded).context(format!("upgrading to version {}", ver))?;
|
||||
if let Some(f) = fresh_sql {
|
||||
compare(&upgraded, *ver, f)?;
|
||||
|
||||
@@ -42,7 +42,7 @@ use uuid::Uuid;
|
||||
|
||||
pub fn run(args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error> {
|
||||
let sample_file_path =
|
||||
args.flag_sample_file_dir
|
||||
args.sample_file_dir
|
||||
.ok_or_else(|| format_err!("--sample-file-dir required when upgrading from \
|
||||
schema version 1 to 2."))?;
|
||||
|
||||
@@ -122,6 +122,9 @@ 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()))?;
|
||||
tx.execute(r#"
|
||||
insert into sample_file_dir (path, uuid, last_complete_open_id)
|
||||
values (?, ?, ?)
|
||||
@@ -293,7 +296,7 @@ pub fn run(args: &super::Args, tx: &rusqlite::Transaction) -> Result<(), Error>
|
||||
/// * optional: reserved sample file uuids.
|
||||
/// * optional: meta and meta-tmp from half-completed update attempts.
|
||||
/// * forbidden: anything else.
|
||||
fn verify_dir_contents(sample_file_path: &str, dir: &mut nix::dir::Dir,
|
||||
fn verify_dir_contents(sample_file_path: &std::path::Path, dir: &mut nix::dir::Dir,
|
||||
tx: &rusqlite::Transaction) -> Result<(), Error> {
|
||||
// Build a hash of the uuids found in the directory.
|
||||
let n: i64 = tx.query_row(r#"
|
||||
@@ -337,7 +340,7 @@ fn verify_dir_contents(sample_file_path: &str, dir: &mut nix::dir::Dir,
|
||||
while let Some(row) = rows.next()? {
|
||||
let uuid: crate::db::FromSqlUuid = row.get(0)?;
|
||||
if !files.remove(&uuid.0) {
|
||||
bail!("{} is missing from dir {}!", uuid.0, sample_file_path);
|
||||
bail!("{} is missing from dir {}!", uuid.0, sample_file_path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -360,7 +363,7 @@ fn verify_dir_contents(sample_file_path: &str, dir: &mut nix::dir::Dir,
|
||||
|
||||
if !files.is_empty() {
|
||||
bail!("{} unexpected sample file uuids in dir {}: {:?}!",
|
||||
files.len(), sample_file_path, files);
|
||||
files.len(), sample_file_path.display(), files);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user