rustic_core/backend/ignore/
mapper.rs1#[cfg(not(windows))]
2pub mod nix_mapper;
3
4use std::{ffi::OsStr, path::Path};
5
6use derive_setters::Setters;
7use ignore::DirEntry;
8use jiff::Timestamp;
9use log::warn;
10use serde::{Deserialize, Serialize};
11use serde_with::serde_as;
12
13use super::{IgnoreErrorKind, IgnoreResult, OpenFile};
14use crate::backend::{
15 ReadSourceEntry,
16 node::{
17 ExtendedAttribute, Metadata, Node, NodeType,
18 modification::{DevIdOption, TimeOption, XattrOption},
19 },
20};
21
22#[cfg(not(windows))]
23use std::os::unix::fs::{FileTypeExt, MetadataExt};
24
25#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
26#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
27#[serde(rename_all = "kebab-case")]
28pub enum BlockdevOption {
29 #[default]
30 Special,
31 File,
32}
33
34#[serde_as]
35#[cfg_attr(feature = "clap", derive(clap::Parser))]
36#[cfg_attr(feature = "merge", derive(conflate::Merge))]
37#[derive(serde::Deserialize, serde::Serialize, Default, Clone, Copy, Debug, Setters)]
38#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
39#[setters(into)]
40#[non_exhaustive]
41pub struct LocalSourceSaveOptions {
43 #[cfg_attr(feature = "clap", clap(long))]
45 #[cfg_attr(feature = "merge", merge(strategy = conflate::option::overwrite_none))]
46 pub set_atime: Option<TimeOption>,
47
48 #[cfg_attr(feature = "clap", clap(long))]
50 #[cfg_attr(feature = "merge", merge(strategy = conflate::option::overwrite_none))]
51 pub set_ctime: Option<TimeOption>,
52
53 #[cfg_attr(feature = "clap", clap(long))]
55 #[cfg_attr(feature = "merge", merge(strategy = conflate::option::overwrite_none))]
56 pub set_devid: Option<DevIdOption>,
57
58 #[cfg_attr(feature = "clap", clap(long))]
60 #[cfg_attr(feature = "merge", merge(strategy = conflate::option::overwrite_none))]
61 pub set_blockdev: Option<BlockdevOption>,
62
63 #[cfg_attr(feature = "clap", clap(long))]
65 #[cfg_attr(feature = "merge", merge(strategy = conflate::option::overwrite_none))]
66 pub set_xattrs: Option<XattrOption>,
67}
68
69impl LocalSourceSaveOptions {
70 pub fn map_entry(self, entry: DirEntry) -> IgnoreResult<ReadSourceEntry<OpenFile>> {
82 let name = entry.file_name();
83 let m = entry
84 .metadata()
85 .map_err(|err| IgnoreErrorKind::AcquiringMetadataFailed {
86 name: name.to_string_lossy().to_string(),
87 source: err,
88 })?;
89
90 let mtime = m.modified().ok().and_then(|t| Timestamp::try_from(t).ok());
91 let atime = || m.accessed().ok().and_then(|t| Timestamp::try_from(t).ok());
92 let atime = self
93 .set_atime
94 .unwrap_or(TimeOption::Mtime)
95 .map_or_else(atime, mtime);
96 let ctime = || Self::ctime(&m);
97 let ctime = self
98 .set_ctime
99 .unwrap_or(TimeOption::Yes)
100 .map_or_else(ctime, mtime);
101
102 let (uid, user, gid, group) = Self::user_group(&m);
103 let size = if m.is_dir() { 0 } else { m.len() };
104 let device_id = self
105 .set_devid
106 .unwrap_or_default()
107 .map_or_else(|| Self::device_id(&m), Self::hardlink(&m));
108 let xattr = || {
109 Self::xattrs(entry.path())
110 .inspect_err(|err| warn!("ignoring error obtaining xargs: {err}"))
111 .unwrap_or_default()
112 };
113 let extended_attributes = self.set_xattrs.unwrap_or_default().map_or_else(xattr);
114 let (mode, inode, links) = Self::nix_infos(&m);
115
116 let meta = Metadata {
117 mode,
118 mtime,
119 atime,
120 ctime,
121 uid,
122 gid,
123 user,
124 group,
125 inode,
126 device_id,
127 size,
128 links,
129 extended_attributes,
130 };
131
132 let node = self.to_node(&entry, &m, meta)?;
133 let path = entry.into_path();
134 let open = Some(OpenFile(path.clone()));
135 Ok(ReadSourceEntry { path, node, open })
136 }
137
138 fn to_node(
139 self,
140 entry: &DirEntry,
141 m: &std::fs::Metadata,
142 meta: Metadata,
143 ) -> IgnoreResult<Node> {
144 let name = entry.file_name();
145 let node = if m.is_dir() {
146 Node::new_node(name, NodeType::Dir, meta)
147 } else if m.is_symlink() {
148 let path = entry.path();
149 let target = std::fs::read_link(path).map_err(|err| IgnoreErrorKind::ErrorLink {
150 path: path.to_path_buf(),
151 source: err,
152 })?;
153 let node_type = NodeType::from_link(&target);
154 Node::new_node(name, node_type, meta)
155 } else {
156 self.to_node_other(name, m, meta)
157 };
158 Ok(node)
159 }
160}
161
162#[cfg(not(windows))]
163impl LocalSourceSaveOptions {
164 fn ctime(m: &std::fs::Metadata) -> Option<Timestamp> {
165 #[allow(clippy::cast_possible_truncation)]
166 Timestamp::new(m.ctime(), m.ctime_nsec() as i32).ok()
167 }
168
169 fn device_id(m: &std::fs::Metadata) -> u64 {
170 m.dev()
171 }
172
173 fn hardlink(m: &std::fs::Metadata) -> bool {
174 m.nlink() > 1 && !m.is_dir()
175 }
176
177 fn user_group(
178 m: &std::fs::Metadata,
179 ) -> (Option<u32>, Option<String>, Option<u32>, Option<String>) {
180 let uid = m.uid();
181 let gid = m.gid();
182 let user = nix_mapper::get_user_by_uid(uid);
183 let group = nix_mapper::get_group_by_gid(gid);
184 (Some(uid), user, Some(gid), group)
185 }
186
187 fn nix_infos(m: &std::fs::Metadata) -> (Option<u32>, u64, u64) {
188 let mode = nix_mapper::map_mode_to_go(m.mode());
189 let inode = m.ino();
190 let links = if m.is_dir() { 0 } else { m.nlink() };
191 (Some(mode), inode, links)
192 }
193
194 #[cfg(not(target_os = "openbsd"))]
204 fn xattrs(path: &Path) -> IgnoreResult<Vec<ExtendedAttribute>> {
205 xattr::list(path)
206 .map_err(|err| IgnoreErrorKind::ErrorXattr {
207 path: path.to_path_buf(),
208 source: err,
209 })?
210 .map(|name| {
211 Ok(ExtendedAttribute {
212 name: name.to_string_lossy().to_string(),
213 value: xattr::get(path, name).map_err(|err| IgnoreErrorKind::ErrorXattr {
214 path: path.to_path_buf(),
215 source: err,
216 })?,
217 })
218 })
219 .collect::<IgnoreResult<Vec<ExtendedAttribute>>>()
220 }
221
222 #[cfg(target_os = "openbsd")]
223 fn xattrs(_path: &Path) -> IgnoreResult<Vec<ExtendedAttribute>> {
224 Ok(Vec::new())
225 }
226
227 fn to_node_other(self, name: &OsStr, m: &std::fs::Metadata, meta: Metadata) -> Node {
228 let filetype = m.file_type();
229 if filetype.is_block_device() {
230 if matches!(self.set_blockdev.unwrap_or_default(), BlockdevOption::File) {
231 Node::new_node(name, NodeType::File, meta)
232 } else {
233 let node_type = NodeType::Dev { device: m.rdev() };
234 Node::new_node(name, node_type, meta)
235 }
236 } else if filetype.is_char_device() {
237 let node_type = NodeType::Chardev { device: m.rdev() };
238 Node::new_node(name, node_type, meta)
239 } else if filetype.is_fifo() {
240 Node::new_node(name, NodeType::Fifo, meta)
241 } else if filetype.is_socket() {
242 Node::new_node(name, NodeType::Socket, meta)
243 } else {
244 Node::new_node(name, NodeType::File, meta)
245 }
246 }
247}
248
249#[cfg(windows)]
250impl LocalSourceSaveOptions {
251 fn ctime(m: &std::fs::Metadata) -> Option<Timestamp> {
252 m.created().ok().and_then(|t| Timestamp::try_from(t).ok())
253 }
254 fn device_id(_m: &std::fs::Metadata) -> u64 {
255 0
256 }
257 fn hardlink(m: &std::fs::Metadata) -> bool {
258 false
259 }
260 fn user_group(
261 _m: &std::fs::Metadata,
262 ) -> (Option<u32>, Option<String>, Option<u32>, Option<String>) {
263 (None, None, None, None)
264 }
265
266 fn nix_infos(_m: &std::fs::Metadata) -> (Option<u32>, u64, u64) {
267 (None, 0, 0)
268 }
269
270 fn xattrs(_path: &Path) -> IgnoreResult<Vec<ExtendedAttribute>> {
271 Ok(Vec::new())
272 }
273
274 fn to_node_other(self, name: &OsStr, _m: &std::fs::Metadata, meta: Metadata) -> Node {
275 Node::new_node(name, NodeType::File, meta)
276 }
277}