]> git.proxmox.com Git - proxmox-backup.git/blob - pbs-client/src/pxar/create.rs
92ffeadfb9f2c690baa5fe817ff4cc42c69a0881
[proxmox-backup.git] / pbs-client / src / pxar / create.rs
1 use std::collections::{HashSet, HashMap};
2 use std::ffi::{CStr, CString, OsStr};
3 use std::fmt;
4 use std::io::{self, Read, Write};
5 use std::os::unix::ffi::OsStrExt;
6 use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
7 use std::path::{Path, PathBuf};
8 use std::sync::{Arc, Mutex};
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};
15 use futures::future::BoxFuture;
16 use futures::FutureExt;
17
18 use pathpatterns::{MatchEntry, MatchFlag, MatchList, MatchType, PatternFlag};
19 use pxar::Metadata;
20 use pxar::encoder::{SeqWrite, LinkOffset};
21
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};
26 use proxmox_io::vec;
27 use proxmox_lang::c_str;
28
29 use pbs_datastore::catalog::BackupCatalogWriter;
30
31 use crate::pxar::metadata::errno_is_unsupported;
32 use crate::pxar::Flags;
33 use crate::pxar::tools::assert_single_path_component;
34
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
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
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
71 #[rustfmt::skip]
72 pub fn is_virtual_file_system(magic: i64) -> bool {
73 use proxmox_sys::linux::magic::*;
74
75 matches!(magic, BINFMTFS_MAGIC |
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 |
92 SYSFS_MAGIC)
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
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.
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
148 struct Archiver {
149 feature_flags: Flags,
150 fs_feature_flags: Flags,
151 fs_magic: i64,
152 patterns: Vec<MatchEntry>,
153 callback: Box<dyn FnMut(&Path) -> Result<(), Error> + Send>,
154 catalog: Option<Arc<Mutex<dyn BackupCatalogWriter + Send>>>,
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)>,
161 errors: ErrorReporter,
162 logger: Logger,
163 file_copy_buffer: Vec<u8>,
164 }
165
166 type Encoder<'a, T> = pxar::encoder::aio::Encoder<'a, T>;
167
168 pub async fn create_archive<T, F>(
169 source_dir: Dir,
170 mut writer: T,
171 feature_flags: Flags,
172 callback: F,
173 catalog: Option<Arc<Mutex<dyn BackupCatalogWriter + Send>>>,
174 options: PxarCreateOptions,
175 ) -> Result<(), Error>
176 where
177 T: SeqWrite + Send,
178 F: FnMut(&Path) -> Result<(), Error> + Send + 'static,
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
185 let mut fs_feature_flags = Flags::from_magic(fs_magic);
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,
193 &mut fs_feature_flags,
194 )
195 .map_err(|err| format_err!("failed to get metadata for source directory: {}", err))?;
196
197 let mut device_set = options.device_set.clone();
198 if let Some(ref mut set) = device_set {
199 set.insert(stat.st_dev);
200 }
201
202 let mut encoder = Encoder::new(&mut writer, &metadata).await?;
203
204 let mut patterns = options.patterns;
205
206 if options.skip_lost_and_found {
207 patterns.push(MatchEntry::parse_pattern(
208 "lost+found",
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,
218 callback: Box::new(callback),
219 patterns,
220 catalog,
221 path: PathBuf::new(),
222 entry_counter: 0,
223 entry_limit: options.entries_max,
224 current_st_dev: stat.st_dev,
225 device_set,
226 hardlinks: HashMap::new(),
227 errors: ErrorReporter,
228 logger: Logger,
229 file_copy_buffer: vec::undefined(4 * 1024 * 1024),
230 };
231
232 archiver.archive_dir_contents(&mut encoder, source_dir, true).await?;
233 encoder.finish().await?;
234 Ok(())
235 }
236
237 struct FileListEntry {
238 name: CString,
239 path: PathBuf,
240 stat: FileStat,
241 }
242
243 impl Archiver {
244 /// Get the currently effective feature flags. (Requested flags masked by the file system
245 /// feature flags).
246 fn flags(&self) -> Flags {
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
258 fn archive_dir_contents<'a, 'b, T: SeqWrite + Send>(
259 &'a mut self,
260 encoder: &'a mut Encoder<'b, T>,
261 mut dir: Dir,
262 is_root: bool,
263 ) -> BoxFuture<'a, Result<(), Error>> {
264 async move {
265 let entry_counter = self.entry_counter;
266
267 let old_patterns_count = self.patterns.len();
268 self.read_pxar_excludes(dir.as_raw_fd())?;
269
270 let mut file_list = self.generate_directory_file_list(&mut dir, is_root)?;
271
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 }
279
280 let dir_fd = dir.as_raw_fd();
281
282 let old_path = std::mem::take(&mut self.path);
283
284 for file_entry in file_list {
285 let file_name = file_entry.name.to_bytes();
286
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 }
291
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);
300
301 Ok(())
302 }.boxed()
303 }
304
305 /// openat() wrapper which allows but logs `EACCES` and turns `ENOENT` into `None`.
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'.
309 fn open_file(
310 &mut self,
311 parent: RawFd,
312 file_name: &CStr,
313 oflags: OFlag,
314 existed: bool,
315 ) -> Result<Option<Fd>, Error> {
316 // common flags we always want to use:
317 let oflags = oflags | OFlag::O_CLOEXEC | OFlag::O_NOCTTY;
318
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)
333 }
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)),
344 }
345 }
346 }
347
348 fn read_pxar_excludes(&mut self, parent: RawFd) -> Result<(), Error> {
349 let fd = match self.open_file(parent, c_str!(".pxarexclude"), OFlag::O_RDONLY, false)? {
350 Some(fd) => fd,
351 None => return Ok(()),
352 };
353
354 let old_pattern_count = self.patterns.len();
355
356 let path_bytes = self.path.as_os_str().as_bytes();
357
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 };
375
376 let line = strip_ascii_whitespace(&line);
377
378 if line.is_empty() || line[0] == b'#' {
379 continue;
380 }
381
382 let mut buf;
383 let (line, mode, anchored) = if line[0] == b'/' {
384 buf = Vec::with_capacity(path_bytes.len() + 1 + line.len());
385 buf.extend(path_bytes);
386 buf.extend(line);
387 (&buf[..], MatchType::Exclude, true)
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 '!'
393 (&buf[..], MatchType::Include, true)
394 } else if line.starts_with(b"!") {
395 (&line[1..], MatchType::Include, false)
396 } else {
397 (line, MatchType::Exclude, false)
398 };
399
400 match MatchEntry::parse_pattern(line, PatternFlag::PATH_NAME, mode) {
401 Ok(pattern) => {
402 if anchored {
403 self.patterns.push(pattern.add_flags(MatchFlag::ANCHORED));
404 } else {
405 self.patterns.push(pattern);
406 }
407 }
408 Err(err) => {
409 let _ = writeln!(self.errors, "bad pattern in {:?}: {}", self.path, err);
410 }
411 }
412 }
413
414 Ok(())
415 }
416
417 async fn encode_pxarexclude_cli<T: SeqWrite + Send>(
418 &mut self,
419 encoder: &mut Encoder<'_, T>,
420 file_name: &CStr,
421 patterns_count: usize,
422 ) -> Result<(), Error> {
423 let content = generate_pxar_excludes_cli(&self.patterns[..patterns_count]);
424 if let Some(ref catalog) = self.catalog {
425 catalog.lock().unwrap().add_file(file_name, content.len() as u64, 0)?;
426 }
427
428 let mut metadata = Metadata::default();
429 metadata.stat.mode = pxar::format::mode::IFREG | 0o600;
430
431 let mut file = encoder.create_file(&metadata, ".pxarexclude-cli", content.len() as u64).await?;
432 file.write_all(&content).await?;
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> {
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
455 if is_root && file_name_bytes == b".pxarexclude-cli" {
456 continue;
457 }
458
459 let os_file_name = OsStr::from_bytes(file_name_bytes);
460 assert_single_path_component(os_file_name)?;
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
473 let match_path = PathBuf::from("/").join(full_path.clone());
474 if self
475 .patterns
476 .matches(match_path.as_os_str().as_bytes(), Some(stat.st_mode as u32))
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
499 fn report_vanished_file(&mut self) -> Result<(), Error> {
500 writeln!(self.errors, "warning: file vanished while reading: {:?}", self.path)?;
501 Ok(())
502 }
503
504 fn report_file_shrunk_while_reading(&mut self) -> Result<(), Error> {
505 writeln!(
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> {
514 writeln!(
515 self.errors,
516 "warning: file size increased while reading: {:?}, file will be truncated!",
517 self.path,
518 )?;
519 Ok(())
520 }
521
522 async fn add_entry<T: SeqWrite + Send>(
523 &mut self,
524 encoder: &mut Encoder<'_, T>,
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;
532 let open_mode = if file_mode == libc::S_IFREG || file_mode == libc::S_IFDIR {
533 OFlag::empty()
534 } else {
535 OFlag::O_PATH
536 };
537
538 let fd = self.open_file(
539 parent,
540 c_file_name,
541 open_mode | OFlag::O_RDONLY | OFlag::O_NOFOLLOW,
542 true,
543 )?;
544
545 let fd = match fd {
546 Some(fd) => fd,
547 None => return Ok(()),
548 };
549
550 let metadata = get_metadata(fd.as_raw_fd(), &stat, self.flags(), self.fs_magic, &mut self.fs_feature_flags)?;
551
552 if self
553 .patterns
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) {
570 if let Some(ref catalog) = self.catalog {
571 catalog.lock().unwrap().add_hardlink(c_file_name)?;
572 }
573
574 encoder.add_hardlink(file_name, path, *offset).await?;
575
576 return Ok(());
577 }
578 }
579
580 let file_size = stat.st_size as u64;
581 if let Some(ref catalog) = self.catalog {
582 catalog.lock().unwrap().add_file(c_file_name, file_size, stat.st_mtime)?;
583 }
584
585 let offset: LinkOffset =
586 self.add_regular_file(encoder, fd, file_name, &metadata, file_size).await?;
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())?;
596
597 if let Some(ref catalog) = self.catalog {
598 catalog.lock().unwrap().start_directory(c_file_name)?;
599 }
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()?;
603 }
604 result
605 }
606 mode::IFSOCK => {
607 if let Some(ref catalog) = self.catalog {
608 catalog.lock().unwrap().add_socket(c_file_name)?;
609 }
610
611 Ok(encoder.add_socket(&metadata, file_name).await?)
612 }
613 mode::IFIFO => {
614 if let Some(ref catalog) = self.catalog {
615 catalog.lock().unwrap().add_fifo(c_file_name)?;
616 }
617
618 Ok(encoder.add_fifo(&metadata, file_name).await?)
619 }
620 mode::IFLNK => {
621 if let Some(ref catalog) = self.catalog {
622 catalog.lock().unwrap().add_symlink(c_file_name)?;
623 }
624
625 self.add_symlink(encoder, fd, file_name, &metadata).await
626 }
627 mode::IFBLK => {
628 if let Some(ref catalog) = self.catalog {
629 catalog.lock().unwrap().add_block_device(c_file_name)?;
630 }
631
632 self.add_device(encoder, file_name, &metadata, &stat).await
633 }
634 mode::IFCHR => {
635 if let Some(ref catalog) = self.catalog {
636 catalog.lock().unwrap().add_char_device(c_file_name)?;
637 }
638
639 self.add_device(encoder, file_name, &metadata, &stat).await
640 }
641 other => bail!(
642 "encountered unknown file type: 0x{:x} (0o{:o})",
643 other,
644 other
645 ),
646 }
647 }
648
649 async fn add_directory<T: SeqWrite + Send>(
650 &mut self,
651 encoder: &mut Encoder<'_, T>,
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
659 let mut encoder = encoder.create_directory(dir_name, &metadata).await?;
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())?;
668 self.fs_feature_flags = Flags::from_magic(self.fs_magic);
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 {
679 writeln!(self.logger, "skipping mount point: {:?}", self.path)?;
680 Ok(())
681 } else {
682 self.archive_dir_contents(&mut encoder, dir, false).await
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
689 encoder.finish().await?;
690 result
691 }
692
693 async fn add_regular_file<T: SeqWrite + Send>(
694 &mut self,
695 encoder: &mut Encoder<'_, T>,
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()) };
702 let mut remaining = file_size;
703 let mut out = encoder.create_file(metadata, file_name, file_size).await?;
704 while remaining != 0 {
705 let mut got = match file.read(&mut self.file_copy_buffer[..]) {
706 Ok(0) => break,
707 Ok(got) => got,
708 Err(err) if err.kind() == std::io::ErrorKind::Interrupted => continue,
709 Err(err) => bail!(err),
710 };
711 if got as u64 > remaining {
712 self.report_file_grew_while_reading()?;
713 got = remaining as usize;
714 }
715 out.write_all(&self.file_copy_buffer[..got]).await?;
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;
724 out.write_all(&self.file_copy_buffer[..fill]).await?;
725 remaining -= fill as u64;
726 }
727 }
728
729 Ok(out.file_offset())
730 }
731
732 async fn add_symlink<T: SeqWrite + Send>(
733 &mut self,
734 encoder: &mut Encoder<'_, T>,
735 fd: Fd,
736 file_name: &Path,
737 metadata: &Metadata,
738 ) -> Result<(), Error> {
739 let dest = nix::fcntl::readlinkat(fd.as_raw_fd(), &b""[..])?;
740 encoder.add_symlink(metadata, file_name, dest).await?;
741 Ok(())
742 }
743
744 async fn add_device<T: SeqWrite + Send>(
745 &mut self,
746 encoder: &mut Encoder<'_, T>,
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),
755 ).await?)
756 }
757 }
758
759 fn get_metadata(fd: RawFd, stat: &FileStat, flags: Flags, fs_magic: i64, fs_feature_flags: &mut Flags) -> Result<Metadata, Error> {
760 // required for some of these
761 let proc_path = Path::new("/proc/self/fd/").join(fd.to_string());
762
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,
769 mtime: pxar::format::StatxTimestamp::new(stat.st_mtime, stat.st_mtime_nsec as u32),
770 },
771 ..Default::default()
772 };
773
774 get_xattr_fcaps_acl(&mut meta, fd, &proc_path, flags, fs_feature_flags)?;
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
781 fn get_fcaps(meta: &mut Metadata, fd: RawFd, flags: Flags, fs_feature_flags: &mut Flags) -> Result<(), Error> {
782 if !flags.contains(Flags::WITH_FCAPS) {
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(()),
792 Err(Errno::EOPNOTSUPP) => {
793 fs_feature_flags.remove(Flags::WITH_FCAPS);
794 Ok(())
795 }
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,
805 flags: Flags,
806 fs_feature_flags: &mut Flags,
807 ) -> Result<(), Error> {
808 if !flags.contains(Flags::WITH_XATTRS) {
809 return Ok(());
810 }
811
812 let xattrs = match xattr::flistxattr(fd) {
813 Ok(names) => names,
814 Err(Errno::EOPNOTSUPP) => {
815 fs_feature_flags.remove(Flags::WITH_XATTRS);
816 return Ok(());
817 },
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) {
824 get_fcaps(meta, fd, flags, fs_feature_flags)?;
825 continue;
826 }
827
828 if xattr::is_acl(&attr) {
829 get_acl(meta, proc_path, flags, fs_feature_flags)?;
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> {
852 let mut attr: libc::c_long = 0;
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
862 metadata.stat.flags |= Flags::from_chattr(attr).bits();
863
864 Ok(())
865 }
866
867 fn get_fat_attr(metadata: &mut Metadata, fd: RawFd, fs_magic: i64) -> Result<(), Error> {
868 use proxmox_sys::linux::magic::*;
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
884 metadata.stat.flags |= Flags::from_fat_attr(attr).bits();
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,
893 flags: Flags,
894 magic: i64,
895 ) -> Result<(), Error> {
896 if !(metadata.is_dir() || metadata.is_regular_file()) {
897 return Ok(());
898 }
899
900 if !flags.contains(Flags::WITH_QUOTA_PROJID) {
901 return Ok(());
902 }
903
904 use proxmox_sys::linux::magic::*;
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
934 fn get_acl(metadata: &mut Metadata, proc_path: &Path, flags: Flags, fs_feature_flags: &mut Flags) -> Result<(), Error> {
935 if !flags.contains(Flags::WITH_ACL) {
936 return Ok(());
937 }
938
939 if metadata.is_symlink() {
940 return Ok(());
941 }
942
943 get_acl_do(metadata, proc_path, acl::ACL_TYPE_ACCESS, fs_feature_flags)?;
944
945 if metadata.is_dir() {
946 get_acl_do(metadata, proc_path, acl::ACL_TYPE_DEFAULT, fs_feature_flags)?;
947 }
948
949 Ok(())
950 }
951
952 fn get_acl_do(
953 metadata: &mut Metadata,
954 proc_path: &Path,
955 acl_type: acl::ACLType,
956 fs_feature_flags: &mut Flags,
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
964 Err(Errno::EOPNOTSUPP) => {
965 fs_feature_flags.remove(Flags::WITH_ACL);
966 return Ok(());
967 }
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;
1034 metadata.acl.group_obj = acl_group_obj;
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;
1054 metadata.acl.default = acl_default;
1055 }
1056 _ => bail!("Unexpected ACL type encountered"),
1057 }
1058
1059 Ok(())
1060 }
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> {
1066 use pathpatterns::MatchPattern;
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 }