Merge pull request #55 from rustic-rs/special-files+metadata

Special files+metadata
This commit is contained in:
aawsome 2022-07-07 14:11:57 +02:00 committed by GitHub
commit 0c2b85da33
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 245 additions and 36 deletions

35
Cargo.lock generated
View File

@ -471,6 +471,18 @@ dependencies = [
"instant",
]
[[package]]
name = "filetime"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.2.13",
"windows-sys",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -916,6 +928,15 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "mime"
version = "0.3.16"
@ -934,6 +955,18 @@ dependencies = [
"windows-sys",
]
[[package]]
name = "nix"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9"
dependencies = [
"bitflags",
"cfg-if",
"libc",
"memoffset",
]
[[package]]
name = "num-integer"
version = "0.1.45"
@ -1333,6 +1366,7 @@ dependencies = [
"derive-getters",
"derive_more",
"dirs 4.0.0",
"filetime",
"futures",
"gethostname",
"hex",
@ -1341,6 +1375,7 @@ dependencies = [
"indicatif",
"itertools",
"lazy_static",
"nix",
"path-absolutize",
"prettytable-rs",
"rand",

View File

@ -50,6 +50,8 @@ zstd = "0.11"
# local backend
walkdir = "2"
ignore = "0.4"
nix = "0.24"
filetime = "0.2"
# rest backend
reqwest = {version = "0.11", default-features = false, features = ["json", "rustls-tls", "stream"] }
# cache

View File

@ -1,5 +1,5 @@
use std::fs::{read_link, File};
use std::os::unix::fs::MetadataExt;
use std::os::unix::fs::{FileTypeExt, MetadataExt};
use std::path::{Path, PathBuf};
use anyhow::Result;
@ -183,20 +183,30 @@ fn map_entry(entry: DirEntry, with_atime: bool, cache: &UsersCache) -> Result<(P
mtime,
atime,
ctime,
mode,
uid,
gid,
mode: Some(mode),
uid: Some(uid),
gid: Some(gid),
user,
group,
inode,
device_id,
links,
};
let filetype = m.file_type();
let node = if m.is_dir() {
Node::new_dir(name, meta)
} else if m.is_symlink() {
let target = read_link(entry.path())?;
Node::new_symlink(name, target, meta)
} else if filetype.is_block_device() {
Node::new_dev(name, meta, m.rdev())
} else if filetype.is_char_device() {
Node::new_chardev(name, meta, m.rdev())
} else if filetype.is_fifo() {
Node::new_fifo(name, meta)
} else if filetype.is_socket() {
Node::new_socket(name, meta)
} else {
Node::new_file(name, meta)
};
@ -207,15 +217,15 @@ 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;
const GO_MODE_SYMLINK: u32 = 0b00001000000000000000000000000000;
const GO_MODE_DEVICE: u32 = 0b00000100000000000000000000000000;
const GO_MODE_FIFO: u32 = 0b00000010000000000000000000000000;
const GO_MODE_SOCKET: u32 = 0b00000001000000000000000000000000;
const GO_MODE_SETUID: u32 = 0b00000000100000000000000000000000;
const GO_MODE_SETGID: u32 = 0b00000000010000000000000000000000;
const GO_MODE_CHARDEV: u32 = 0b00000000001000000000000000000000;
const GO_MODE_STICKY: u32 = 0b00000000000100000000000000000000;
const GO_MODE_IRREG: u32 = 0b00000000000010000000000000000000;
// consts from man page inode(7)
const S_IFFORMAT: u32 = 0o170000; // File mask
@ -242,7 +252,7 @@ fn map_mode_to_go(mode: u32) -> u32 {
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_IFCHR => go_mode |= GO_MODE_CHARDEV & GO_MODE_DEVICE, // no idea why go sets both for char devices...
S_IFIFO => go_mode |= GO_MODE_FIFO,
// note that POSIX specifies regular files, whereas golang specifies irregular files
S_IFREG => {}
@ -261,3 +271,38 @@ fn map_mode_to_go(mode: u32) -> u32 {
go_mode
}
/// map gloangs mode definition (https://pkg.go.dev/io/fs#ModeType) to t_mode from POSIX (inode(7))
/// This is the inverse function to map_mode_to_go()
pub fn map_mode_from_go(go_mode: u32) -> u32 {
let mut mode = go_mode & MODE_PERM;
if go_mode & GO_MODE_SOCKET > 0 {
mode |= S_IFSOCK
} else if go_mode & GO_MODE_SYMLINK > 0 {
mode |= S_IFLNK
} else if go_mode & GO_MODE_DEVICE > 0 && go_mode & GO_MODE_CHARDEV == 0 {
mode |= S_IFBLK;
} else if go_mode & GO_MODE_DIR > 0 {
mode |= S_IFDIR;
} else if go_mode & (GO_MODE_CHARDEV | GO_MODE_DEVICE) > 0 {
mode |= S_IFCHR;
} else if go_mode & GO_MODE_FIFO > 0 {
mode |= S_IFIFO;
} else if go_mode & GO_MODE_IRREG > 0 {
} else {
mode |= S_IFREG;
}
if go_mode & GO_MODE_SETUID > 0 {
mode |= S_ISUID;
}
if go_mode & GO_MODE_SETGID > 0 {
mode |= S_ISGID;
}
if go_mode & GO_MODE_STICKY > 0 {
mode |= S_ISVTX;
}
mode
}

View File

@ -1,15 +1,19 @@
use std::fs::{self, File};
use std::io::{copy, Read, Seek, SeekFrom, Write};
use std::os::unix::fs::FileExt;
use std::os::unix::fs::PermissionsExt;
use std::os::unix::fs::{symlink, FileExt, PermissionsExt};
use std::path::{Path, PathBuf};
use anyhow::Result;
use async_trait::async_trait;
use filetime::{set_file_atime, set_file_mtime, FileTime};
use nix::sys::stat::{mknod, Mode, SFlag};
use nix::unistd::chown;
use nix::unistd::{Gid, Group, Uid, User};
use vlog::*;
use walkdir::WalkDir;
use super::{node::Metadata, FileType, Id, ReadBackend, WriteBackend, ALL_FILE_TYPES};
use super::node::{Metadata, Node, NodeType};
use super::{map_mode_from_go, FileType, Id, ReadBackend, WriteBackend, ALL_FILE_TYPES};
#[derive(Clone)]
pub struct LocalBackend {
@ -190,20 +194,57 @@ impl LocalBackend {
fs::create_dir(&dirname).unwrap();
}
pub fn create_symlink(&self, item: impl AsRef<Path>, dest: impl AsRef<Path>) {
pub fn set_times(&self, item: impl AsRef<Path>, meta: &Metadata) -> Result<()> {
let filename = self.path.join(item);
std::os::unix::fs::symlink(dest, filename).unwrap();
if let Some(mtime) = meta.mtime.map(|t| FileTime::from_system_time(t.into())) {
set_file_mtime(&filename, mtime)?;
}
if let Some(atime) = meta.atime.map(|t| FileTime::from_system_time(t.into())) {
set_file_atime(&filename, atime)?;
}
Ok(())
}
// TODO: uid/gid and times
pub fn set_metadata(&self, item: impl AsRef<Path>, meta: &Metadata) {
let mode = *meta.mode();
if mode == 0 {
return;
}
pub fn set_user_group(&self, item: impl AsRef<Path>, meta: &Metadata) -> Result<()> {
let filename = self.path.join(item);
std::fs::set_permissions(&filename, fs::Permissions::from_mode(mode))
.unwrap_or_else(|_| panic!("error chmod {:?}", filename));
let user = meta
.user
.as_ref()
.and_then(|name| User::from_name(name).unwrap());
// use uid from user if valid, else from saved uid (if saved)
let uid = user.map(|u| u.uid).or_else(|| meta.uid.map(Uid::from_raw));
let group = meta
.group
.as_ref()
.and_then(|name| Group::from_name(name).unwrap());
// use gid from group if valid, else from saved gid (if saved)
let gid = group.map(|g| g.gid).or_else(|| meta.gid.map(Gid::from_raw));
chown(&filename, uid, gid)?;
Ok(())
}
pub fn set_uid_gid(&self, item: impl AsRef<Path>, meta: &Metadata) -> Result<()> {
let filename = self.path.join(item);
let uid = meta.uid.map(Uid::from_raw);
let gid = meta.gid.map(Gid::from_raw);
chown(&filename, uid, gid)?;
Ok(())
}
pub fn set_permission(&self, item: impl AsRef<Path>, meta: &Metadata) -> Result<()> {
let filename = self.path.join(item);
if let Some(mode) = meta.mode() {
let mode = map_mode_from_go(*mode);
std::fs::set_permissions(&filename, fs::Permissions::from_mode(mode))?;
}
Ok(())
}
pub fn create_file(&self, item: impl AsRef<Path>, size: u64) {
@ -212,6 +253,38 @@ impl LocalBackend {
f.set_len(size).unwrap();
}
pub fn create_special(&self, item: impl AsRef<Path>, node: &Node) -> Result<()> {
let filename = self.path.join(item);
match node.node_type() {
NodeType::Symlink { linktarget } => {
symlink(linktarget, filename)?;
}
NodeType::Dev { device } => {
#[cfg(not(target_os = "macos"))]
let device = *device;
#[cfg(target_os = "macos")]
let device = *device as i32;
mknod(&filename, SFlag::S_IFBLK, Mode::empty(), device)?;
}
NodeType::Chardev { device } => {
#[cfg(not(target_os = "macos"))]
let device = *device;
#[cfg(target_os = "macos")]
let device = *device as i32;
mknod(&filename, SFlag::S_IFCHR, Mode::empty(), device)?;
}
NodeType::Fifo => {
mknod(&filename, SFlag::S_IFIFO, Mode::empty(), 0)?;
}
NodeType::Socket => {
mknod(&filename, SFlag::S_IFSOCK, Mode::empty(), 0)?;
}
_ => {}
}
Ok(())
}
pub fn write_at(&self, item: impl AsRef<Path>, offset: u64, data: &[u8]) {
let filename = self.path.join(item);
let file = fs::OpenOptions::new()

View File

@ -45,18 +45,18 @@ pub enum NodeType {
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize, Getters)]
pub struct Metadata {
#[serde(default, skip_serializing_if = "is_default")]
pub mode: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mode: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub mtime: Option<DateTime<Local>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub atime: Option<DateTime<Local>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ctime: Option<DateTime<Local>>,
#[serde(default, skip_serializing_if = "is_default")]
pub uid: u32,
#[serde(default, skip_serializing_if = "is_default")]
pub gid: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub uid: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gid: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub user: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
@ -108,6 +108,46 @@ impl Node {
}
}
pub fn new_dev(name: OsString, meta: Metadata, device: u64) -> Self {
Self {
name: name.to_str().expect("no unicode").to_string(),
node_type: NodeType::Dev { device },
content: None,
subtree: None,
meta,
}
}
pub fn new_chardev(name: OsString, meta: Metadata, device: u64) -> Self {
Self {
name: name.to_str().expect("no unicode").to_string(),
node_type: NodeType::Chardev { device },
content: None,
subtree: None,
meta,
}
}
pub fn new_fifo(name: OsString, meta: Metadata) -> Self {
Self {
name: name.to_str().expect("no unicode").to_string(),
node_type: NodeType::Fifo,
content: None,
subtree: None,
meta,
}
}
pub fn new_socket(name: OsString, meta: Metadata) -> Self {
Self {
name: name.to_str().expect("no unicode").to_string(),
node_type: NodeType::Socket,
content: None,
subtree: None,
meta,
}
}
pub fn is_dir(&self) -> bool {
self.node_type == NodeType::Dir
}

View File

@ -26,6 +26,10 @@ pub(super) struct Opts {
#[clap(long)]
delete: bool,
/// use numeric ids instead of user/groug when restoring uid/gui
#[clap(long)]
numeric_id: bool,
/// snapshot to restore
id: String,
@ -150,10 +154,20 @@ async fn restore_metadata(
let mut node_streamer = NodeStreamer::new(index, tree).await?;
while let Some((path, node)) = node_streamer.try_next().await? {
if !opts.dry_run {
if let NodeType::Symlink { linktarget } = node.node_type() {
dest.create_symlink(&path, linktarget);
dest.create_special(&path, &node)
.unwrap_or_else(|_| eprintln!("restore {:?}: creating special file failed.", path));
if opts.numeric_id {
dest.set_uid_gid(&path, node.meta())
.unwrap_or_else(|_| eprintln!("restore {:?}: setting UID/GID failed.", path));
} else {
dest.set_user_group(&path, node.meta()).unwrap_or_else(|_| {
eprintln!("restore {:?}: setting User/Group failed.", path)
});
}
dest.set_metadata(&path, node.meta());
dest.set_permission(&path, node.meta())
.unwrap_or_else(|_| eprintln!("restore {:?}: chmod failed.", path));
dest.set_times(&path, node.meta())
.unwrap_or_else(|_| eprintln!("restore {:?}: setting file times failed.", path));
}
}