]>
Commit | Line | Data |
---|---|---|
c443f58b WB |
1 | use std::collections::{HashSet, HashMap}; |
2 | use std::convert::TryFrom; | |
3 | use std::ffi::{CStr, CString, OsStr}; | |
4 | use std::fmt; | |
5 | use std::os::unix::ffi::OsStrExt; | |
6 | use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd}; | |
7 | use std::path::{Path, PathBuf}; | |
8 | ||
9 | use anyhow::{bail, format_err, Error}; | |
10 | use nix::dir::Dir; | |
11 | use nix::errno::Errno; | |
12 | use nix::fcntl::OFlag; | |
13 | use nix::sys::stat::{FileStat, Mode}; | |
14 | ||
15 | use pathpatterns::{MatchEntry, MatchList, MatchType, PatternFlag}; | |
16 | use pxar::Metadata; | |
17 | use pxar::encoder::LinkOffset; | |
18 | ||
19 | use proxmox::sys::error::SysError; | |
20 | use proxmox::tools::fd::RawFdNum; | |
21 | ||
22 | use crate::pxar::catalog::BackupCatalogWriter; | |
23 | use crate::pxar::flags; | |
7eacdc76 | 24 | use crate::pxar::tools::assert_single_path_component; |
c443f58b WB |
25 | use crate::tools::{acl, fs, xattr, Fd}; |
26 | ||
27 | fn 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 | ||
36 | pub 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)] | |
63 | struct ArchiveError { | |
64 | path: PathBuf, | |
65 | error: Error, | |
66 | } | |
67 | ||
68 | impl ArchiveError { | |
69 | fn new(path: PathBuf, error: Error) -> Self { | |
70 | Self { path, error } | |
71 | } | |
72 | } | |
73 | ||
74 | impl std::error::Error for ArchiveError {} | |
75 | ||
76 | impl 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)] | |
83 | struct HardLinkInfo { | |
84 | st_dev: u64, | |
85 | st_ino: u64, | |
86 | } | |
87 | ||
88 | struct 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 | ||
104 | type Encoder<'a, 'b> = pxar::encoder::Encoder<'a, &'b mut dyn pxar::encoder::SeqWrite>; | |
105 | ||
106 | pub 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> | |
117 | where | |
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 | ||
184 | struct FileListEntry { | |
185 | name: CString, | |
186 | path: PathBuf, | |
187 | stat: FileStat, | |
188 | } | |
189 | ||
190 | impl<'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 | ||
478 | fn 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 | ||
503 | fn 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 | ||
510 | fn 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 | ||
527 | fn 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 | ||
573 | fn 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 | ||
589 | fn 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 | |
612 | fn 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 | ||
656 | fn 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 | ||
674 | fn 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 | ||
696 | fn 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. | |
784 | fn 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 | } |