]> git.proxmox.com Git - proxmox-backup.git/blob - src/pxar/create.rs
pxar: less confusing logic
[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::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 if let Some(fd) = fd {
293 let file = unsafe { std::fs::File::from_raw_fd(fd.into_raw_fd()) };
294
295 use io::BufRead;
296 for line in io::BufReader::new(file).lines() {
297 let line = match line {
298 Ok(line) => line,
299 Err(err) => {
300 let _ = writeln!(
301 self.errors,
302 "ignoring .pxarexclude after read error in {:?}: {}",
303 self.path,
304 err,
305 );
306 self.patterns.truncate(old_pattern_count);
307 return Ok(());
308 }
309 };
310
311 let line = line.trim();
312
313 if line.is_empty() || line.starts_with('#') {
314 continue;
315 }
316
317 match MatchEntry::parse_pattern(line, PatternFlag::PATH_NAME, MatchType::Exclude) {
318 Ok(pattern) => self.patterns.push(pattern),
319 Err(err) => {
320 let _ = writeln!(self.errors, "bad pattern in {:?}: {}", self.path, err);
321 }
322 }
323 }
324 }
325
326 Ok(())
327 }
328
329 fn encode_pxarexclude_cli(
330 &mut self,
331 encoder: &mut Encoder,
332 file_name: &CStr,
333 ) -> Result<(), Error> {
334 let content = generate_pxar_excludes_cli(&self.patterns);
335
336 if let Some(ref mut catalog) = self.catalog {
337 catalog.add_file(file_name, content.len() as u64, 0)?;
338 }
339
340 let mut metadata = Metadata::default();
341 metadata.stat.mode = pxar::format::mode::IFREG | 0o600;
342
343 let mut file = encoder.create_file(&metadata, ".pxarexclude-cli", content.len() as u64)?;
344 file.write_all(&content)?;
345
346 Ok(())
347 }
348
349 fn generate_directory_file_list(
350 &mut self,
351 dir: &mut Dir,
352 is_root: bool,
353 ) -> Result<Vec<FileListEntry>, Error> {
354 let dir_fd = dir.as_raw_fd();
355
356 let mut file_list = Vec::new();
357
358 if is_root && !self.patterns.is_empty() {
359 file_list.push(FileListEntry {
360 name: CString::new(".pxarexclude-cli").unwrap(),
361 path: PathBuf::new(),
362 stat: unsafe { std::mem::zeroed() },
363 });
364 }
365
366 for file in dir.iter() {
367 let file = file?;
368
369 let file_name = file.file_name().to_owned();
370 let file_name_bytes = file_name.to_bytes();
371 if file_name_bytes == b"." || file_name_bytes == b".." {
372 continue;
373 }
374
375 if is_root && file_name_bytes == b".pxarexclude-cli" {
376 continue;
377 }
378
379 if file_name_bytes == b".pxarexclude" {
380 continue;
381 }
382
383 let os_file_name = OsStr::from_bytes(file_name_bytes);
384 assert_single_path_component(os_file_name)?;
385 let full_path = self.path.join(os_file_name);
386
387 let stat = match nix::sys::stat::fstatat(
388 dir_fd,
389 file_name.as_c_str(),
390 nix::fcntl::AtFlags::AT_SYMLINK_NOFOLLOW,
391 ) {
392 Ok(stat) => stat,
393 Err(ref err) if err.not_found() => continue,
394 Err(err) => bail!("stat failed on {:?}: {}", full_path, err),
395 };
396
397 if self
398 .patterns
399 .matches(full_path.as_os_str().as_bytes(), Some(stat.st_mode as u32))
400 == Some(MatchType::Exclude)
401 {
402 continue;
403 }
404
405 self.entry_counter += 1;
406 if self.entry_counter > self.entry_limit {
407 bail!("exceeded allowed number of file entries (> {})",self.entry_limit);
408 }
409
410 file_list.push(FileListEntry {
411 name: file_name,
412 path: full_path,
413 stat
414 });
415 }
416
417 file_list.sort_unstable_by(|a, b| a.name.cmp(&b.name));
418
419 Ok(file_list)
420 }
421
422 fn report_vanished_file(&mut self) -> Result<(), Error> {
423 writeln!(self.errors, "warning: file vanished while reading: {:?}", self.path)?;
424 Ok(())
425 }
426
427 fn report_file_shrunk_while_reading(&mut self) -> Result<(), Error> {
428 writeln!(
429 self.errors,
430 "warning: file size shrunk while reading: {:?}, file will be padded with zeros!",
431 self.path,
432 )?;
433 Ok(())
434 }
435
436 fn report_file_grew_while_reading(&mut self) -> Result<(), Error> {
437 writeln!(
438 self.errors,
439 "warning: file size increased while reading: {:?}, file will be truncated!",
440 self.path,
441 )?;
442 Ok(())
443 }
444
445 fn add_entry(
446 &mut self,
447 encoder: &mut Encoder,
448 parent: RawFd,
449 c_file_name: &CStr,
450 stat: &FileStat,
451 ) -> Result<(), Error> {
452 use pxar::format::mode;
453
454 let file_mode = stat.st_mode & libc::S_IFMT;
455 let open_mode = if file_mode == libc::S_IFREG || file_mode == libc::S_IFDIR {
456 OFlag::empty()
457 } else {
458 OFlag::O_PATH
459 };
460
461 let fd = self.open_file(
462 parent,
463 c_file_name,
464 open_mode | OFlag::O_RDONLY | OFlag::O_NOFOLLOW | OFlag::O_CLOEXEC | OFlag::O_NOCTTY,
465 true,
466 )?;
467
468 let fd = match fd {
469 Some(fd) => fd,
470 None => return Ok(()),
471 };
472
473 let metadata = get_metadata(fd.as_raw_fd(), &stat, self.flags(), self.fs_magic)?;
474
475 if self
476 .patterns
477 .matches(self.path.as_os_str().as_bytes(), Some(stat.st_mode as u32))
478 == Some(MatchType::Exclude)
479 {
480 return Ok(());
481 }
482
483 let file_name: &Path = OsStr::from_bytes(c_file_name.to_bytes()).as_ref();
484 match metadata.file_type() {
485 mode::IFREG => {
486 let link_info = HardLinkInfo {
487 st_dev: stat.st_dev,
488 st_ino: stat.st_ino,
489 };
490
491 if stat.st_nlink > 1 {
492 if let Some((path, offset)) = self.hardlinks.get(&link_info) {
493 if let Some(ref mut catalog) = self.catalog {
494 catalog.add_hardlink(c_file_name)?;
495 }
496
497 encoder.add_hardlink(file_name, path, *offset)?;
498
499 return Ok(());
500 }
501 }
502
503 let file_size = stat.st_size as u64;
504 if let Some(ref mut catalog) = self.catalog {
505 catalog.add_file(c_file_name, file_size, stat.st_mtime as u64)?;
506 }
507
508 let offset: LinkOffset =
509 self.add_regular_file(encoder, fd, file_name, &metadata, file_size)?;
510
511 if stat.st_nlink > 1 {
512 self.hardlinks.insert(link_info, (self.path.clone(), offset));
513 }
514
515 Ok(())
516 }
517 mode::IFDIR => {
518 let dir = Dir::from_fd(fd.into_raw_fd())?;
519
520 if let Some(ref mut catalog) = self.catalog {
521 catalog.start_directory(c_file_name)?;
522 }
523 let result = self.add_directory(encoder, dir, c_file_name, &metadata, stat);
524 if let Some(ref mut catalog) = self.catalog {
525 catalog.end_directory()?;
526 }
527 result
528 }
529 mode::IFSOCK => {
530 if let Some(ref mut catalog) = self.catalog {
531 catalog.add_socket(c_file_name)?;
532 }
533
534 Ok(encoder.add_socket(&metadata, file_name)?)
535 }
536 mode::IFIFO => {
537 if let Some(ref mut catalog) = self.catalog {
538 catalog.add_fifo(c_file_name)?;
539 }
540
541 Ok(encoder.add_fifo(&metadata, file_name)?)
542 }
543 mode::IFLNK => {
544 if let Some(ref mut catalog) = self.catalog {
545 catalog.add_symlink(c_file_name)?;
546 }
547
548 self.add_symlink(encoder, fd, file_name, &metadata)
549 }
550 mode::IFBLK => {
551 if let Some(ref mut catalog) = self.catalog {
552 catalog.add_block_device(c_file_name)?;
553 }
554
555 self.add_device(encoder, file_name, &metadata, &stat)
556 }
557 mode::IFCHR => {
558 if let Some(ref mut catalog) = self.catalog {
559 catalog.add_char_device(c_file_name)?;
560 }
561
562 self.add_device(encoder, file_name, &metadata, &stat)
563 }
564 other => bail!(
565 "encountered unknown file type: 0x{:x} (0o{:o})",
566 other,
567 other
568 ),
569 }
570 }
571
572 fn add_directory(
573 &mut self,
574 encoder: &mut Encoder,
575 dir: Dir,
576 dir_name: &CStr,
577 metadata: &Metadata,
578 stat: &FileStat,
579 ) -> Result<(), Error> {
580 let dir_name = OsStr::from_bytes(dir_name.to_bytes());
581
582 let mut encoder = encoder.create_directory(dir_name, &metadata)?;
583
584 let old_fs_magic = self.fs_magic;
585 let old_fs_feature_flags = self.fs_feature_flags;
586 let old_st_dev = self.current_st_dev;
587
588 let mut skip_contents = false;
589 if old_st_dev != stat.st_dev {
590 self.fs_magic = detect_fs_type(dir.as_raw_fd())?;
591 self.fs_feature_flags = Flags::from_magic(self.fs_magic);
592 self.current_st_dev = stat.st_dev;
593
594 if is_virtual_file_system(self.fs_magic) {
595 skip_contents = true;
596 } else if let Some(set) = &self.device_set {
597 skip_contents = !set.contains(&stat.st_dev);
598 }
599 }
600
601 let result = if skip_contents {
602 Ok(())
603 } else {
604 self.archive_dir_contents(&mut encoder, dir, false)
605 };
606
607 self.fs_magic = old_fs_magic;
608 self.fs_feature_flags = old_fs_feature_flags;
609 self.current_st_dev = old_st_dev;
610
611 encoder.finish()?;
612 result
613 }
614
615 fn add_regular_file(
616 &mut self,
617 encoder: &mut Encoder,
618 fd: Fd,
619 file_name: &Path,
620 metadata: &Metadata,
621 file_size: u64,
622 ) -> Result<LinkOffset, Error> {
623 let mut file = unsafe { std::fs::File::from_raw_fd(fd.into_raw_fd()) };
624 let mut remaining = file_size;
625 let mut out = encoder.create_file(metadata, file_name, file_size)?;
626 while remaining != 0 {
627 let mut got = file.read(&mut self.file_copy_buffer[..])?;
628 if got as u64 > remaining {
629 self.report_file_grew_while_reading()?;
630 got = remaining as usize;
631 }
632 out.write_all(&self.file_copy_buffer[..got])?;
633 remaining -= got as u64;
634 }
635 if remaining > 0 {
636 self.report_file_shrunk_while_reading()?;
637 let to_zero = remaining.min(self.file_copy_buffer.len() as u64) as usize;
638 vec::clear(&mut self.file_copy_buffer[..to_zero]);
639 while remaining != 0 {
640 let fill = remaining.min(self.file_copy_buffer.len() as u64) as usize;
641 out.write_all(&self.file_copy_buffer[..fill])?;
642 remaining -= fill as u64;
643 }
644 }
645
646 Ok(out.file_offset())
647 }
648
649 fn add_symlink(
650 &mut self,
651 encoder: &mut Encoder,
652 fd: Fd,
653 file_name: &Path,
654 metadata: &Metadata,
655 ) -> Result<(), Error> {
656 let dest = nix::fcntl::readlinkat(fd.as_raw_fd(), &b""[..])?;
657 encoder.add_symlink(metadata, file_name, dest)?;
658 Ok(())
659 }
660
661 fn add_device(
662 &mut self,
663 encoder: &mut Encoder,
664 file_name: &Path,
665 metadata: &Metadata,
666 stat: &FileStat,
667 ) -> Result<(), Error> {
668 Ok(encoder.add_device(
669 metadata,
670 file_name,
671 pxar::format::Device::from_dev_t(stat.st_rdev),
672 )?)
673 }
674 }
675
676 fn get_metadata(fd: RawFd, stat: &FileStat, flags: Flags, fs_magic: i64) -> Result<Metadata, Error> {
677 // required for some of these
678 let proc_path = Path::new("/proc/self/fd/").join(fd.to_string());
679
680 let mtime = u64::try_from(stat.st_mtime * 1_000_000_000 + stat.st_mtime_nsec)
681 .map_err(|_| format_err!("file with negative mtime"))?;
682
683 let mut meta = Metadata {
684 stat: pxar::Stat {
685 mode: u64::from(stat.st_mode),
686 flags: 0,
687 uid: stat.st_uid,
688 gid: stat.st_gid,
689 mtime,
690 },
691 ..Default::default()
692 };
693
694 get_xattr_fcaps_acl(&mut meta, fd, &proc_path, flags)?;
695 get_chattr(&mut meta, fd)?;
696 get_fat_attr(&mut meta, fd, fs_magic)?;
697 get_quota_project_id(&mut meta, fd, flags, fs_magic)?;
698 Ok(meta)
699 }
700
701 fn errno_is_unsupported(errno: Errno) -> bool {
702 match errno {
703 Errno::ENOTTY | Errno::ENOSYS | Errno::EBADF | Errno::EOPNOTSUPP | Errno::EINVAL => true,
704 _ => false,
705 }
706 }
707
708 fn get_fcaps(meta: &mut Metadata, fd: RawFd, flags: Flags) -> Result<(), Error> {
709 if flags.contains(Flags::WITH_FCAPS) {
710 return Ok(());
711 }
712
713 match xattr::fgetxattr(fd, xattr::xattr_name_fcaps()) {
714 Ok(data) => {
715 meta.fcaps = Some(pxar::format::FCaps { data });
716 Ok(())
717 }
718 Err(Errno::ENODATA) => Ok(()),
719 Err(Errno::EOPNOTSUPP) => Ok(()),
720 Err(Errno::EBADF) => Ok(()), // symlinks
721 Err(err) => bail!("failed to read file capabilities: {}", err),
722 }
723 }
724
725 fn get_xattr_fcaps_acl(
726 meta: &mut Metadata,
727 fd: RawFd,
728 proc_path: &Path,
729 flags: Flags,
730 ) -> Result<(), Error> {
731 if flags.contains(Flags::WITH_XATTRS) {
732 return Ok(());
733 }
734
735 let xattrs = match xattr::flistxattr(fd) {
736 Ok(names) => names,
737 Err(Errno::EOPNOTSUPP) => return Ok(()),
738 Err(Errno::EBADF) => return Ok(()), // symlinks
739 Err(err) => bail!("failed to read xattrs: {}", err),
740 };
741
742 for attr in &xattrs {
743 if xattr::is_security_capability(&attr) {
744 get_fcaps(meta, fd, flags)?;
745 continue;
746 }
747
748 if xattr::is_acl(&attr) {
749 get_acl(meta, proc_path, flags)?;
750 continue;
751 }
752
753 if !xattr::is_valid_xattr_name(&attr) {
754 continue;
755 }
756
757 match xattr::fgetxattr(fd, attr) {
758 Ok(data) => meta
759 .xattrs
760 .push(pxar::format::XAttr::new(attr.to_bytes(), data)),
761 Err(Errno::ENODATA) => (), // it got removed while we were iterating...
762 Err(Errno::EOPNOTSUPP) => (), // shouldn't be possible so just ignore this
763 Err(Errno::EBADF) => (), // symlinks, shouldn't be able to reach this either
764 Err(err) => bail!("error reading extended attribute {:?}: {}", attr, err),
765 }
766 }
767
768 Ok(())
769 }
770
771 fn get_chattr(metadata: &mut Metadata, fd: RawFd) -> Result<(), Error> {
772 let mut attr: usize = 0;
773
774 match unsafe { fs::read_attr_fd(fd, &mut attr) } {
775 Ok(_) => (),
776 Err(nix::Error::Sys(errno)) if errno_is_unsupported(errno) => {
777 return Ok(());
778 }
779 Err(err) => bail!("failed to read file attributes: {}", err),
780 }
781
782 metadata.stat.flags |= Flags::from_chattr(attr as u32).bits();
783
784 Ok(())
785 }
786
787 fn get_fat_attr(metadata: &mut Metadata, fd: RawFd, fs_magic: i64) -> Result<(), Error> {
788 use proxmox::sys::linux::magic::*;
789
790 if fs_magic != MSDOS_SUPER_MAGIC && fs_magic != FUSE_SUPER_MAGIC {
791 return Ok(());
792 }
793
794 let mut attr: u32 = 0;
795
796 match unsafe { fs::read_fat_attr_fd(fd, &mut attr) } {
797 Ok(_) => (),
798 Err(nix::Error::Sys(errno)) if errno_is_unsupported(errno) => {
799 return Ok(());
800 }
801 Err(err) => bail!("failed to read fat attributes: {}", err),
802 }
803
804 metadata.stat.flags |= Flags::from_fat_attr(attr).bits();
805
806 Ok(())
807 }
808
809 /// Read the quota project id for an inode, supported on ext4/XFS/FUSE/ZFS filesystems
810 fn get_quota_project_id(
811 metadata: &mut Metadata,
812 fd: RawFd,
813 flags: Flags,
814 magic: i64,
815 ) -> Result<(), Error> {
816 if !(metadata.is_dir() || metadata.is_regular_file()) {
817 return Ok(());
818 }
819
820 if flags.contains(Flags::WITH_QUOTA_PROJID) {
821 return Ok(());
822 }
823
824 use proxmox::sys::linux::magic::*;
825
826 match magic {
827 EXT4_SUPER_MAGIC | XFS_SUPER_MAGIC | FUSE_SUPER_MAGIC | ZFS_SUPER_MAGIC => (),
828 _ => return Ok(()),
829 }
830
831 let mut fsxattr = fs::FSXAttr::default();
832 let res = unsafe { fs::fs_ioc_fsgetxattr(fd, &mut fsxattr) };
833
834 // On some FUSE filesystems it can happen that ioctl is not supported.
835 // For these cases projid is set to 0 while the error is ignored.
836 if let Err(err) = res {
837 let errno = err
838 .as_errno()
839 .ok_or_else(|| format_err!("error while reading quota project id"))?;
840 if errno_is_unsupported(errno) {
841 return Ok(());
842 } else {
843 bail!("error while reading quota project id ({})", errno);
844 }
845 }
846
847 let projid = fsxattr.fsx_projid as u64;
848 if projid != 0 {
849 metadata.quota_project_id = Some(pxar::format::QuotaProjectId { projid });
850 }
851 Ok(())
852 }
853
854 fn get_acl(metadata: &mut Metadata, proc_path: &Path, flags: Flags) -> Result<(), Error> {
855 if flags.contains(Flags::WITH_ACL) {
856 return Ok(());
857 }
858
859 if metadata.is_symlink() {
860 return Ok(());
861 }
862
863 get_acl_do(metadata, proc_path, acl::ACL_TYPE_ACCESS)?;
864
865 if metadata.is_dir() {
866 get_acl_do(metadata, proc_path, acl::ACL_TYPE_DEFAULT)?;
867 }
868
869 Ok(())
870 }
871
872 fn get_acl_do(
873 metadata: &mut Metadata,
874 proc_path: &Path,
875 acl_type: acl::ACLType,
876 ) -> Result<(), Error> {
877 // In order to be able to get ACLs with type ACL_TYPE_DEFAULT, we have
878 // to create a path for acl_get_file(). acl_get_fd() only allows to get
879 // ACL_TYPE_ACCESS attributes.
880 let acl = match acl::ACL::get_file(&proc_path, acl_type) {
881 Ok(acl) => acl,
882 // Don't bail if underlying endpoint does not support acls
883 Err(Errno::EOPNOTSUPP) => return Ok(()),
884 // Don't bail if the endpoint cannot carry acls
885 Err(Errno::EBADF) => return Ok(()),
886 // Don't bail if there is no data
887 Err(Errno::ENODATA) => return Ok(()),
888 Err(err) => bail!("error while reading ACL - {}", err),
889 };
890
891 process_acl(metadata, acl, acl_type)
892 }
893
894 fn process_acl(
895 metadata: &mut Metadata,
896 acl: acl::ACL,
897 acl_type: acl::ACLType,
898 ) -> Result<(), Error> {
899 use pxar::format::acl as pxar_acl;
900 use pxar::format::acl::{Group, GroupObject, Permissions, User};
901
902 let mut acl_user = Vec::new();
903 let mut acl_group = Vec::new();
904 let mut acl_group_obj = None;
905 let mut acl_default = None;
906 let mut user_obj_permissions = None;
907 let mut group_obj_permissions = None;
908 let mut other_permissions = None;
909 let mut mask_permissions = None;
910
911 for entry in &mut acl.entries() {
912 let tag = entry.get_tag_type()?;
913 let permissions = entry.get_permissions()?;
914 match tag {
915 acl::ACL_USER_OBJ => user_obj_permissions = Some(Permissions(permissions)),
916 acl::ACL_GROUP_OBJ => group_obj_permissions = Some(Permissions(permissions)),
917 acl::ACL_OTHER => other_permissions = Some(Permissions(permissions)),
918 acl::ACL_MASK => mask_permissions = Some(Permissions(permissions)),
919 acl::ACL_USER => {
920 acl_user.push(User {
921 uid: entry.get_qualifier()?,
922 permissions: Permissions(permissions),
923 });
924 }
925 acl::ACL_GROUP => {
926 acl_group.push(Group {
927 gid: entry.get_qualifier()?,
928 permissions: Permissions(permissions),
929 });
930 }
931 _ => bail!("Unexpected ACL tag encountered!"),
932 }
933 }
934
935 acl_user.sort();
936 acl_group.sort();
937
938 match acl_type {
939 acl::ACL_TYPE_ACCESS => {
940 // The mask permissions are mapped to the stat group permissions
941 // in case that the ACL group permissions were set.
942 // Only in that case we need to store the group permissions,
943 // in the other cases they are identical to the stat group permissions.
944 if let (Some(gop), true) = (group_obj_permissions, mask_permissions.is_some()) {
945 acl_group_obj = Some(GroupObject { permissions: gop });
946 }
947
948 metadata.acl.users = acl_user;
949 metadata.acl.groups = acl_group;
950 }
951 acl::ACL_TYPE_DEFAULT => {
952 if user_obj_permissions != None
953 || group_obj_permissions != None
954 || other_permissions != None
955 || mask_permissions != None
956 {
957 acl_default = Some(pxar_acl::Default {
958 // The value is set to UINT64_MAX as placeholder if one
959 // of the permissions is not set
960 user_obj_permissions: user_obj_permissions.unwrap_or(Permissions::NO_MASK),
961 group_obj_permissions: group_obj_permissions.unwrap_or(Permissions::NO_MASK),
962 other_permissions: other_permissions.unwrap_or(Permissions::NO_MASK),
963 mask_permissions: mask_permissions.unwrap_or(Permissions::NO_MASK),
964 });
965 }
966
967 metadata.acl.default_users = acl_user;
968 metadata.acl.default_groups = acl_group;
969 }
970 _ => bail!("Unexpected ACL type encountered"),
971 }
972
973 metadata.acl.group_obj = acl_group_obj;
974 metadata.acl.default = acl_default;
975
976 Ok(())
977 }
978
979 /// Note that our pattern lists are "positive". `MatchType::Include` means the file is included.
980 /// Since we are generating an *exclude* list, we need to invert this, so includes get a `'!'`
981 /// prefix.
982 fn generate_pxar_excludes_cli(patterns: &[MatchEntry]) -> Vec<u8> {
983 use pathpatterns::{MatchFlag, MatchPattern};
984
985 let mut content = Vec::new();
986
987 for pattern in patterns {
988 match pattern.match_type() {
989 MatchType::Include => content.push(b'!'),
990 MatchType::Exclude => (),
991 }
992
993 match pattern.pattern() {
994 MatchPattern::Literal(lit) => content.extend(lit),
995 MatchPattern::Pattern(pat) => content.extend(pat.pattern().to_bytes()),
996 }
997
998 if pattern.match_flags() == MatchFlag::MATCH_DIRECTORIES && content.last() != Some(&b'/') {
999 content.push(b'/');
1000 }
1001
1002 content.push(b'\n');
1003 }
1004
1005 content
1006 }