]>
Commit | Line | Data |
---|---|---|
c443f58b | 1 | use std::collections::{HashSet, HashMap}; |
c443f58b WB |
2 | use std::ffi::{CStr, CString, OsStr}; |
3 | use std::fmt; | |
a8e2940f | 4 | use std::io::{self, Read, Write}; |
c443f58b WB |
5 | use std::os::unix::ffi::OsStrExt; |
6 | use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; | |
7 | use std::path::{Path, PathBuf}; | |
6afb60ab | 8 | use std::sync::{Arc, Mutex}; |
c443f58b WB |
9 | |
10 | use anyhow::{bail, format_err, Error}; | |
11 | use nix::dir::Dir; | |
12 | use nix::errno::Errno; | |
13 | use nix::fcntl::OFlag; | |
14 | use nix::sys::stat::{FileStat, Mode}; | |
6afb60ab SR |
15 | use futures::future::BoxFuture; |
16 | use futures::FutureExt; | |
c443f58b | 17 | |
8d841f81 | 18 | use pathpatterns::{MatchEntry, MatchFlag, MatchList, MatchType, PatternFlag}; |
c443f58b | 19 | use pxar::Metadata; |
6afb60ab | 20 | use pxar::encoder::{SeqWrite, LinkOffset}; |
c443f58b | 21 | |
25877d05 DM |
22 | use proxmox_sys::error::SysError; |
23 | use proxmox_sys::fd::RawFdNum; | |
24 | use proxmox_sys::fd::Fd; | |
25 | use proxmox_sys::fs::{self, acl, xattr}; | |
6ef1b649 WB |
26 | use proxmox_io::vec; |
27 | use proxmox_lang::c_str; | |
c443f58b | 28 | |
f323e906 | 29 | use pbs_datastore::catalog::BackupCatalogWriter; |
770a36e5 | 30 | |
032cd1b8 | 31 | use crate::pxar::metadata::errno_is_unsupported; |
5444fa94 | 32 | use crate::pxar::Flags; |
7eacdc76 | 33 | use crate::pxar::tools::assert_single_path_component; |
c443f58b | 34 | |
77486a60 FG |
35 | /// Pxar options for creating a pxar archive/stream |
36 | #[derive(Default, Clone)] | |
37 | pub struct PxarCreateOptions { | |
38 | /// Device/mountpoint st_dev numbers that should be included. None for no limitation. | |
39 | pub device_set: Option<HashSet<u64>>, | |
40 | /// Exclusion patterns | |
41 | pub patterns: Vec<MatchEntry>, | |
42 | /// Maximum number of entries to hold in memory | |
43 | pub entries_max: usize, | |
44 | /// Skip lost+found directory | |
45 | pub skip_lost_and_found: bool, | |
46 | /// Verbose output | |
47 | pub verbose: bool, | |
48 | } | |
49 | ||
50 | ||
c443f58b WB |
51 | fn detect_fs_type(fd: RawFd) -> Result<i64, Error> { |
52 | let mut fs_stat = std::mem::MaybeUninit::uninit(); | |
53 | let res = unsafe { libc::fstatfs(fd, fs_stat.as_mut_ptr()) }; | |
54 | Errno::result(res)?; | |
55 | let fs_stat = unsafe { fs_stat.assume_init() }; | |
56 | ||
57 | Ok(fs_stat.f_type) | |
58 | } | |
59 | ||
92ef0b56 DM |
60 | fn strip_ascii_whitespace(line: &[u8]) -> &[u8] { |
61 | let line = match line.iter().position(|&b| !b.is_ascii_whitespace()) { | |
62 | Some(n) => &line[n..], | |
63 | None => return &[], | |
64 | }; | |
65 | match line.iter().rev().position(|&b| !b.is_ascii_whitespace()) { | |
66 | Some(n) => &line[..(line.len() - n)], | |
67 | None => &[], | |
68 | } | |
69 | } | |
70 | ||
a8e2940f | 71 | #[rustfmt::skip] |
c443f58b | 72 | pub fn is_virtual_file_system(magic: i64) -> bool { |
25877d05 | 73 | use proxmox_sys::linux::magic::*; |
c443f58b | 74 | |
a6bd6698 | 75 | matches!(magic, BINFMTFS_MAGIC | |
c443f58b WB |
76 | CGROUP2_SUPER_MAGIC | |
77 | CGROUP_SUPER_MAGIC | | |
78 | CONFIGFS_MAGIC | | |
79 | DEBUGFS_MAGIC | | |
80 | DEVPTS_SUPER_MAGIC | | |
81 | EFIVARFS_MAGIC | | |
82 | FUSE_CTL_SUPER_MAGIC | | |
83 | HUGETLBFS_MAGIC | | |
84 | MQUEUE_MAGIC | | |
85 | NFSD_MAGIC | | |
86 | PROC_SUPER_MAGIC | | |
87 | PSTOREFS_MAGIC | | |
88 | RPCAUTH_GSSMAGIC | | |
89 | SECURITYFS_MAGIC | | |
90 | SELINUX_MAGIC | | |
91 | SMACK_MAGIC | | |
a6bd6698 | 92 | SYSFS_MAGIC) |
c443f58b WB |
93 | } |
94 | ||
95 | #[derive(Debug)] | |
96 | struct ArchiveError { | |
97 | path: PathBuf, | |
98 | error: Error, | |
99 | } | |
100 | ||
101 | impl ArchiveError { | |
102 | fn new(path: PathBuf, error: Error) -> Self { | |
103 | Self { path, error } | |
104 | } | |
105 | } | |
106 | ||
107 | impl std::error::Error for ArchiveError {} | |
108 | ||
109 | impl fmt::Display for ArchiveError { | |
110 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | |
111 | write!(f, "error at {:?}: {}", self.path, self.error) | |
112 | } | |
113 | } | |
114 | ||
115 | #[derive(Eq, PartialEq, Hash)] | |
116 | struct HardLinkInfo { | |
117 | st_dev: u64, | |
118 | st_ino: u64, | |
119 | } | |
120 | ||
47208b41 WB |
121 | /// TODO: make a builder for the create_archive call for fewer parameters and add a method to add a |
122 | /// logger which does not write to stderr. | |
123 | struct Logger; | |
124 | ||
125 | impl std::io::Write for Logger { | |
126 | fn write(&mut self, data: &[u8]) -> io::Result<usize> { | |
127 | std::io::stderr().write(data) | |
128 | } | |
129 | ||
130 | fn flush(&mut self) -> io::Result<()> { | |
131 | std::io::stderr().flush() | |
132 | } | |
133 | } | |
134 | ||
135 | /// And the error case. | |
3d68536f WB |
136 | struct ErrorReporter; |
137 | ||
138 | impl std::io::Write for ErrorReporter { | |
139 | fn write(&mut self, data: &[u8]) -> io::Result<usize> { | |
140 | std::io::stderr().write(data) | |
141 | } | |
142 | ||
143 | fn flush(&mut self) -> io::Result<()> { | |
144 | std::io::stderr().flush() | |
145 | } | |
146 | } | |
147 | ||
6afb60ab | 148 | struct Archiver { |
5444fa94 WB |
149 | feature_flags: Flags, |
150 | fs_feature_flags: Flags, | |
c443f58b | 151 | fs_magic: i64, |
3d68536f | 152 | patterns: Vec<MatchEntry>, |
6afb60ab SR |
153 | callback: Box<dyn FnMut(&Path) -> Result<(), Error> + Send>, |
154 | catalog: Option<Arc<Mutex<dyn BackupCatalogWriter + Send>>>, | |
c443f58b WB |
155 | path: PathBuf, |
156 | entry_counter: usize, | |
157 | entry_limit: usize, | |
158 | current_st_dev: libc::dev_t, | |
159 | device_set: Option<HashSet<u64>>, | |
160 | hardlinks: HashMap<HardLinkInfo, (PathBuf, LinkOffset)>, | |
3d68536f | 161 | errors: ErrorReporter, |
47208b41 | 162 | logger: Logger, |
a8e2940f | 163 | file_copy_buffer: Vec<u8>, |
c443f58b WB |
164 | } |
165 | ||
6afb60ab | 166 | type Encoder<'a, T> = pxar::encoder::aio::Encoder<'a, T>; |
c443f58b | 167 | |
6afb60ab | 168 | pub async fn create_archive<T, F>( |
c443f58b WB |
169 | source_dir: Dir, |
170 | mut writer: T, | |
5444fa94 | 171 | feature_flags: Flags, |
6afb60ab SR |
172 | callback: F, |
173 | catalog: Option<Arc<Mutex<dyn BackupCatalogWriter + Send>>>, | |
77486a60 | 174 | options: PxarCreateOptions, |
c443f58b WB |
175 | ) -> Result<(), Error> |
176 | where | |
6afb60ab SR |
177 | T: SeqWrite + Send, |
178 | F: FnMut(&Path) -> Result<(), Error> + Send + 'static, | |
c443f58b WB |
179 | { |
180 | let fs_magic = detect_fs_type(source_dir.as_raw_fd())?; | |
181 | if is_virtual_file_system(fs_magic) { | |
182 | bail!("refusing to backup a virtual file system"); | |
183 | } | |
184 | ||
5667b763 | 185 | let mut fs_feature_flags = Flags::from_magic(fs_magic); |
c443f58b WB |
186 | |
187 | let stat = nix::sys::stat::fstat(source_dir.as_raw_fd())?; | |
188 | let metadata = get_metadata( | |
189 | source_dir.as_raw_fd(), | |
190 | &stat, | |
191 | feature_flags & fs_feature_flags, | |
192 | fs_magic, | |
5667b763 | 193 | &mut fs_feature_flags, |
c443f58b WB |
194 | ) |
195 | .map_err(|err| format_err!("failed to get metadata for source directory: {}", err))?; | |
196 | ||
77486a60 | 197 | let mut device_set = options.device_set.clone(); |
c443f58b WB |
198 | if let Some(ref mut set) = device_set { |
199 | set.insert(stat.st_dev); | |
200 | } | |
201 | ||
6afb60ab | 202 | let mut encoder = Encoder::new(&mut writer, &metadata).await?; |
c443f58b | 203 | |
340c0bf9 | 204 | let mut patterns = options.patterns; |
77486a60 FG |
205 | |
206 | if options.skip_lost_and_found { | |
239e49f9 | 207 | patterns.push(MatchEntry::parse_pattern( |
bf7e2a46 | 208 | "lost+found", |
c443f58b WB |
209 | PatternFlag::PATH_NAME, |
210 | MatchType::Exclude, | |
211 | )?); | |
212 | } | |
213 | ||
214 | let mut archiver = Archiver { | |
215 | feature_flags, | |
216 | fs_feature_flags, | |
217 | fs_magic, | |
6afb60ab | 218 | callback: Box::new(callback), |
3d68536f | 219 | patterns, |
c443f58b WB |
220 | catalog, |
221 | path: PathBuf::new(), | |
222 | entry_counter: 0, | |
77486a60 | 223 | entry_limit: options.entries_max, |
c443f58b WB |
224 | current_st_dev: stat.st_dev, |
225 | device_set, | |
226 | hardlinks: HashMap::new(), | |
3d68536f | 227 | errors: ErrorReporter, |
47208b41 | 228 | logger: Logger, |
a8e2940f | 229 | file_copy_buffer: vec::undefined(4 * 1024 * 1024), |
c443f58b WB |
230 | }; |
231 | ||
6afb60ab SR |
232 | archiver.archive_dir_contents(&mut encoder, source_dir, true).await?; |
233 | encoder.finish().await?; | |
c443f58b WB |
234 | Ok(()) |
235 | } | |
236 | ||
237 | struct FileListEntry { | |
238 | name: CString, | |
239 | path: PathBuf, | |
240 | stat: FileStat, | |
241 | } | |
242 | ||
6afb60ab | 243 | impl Archiver { |
5444fa94 WB |
244 | /// Get the currently effective feature flags. (Requested flags masked by the file system |
245 | /// feature flags). | |
246 | fn flags(&self) -> Flags { | |
c443f58b WB |
247 | self.feature_flags & self.fs_feature_flags |
248 | } | |
249 | ||
250 | fn wrap_err(&self, err: Error) -> Error { | |
251 | if err.downcast_ref::<ArchiveError>().is_some() { | |
252 | err | |
253 | } else { | |
254 | ArchiveError::new(self.path.clone(), err).into() | |
255 | } | |
256 | } | |
257 | ||
6afb60ab SR |
258 | fn archive_dir_contents<'a, 'b, T: SeqWrite + Send>( |
259 | &'a mut self, | |
260 | encoder: &'a mut Encoder<'b, T>, | |
30140886 WB |
261 | mut dir: Dir, |
262 | is_root: bool, | |
6afb60ab SR |
263 | ) -> BoxFuture<'a, Result<(), Error>> { |
264 | async move { | |
265 | let entry_counter = self.entry_counter; | |
c443f58b | 266 | |
6afb60ab SR |
267 | let old_patterns_count = self.patterns.len(); |
268 | self.read_pxar_excludes(dir.as_raw_fd())?; | |
3d68536f | 269 | |
6afb60ab | 270 | let mut file_list = self.generate_directory_file_list(&mut dir, is_root)?; |
fa4bcbca | 271 | |
6afb60ab SR |
272 | if is_root && old_patterns_count > 0 { |
273 | file_list.push(FileListEntry { | |
274 | name: CString::new(".pxarexclude-cli").unwrap(), | |
275 | path: PathBuf::new(), | |
276 | stat: unsafe { std::mem::zeroed() }, | |
277 | }); | |
278 | } | |
c443f58b | 279 | |
6afb60ab | 280 | let dir_fd = dir.as_raw_fd(); |
c443f58b | 281 | |
6afb60ab | 282 | let old_path = std::mem::take(&mut self.path); |
3d68536f | 283 | |
6afb60ab SR |
284 | for file_entry in file_list { |
285 | let file_name = file_entry.name.to_bytes(); | |
3d68536f | 286 | |
6afb60ab SR |
287 | if is_root && file_name == b".pxarexclude-cli" { |
288 | self.encode_pxarexclude_cli(encoder, &file_entry.name, old_patterns_count).await?; | |
289 | continue; | |
290 | } | |
3d68536f | 291 | |
6afb60ab SR |
292 | (self.callback)(&file_entry.path)?; |
293 | self.path = file_entry.path; | |
294 | self.add_entry(encoder, dir_fd, &file_entry.name, &file_entry.stat).await | |
295 | .map_err(|err| self.wrap_err(err))?; | |
296 | } | |
297 | self.path = old_path; | |
298 | self.entry_counter = entry_counter; | |
299 | self.patterns.truncate(old_patterns_count); | |
3d68536f | 300 | |
6afb60ab SR |
301 | Ok(()) |
302 | }.boxed() | |
3d68536f WB |
303 | } |
304 | ||
305 | /// openat() wrapper which allows but logs `EACCES` and turns `ENOENT` into `None`. | |
7d0754a6 WB |
306 | /// |
307 | /// The `existed` flag is set when iterating through a directory to note that we know the file | |
308 | /// is supposed to exist and we should warn if it doesnt'. | |
3d68536f WB |
309 | fn open_file( |
310 | &mut self, | |
311 | parent: RawFd, | |
312 | file_name: &CStr, | |
313 | oflags: OFlag, | |
7d0754a6 | 314 | existed: bool, |
3d68536f | 315 | ) -> Result<Option<Fd>, Error> { |
e51be338 WB |
316 | // common flags we always want to use: |
317 | let oflags = oflags | OFlag::O_CLOEXEC | OFlag::O_NOCTTY; | |
318 | ||
30c3c5d6 WB |
319 | let mut noatime = OFlag::O_NOATIME; |
320 | loop { | |
321 | return match Fd::openat( | |
322 | &unsafe { RawFdNum::from_raw_fd(parent) }, | |
323 | file_name, | |
324 | oflags | noatime, | |
325 | Mode::empty(), | |
326 | ) { | |
327 | Ok(fd) => Ok(Some(fd)), | |
328 | Err(nix::Error::Sys(Errno::ENOENT)) => { | |
329 | if existed { | |
330 | self.report_vanished_file()?; | |
331 | } | |
332 | Ok(None) | |
7d0754a6 | 333 | } |
30c3c5d6 WB |
334 | Err(nix::Error::Sys(Errno::EACCES)) => { |
335 | writeln!(self.errors, "failed to open file: {:?}: access denied", file_name)?; | |
336 | Ok(None) | |
337 | } | |
338 | Err(nix::Error::Sys(Errno::EPERM)) if !noatime.is_empty() => { | |
339 | // Retry without O_NOATIME: | |
340 | noatime = OFlag::empty(); | |
341 | continue; | |
342 | } | |
343 | Err(other) => Err(Error::from(other)), | |
3d68536f | 344 | } |
3d68536f WB |
345 | } |
346 | } | |
347 | ||
348 | fn read_pxar_excludes(&mut self, parent: RawFd) -> Result<(), Error> { | |
32a4695c WB |
349 | let fd = match self.open_file(parent, c_str!(".pxarexclude"), OFlag::O_RDONLY, false)? { |
350 | Some(fd) => fd, | |
351 | None => return Ok(()), | |
352 | }; | |
3d68536f WB |
353 | |
354 | let old_pattern_count = self.patterns.len(); | |
355 | ||
b25deec0 WB |
356 | let path_bytes = self.path.as_os_str().as_bytes(); |
357 | ||
32a4695c WB |
358 | let file = unsafe { std::fs::File::from_raw_fd(fd.into_raw_fd()) }; |
359 | ||
360 | use io::BufRead; | |
361 | for line in io::BufReader::new(file).split(b'\n') { | |
362 | let line = match line { | |
363 | Ok(line) => line, | |
364 | Err(err) => { | |
365 | let _ = writeln!( | |
366 | self.errors, | |
367 | "ignoring .pxarexclude after read error in {:?}: {}", | |
368 | self.path, | |
369 | err, | |
370 | ); | |
371 | self.patterns.truncate(old_pattern_count); | |
372 | return Ok(()); | |
373 | } | |
374 | }; | |
3d68536f | 375 | |
2b7f8dd5 | 376 | let line = strip_ascii_whitespace(&line); |
3d68536f | 377 | |
32a4695c WB |
378 | if line.is_empty() || line[0] == b'#' { |
379 | continue; | |
380 | } | |
3d68536f | 381 | |
32a4695c | 382 | let mut buf; |
8d841f81 | 383 | let (line, mode, anchored) = if line[0] == b'/' { |
32a4695c WB |
384 | buf = Vec::with_capacity(path_bytes.len() + 1 + line.len()); |
385 | buf.extend(path_bytes); | |
386 | buf.extend(line); | |
8d841f81 | 387 | (&buf[..], MatchType::Exclude, true) |
32a4695c WB |
388 | } else if line.starts_with(b"!/") { |
389 | // inverted case with absolute path | |
390 | buf = Vec::with_capacity(path_bytes.len() + line.len()); | |
391 | buf.extend(path_bytes); | |
392 | buf.extend(&line[1..]); // without the '!' | |
8d841f81 | 393 | (&buf[..], MatchType::Include, true) |
32a4695c | 394 | } else if line.starts_with(b"!") { |
8d841f81 | 395 | (&line[1..], MatchType::Include, false) |
32a4695c | 396 | } else { |
8d841f81 | 397 | (line, MatchType::Exclude, false) |
32a4695c WB |
398 | }; |
399 | ||
400 | match MatchEntry::parse_pattern(line, PatternFlag::PATH_NAME, mode) { | |
8d841f81 WB |
401 | Ok(pattern) => { |
402 | if anchored { | |
403 | self.patterns.push(pattern.add_flags(MatchFlag::ANCHORED)); | |
404 | } else { | |
405 | self.patterns.push(pattern); | |
406 | } | |
407 | } | |
32a4695c WB |
408 | Err(err) => { |
409 | let _ = writeln!(self.errors, "bad pattern in {:?}: {}", self.path, err); | |
3d68536f WB |
410 | } |
411 | } | |
412 | } | |
c443f58b WB |
413 | |
414 | Ok(()) | |
415 | } | |
416 | ||
6afb60ab | 417 | async fn encode_pxarexclude_cli<T: SeqWrite + Send>( |
30140886 | 418 | &mut self, |
6afb60ab | 419 | encoder: &mut Encoder<'_, T>, |
30140886 | 420 | file_name: &CStr, |
c9097ff8 | 421 | patterns_count: usize, |
30140886 | 422 | ) -> Result<(), Error> { |
c9097ff8 | 423 | let content = generate_pxar_excludes_cli(&self.patterns[..patterns_count]); |
6afb60ab SR |
424 | if let Some(ref catalog) = self.catalog { |
425 | catalog.lock().unwrap().add_file(file_name, content.len() as u64, 0)?; | |
30140886 WB |
426 | } |
427 | ||
428 | let mut metadata = Metadata::default(); | |
429 | metadata.stat.mode = pxar::format::mode::IFREG | 0o600; | |
430 | ||
6afb60ab SR |
431 | let mut file = encoder.create_file(&metadata, ".pxarexclude-cli", content.len() as u64).await?; |
432 | file.write_all(&content).await?; | |
30140886 WB |
433 | |
434 | Ok(()) | |
435 | } | |
436 | ||
437 | fn generate_directory_file_list( | |
438 | &mut self, | |
439 | dir: &mut Dir, | |
440 | is_root: bool, | |
441 | ) -> Result<Vec<FileListEntry>, Error> { | |
c443f58b WB |
442 | let dir_fd = dir.as_raw_fd(); |
443 | ||
444 | let mut file_list = Vec::new(); | |
445 | ||
446 | for file in dir.iter() { | |
447 | let file = file?; | |
448 | ||
449 | let file_name = file.file_name().to_owned(); | |
450 | let file_name_bytes = file_name.to_bytes(); | |
451 | if file_name_bytes == b"." || file_name_bytes == b".." { | |
452 | continue; | |
453 | } | |
454 | ||
30140886 WB |
455 | if is_root && file_name_bytes == b".pxarexclude-cli" { |
456 | continue; | |
457 | } | |
458 | ||
c443f58b | 459 | let os_file_name = OsStr::from_bytes(file_name_bytes); |
7eacdc76 | 460 | assert_single_path_component(os_file_name)?; |
c443f58b WB |
461 | let full_path = self.path.join(os_file_name); |
462 | ||
463 | let stat = match nix::sys::stat::fstatat( | |
464 | dir_fd, | |
465 | file_name.as_c_str(), | |
466 | nix::fcntl::AtFlags::AT_SYMLINK_NOFOLLOW, | |
467 | ) { | |
468 | Ok(stat) => stat, | |
469 | Err(ref err) if err.not_found() => continue, | |
470 | Err(err) => bail!("stat failed on {:?}: {}", full_path, err), | |
471 | }; | |
472 | ||
874bd545 | 473 | let match_path = PathBuf::from("/").join(full_path.clone()); |
c443f58b | 474 | if self |
239e49f9 | 475 | .patterns |
874bd545 | 476 | .matches(match_path.as_os_str().as_bytes(), Some(stat.st_mode as u32)) |
c443f58b WB |
477 | == Some(MatchType::Exclude) |
478 | { | |
479 | continue; | |
480 | } | |
481 | ||
482 | self.entry_counter += 1; | |
483 | if self.entry_counter > self.entry_limit { | |
484 | bail!("exceeded allowed number of file entries (> {})",self.entry_limit); | |
485 | } | |
486 | ||
487 | file_list.push(FileListEntry { | |
488 | name: file_name, | |
489 | path: full_path, | |
490 | stat | |
491 | }); | |
492 | } | |
493 | ||
494 | file_list.sort_unstable_by(|a, b| a.name.cmp(&b.name)); | |
495 | ||
496 | Ok(file_list) | |
497 | } | |
498 | ||
3d68536f | 499 | fn report_vanished_file(&mut self) -> Result<(), Error> { |
5afa0755 | 500 | writeln!(self.errors, "warning: file vanished while reading: {:?}", self.path)?; |
3d68536f WB |
501 | Ok(()) |
502 | } | |
503 | ||
a8e2940f | 504 | fn report_file_shrunk_while_reading(&mut self) -> Result<(), Error> { |
5afa0755 | 505 | writeln!( |
a8e2940f WB |
506 | self.errors, |
507 | "warning: file size shrunk while reading: {:?}, file will be padded with zeros!", | |
508 | self.path, | |
509 | )?; | |
510 | Ok(()) | |
511 | } | |
512 | ||
513 | fn report_file_grew_while_reading(&mut self) -> Result<(), Error> { | |
5afa0755 | 514 | writeln!( |
a8e2940f WB |
515 | self.errors, |
516 | "warning: file size increased while reading: {:?}, file will be truncated!", | |
517 | self.path, | |
518 | )?; | |
519 | Ok(()) | |
520 | } | |
521 | ||
6afb60ab | 522 | async fn add_entry<T: SeqWrite + Send>( |
c443f58b | 523 | &mut self, |
6afb60ab | 524 | encoder: &mut Encoder<'_, T>, |
c443f58b WB |
525 | parent: RawFd, |
526 | c_file_name: &CStr, | |
527 | stat: &FileStat, | |
528 | ) -> Result<(), Error> { | |
529 | use pxar::format::mode; | |
530 | ||
531 | let file_mode = stat.st_mode & libc::S_IFMT; | |
1008a69a | 532 | let open_mode = if file_mode == libc::S_IFREG || file_mode == libc::S_IFDIR { |
c443f58b | 533 | OFlag::empty() |
1008a69a WB |
534 | } else { |
535 | OFlag::O_PATH | |
c443f58b WB |
536 | }; |
537 | ||
3d68536f WB |
538 | let fd = self.open_file( |
539 | parent, | |
c443f58b | 540 | c_file_name, |
e51be338 | 541 | open_mode | OFlag::O_RDONLY | OFlag::O_NOFOLLOW, |
7d0754a6 | 542 | true, |
c443f58b WB |
543 | )?; |
544 | ||
3d68536f WB |
545 | let fd = match fd { |
546 | Some(fd) => fd, | |
7d0754a6 | 547 | None => return Ok(()), |
3d68536f WB |
548 | }; |
549 | ||
5667b763 | 550 | let metadata = get_metadata(fd.as_raw_fd(), &stat, self.flags(), self.fs_magic, &mut self.fs_feature_flags)?; |
c443f58b WB |
551 | |
552 | if self | |
239e49f9 | 553 | .patterns |
c443f58b WB |
554 | .matches(self.path.as_os_str().as_bytes(), Some(stat.st_mode as u32)) |
555 | == Some(MatchType::Exclude) | |
556 | { | |
557 | return Ok(()); | |
558 | } | |
559 | ||
560 | let file_name: &Path = OsStr::from_bytes(c_file_name.to_bytes()).as_ref(); | |
561 | match metadata.file_type() { | |
562 | mode::IFREG => { | |
563 | let link_info = HardLinkInfo { | |
564 | st_dev: stat.st_dev, | |
565 | st_ino: stat.st_ino, | |
566 | }; | |
567 | ||
568 | if stat.st_nlink > 1 { | |
569 | if let Some((path, offset)) = self.hardlinks.get(&link_info) { | |
6afb60ab SR |
570 | if let Some(ref catalog) = self.catalog { |
571 | catalog.lock().unwrap().add_hardlink(c_file_name)?; | |
c443f58b WB |
572 | } |
573 | ||
6afb60ab | 574 | encoder.add_hardlink(file_name, path, *offset).await?; |
c443f58b WB |
575 | |
576 | return Ok(()); | |
577 | } | |
578 | } | |
579 | ||
580 | let file_size = stat.st_size as u64; | |
6afb60ab SR |
581 | if let Some(ref catalog) = self.catalog { |
582 | catalog.lock().unwrap().add_file(c_file_name, file_size, stat.st_mtime)?; | |
c443f58b WB |
583 | } |
584 | ||
585 | let offset: LinkOffset = | |
6afb60ab | 586 | self.add_regular_file(encoder, fd, file_name, &metadata, file_size).await?; |
c443f58b WB |
587 | |
588 | if stat.st_nlink > 1 { | |
589 | self.hardlinks.insert(link_info, (self.path.clone(), offset)); | |
590 | } | |
591 | ||
592 | Ok(()) | |
593 | } | |
594 | mode::IFDIR => { | |
595 | let dir = Dir::from_fd(fd.into_raw_fd())?; | |
9321bbd1 | 596 | |
6afb60ab SR |
597 | if let Some(ref catalog) = self.catalog { |
598 | catalog.lock().unwrap().start_directory(c_file_name)?; | |
9321bbd1 | 599 | } |
6afb60ab SR |
600 | let result = self.add_directory(encoder, dir, c_file_name, &metadata, stat).await; |
601 | if let Some(ref catalog) = self.catalog { | |
602 | catalog.lock().unwrap().end_directory()?; | |
9321bbd1 WB |
603 | } |
604 | result | |
c443f58b WB |
605 | } |
606 | mode::IFSOCK => { | |
6afb60ab SR |
607 | if let Some(ref catalog) = self.catalog { |
608 | catalog.lock().unwrap().add_socket(c_file_name)?; | |
c443f58b WB |
609 | } |
610 | ||
6afb60ab | 611 | Ok(encoder.add_socket(&metadata, file_name).await?) |
c443f58b WB |
612 | } |
613 | mode::IFIFO => { | |
6afb60ab SR |
614 | if let Some(ref catalog) = self.catalog { |
615 | catalog.lock().unwrap().add_fifo(c_file_name)?; | |
c443f58b WB |
616 | } |
617 | ||
6afb60ab | 618 | Ok(encoder.add_fifo(&metadata, file_name).await?) |
c443f58b WB |
619 | } |
620 | mode::IFLNK => { | |
6afb60ab SR |
621 | if let Some(ref catalog) = self.catalog { |
622 | catalog.lock().unwrap().add_symlink(c_file_name)?; | |
c443f58b WB |
623 | } |
624 | ||
6afb60ab | 625 | self.add_symlink(encoder, fd, file_name, &metadata).await |
c443f58b WB |
626 | } |
627 | mode::IFBLK => { | |
6afb60ab SR |
628 | if let Some(ref catalog) = self.catalog { |
629 | catalog.lock().unwrap().add_block_device(c_file_name)?; | |
c443f58b WB |
630 | } |
631 | ||
6afb60ab | 632 | self.add_device(encoder, file_name, &metadata, &stat).await |
c443f58b WB |
633 | } |
634 | mode::IFCHR => { | |
6afb60ab SR |
635 | if let Some(ref catalog) = self.catalog { |
636 | catalog.lock().unwrap().add_char_device(c_file_name)?; | |
c443f58b WB |
637 | } |
638 | ||
6afb60ab | 639 | self.add_device(encoder, file_name, &metadata, &stat).await |
c443f58b WB |
640 | } |
641 | other => bail!( | |
642 | "encountered unknown file type: 0x{:x} (0o{:o})", | |
643 | other, | |
644 | other | |
645 | ), | |
646 | } | |
647 | } | |
648 | ||
6afb60ab | 649 | async fn add_directory<T: SeqWrite + Send>( |
c443f58b | 650 | &mut self, |
6afb60ab | 651 | encoder: &mut Encoder<'_, T>, |
c443f58b WB |
652 | dir: Dir, |
653 | dir_name: &CStr, | |
654 | metadata: &Metadata, | |
655 | stat: &FileStat, | |
656 | ) -> Result<(), Error> { | |
657 | let dir_name = OsStr::from_bytes(dir_name.to_bytes()); | |
658 | ||
6afb60ab | 659 | let mut encoder = encoder.create_directory(dir_name, &metadata).await?; |
c443f58b WB |
660 | |
661 | let old_fs_magic = self.fs_magic; | |
662 | let old_fs_feature_flags = self.fs_feature_flags; | |
663 | let old_st_dev = self.current_st_dev; | |
664 | ||
665 | let mut skip_contents = false; | |
666 | if old_st_dev != stat.st_dev { | |
667 | self.fs_magic = detect_fs_type(dir.as_raw_fd())?; | |
5444fa94 | 668 | self.fs_feature_flags = Flags::from_magic(self.fs_magic); |
c443f58b WB |
669 | self.current_st_dev = stat.st_dev; |
670 | ||
671 | if is_virtual_file_system(self.fs_magic) { | |
672 | skip_contents = true; | |
673 | } else if let Some(set) = &self.device_set { | |
674 | skip_contents = !set.contains(&stat.st_dev); | |
675 | } | |
676 | } | |
677 | ||
678 | let result = if skip_contents { | |
47208b41 | 679 | writeln!(self.logger, "skipping mount point: {:?}", self.path)?; |
c443f58b WB |
680 | Ok(()) |
681 | } else { | |
6afb60ab | 682 | self.archive_dir_contents(&mut encoder, dir, false).await |
c443f58b WB |
683 | }; |
684 | ||
685 | self.fs_magic = old_fs_magic; | |
686 | self.fs_feature_flags = old_fs_feature_flags; | |
687 | self.current_st_dev = old_st_dev; | |
688 | ||
6afb60ab | 689 | encoder.finish().await?; |
c443f58b WB |
690 | result |
691 | } | |
692 | ||
6afb60ab | 693 | async fn add_regular_file<T: SeqWrite + Send>( |
c443f58b | 694 | &mut self, |
6afb60ab | 695 | encoder: &mut Encoder<'_, T>, |
c443f58b WB |
696 | fd: Fd, |
697 | file_name: &Path, | |
698 | metadata: &Metadata, | |
699 | file_size: u64, | |
700 | ) -> Result<LinkOffset, Error> { | |
701 | let mut file = unsafe { std::fs::File::from_raw_fd(fd.into_raw_fd()) }; | |
a8e2940f | 702 | let mut remaining = file_size; |
6afb60ab | 703 | let mut out = encoder.create_file(metadata, file_name, file_size).await?; |
a8e2940f | 704 | while remaining != 0 { |
3c2dd8ad | 705 | let mut got = match file.read(&mut self.file_copy_buffer[..]) { |
e144810d | 706 | Ok(0) => break, |
3c2dd8ad DC |
707 | Ok(got) => got, |
708 | Err(err) if err.kind() == std::io::ErrorKind::Interrupted => continue, | |
709 | Err(err) => bail!(err), | |
710 | }; | |
a8e2940f WB |
711 | if got as u64 > remaining { |
712 | self.report_file_grew_while_reading()?; | |
713 | got = remaining as usize; | |
714 | } | |
6afb60ab | 715 | out.write_all(&self.file_copy_buffer[..got]).await?; |
a8e2940f WB |
716 | remaining -= got as u64; |
717 | } | |
718 | if remaining > 0 { | |
719 | self.report_file_shrunk_while_reading()?; | |
720 | let to_zero = remaining.min(self.file_copy_buffer.len() as u64) as usize; | |
721 | vec::clear(&mut self.file_copy_buffer[..to_zero]); | |
722 | while remaining != 0 { | |
723 | let fill = remaining.min(self.file_copy_buffer.len() as u64) as usize; | |
6afb60ab | 724 | out.write_all(&self.file_copy_buffer[..fill]).await?; |
a8e2940f WB |
725 | remaining -= fill as u64; |
726 | } | |
727 | } | |
728 | ||
729 | Ok(out.file_offset()) | |
c443f58b WB |
730 | } |
731 | ||
6afb60ab | 732 | async fn add_symlink<T: SeqWrite + Send>( |
c443f58b | 733 | &mut self, |
6afb60ab | 734 | encoder: &mut Encoder<'_, T>, |
c443f58b WB |
735 | fd: Fd, |
736 | file_name: &Path, | |
737 | metadata: &Metadata, | |
738 | ) -> Result<(), Error> { | |
739 | let dest = nix::fcntl::readlinkat(fd.as_raw_fd(), &b""[..])?; | |
6afb60ab | 740 | encoder.add_symlink(metadata, file_name, dest).await?; |
c443f58b WB |
741 | Ok(()) |
742 | } | |
743 | ||
6afb60ab | 744 | async fn add_device<T: SeqWrite + Send>( |
c443f58b | 745 | &mut self, |
6afb60ab | 746 | encoder: &mut Encoder<'_, T>, |
c443f58b WB |
747 | file_name: &Path, |
748 | metadata: &Metadata, | |
749 | stat: &FileStat, | |
750 | ) -> Result<(), Error> { | |
751 | Ok(encoder.add_device( | |
752 | metadata, | |
753 | file_name, | |
754 | pxar::format::Device::from_dev_t(stat.st_rdev), | |
6afb60ab | 755 | ).await?) |
c443f58b WB |
756 | } |
757 | } | |
758 | ||
5667b763 | 759 | fn get_metadata(fd: RawFd, stat: &FileStat, flags: Flags, fs_magic: i64, fs_feature_flags: &mut Flags) -> Result<Metadata, Error> { |
c443f58b WB |
760 | // required for some of these |
761 | let proc_path = Path::new("/proc/self/fd/").join(fd.to_string()); | |
762 | ||
c443f58b WB |
763 | let mut meta = Metadata { |
764 | stat: pxar::Stat { | |
765 | mode: u64::from(stat.st_mode), | |
766 | flags: 0, | |
767 | uid: stat.st_uid, | |
768 | gid: stat.st_gid, | |
b31cdec2 | 769 | mtime: pxar::format::StatxTimestamp::new(stat.st_mtime, stat.st_mtime_nsec as u32), |
c443f58b WB |
770 | }, |
771 | ..Default::default() | |
772 | }; | |
773 | ||
5667b763 | 774 | get_xattr_fcaps_acl(&mut meta, fd, &proc_path, flags, fs_feature_flags)?; |
c443f58b WB |
775 | get_chattr(&mut meta, fd)?; |
776 | get_fat_attr(&mut meta, fd, fs_magic)?; | |
777 | get_quota_project_id(&mut meta, fd, flags, fs_magic)?; | |
778 | Ok(meta) | |
779 | } | |
780 | ||
5667b763 | 781 | fn get_fcaps(meta: &mut Metadata, fd: RawFd, flags: Flags, fs_feature_flags: &mut Flags) -> Result<(), Error> { |
591b120d | 782 | if !flags.contains(Flags::WITH_FCAPS) { |
c443f58b WB |
783 | return Ok(()); |
784 | } | |
785 | ||
786 | match xattr::fgetxattr(fd, xattr::xattr_name_fcaps()) { | |
787 | Ok(data) => { | |
788 | meta.fcaps = Some(pxar::format::FCaps { data }); | |
789 | Ok(()) | |
790 | } | |
791 | Err(Errno::ENODATA) => Ok(()), | |
5667b763 DC |
792 | Err(Errno::EOPNOTSUPP) => { |
793 | fs_feature_flags.remove(Flags::WITH_FCAPS); | |
794 | Ok(()) | |
795 | } | |
c443f58b WB |
796 | Err(Errno::EBADF) => Ok(()), // symlinks |
797 | Err(err) => bail!("failed to read file capabilities: {}", err), | |
798 | } | |
799 | } | |
800 | ||
801 | fn get_xattr_fcaps_acl( | |
802 | meta: &mut Metadata, | |
803 | fd: RawFd, | |
804 | proc_path: &Path, | |
5444fa94 | 805 | flags: Flags, |
5667b763 | 806 | fs_feature_flags: &mut Flags, |
c443f58b | 807 | ) -> Result<(), Error> { |
591b120d | 808 | if !flags.contains(Flags::WITH_XATTRS) { |
c443f58b WB |
809 | return Ok(()); |
810 | } | |
811 | ||
812 | let xattrs = match xattr::flistxattr(fd) { | |
813 | Ok(names) => names, | |
5667b763 DC |
814 | Err(Errno::EOPNOTSUPP) => { |
815 | fs_feature_flags.remove(Flags::WITH_XATTRS); | |
816 | return Ok(()); | |
817 | }, | |
c443f58b WB |
818 | Err(Errno::EBADF) => return Ok(()), // symlinks |
819 | Err(err) => bail!("failed to read xattrs: {}", err), | |
820 | }; | |
821 | ||
822 | for attr in &xattrs { | |
823 | if xattr::is_security_capability(&attr) { | |
5667b763 | 824 | get_fcaps(meta, fd, flags, fs_feature_flags)?; |
c443f58b WB |
825 | continue; |
826 | } | |
827 | ||
828 | if xattr::is_acl(&attr) { | |
5667b763 | 829 | get_acl(meta, proc_path, flags, fs_feature_flags)?; |
c443f58b WB |
830 | continue; |
831 | } | |
832 | ||
833 | if !xattr::is_valid_xattr_name(&attr) { | |
834 | continue; | |
835 | } | |
836 | ||
837 | match xattr::fgetxattr(fd, attr) { | |
838 | Ok(data) => meta | |
839 | .xattrs | |
840 | .push(pxar::format::XAttr::new(attr.to_bytes(), data)), | |
841 | Err(Errno::ENODATA) => (), // it got removed while we were iterating... | |
842 | Err(Errno::EOPNOTSUPP) => (), // shouldn't be possible so just ignore this | |
843 | Err(Errno::EBADF) => (), // symlinks, shouldn't be able to reach this either | |
844 | Err(err) => bail!("error reading extended attribute {:?}: {}", attr, err), | |
845 | } | |
846 | } | |
847 | ||
848 | Ok(()) | |
849 | } | |
850 | ||
851 | fn get_chattr(metadata: &mut Metadata, fd: RawFd) -> Result<(), Error> { | |
032cd1b8 | 852 | let mut attr: libc::c_long = 0; |
c443f58b WB |
853 | |
854 | match unsafe { fs::read_attr_fd(fd, &mut attr) } { | |
855 | Ok(_) => (), | |
856 | Err(nix::Error::Sys(errno)) if errno_is_unsupported(errno) => { | |
857 | return Ok(()); | |
858 | } | |
859 | Err(err) => bail!("failed to read file attributes: {}", err), | |
860 | } | |
861 | ||
032cd1b8 | 862 | metadata.stat.flags |= Flags::from_chattr(attr).bits(); |
c443f58b WB |
863 | |
864 | Ok(()) | |
865 | } | |
866 | ||
867 | fn get_fat_attr(metadata: &mut Metadata, fd: RawFd, fs_magic: i64) -> Result<(), Error> { | |
25877d05 | 868 | use proxmox_sys::linux::magic::*; |
c443f58b WB |
869 | |
870 | if fs_magic != MSDOS_SUPER_MAGIC && fs_magic != FUSE_SUPER_MAGIC { | |
871 | return Ok(()); | |
872 | } | |
873 | ||
874 | let mut attr: u32 = 0; | |
875 | ||
876 | match unsafe { fs::read_fat_attr_fd(fd, &mut attr) } { | |
877 | Ok(_) => (), | |
878 | Err(nix::Error::Sys(errno)) if errno_is_unsupported(errno) => { | |
879 | return Ok(()); | |
880 | } | |
881 | Err(err) => bail!("failed to read fat attributes: {}", err), | |
882 | } | |
883 | ||
5444fa94 | 884 | metadata.stat.flags |= Flags::from_fat_attr(attr).bits(); |
c443f58b WB |
885 | |
886 | Ok(()) | |
887 | } | |
888 | ||
889 | /// Read the quota project id for an inode, supported on ext4/XFS/FUSE/ZFS filesystems | |
890 | fn get_quota_project_id( | |
891 | metadata: &mut Metadata, | |
892 | fd: RawFd, | |
5444fa94 | 893 | flags: Flags, |
c443f58b WB |
894 | magic: i64, |
895 | ) -> Result<(), Error> { | |
896 | if !(metadata.is_dir() || metadata.is_regular_file()) { | |
897 | return Ok(()); | |
898 | } | |
899 | ||
591b120d | 900 | if !flags.contains(Flags::WITH_QUOTA_PROJID) { |
c443f58b WB |
901 | return Ok(()); |
902 | } | |
903 | ||
25877d05 | 904 | use proxmox_sys::linux::magic::*; |
c443f58b WB |
905 | |
906 | match magic { | |
907 | EXT4_SUPER_MAGIC | XFS_SUPER_MAGIC | FUSE_SUPER_MAGIC | ZFS_SUPER_MAGIC => (), | |
908 | _ => return Ok(()), | |
909 | } | |
910 | ||
911 | let mut fsxattr = fs::FSXAttr::default(); | |
912 | let res = unsafe { fs::fs_ioc_fsgetxattr(fd, &mut fsxattr) }; | |
913 | ||
914 | // On some FUSE filesystems it can happen that ioctl is not supported. | |
915 | // For these cases projid is set to 0 while the error is ignored. | |
916 | if let Err(err) = res { | |
917 | let errno = err | |
918 | .as_errno() | |
919 | .ok_or_else(|| format_err!("error while reading quota project id"))?; | |
920 | if errno_is_unsupported(errno) { | |
921 | return Ok(()); | |
922 | } else { | |
923 | bail!("error while reading quota project id ({})", errno); | |
924 | } | |
925 | } | |
926 | ||
927 | let projid = fsxattr.fsx_projid as u64; | |
928 | if projid != 0 { | |
929 | metadata.quota_project_id = Some(pxar::format::QuotaProjectId { projid }); | |
930 | } | |
931 | Ok(()) | |
932 | } | |
933 | ||
5667b763 | 934 | fn get_acl(metadata: &mut Metadata, proc_path: &Path, flags: Flags, fs_feature_flags: &mut Flags) -> Result<(), Error> { |
591b120d | 935 | if !flags.contains(Flags::WITH_ACL) { |
c443f58b WB |
936 | return Ok(()); |
937 | } | |
938 | ||
939 | if metadata.is_symlink() { | |
940 | return Ok(()); | |
941 | } | |
942 | ||
5667b763 | 943 | get_acl_do(metadata, proc_path, acl::ACL_TYPE_ACCESS, fs_feature_flags)?; |
c443f58b WB |
944 | |
945 | if metadata.is_dir() { | |
5667b763 | 946 | get_acl_do(metadata, proc_path, acl::ACL_TYPE_DEFAULT, fs_feature_flags)?; |
c443f58b WB |
947 | } |
948 | ||
949 | Ok(()) | |
950 | } | |
951 | ||
952 | fn get_acl_do( | |
953 | metadata: &mut Metadata, | |
954 | proc_path: &Path, | |
955 | acl_type: acl::ACLType, | |
5667b763 | 956 | fs_feature_flags: &mut Flags, |
c443f58b WB |
957 | ) -> Result<(), Error> { |
958 | // In order to be able to get ACLs with type ACL_TYPE_DEFAULT, we have | |
959 | // to create a path for acl_get_file(). acl_get_fd() only allows to get | |
960 | // ACL_TYPE_ACCESS attributes. | |
961 | let acl = match acl::ACL::get_file(&proc_path, acl_type) { | |
962 | Ok(acl) => acl, | |
963 | // Don't bail if underlying endpoint does not support acls | |
5667b763 DC |
964 | Err(Errno::EOPNOTSUPP) => { |
965 | fs_feature_flags.remove(Flags::WITH_ACL); | |
966 | return Ok(()); | |
967 | } | |
c443f58b WB |
968 | // Don't bail if the endpoint cannot carry acls |
969 | Err(Errno::EBADF) => return Ok(()), | |
970 | // Don't bail if there is no data | |
971 | Err(Errno::ENODATA) => return Ok(()), | |
972 | Err(err) => bail!("error while reading ACL - {}", err), | |
973 | }; | |
974 | ||
975 | process_acl(metadata, acl, acl_type) | |
976 | } | |
977 | ||
978 | fn process_acl( | |
979 | metadata: &mut Metadata, | |
980 | acl: acl::ACL, | |
981 | acl_type: acl::ACLType, | |
982 | ) -> Result<(), Error> { | |
983 | use pxar::format::acl as pxar_acl; | |
984 | use pxar::format::acl::{Group, GroupObject, Permissions, User}; | |
985 | ||
986 | let mut acl_user = Vec::new(); | |
987 | let mut acl_group = Vec::new(); | |
988 | let mut acl_group_obj = None; | |
989 | let mut acl_default = None; | |
990 | let mut user_obj_permissions = None; | |
991 | let mut group_obj_permissions = None; | |
992 | let mut other_permissions = None; | |
993 | let mut mask_permissions = None; | |
994 | ||
995 | for entry in &mut acl.entries() { | |
996 | let tag = entry.get_tag_type()?; | |
997 | let permissions = entry.get_permissions()?; | |
998 | match tag { | |
999 | acl::ACL_USER_OBJ => user_obj_permissions = Some(Permissions(permissions)), | |
1000 | acl::ACL_GROUP_OBJ => group_obj_permissions = Some(Permissions(permissions)), | |
1001 | acl::ACL_OTHER => other_permissions = Some(Permissions(permissions)), | |
1002 | acl::ACL_MASK => mask_permissions = Some(Permissions(permissions)), | |
1003 | acl::ACL_USER => { | |
1004 | acl_user.push(User { | |
1005 | uid: entry.get_qualifier()?, | |
1006 | permissions: Permissions(permissions), | |
1007 | }); | |
1008 | } | |
1009 | acl::ACL_GROUP => { | |
1010 | acl_group.push(Group { | |
1011 | gid: entry.get_qualifier()?, | |
1012 | permissions: Permissions(permissions), | |
1013 | }); | |
1014 | } | |
1015 | _ => bail!("Unexpected ACL tag encountered!"), | |
1016 | } | |
1017 | } | |
1018 | ||
1019 | acl_user.sort(); | |
1020 | acl_group.sort(); | |
1021 | ||
1022 | match acl_type { | |
1023 | acl::ACL_TYPE_ACCESS => { | |
1024 | // The mask permissions are mapped to the stat group permissions | |
1025 | // in case that the ACL group permissions were set. | |
1026 | // Only in that case we need to store the group permissions, | |
1027 | // in the other cases they are identical to the stat group permissions. | |
1028 | if let (Some(gop), true) = (group_obj_permissions, mask_permissions.is_some()) { | |
1029 | acl_group_obj = Some(GroupObject { permissions: gop }); | |
1030 | } | |
1031 | ||
1032 | metadata.acl.users = acl_user; | |
1033 | metadata.acl.groups = acl_group; | |
9f40e09d | 1034 | metadata.acl.group_obj = acl_group_obj; |
c443f58b WB |
1035 | } |
1036 | acl::ACL_TYPE_DEFAULT => { | |
1037 | if user_obj_permissions != None | |
1038 | || group_obj_permissions != None | |
1039 | || other_permissions != None | |
1040 | || mask_permissions != None | |
1041 | { | |
1042 | acl_default = Some(pxar_acl::Default { | |
1043 | // The value is set to UINT64_MAX as placeholder if one | |
1044 | // of the permissions is not set | |
1045 | user_obj_permissions: user_obj_permissions.unwrap_or(Permissions::NO_MASK), | |
1046 | group_obj_permissions: group_obj_permissions.unwrap_or(Permissions::NO_MASK), | |
1047 | other_permissions: other_permissions.unwrap_or(Permissions::NO_MASK), | |
1048 | mask_permissions: mask_permissions.unwrap_or(Permissions::NO_MASK), | |
1049 | }); | |
1050 | } | |
1051 | ||
1052 | metadata.acl.default_users = acl_user; | |
1053 | metadata.acl.default_groups = acl_group; | |
9f40e09d | 1054 | metadata.acl.default = acl_default; |
c443f58b WB |
1055 | } |
1056 | _ => bail!("Unexpected ACL type encountered"), | |
1057 | } | |
1058 | ||
c443f58b WB |
1059 | Ok(()) |
1060 | } | |
239e49f9 WB |
1061 | |
1062 | /// Note that our pattern lists are "positive". `MatchType::Include` means the file is included. | |
1063 | /// Since we are generating an *exclude* list, we need to invert this, so includes get a `'!'` | |
1064 | /// prefix. | |
1065 | fn generate_pxar_excludes_cli(patterns: &[MatchEntry]) -> Vec<u8> { | |
8d841f81 | 1066 | use pathpatterns::MatchPattern; |
239e49f9 WB |
1067 | |
1068 | let mut content = Vec::new(); | |
1069 | ||
1070 | for pattern in patterns { | |
1071 | match pattern.match_type() { | |
1072 | MatchType::Include => content.push(b'!'), | |
1073 | MatchType::Exclude => (), | |
1074 | } | |
1075 | ||
1076 | match pattern.pattern() { | |
1077 | MatchPattern::Literal(lit) => content.extend(lit), | |
1078 | MatchPattern::Pattern(pat) => content.extend(pat.pattern().to_bytes()), | |
1079 | } | |
1080 | ||
1081 | if pattern.match_flags() == MatchFlag::MATCH_DIRECTORIES && content.last() != Some(&b'/') { | |
1082 | content.push(b'/'); | |
1083 | } | |
1084 | ||
1085 | content.push(b'\n'); | |
1086 | } | |
1087 | ||
1088 | content | |
1089 | } |