diff --git a/src/archiver/archiver_impl.rs b/src/archiver/archiver_impl.rs index 845a063..5d3c54d 100644 --- a/src/archiver/archiver_impl.rs +++ b/src/archiver/archiver_impl.rs @@ -107,7 +107,8 @@ impl Archiver { pub async fn finish_trees(&mut self, path: &Path) -> Result<()> { while !path.starts_with(&self.path) { // save tree and go back to parent dir - let chunk = self.tree.serialize()?; + let mut chunk = self.tree.serialize()?; + chunk.push('\n' as u8); // for whatever reason, restic adds a newline, so to be compatible... let id = hash(&chunk); let (mut node, tree, parent) = self diff --git a/src/backend/ignore.rs b/src/backend/ignore.rs index 5ba8c73..fc50ff3 100644 --- a/src/backend/ignore.rs +++ b/src/backend/ignore.rs @@ -182,10 +182,10 @@ fn map_entry(entry: DirEntry, with_atime: bool, cache: &UsersCache) -> Result<(P .into(), ); let size = if m.is_dir() { 0 } else { m.len() }; - let mode = m.st_mode(); + let mode = map_mode_to_go(m.st_mode()); let inode = m.st_ino(); let device_id = m.st_dev(); - let links = m.st_nlink(); + let links = if m.is_dir() { 0 } else { m.st_nlink() }; let meta = Metadata { size, @@ -211,3 +211,62 @@ fn map_entry(entry: DirEntry, with_atime: bool, cache: &UsersCache) -> Result<(P }; Ok((entry.path().to_path_buf(), node)) } + +const MODE_PERM: u32 = 0o777; // permission bits + +// consts from https://pkg.go.dev/io/fs#ModeType +const GO_MODE_DIR: u32 = 0b10000000000000000000000000000000; +const GO_MODE_SYMLINK: u32 = 0b00000100000000000000000000000000; +const GO_MODE_DEVICE: u32 = 0b00000010000000000000000000000000; +const GO_MODE_FIFO: u32 = 0b00000001000000000000000000000000; +const GO_MODE_SOCKET: u32 = 0b00000000100000000000000000000000; +const GO_MODE_SETUID: u32 = 0b00000000010000000000000000000000; +const GO_MODE_SETGID: u32 = 0b00000000001000000000000000000000; +const GO_MODE_CHARDEV: u32 = 0b00000000000100000000000000000000; +const GO_MODE_STICKY: u32 = 0b00000000000010000000000000000000; +const GO_MODE_IRREG: u32 = 0b00000000000001000000000000000000; + +// consts from man page inode(7) +const S_IFFORMAT: u32 = 0o170000; // File mask +const S_IFSOCK: u32 = 0o140000; // socket +const S_IFLNK: u32 = 0o120000; // symbolic link +const S_IFREG: u32 = 0o100000; // regular file +const S_IFBLK: u32 = 0o060000; // block device +const S_IFDIR: u32 = 0o040000; // directory +const S_IFCHR: u32 = 0o020000; // character device +const S_IFIFO: u32 = 0o010000; // FIFO + +const S_ISUID: u32 = 0o4000; // set-user-ID bit (see execve(2)) +const S_ISGID: u32 = 0o2000; // set-group-ID bit (see below) +const S_ISVTX: u32 = 0o1000; // sticky bit (see below) + +/// map st_mode from POSIX (inode(7)) to golang's definition (https://pkg.go.dev/io/fs#ModeType) +/// Note, that it only sets the bits os.ModePerm | os.ModeType | os.ModeSetuid | os.ModeSetgid | os.ModeSticky +/// to stay compatible with the restic implementation +fn map_mode_to_go(mode: u32) -> u32 { + let mut go_mode = mode & MODE_PERM; + + match mode & S_IFFORMAT { + S_IFSOCK => go_mode |= GO_MODE_SOCKET, + S_IFLNK => go_mode |= GO_MODE_SYMLINK, + S_IFBLK => go_mode |= GO_MODE_DEVICE, + S_IFDIR => go_mode |= GO_MODE_DIR, + S_IFCHR => go_mode |= GO_MODE_CHARDEV, + S_IFIFO => go_mode |= GO_MODE_FIFO, + // note that POSIX specifies regular files, whereas golang specifies irregular files + S_IFREG => {} + _ => go_mode |= GO_MODE_IRREG, + }; + + if mode & S_ISUID > 0 { + go_mode |= GO_MODE_SETUID; + } + if mode & S_ISGID > 0 { + go_mode |= GO_MODE_SETGID; + } + if mode & S_ISVTX > 0 { + go_mode |= GO_MODE_STICKY; + } + + go_mode +} diff --git a/src/backend/node.rs b/src/backend/node.rs index 0994c96..510a0bc 100644 --- a/src/backend/node.rs +++ b/src/backend/node.rs @@ -10,7 +10,7 @@ use serde_aux::prelude::*; use crate::id::Id; -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Constructor, Getters)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Constructor)] pub struct Node { name: String, #[serde(flatten)] @@ -18,7 +18,7 @@ pub struct Node { #[serde(flatten)] meta: Metadata, #[serde(default, deserialize_with = "deserialize_default_from_null")] - content: Vec, + content: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] subtree: Option, } @@ -69,7 +69,7 @@ impl Node { Self { name: name.to_str().expect("no unicode").to_string(), node_type: NodeType::File, - content: Vec::new(), + content: None, subtree: None, meta, } @@ -79,7 +79,7 @@ impl Node { Self { name: name.to_str().expect("no unicode").to_string(), node_type: NodeType::Dir, - content: Vec::new(), + content: None, subtree: None, meta, } @@ -91,7 +91,7 @@ impl Node { node_type: NodeType::Symlink { linktarget: target.to_str().expect("no unicode").to_string(), }, - content: Vec::new(), + content: None, subtree: None, meta, } @@ -106,6 +106,26 @@ impl Node { } pub fn set_content(&mut self, content: Vec) { - self.content = content; + self.content = Some(content); + } + + pub fn name(&self) -> &String { + &self.name + } + + pub fn node_type(&self) -> &NodeType { + &self.node_type + } + + pub fn meta(&self) -> &Metadata { + &self.meta + } + + pub fn content(&self) -> &Vec { + self.content.as_ref().unwrap() + } + + pub fn subtree(&self) -> &Option { + &self.subtree } }