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