summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2022-05-03 11:22:23 +0300
committerLars Wirzenius <liw@liw.fi>2022-05-03 11:25:46 +0300
commit4913347201f4d00ccaf959c53357241d5bc3f9e0 (patch)
treeb04294a85b4bcfb540107e39490018e8f42d5a6f
parent3d50e1497dd07929655636cfea0c48ccadd3ab8e (diff)
downloadobnam2-4913347201f4d00ccaf959c53357241d5bc3f9e0.tar.gz
refactor: add a builder for file system entries
The previous commit introduced a function to create FilesystemEntry values from arbitrary data. Previously one could only be created from std::fs::Metadata. This complicated our own testing, which (now) needs to construct an arbitrary entry structure. However, while the function added in the last commit was straightforward, it had 11 arguments, and that's hard to keep track of. Replace that function with an EntryBuilder struct, for clarity. Sponsored-by: author
-rw-r--r--src/fsentry.rs201
-rw-r--r--src/generation.rs27
2 files changed, 145 insertions, 83 deletions
diff --git a/src/fsentry.rs b/src/fsentry.rs
index a04a3de..276e3f9 100644
--- a/src/fsentry.rs
+++ b/src/fsentry.rs
@@ -70,76 +70,22 @@ pub enum FsEntryError {
#[allow(clippy::len_without_is_empty)]
impl FilesystemEntry {
/// Create an `FsEntry` from a file's metadata.
- pub(crate) fn new(
- path: &Path,
- kind: FilesystemKind,
- uid: u32,
- gid: u32,
- len: u64,
- mode: u32,
- mtime: i64,
- mtime_ns: i64,
- atime: i64,
- atime_ns: i64,
- cache: &mut UsersCache,
- ) -> Result<Self, FsEntryError> {
- let symlink_target = if kind == FilesystemKind::Symlink {
- debug!("reading symlink target for {:?}", path);
- let target =
- read_link(path).map_err(|err| FsEntryError::ReadLink(path.to_path_buf(), err))?;
- Some(target)
- } else {
- None
- };
-
- let user: String = if let Some(user) = cache.get_user_by_uid(uid) {
- user.name().to_string_lossy().to_string()
- } else {
- "".to_string()
- };
- let group = if let Some(group) = cache.get_group_by_gid(gid) {
- group.name().to_string_lossy().to_string()
- } else {
- "".to_string()
- };
-
- Ok(Self {
- path: path.to_path_buf().into_os_string().into_vec(),
- kind,
- len,
- mode,
- mtime,
- mtime_ns,
- atime,
- atime_ns,
- symlink_target,
- uid,
- gid,
- user,
- group,
- })
- }
-
- /// Create an `FsEntry` from a file's metadata.
pub fn from_metadata(
path: &Path,
meta: &Metadata,
cache: &mut UsersCache,
) -> Result<Self, FsEntryError> {
let kind = FilesystemKind::from_file_type(meta.file_type());
- Self::new(
- path,
- kind,
- meta.st_uid(),
- meta.st_gid(),
- meta.len(),
- meta.st_mode(),
- meta.st_mtime(),
- meta.st_mtime_nsec(),
- meta.st_atime(),
- meta.st_atime_nsec(),
- cache,
- )
+ Ok(EntryBuilder::new(kind)
+ .path(path.to_path_buf())
+ .len(meta.len())
+ .mode(meta.st_mode())
+ .mtime(meta.st_mtime(), meta.st_mtime_nsec())
+ .atime(meta.st_atime(), meta.st_atime_nsec())
+ .user(meta.st_uid(), cache)?
+ .group(meta.st_uid(), cache)?
+ .symlink_target()?
+ .build())
}
/// Return the kind of file the entry refers to.
@@ -194,6 +140,133 @@ impl FilesystemEntry {
}
}
+#[derive(Debug)]
+pub(crate) struct EntryBuilder {
+ kind: FilesystemKind,
+ path: PathBuf,
+ len: u64,
+
+ // 16 bits should be enough for a Unix mode_t.
+ // https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/sys_stat.h.html
+ // However, it's 32 bits on Linux, so that's what we store.
+ mode: u32,
+
+ // Linux can store file system time stamps in nanosecond
+ // resolution. We store them as two 64-bit integers.
+ mtime: i64,
+ mtime_ns: i64,
+ atime: i64,
+ atime_ns: i64,
+
+ // The target of a symbolic link, if any.
+ symlink_target: Option<PathBuf>,
+
+ // User and group owning the file. We store them as both the
+ // numeric id and the textual name corresponding to the numeric id
+ // at the time of the backup.
+ uid: u32,
+ gid: u32,
+ user: String,
+ group: String,
+}
+
+impl EntryBuilder {
+ pub(crate) fn new(kind: FilesystemKind) -> Self {
+ Self {
+ kind,
+ path: PathBuf::new(),
+ len: 0,
+ mode: 0,
+ mtime: 0,
+ mtime_ns: 0,
+ atime: 0,
+ atime_ns: 0,
+ symlink_target: None,
+ uid: 0,
+ user: "".to_string(),
+ gid: 0,
+ group: "".to_string(),
+ }
+ }
+
+ pub(crate) fn build(self) -> FilesystemEntry {
+ FilesystemEntry {
+ kind: self.kind,
+ path: self.path.into_os_string().into_vec(),
+ len: self.len,
+ mode: self.mode,
+ mtime: self.mtime,
+ mtime_ns: self.mtime_ns,
+ atime: self.atime,
+ atime_ns: self.atime_ns,
+ symlink_target: self.symlink_target,
+ uid: self.uid,
+ user: self.user,
+ gid: self.gid,
+ group: self.group,
+ }
+ }
+
+ pub(crate) fn path(mut self, path: PathBuf) -> Self {
+ self.path = path;
+ self
+ }
+
+ pub(crate) fn len(mut self, len: u64) -> Self {
+ self.len = len;
+ self
+ }
+
+ pub(crate) fn mode(mut self, mode: u32) -> Self {
+ self.mode = mode;
+ self
+ }
+
+ pub(crate) fn mtime(mut self, secs: i64, nsec: i64) -> Self {
+ self.mtime = secs;
+ self.mtime_ns = nsec;
+ self
+ }
+
+ pub(crate) fn atime(mut self, secs: i64, nsec: i64) -> Self {
+ self.atime = secs;
+ self.atime_ns = nsec;
+ self
+ }
+
+ pub(crate) fn symlink_target(mut self) -> Result<Self, FsEntryError> {
+ self.symlink_target = if self.kind == FilesystemKind::Symlink {
+ debug!("reading symlink target for {:?}", self.path);
+ let target = read_link(&self.path)
+ .map_err(|err| FsEntryError::ReadLink(self.path.clone(), err))?;
+ Some(target)
+ } else {
+ None
+ };
+ Ok(self)
+ }
+
+ pub(crate) fn user(mut self, uid: u32, cache: &mut UsersCache) -> Result<Self, FsEntryError> {
+ self.uid = uid;
+ self.user = if let Some(user) = cache.get_user_by_uid(uid) {
+ user.name().to_string_lossy().to_string()
+ } else {
+ "".to_string()
+ };
+ Ok(self)
+ }
+
+ pub(crate) fn group(mut self, gid: u32, cache: &mut UsersCache) -> Result<Self, FsEntryError> {
+ self.gid = gid;
+ self.group = if let Some(group) = cache.get_group_by_gid(gid) {
+ group.name().to_string_lossy().to_string()
+ } else {
+ "".to_string()
+ };
+ Ok(self)
+ }
+}
+
/// Different types of file system entries.
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
pub enum FilesystemKind {
diff --git a/src/generation.rs b/src/generation.rs
index cec3d14..477edc0 100644
--- a/src/generation.rs
+++ b/src/generation.rs
@@ -297,33 +297,22 @@ impl LocalGeneration {
#[cfg(test)]
mod test {
use super::{LabelChecksumKind, LocalGeneration, NascentGeneration, Reason, SchemaVersion};
- use crate::fsentry::FilesystemEntry;
+ use crate::fsentry::EntryBuilder;
use crate::fsentry::FilesystemKind;
- use std::path::Path;
+ use std::path::PathBuf;
use tempfile::{tempdir, NamedTempFile};
- use users::UsersCache;
#[test]
fn round_trips_u64_max() {
let tmp = tempdir().unwrap();
let filename = tmp.path().join("test.db");
- let mut cache = UsersCache::new();
+ let path = PathBuf::from("/");
let schema = SchemaVersion::new(0, 0);
{
- let e = FilesystemEntry::new(
- Path::new("/"),
- FilesystemKind::Directory,
- 0,
- 0,
- u64::MAX,
- 0,
- 0,
- 0,
- 0,
- 0,
- &mut cache,
- )
- .unwrap();
+ let e = EntryBuilder::new(FilesystemKind::Directory)
+ .path(path.clone())
+ .len(u64::MAX)
+ .build();
let mut gen =
NascentGeneration::create(&filename, schema, LabelChecksumKind::Sha256).unwrap();
gen.insert(e, &[], Reason::IsNew, false).unwrap();
@@ -331,7 +320,7 @@ mod test {
}
let db = LocalGeneration::open(&filename).unwrap();
- let e = db.get_file(Path::new("/")).unwrap().unwrap();
+ let e = db.get_file(&path).unwrap().unwrap();
assert_eq!(e.len(), u64::MAX);
}