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