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