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