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