#[cfg(not(windows))]
pub mod nix_mapper;
use std::{ffi::OsStr, path::Path};
use derive_setters::Setters;
use ignore::DirEntry;
use jiff::Timestamp;
use log::warn;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use super::{IgnoreErrorKind, IgnoreResult, OpenFile};
use crate::backend::{
ReadSourceEntry,
node::{
ExtendedAttribute, Metadata, Node, NodeType,
modification::{DevIdOption, TimeOption, XattrOption},
},
};
#[cfg(not(windows))]
use std::os::unix::fs::{FileTypeExt, MetadataExt};
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum BlockdevOption {
#[default]
Special,
File,
}
#[serde_as]
#[cfg_attr(feature = "clap", derive(clap::Parser))]
#[cfg_attr(feature = "merge", derive(conflate::Merge))]
#[derive(serde::Deserialize, serde::Serialize, Default, Clone, Copy, Debug, Setters)]
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
#[setters(into)]
#[non_exhaustive]
pub struct LocalSourceSaveOptions {
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "merge", merge(strategy = conflate::option::overwrite_none))]
pub set_atime: Option<TimeOption>,
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "merge", merge(strategy = conflate::option::overwrite_none))]
pub set_ctime: Option<TimeOption>,
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "merge", merge(strategy = conflate::option::overwrite_none))]
pub set_devid: Option<DevIdOption>,
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "merge", merge(strategy = conflate::option::overwrite_none))]
pub set_blockdev: Option<BlockdevOption>,
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "merge", merge(strategy = conflate::option::overwrite_none))]
pub set_xattrs: Option<XattrOption>,
}
impl LocalSourceSaveOptions {
pub fn map_entry(self, entry: DirEntry) -> IgnoreResult<ReadSourceEntry<OpenFile>> {
let name = entry.file_name();
let m = entry
.metadata()
.map_err(|err| IgnoreErrorKind::AcquiringMetadataFailed {
name: name.to_string_lossy().to_string(),
source: err,
})?;
let mtime = m.modified().ok().and_then(|t| Timestamp::try_from(t).ok());
let atime = || m.accessed().ok().and_then(|t| Timestamp::try_from(t).ok());
let atime = self
.set_atime
.unwrap_or(TimeOption::Mtime)
.map_or_else(atime, mtime);
let ctime = || Self::ctime(&m);
let ctime = self
.set_ctime
.unwrap_or(TimeOption::Yes)
.map_or_else(ctime, mtime);
let (uid, user, gid, group) = Self::user_group(&m);
let size = if m.is_dir() { 0 } else { m.len() };
let device_id = self
.set_devid
.unwrap_or_default()
.map_or_else(|| Self::device_id(&m), Self::hardlink(&m));
let xattr = || {
Self::xattrs(entry.path())
.inspect_err(|err| warn!("ignoring error obtaining xargs: {err}"))
.unwrap_or_default()
};
let extended_attributes = self.set_xattrs.unwrap_or_default().map_or_else(xattr);
let (mode, inode, links) = Self::nix_infos(&m);
let meta = Metadata {
mode,
mtime,
atime,
ctime,
uid,
gid,
user,
group,
inode,
device_id,
size,
links,
extended_attributes,
};
let node = self.to_node(&entry, &m, meta)?;
let path = entry.into_path();
let open = Some(OpenFile(path.clone()));
Ok(ReadSourceEntry { path, node, open })
}
fn to_node(
self,
entry: &DirEntry,
m: &std::fs::Metadata,
meta: Metadata,
) -> IgnoreResult<Node> {
let name = entry.file_name();
let node = if m.is_dir() {
Node::new_node(name, NodeType::Dir, meta)
} else if m.is_symlink() {
let path = entry.path();
let target = std::fs::read_link(path).map_err(|err| IgnoreErrorKind::ErrorLink {
path: path.to_path_buf(),
source: err,
})?;
let node_type = NodeType::from_link(&target);
Node::new_node(name, node_type, meta)
} else {
self.to_node_other(name, m, meta)
};
Ok(node)
}
}
#[cfg(not(windows))]
impl LocalSourceSaveOptions {
fn ctime(m: &std::fs::Metadata) -> Option<Timestamp> {
#[allow(clippy::cast_possible_truncation)]
Timestamp::new(m.ctime(), m.ctime_nsec() as i32).ok()
}
fn device_id(m: &std::fs::Metadata) -> u64 {
m.dev()
}
fn hardlink(m: &std::fs::Metadata) -> bool {
m.nlink() > 1 && !m.is_dir()
}
fn user_group(
m: &std::fs::Metadata,
) -> (Option<u32>, Option<String>, Option<u32>, Option<String>) {
let uid = m.uid();
let gid = m.gid();
let user = nix_mapper::get_user_by_uid(uid);
let group = nix_mapper::get_group_by_gid(gid);
(Some(uid), user, Some(gid), group)
}
fn nix_infos(m: &std::fs::Metadata) -> (Option<u32>, u64, u64) {
let mode = nix_mapper::map_mode_to_go(m.mode());
let inode = m.ino();
let links = if m.is_dir() { 0 } else { m.nlink() };
(Some(mode), inode, links)
}
#[cfg(not(target_os = "openbsd"))]
fn xattrs(path: &Path) -> IgnoreResult<Vec<ExtendedAttribute>> {
xattr::list(path)
.map_err(|err| IgnoreErrorKind::ErrorXattr {
path: path.to_path_buf(),
source: err,
})?
.map(|name| {
Ok(ExtendedAttribute {
name: name.to_string_lossy().to_string(),
value: xattr::get(path, name).map_err(|err| IgnoreErrorKind::ErrorXattr {
path: path.to_path_buf(),
source: err,
})?,
})
})
.collect::<IgnoreResult<Vec<ExtendedAttribute>>>()
}
#[cfg(target_os = "openbsd")]
fn xattrs(_path: &Path) -> IgnoreResult<Vec<ExtendedAttribute>> {
Ok(Vec::new())
}
fn to_node_other(self, name: &OsStr, m: &std::fs::Metadata, meta: Metadata) -> Node {
let filetype = m.file_type();
if filetype.is_block_device() {
if matches!(self.set_blockdev.unwrap_or_default(), BlockdevOption::File) {
Node::new_node(name, NodeType::File, meta)
} else {
let node_type = NodeType::Dev { device: m.rdev() };
Node::new_node(name, node_type, meta)
}
} else if filetype.is_char_device() {
let node_type = NodeType::Chardev { device: m.rdev() };
Node::new_node(name, node_type, meta)
} else if filetype.is_fifo() {
Node::new_node(name, NodeType::Fifo, meta)
} else if filetype.is_socket() {
Node::new_node(name, NodeType::Socket, meta)
} else {
Node::new_node(name, NodeType::File, meta)
}
}
}
#[cfg(windows)]
impl LocalSourceSaveOptions {
fn ctime(m: &std::fs::Metadata) -> Option<Timestamp> {
m.created().ok().and_then(|t| Timestamp::try_from(t).ok())
}
fn device_id(_m: &std::fs::Metadata) -> u64 {
0
}
fn hardlink(m: &std::fs::Metadata) -> bool {
false
}
fn user_group(
_m: &std::fs::Metadata,
) -> (Option<u32>, Option<String>, Option<u32>, Option<String>) {
(None, None, None, None)
}
fn nix_infos(_m: &std::fs::Metadata) -> (Option<u32>, u64, u64) {
(None, 0, 0)
}
fn xattrs(_path: &Path) -> IgnoreResult<Vec<ExtendedAttribute>> {
Ok(Vec::new())
}
fn to_node_other(self, name: &OsStr, _m: &std::fs::Metadata, meta: Metadata) -> Node {
Node::new_node(name, NodeType::File, meta)
}
}