1 use std
::collections
::{HashSet, HashMap}
;
2 use std
::ffi
::{CStr, CString, OsStr}
;
4 use std
::io
::{self, Read, Write}
;
5 use std
::os
::unix
::ffi
::OsStrExt
;
6 use std
::os
::unix
::io
::{AsRawFd, FromRawFd, IntoRawFd, RawFd}
;
7 use std
::path
::{Path, PathBuf}
;
8 use std
::sync
::{Arc, Mutex}
;
10 use anyhow
::{bail, format_err, Error}
;
12 use nix
::errno
::Errno
;
13 use nix
::fcntl
::OFlag
;
14 use nix
::sys
::stat
::{FileStat, Mode}
;
15 use futures
::future
::BoxFuture
;
16 use futures
::FutureExt
;
18 use pathpatterns
::{MatchEntry, MatchFlag, MatchList, MatchType, PatternFlag}
;
20 use pxar
::encoder
::{SeqWrite, LinkOffset}
;
22 use proxmox_sys
::error
::SysError
;
23 use proxmox_sys
::fd
::RawFdNum
;
24 use proxmox_sys
::fd
::Fd
;
25 use proxmox_sys
::fs
::{self, acl, xattr}
;
27 use proxmox_lang
::c_str
;
29 use pbs_datastore
::catalog
::BackupCatalogWriter
;
31 use crate::pxar
::metadata
::errno_is_unsupported
;
32 use crate::pxar
::Flags
;
33 use crate::pxar
::tools
::assert_single_path_component
;
35 /// Pxar options for creating a pxar archive/stream
36 #[derive(Default, Clone)]
37 pub struct PxarCreateOptions
{
38 /// Device/mountpoint st_dev numbers that should be included. None for no limitation.
39 pub device_set
: Option
<HashSet
<u64>>,
40 /// Exclusion patterns
41 pub patterns
: Vec
<MatchEntry
>,
42 /// Maximum number of entries to hold in memory
43 pub entries_max
: usize,
44 /// Skip lost+found directory
45 pub skip_lost_and_found
: bool
,
51 fn detect_fs_type(fd
: RawFd
) -> Result
<i64, Error
> {
52 let mut fs_stat
= std
::mem
::MaybeUninit
::uninit();
53 let res
= unsafe { libc::fstatfs(fd, fs_stat.as_mut_ptr()) }
;
55 let fs_stat
= unsafe { fs_stat.assume_init() }
;
60 fn strip_ascii_whitespace(line
: &[u8]) -> &[u8] {
61 let line
= match line
.iter().position(|&b
| !b
.is_ascii_whitespace()) {
62 Some(n
) => &line
[n
..],
65 match line
.iter().rev().position(|&b
| !b
.is_ascii_whitespace()) {
66 Some(n
) => &line
[..(line
.len() - n
)],
72 pub fn is_virtual_file_system(magic
: i64) -> bool
{
73 use proxmox_sys
::linux
::magic
::*;
75 matches
!(magic
, BINFMTFS_MAGIC
|
82 FUSE_CTL_SUPER_MAGIC
|
102 fn new(path
: PathBuf
, error
: Error
) -> Self {
107 impl std
::error
::Error
for ArchiveError {}
109 impl fmt
::Display
for ArchiveError
{
110 fn fmt(&self, f
: &mut fmt
::Formatter
) -> fmt
::Result
{
111 write
!(f
, "error at {:?}: {}", self.path
, self.error
)
115 #[derive(Eq, PartialEq, Hash)]
116 struct HardLinkInfo
{
121 /// TODO: make a builder for the create_archive call for fewer parameters and add a method to add a
122 /// logger which does not write to stderr.
125 impl std
::io
::Write
for Logger
{
126 fn write(&mut self, data
: &[u8]) -> io
::Result
<usize> {
127 std
::io
::stderr().write(data
)
130 fn flush(&mut self) -> io
::Result
<()> {
131 std
::io
::stderr().flush()
135 /// And the error case.
136 struct ErrorReporter
;
138 impl std
::io
::Write
for ErrorReporter
{
139 fn write(&mut self, data
: &[u8]) -> io
::Result
<usize> {
140 std
::io
::stderr().write(data
)
143 fn flush(&mut self) -> io
::Result
<()> {
144 std
::io
::stderr().flush()
149 feature_flags
: Flags
,
150 fs_feature_flags
: Flags
,
152 patterns
: Vec
<MatchEntry
>,
153 callback
: Box
<dyn FnMut(&Path
) -> Result
<(), Error
> + Send
>,
154 catalog
: Option
<Arc
<Mutex
<dyn BackupCatalogWriter
+ Send
>>>,
156 entry_counter
: usize,
158 current_st_dev
: libc
::dev_t
,
159 device_set
: Option
<HashSet
<u64>>,
160 hardlinks
: HashMap
<HardLinkInfo
, (PathBuf
, LinkOffset
)>,
161 errors
: ErrorReporter
,
163 file_copy_buffer
: Vec
<u8>,
166 type Encoder
<'a
, T
> = pxar
::encoder
::aio
::Encoder
<'a
, T
>;
168 pub async
fn create_archive
<T
, F
>(
171 feature_flags
: Flags
,
173 catalog
: Option
<Arc
<Mutex
<dyn BackupCatalogWriter
+ Send
>>>,
174 options
: PxarCreateOptions
,
175 ) -> Result
<(), Error
>
178 F
: FnMut(&Path
) -> Result
<(), Error
> + Send
+ '
static,
180 let fs_magic
= detect_fs_type(source_dir
.as_raw_fd())?
;
181 if is_virtual_file_system(fs_magic
) {
182 bail
!("refusing to backup a virtual file system");
185 let mut fs_feature_flags
= Flags
::from_magic(fs_magic
);
187 let stat
= nix
::sys
::stat
::fstat(source_dir
.as_raw_fd())?
;
188 let metadata
= get_metadata(
189 source_dir
.as_raw_fd(),
191 feature_flags
& fs_feature_flags
,
193 &mut fs_feature_flags
,
195 .map_err(|err
| format_err
!("failed to get metadata for source directory: {}", err
))?
;
197 let mut device_set
= options
.device_set
.clone();
198 if let Some(ref mut set
) = device_set
{
199 set
.insert(stat
.st_dev
);
202 let mut encoder
= Encoder
::new(&mut writer
, &metadata
).await?
;
204 let mut patterns
= options
.patterns
;
206 if options
.skip_lost_and_found
{
207 patterns
.push(MatchEntry
::parse_pattern(
209 PatternFlag
::PATH_NAME
,
214 let mut archiver
= Archiver
{
218 callback
: Box
::new(callback
),
221 path
: PathBuf
::new(),
223 entry_limit
: options
.entries_max
,
224 current_st_dev
: stat
.st_dev
,
226 hardlinks
: HashMap
::new(),
227 errors
: ErrorReporter
,
229 file_copy_buffer
: vec
::undefined(4 * 1024 * 1024),
232 archiver
.archive_dir_contents(&mut encoder
, source_dir
, true).await?
;
233 encoder
.finish().await?
;
237 struct FileListEntry
{
244 /// Get the currently effective feature flags. (Requested flags masked by the file system
246 fn flags(&self) -> Flags
{
247 self.feature_flags
& self.fs_feature_flags
250 fn wrap_err(&self, err
: Error
) -> Error
{
251 if err
.downcast_ref
::<ArchiveError
>().is_some() {
254 ArchiveError
::new(self.path
.clone(), err
).into()
258 fn archive_dir_contents
<'a
, 'b
, T
: SeqWrite
+ Send
>(
260 encoder
: &'a
mut Encoder
<'b
, T
>,
263 ) -> BoxFuture
<'a
, Result
<(), Error
>> {
265 let entry_counter
= self.entry_counter
;
267 let old_patterns_count
= self.patterns
.len();
268 self.read_pxar_excludes(dir
.as_raw_fd())?
;
270 let mut file_list
= self.generate_directory_file_list(&mut dir
, is_root
)?
;
272 if is_root
&& old_patterns_count
> 0 {
273 file_list
.push(FileListEntry
{
274 name
: CString
::new(".pxarexclude-cli").unwrap(),
275 path
: PathBuf
::new(),
276 stat
: unsafe { std::mem::zeroed() }
,
280 let dir_fd
= dir
.as_raw_fd();
282 let old_path
= std
::mem
::take(&mut self.path
);
284 for file_entry
in file_list
{
285 let file_name
= file_entry
.name
.to_bytes();
287 if is_root
&& file_name
== b
".pxarexclude-cli" {
288 self.encode_pxarexclude_cli(encoder
, &file_entry
.name
, old_patterns_count
).await?
;
292 (self.callback
)(&file_entry
.path
)?
;
293 self.path
= file_entry
.path
;
294 self.add_entry(encoder
, dir_fd
, &file_entry
.name
, &file_entry
.stat
).await
295 .map_err(|err
| self.wrap_err(err
))?
;
297 self.path
= old_path
;
298 self.entry_counter
= entry_counter
;
299 self.patterns
.truncate(old_patterns_count
);
305 /// openat() wrapper which allows but logs `EACCES` and turns `ENOENT` into `None`.
307 /// The `existed` flag is set when iterating through a directory to note that we know the file
308 /// is supposed to exist and we should warn if it doesnt'.
315 ) -> Result
<Option
<Fd
>, Error
> {
316 // common flags we always want to use:
317 let oflags
= oflags
| OFlag
::O_CLOEXEC
| OFlag
::O_NOCTTY
;
319 let mut noatime
= OFlag
::O_NOATIME
;
321 return match Fd
::openat(
322 &unsafe { RawFdNum::from_raw_fd(parent) }
,
327 Ok(fd
) => Ok(Some(fd
)),
328 Err(nix
::Error
::Sys(Errno
::ENOENT
)) => {
330 self.report_vanished_file()?
;
334 Err(nix
::Error
::Sys(Errno
::EACCES
)) => {
335 writeln
!(self.errors
, "failed to open file: {:?}: access denied", file_name
)?
;
338 Err(nix
::Error
::Sys(Errno
::EPERM
)) if !noatime
.is_empty() => {
339 // Retry without O_NOATIME:
340 noatime
= OFlag
::empty();
343 Err(other
) => Err(Error
::from(other
)),
348 fn read_pxar_excludes(&mut self, parent
: RawFd
) -> Result
<(), Error
> {
349 let fd
= match self.open_file(parent
, c_str
!(".pxarexclude"), OFlag
::O_RDONLY
, false)?
{
351 None
=> return Ok(()),
354 let old_pattern_count
= self.patterns
.len();
356 let path_bytes
= self.path
.as_os_str().as_bytes();
358 let file
= unsafe { std::fs::File::from_raw_fd(fd.into_raw_fd()) }
;
361 for line
in io
::BufReader
::new(file
).split(b'
\n'
) {
362 let line
= match line
{
367 "ignoring .pxarexclude after read error in {:?}: {}",
371 self.patterns
.truncate(old_pattern_count
);
376 let line
= strip_ascii_whitespace(&line
);
378 if line
.is_empty() || line
[0] == b'
#' {
383 let (line
, mode
, anchored
) = if line
[0] == b'
/'
{
384 buf
= Vec
::with_capacity(path_bytes
.len() + 1 + line
.len());
385 buf
.extend(path_bytes
);
387 (&buf
[..], MatchType
::Exclude
, true)
388 } else if line
.starts_with(b
"!/") {
389 // inverted case with absolute path
390 buf
= Vec
::with_capacity(path_bytes
.len() + line
.len());
391 buf
.extend(path_bytes
);
392 buf
.extend(&line
[1..]); // without the '!'
393 (&buf
[..], MatchType
::Include
, true)
394 } else if line
.starts_with(b
"!") {
395 (&line
[1..], MatchType
::Include
, false)
397 (line
, MatchType
::Exclude
, false)
400 match MatchEntry
::parse_pattern(line
, PatternFlag
::PATH_NAME
, mode
) {
403 self.patterns
.push(pattern
.add_flags(MatchFlag
::ANCHORED
));
405 self.patterns
.push(pattern
);
409 let _
= writeln
!(self.errors
, "bad pattern in {:?}: {}", self.path
, err
);
417 async
fn encode_pxarexclude_cli
<T
: SeqWrite
+ Send
>(
419 encoder
: &mut Encoder
<'_
, T
>,
421 patterns_count
: usize,
422 ) -> Result
<(), Error
> {
423 let content
= generate_pxar_excludes_cli(&self.patterns
[..patterns_count
]);
424 if let Some(ref catalog
) = self.catalog
{
425 catalog
.lock().unwrap().add_file(file_name
, content
.len() as u64, 0)?
;
428 let mut metadata
= Metadata
::default();
429 metadata
.stat
.mode
= pxar
::format
::mode
::IFREG
| 0o600;
431 let mut file
= encoder
.create_file(&metadata
, ".pxarexclude-cli", content
.len() as u64).await?
;
432 file
.write_all(&content
).await?
;
437 fn generate_directory_file_list(
441 ) -> Result
<Vec
<FileListEntry
>, Error
> {
442 let dir_fd
= dir
.as_raw_fd();
444 let mut file_list
= Vec
::new();
446 for file
in dir
.iter() {
449 let file_name
= file
.file_name().to_owned();
450 let file_name_bytes
= file_name
.to_bytes();
451 if file_name_bytes
== b
"." || file_name_bytes
== b
".." {
455 if is_root
&& file_name_bytes
== b
".pxarexclude-cli" {
459 let os_file_name
= OsStr
::from_bytes(file_name_bytes
);
460 assert_single_path_component(os_file_name
)?
;
461 let full_path
= self.path
.join(os_file_name
);
463 let stat
= match nix
::sys
::stat
::fstatat(
465 file_name
.as_c_str(),
466 nix
::fcntl
::AtFlags
::AT_SYMLINK_NOFOLLOW
,
469 Err(ref err
) if err
.not_found() => continue,
470 Err(err
) => bail
!("stat failed on {:?}: {}", full_path
, err
),
473 let match_path
= PathBuf
::from("/").join(full_path
.clone());
476 .matches(match_path
.as_os_str().as_bytes(), Some(stat
.st_mode
as u32))
477 == Some(MatchType
::Exclude
)
482 self.entry_counter
+= 1;
483 if self.entry_counter
> self.entry_limit
{
484 bail
!("exceeded allowed number of file entries (> {})",self.entry_limit
);
487 file_list
.push(FileListEntry
{
494 file_list
.sort_unstable_by(|a
, b
| a
.name
.cmp(&b
.name
));
499 fn report_vanished_file(&mut self) -> Result
<(), Error
> {
500 writeln
!(self.errors
, "warning: file vanished while reading: {:?}", self.path
)?
;
504 fn report_file_shrunk_while_reading(&mut self) -> Result
<(), Error
> {
507 "warning: file size shrunk while reading: {:?}, file will be padded with zeros!",
513 fn report_file_grew_while_reading(&mut self) -> Result
<(), Error
> {
516 "warning: file size increased while reading: {:?}, file will be truncated!",
522 async
fn add_entry
<T
: SeqWrite
+ Send
>(
524 encoder
: &mut Encoder
<'_
, T
>,
528 ) -> Result
<(), Error
> {
529 use pxar
::format
::mode
;
531 let file_mode
= stat
.st_mode
& libc
::S_IFMT
;
532 let open_mode
= if file_mode
== libc
::S_IFREG
|| file_mode
== libc
::S_IFDIR
{
538 let fd
= self.open_file(
541 open_mode
| OFlag
::O_RDONLY
| OFlag
::O_NOFOLLOW
,
547 None
=> return Ok(()),
550 let metadata
= get_metadata(fd
.as_raw_fd(), &stat
, self.flags(), self.fs_magic
, &mut self.fs_feature_flags
)?
;
554 .matches(self.path
.as_os_str().as_bytes(), Some(stat
.st_mode
as u32))
555 == Some(MatchType
::Exclude
)
560 let file_name
: &Path
= OsStr
::from_bytes(c_file_name
.to_bytes()).as_ref();
561 match metadata
.file_type() {
563 let link_info
= HardLinkInfo
{
568 if stat
.st_nlink
> 1 {
569 if let Some((path
, offset
)) = self.hardlinks
.get(&link_info
) {
570 if let Some(ref catalog
) = self.catalog
{
571 catalog
.lock().unwrap().add_hardlink(c_file_name
)?
;
574 encoder
.add_hardlink(file_name
, path
, *offset
).await?
;
580 let file_size
= stat
.st_size
as u64;
581 if let Some(ref catalog
) = self.catalog
{
582 catalog
.lock().unwrap().add_file(c_file_name
, file_size
, stat
.st_mtime
)?
;
585 let offset
: LinkOffset
=
586 self.add_regular_file(encoder
, fd
, file_name
, &metadata
, file_size
).await?
;
588 if stat
.st_nlink
> 1 {
589 self.hardlinks
.insert(link_info
, (self.path
.clone(), offset
));
595 let dir
= Dir
::from_fd(fd
.into_raw_fd())?
;
597 if let Some(ref catalog
) = self.catalog
{
598 catalog
.lock().unwrap().start_directory(c_file_name
)?
;
600 let result
= self.add_directory(encoder
, dir
, c_file_name
, &metadata
, stat
).await
;
601 if let Some(ref catalog
) = self.catalog
{
602 catalog
.lock().unwrap().end_directory()?
;
607 if let Some(ref catalog
) = self.catalog
{
608 catalog
.lock().unwrap().add_socket(c_file_name
)?
;
611 Ok(encoder
.add_socket(&metadata
, file_name
).await?
)
614 if let Some(ref catalog
) = self.catalog
{
615 catalog
.lock().unwrap().add_fifo(c_file_name
)?
;
618 Ok(encoder
.add_fifo(&metadata
, file_name
).await?
)
621 if let Some(ref catalog
) = self.catalog
{
622 catalog
.lock().unwrap().add_symlink(c_file_name
)?
;
625 self.add_symlink(encoder
, fd
, file_name
, &metadata
).await
628 if let Some(ref catalog
) = self.catalog
{
629 catalog
.lock().unwrap().add_block_device(c_file_name
)?
;
632 self.add_device(encoder
, file_name
, &metadata
, &stat
).await
635 if let Some(ref catalog
) = self.catalog
{
636 catalog
.lock().unwrap().add_char_device(c_file_name
)?
;
639 self.add_device(encoder
, file_name
, &metadata
, &stat
).await
642 "encountered unknown file type: 0x{:x} (0o{:o})",
649 async
fn add_directory
<T
: SeqWrite
+ Send
>(
651 encoder
: &mut Encoder
<'_
, T
>,
656 ) -> Result
<(), Error
> {
657 let dir_name
= OsStr
::from_bytes(dir_name
.to_bytes());
659 let mut encoder
= encoder
.create_directory(dir_name
, &metadata
).await?
;
661 let old_fs_magic
= self.fs_magic
;
662 let old_fs_feature_flags
= self.fs_feature_flags
;
663 let old_st_dev
= self.current_st_dev
;
665 let mut skip_contents
= false;
666 if old_st_dev
!= stat
.st_dev
{
667 self.fs_magic
= detect_fs_type(dir
.as_raw_fd())?
;
668 self.fs_feature_flags
= Flags
::from_magic(self.fs_magic
);
669 self.current_st_dev
= stat
.st_dev
;
671 if is_virtual_file_system(self.fs_magic
) {
672 skip_contents
= true;
673 } else if let Some(set
) = &self.device_set
{
674 skip_contents
= !set
.contains(&stat
.st_dev
);
678 let result
= if skip_contents
{
679 writeln
!(self.logger
, "skipping mount point: {:?}", self.path
)?
;
682 self.archive_dir_contents(&mut encoder
, dir
, false).await
685 self.fs_magic
= old_fs_magic
;
686 self.fs_feature_flags
= old_fs_feature_flags
;
687 self.current_st_dev
= old_st_dev
;
689 encoder
.finish().await?
;
693 async
fn add_regular_file
<T
: SeqWrite
+ Send
>(
695 encoder
: &mut Encoder
<'_
, T
>,
700 ) -> Result
<LinkOffset
, Error
> {
701 let mut file
= unsafe { std::fs::File::from_raw_fd(fd.into_raw_fd()) }
;
702 let mut remaining
= file_size
;
703 let mut out
= encoder
.create_file(metadata
, file_name
, file_size
).await?
;
704 while remaining
!= 0 {
705 let mut got
= match file
.read(&mut self.file_copy_buffer
[..]) {
708 Err(err
) if err
.kind() == std
::io
::ErrorKind
::Interrupted
=> continue,
709 Err(err
) => bail
!(err
),
711 if got
as u64 > remaining
{
712 self.report_file_grew_while_reading()?
;
713 got
= remaining
as usize;
715 out
.write_all(&self.file_copy_buffer
[..got
]).await?
;
716 remaining
-= got
as u64;
719 self.report_file_shrunk_while_reading()?
;
720 let to_zero
= remaining
.min(self.file_copy_buffer
.len() as u64) as usize;
721 vec
::clear(&mut self.file_copy_buffer
[..to_zero
]);
722 while remaining
!= 0 {
723 let fill
= remaining
.min(self.file_copy_buffer
.len() as u64) as usize;
724 out
.write_all(&self.file_copy_buffer
[..fill
]).await?
;
725 remaining
-= fill
as u64;
729 Ok(out
.file_offset())
732 async
fn add_symlink
<T
: SeqWrite
+ Send
>(
734 encoder
: &mut Encoder
<'_
, T
>,
738 ) -> Result
<(), Error
> {
739 let dest
= nix
::fcntl
::readlinkat(fd
.as_raw_fd(), &b
""[..])?
;
740 encoder
.add_symlink(metadata
, file_name
, dest
).await?
;
744 async
fn add_device
<T
: SeqWrite
+ Send
>(
746 encoder
: &mut Encoder
<'_
, T
>,
750 ) -> Result
<(), Error
> {
751 Ok(encoder
.add_device(
754 pxar
::format
::Device
::from_dev_t(stat
.st_rdev
),
759 fn get_metadata(fd
: RawFd
, stat
: &FileStat
, flags
: Flags
, fs_magic
: i64, fs_feature_flags
: &mut Flags
) -> Result
<Metadata
, Error
> {
760 // required for some of these
761 let proc_path
= Path
::new("/proc/self/fd/").join(fd
.to_string());
763 let mut meta
= Metadata
{
765 mode
: u64::from(stat
.st_mode
),
769 mtime
: pxar
::format
::StatxTimestamp
::new(stat
.st_mtime
, stat
.st_mtime_nsec
as u32),
774 get_xattr_fcaps_acl(&mut meta
, fd
, &proc_path
, flags
, fs_feature_flags
)?
;
775 get_chattr(&mut meta
, fd
)?
;
776 get_fat_attr(&mut meta
, fd
, fs_magic
)?
;
777 get_quota_project_id(&mut meta
, fd
, flags
, fs_magic
)?
;
781 fn get_fcaps(meta
: &mut Metadata
, fd
: RawFd
, flags
: Flags
, fs_feature_flags
: &mut Flags
) -> Result
<(), Error
> {
782 if !flags
.contains(Flags
::WITH_FCAPS
) {
786 match xattr
::fgetxattr(fd
, xattr
::xattr_name_fcaps()) {
788 meta
.fcaps
= Some(pxar
::format
::FCaps { data }
);
791 Err(Errno
::ENODATA
) => Ok(()),
792 Err(Errno
::EOPNOTSUPP
) => {
793 fs_feature_flags
.remove(Flags
::WITH_FCAPS
);
796 Err(Errno
::EBADF
) => Ok(()), // symlinks
797 Err(err
) => bail
!("failed to read file capabilities: {}", err
),
801 fn get_xattr_fcaps_acl(
806 fs_feature_flags
: &mut Flags
,
807 ) -> Result
<(), Error
> {
808 if !flags
.contains(Flags
::WITH_XATTRS
) {
812 let xattrs
= match xattr
::flistxattr(fd
) {
814 Err(Errno
::EOPNOTSUPP
) => {
815 fs_feature_flags
.remove(Flags
::WITH_XATTRS
);
818 Err(Errno
::EBADF
) => return Ok(()), // symlinks
819 Err(err
) => bail
!("failed to read xattrs: {}", err
),
822 for attr
in &xattrs
{
823 if xattr
::is_security_capability(&attr
) {
824 get_fcaps(meta
, fd
, flags
, fs_feature_flags
)?
;
828 if xattr
::is_acl(&attr
) {
829 get_acl(meta
, proc_path
, flags
, fs_feature_flags
)?
;
833 if !xattr
::is_valid_xattr_name(&attr
) {
837 match xattr
::fgetxattr(fd
, attr
) {
840 .push(pxar
::format
::XAttr
::new(attr
.to_bytes(), data
)),
841 Err(Errno
::ENODATA
) => (), // it got removed while we were iterating...
842 Err(Errno
::EOPNOTSUPP
) => (), // shouldn't be possible so just ignore this
843 Err(Errno
::EBADF
) => (), // symlinks, shouldn't be able to reach this either
844 Err(err
) => bail
!("error reading extended attribute {:?}: {}", attr
, err
),
851 fn get_chattr(metadata
: &mut Metadata
, fd
: RawFd
) -> Result
<(), Error
> {
852 let mut attr
: libc
::c_long
= 0;
854 match unsafe { fs::read_attr_fd(fd, &mut attr) }
{
856 Err(nix
::Error
::Sys(errno
)) if errno_is_unsupported(errno
) => {
859 Err(err
) => bail
!("failed to read file attributes: {}", err
),
862 metadata
.stat
.flags
|= Flags
::from_chattr(attr
).bits();
867 fn get_fat_attr(metadata
: &mut Metadata
, fd
: RawFd
, fs_magic
: i64) -> Result
<(), Error
> {
868 use proxmox_sys
::linux
::magic
::*;
870 if fs_magic
!= MSDOS_SUPER_MAGIC
&& fs_magic
!= FUSE_SUPER_MAGIC
{
874 let mut attr
: u32 = 0;
876 match unsafe { fs::read_fat_attr_fd(fd, &mut attr) }
{
878 Err(nix
::Error
::Sys(errno
)) if errno_is_unsupported(errno
) => {
881 Err(err
) => bail
!("failed to read fat attributes: {}", err
),
884 metadata
.stat
.flags
|= Flags
::from_fat_attr(attr
).bits();
889 /// Read the quota project id for an inode, supported on ext4/XFS/FUSE/ZFS filesystems
890 fn get_quota_project_id(
891 metadata
: &mut Metadata
,
895 ) -> Result
<(), Error
> {
896 if !(metadata
.is_dir() || metadata
.is_regular_file()) {
900 if !flags
.contains(Flags
::WITH_QUOTA_PROJID
) {
904 use proxmox_sys
::linux
::magic
::*;
907 EXT4_SUPER_MAGIC
| XFS_SUPER_MAGIC
| FUSE_SUPER_MAGIC
| ZFS_SUPER_MAGIC
=> (),
911 let mut fsxattr
= fs
::FSXAttr
::default();
912 let res
= unsafe { fs::fs_ioc_fsgetxattr(fd, &mut fsxattr) }
;
914 // On some FUSE filesystems it can happen that ioctl is not supported.
915 // For these cases projid is set to 0 while the error is ignored.
916 if let Err(err
) = res
{
919 .ok_or_else(|| format_err
!("error while reading quota project id"))?
;
920 if errno_is_unsupported(errno
) {
923 bail
!("error while reading quota project id ({})", errno
);
927 let projid
= fsxattr
.fsx_projid
as u64;
929 metadata
.quota_project_id
= Some(pxar
::format
::QuotaProjectId { projid }
);
934 fn get_acl(metadata
: &mut Metadata
, proc_path
: &Path
, flags
: Flags
, fs_feature_flags
: &mut Flags
) -> Result
<(), Error
> {
935 if !flags
.contains(Flags
::WITH_ACL
) {
939 if metadata
.is_symlink() {
943 get_acl_do(metadata
, proc_path
, acl
::ACL_TYPE_ACCESS
, fs_feature_flags
)?
;
945 if metadata
.is_dir() {
946 get_acl_do(metadata
, proc_path
, acl
::ACL_TYPE_DEFAULT
, fs_feature_flags
)?
;
953 metadata
: &mut Metadata
,
955 acl_type
: acl
::ACLType
,
956 fs_feature_flags
: &mut Flags
,
957 ) -> Result
<(), Error
> {
958 // In order to be able to get ACLs with type ACL_TYPE_DEFAULT, we have
959 // to create a path for acl_get_file(). acl_get_fd() only allows to get
960 // ACL_TYPE_ACCESS attributes.
961 let acl
= match acl
::ACL
::get_file(&proc_path
, acl_type
) {
963 // Don't bail if underlying endpoint does not support acls
964 Err(Errno
::EOPNOTSUPP
) => {
965 fs_feature_flags
.remove(Flags
::WITH_ACL
);
968 // Don't bail if the endpoint cannot carry acls
969 Err(Errno
::EBADF
) => return Ok(()),
970 // Don't bail if there is no data
971 Err(Errno
::ENODATA
) => return Ok(()),
972 Err(err
) => bail
!("error while reading ACL - {}", err
),
975 process_acl(metadata
, acl
, acl_type
)
979 metadata
: &mut Metadata
,
981 acl_type
: acl
::ACLType
,
982 ) -> Result
<(), Error
> {
983 use pxar
::format
::acl
as pxar_acl
;
984 use pxar
::format
::acl
::{Group, GroupObject, Permissions, User}
;
986 let mut acl_user
= Vec
::new();
987 let mut acl_group
= Vec
::new();
988 let mut acl_group_obj
= None
;
989 let mut acl_default
= None
;
990 let mut user_obj_permissions
= None
;
991 let mut group_obj_permissions
= None
;
992 let mut other_permissions
= None
;
993 let mut mask_permissions
= None
;
995 for entry
in &mut acl
.entries() {
996 let tag
= entry
.get_tag_type()?
;
997 let permissions
= entry
.get_permissions()?
;
999 acl
::ACL_USER_OBJ
=> user_obj_permissions
= Some(Permissions(permissions
)),
1000 acl
::ACL_GROUP_OBJ
=> group_obj_permissions
= Some(Permissions(permissions
)),
1001 acl
::ACL_OTHER
=> other_permissions
= Some(Permissions(permissions
)),
1002 acl
::ACL_MASK
=> mask_permissions
= Some(Permissions(permissions
)),
1004 acl_user
.push(User
{
1005 uid
: entry
.get_qualifier()?
,
1006 permissions
: Permissions(permissions
),
1010 acl_group
.push(Group
{
1011 gid
: entry
.get_qualifier()?
,
1012 permissions
: Permissions(permissions
),
1015 _
=> bail
!("Unexpected ACL tag encountered!"),
1023 acl
::ACL_TYPE_ACCESS
=> {
1024 // The mask permissions are mapped to the stat group permissions
1025 // in case that the ACL group permissions were set.
1026 // Only in that case we need to store the group permissions,
1027 // in the other cases they are identical to the stat group permissions.
1028 if let (Some(gop
), true) = (group_obj_permissions
, mask_permissions
.is_some()) {
1029 acl_group_obj
= Some(GroupObject { permissions: gop }
);
1032 metadata
.acl
.users
= acl_user
;
1033 metadata
.acl
.groups
= acl_group
;
1034 metadata
.acl
.group_obj
= acl_group_obj
;
1036 acl
::ACL_TYPE_DEFAULT
=> {
1037 if user_obj_permissions
!= None
1038 || group_obj_permissions
!= None
1039 || other_permissions
!= None
1040 || mask_permissions
!= None
1042 acl_default
= Some(pxar_acl
::Default
{
1043 // The value is set to UINT64_MAX as placeholder if one
1044 // of the permissions is not set
1045 user_obj_permissions
: user_obj_permissions
.unwrap_or(Permissions
::NO_MASK
),
1046 group_obj_permissions
: group_obj_permissions
.unwrap_or(Permissions
::NO_MASK
),
1047 other_permissions
: other_permissions
.unwrap_or(Permissions
::NO_MASK
),
1048 mask_permissions
: mask_permissions
.unwrap_or(Permissions
::NO_MASK
),
1052 metadata
.acl
.default_users
= acl_user
;
1053 metadata
.acl
.default_groups
= acl_group
;
1054 metadata
.acl
.default = acl_default
;
1056 _
=> bail
!("Unexpected ACL type encountered"),
1062 /// Note that our pattern lists are "positive". `MatchType::Include` means the file is included.
1063 /// Since we are generating an *exclude* list, we need to invert this, so includes get a `'!'`
1065 fn generate_pxar_excludes_cli(patterns
: &[MatchEntry
]) -> Vec
<u8> {
1066 use pathpatterns
::MatchPattern
;
1068 let mut content
= Vec
::new();
1070 for pattern
in patterns
{
1071 match pattern
.match_type() {
1072 MatchType
::Include
=> content
.push(b'
!'
),
1073 MatchType
::Exclude
=> (),
1076 match pattern
.pattern() {
1077 MatchPattern
::Literal(lit
) => content
.extend(lit
),
1078 MatchPattern
::Pattern(pat
) => content
.extend(pat
.pattern().to_bytes()),
1081 if pattern
.match_flags() == MatchFlag
::MATCH_DIRECTORIES
&& content
.last() != Some(&b'
/'
) {
1085 content
.push(b'
\n'
);