diff options
Diffstat (limited to 'src/fsentry.rs')
-rw-r--r-- | src/fsentry.rs | 234 |
1 files changed, 208 insertions, 26 deletions
diff --git a/src/fsentry.rs b/src/fsentry.rs index eae11b4..f31d6b5 100644 --- a/src/fsentry.rs +++ b/src/fsentry.rs @@ -1,10 +1,20 @@ +//! An entry in the file system. + +use log::{debug, error}; use serde::{Deserialize, Serialize}; use std::ffi::OsString; use std::fs::read_link; use std::fs::{FileType, Metadata}; -use std::os::linux::fs::MetadataExt; use std::os::unix::ffi::OsStringExt; +use std::os::unix::fs::FileTypeExt; use std::path::{Path, PathBuf}; +use users::{Groups, Users, UsersCache}; + +#[cfg(target_os = "linux")] +use std::os::linux::fs::MetadataExt; + +#[cfg(target_os = "macos")] +use std::os::macos::fs::MetadataExt; /// A file system entry. /// @@ -35,80 +45,245 @@ pub struct FilesystemEntry { // 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, +} + +/// Possible errors related to file system entries. +#[derive(Debug, thiserror::Error)] +pub enum FsEntryError { + /// File kind numeric representation is unknown. + #[error("Unknown file kind {0}")] + UnknownFileKindCode(u8), + + /// Failed to read a symbolic link's target. + #[error("failed to read symbolic link target {0}: {1}")] + ReadLink(PathBuf, std::io::Error), } #[allow(clippy::len_without_is_empty)] impl FilesystemEntry { - pub fn from_metadata(path: &Path, meta: &Metadata) -> anyhow::Result<Self> { + /// 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()); - Ok(Self { - path: path.to_path_buf().into_os_string().into_vec(), - kind: FilesystemKind::from_file_type(meta.file_type()), - len: meta.len(), - mode: meta.st_mode(), - mtime: meta.st_mtime(), - mtime_ns: meta.st_mtime_nsec(), - atime: meta.st_atime(), - atime_ns: meta.st_atime_nsec(), - symlink_target: if kind == FilesystemKind::Symlink { - Some(read_link(path)?) - } else { - None - }, - }) + 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. pub fn kind(&self) -> FilesystemKind { self.kind } + /// Return full path to the entry. pub fn pathbuf(&self) -> PathBuf { let path = self.path.clone(); PathBuf::from(OsString::from_vec(path)) } + /// Return number of bytes for the entity represented by the entry. pub fn len(&self) -> u64 { self.len } + /// Return the entry's mode bits. pub fn mode(&self) -> u32 { self.mode } + /// Return the entry's access time, whole seconds. pub fn atime(&self) -> i64 { self.atime } + /// Return the entry's access time, nanoseconds since the last full second. pub fn atime_ns(&self) -> i64 { self.atime_ns } + /// Return the entry's modification time, whole seconds. pub fn mtime(&self) -> i64 { self.mtime } + /// Return the entry's modification time, nanoseconds since the last full second. pub fn mtime_ns(&self) -> i64 { self.mtime_ns } + /// Does the entry represent a directory? pub fn is_dir(&self) -> bool { self.kind() == FilesystemKind::Directory } + /// Return target of the symlink the entry represents. pub fn symlink_target(&self) -> Option<PathBuf> { self.symlink_target.clone() } } +#[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)] +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] pub enum FilesystemKind { + /// Regular file, including a hard link to one. Regular, + /// A directory. Directory, + /// A symbolic link. Symlink, + /// A UNIX domain socket. + Socket, + /// A UNIX named pipe. + Fifo, } impl FilesystemKind { + /// Create a kind from a file type. pub fn from_file_type(file_type: FileType) -> Self { if file_type.is_file() { FilesystemKind::Regular @@ -116,35 +291,39 @@ impl FilesystemKind { FilesystemKind::Directory } else if file_type.is_symlink() { FilesystemKind::Symlink + } else if file_type.is_socket() { + FilesystemKind::Socket + } else if file_type.is_fifo() { + FilesystemKind::Fifo } else { panic!("unknown file type {:?}", file_type); } } + /// Represent a kind as a numeric code. pub fn as_code(&self) -> u8 { match self { FilesystemKind::Regular => 0, FilesystemKind::Directory => 1, FilesystemKind::Symlink => 2, + FilesystemKind::Socket => 3, + FilesystemKind::Fifo => 4, } } - pub fn from_code(code: u8) -> anyhow::Result<Self> { + /// Create a kind from a numeric code. + pub fn from_code(code: u8) -> Result<Self, FsEntryError> { match code { 0 => Ok(FilesystemKind::Regular), 1 => Ok(FilesystemKind::Directory), 2 => Ok(FilesystemKind::Symlink), - _ => Err(Error::UnknownFileKindCode(code).into()), + 3 => Ok(FilesystemKind::Socket), + 4 => Ok(FilesystemKind::Fifo), + _ => Err(FsEntryError::UnknownFileKindCode(code)), } } } -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("unknown file kind code {0}")] - UnknownFileKindCode(u8), -} - #[cfg(test)] mod test { use super::FilesystemKind; @@ -153,6 +332,9 @@ mod test { fn file_kind_regular_round_trips() { one_file_kind_round_trip(FilesystemKind::Regular); one_file_kind_round_trip(FilesystemKind::Directory); + one_file_kind_round_trip(FilesystemKind::Symlink); + one_file_kind_round_trip(FilesystemKind::Socket); + one_file_kind_round_trip(FilesystemKind::Fifo); } fn one_file_kind_round_trip(kind: FilesystemKind) { |