]> git.proxmox.com Git - proxmox-backup.git/blob - src/pxar/metadata.rs
add pbs-tools subcrate
[proxmox-backup.git] / src / pxar / metadata.rs
1 use std::ffi::{CStr, CString};
2 use std::os::unix::io::{AsRawFd, FromRawFd, RawFd};
3 use std::path::Path;
4
5 use anyhow::{bail, format_err, Error};
6 use nix::errno::Errno;
7 use nix::fcntl::OFlag;
8 use nix::sys::stat::Mode;
9
10 use pxar::Metadata;
11
12 use proxmox::c_result;
13 use proxmox::sys::error::SysError;
14 use proxmox::tools::fd::RawFdNum;
15
16 use pbs_tools::fs;
17
18 use crate::pxar::tools::perms_from_metadata;
19 use crate::pxar::Flags;
20 use crate::tools::{acl, xattr};
21
22 //
23 // utility functions
24 //
25
26 fn allow_notsupp<E: SysError>(err: E) -> Result<(), E> {
27 if err.is_errno(Errno::EOPNOTSUPP) {
28 Ok(())
29 } else {
30 Err(err)
31 }
32 }
33
34 fn allow_notsupp_remember<E: SysError>(err: E, not_supp: &mut bool) -> Result<(), E> {
35 if err.is_errno(Errno::EOPNOTSUPP) {
36 *not_supp = true;
37 Ok(())
38 } else {
39 Err(err)
40 }
41 }
42
43 fn timestamp_to_update_timespec(mtime: &pxar::format::StatxTimestamp) -> [libc::timespec; 2] {
44 // restore mtime
45 const UTIME_OMIT: i64 = (1 << 30) - 2;
46
47 [
48 libc::timespec {
49 tv_sec: 0,
50 tv_nsec: UTIME_OMIT,
51 },
52 libc::timespec {
53 tv_sec: mtime.secs,
54 tv_nsec: mtime.nanos as _,
55 },
56 ]
57 }
58
59 //
60 // metadata application:
61 //
62
63 pub fn apply_at(
64 flags: Flags,
65 metadata: &Metadata,
66 parent: RawFd,
67 file_name: &CStr,
68 path_info: &Path,
69 on_error: &mut (dyn FnMut(Error) -> Result<(), Error> + Send),
70 ) -> Result<(), Error> {
71 let fd = proxmox::tools::fd::Fd::openat(
72 &unsafe { RawFdNum::from_raw_fd(parent) },
73 file_name,
74 OFlag::O_PATH | OFlag::O_CLOEXEC | OFlag::O_NOFOLLOW,
75 Mode::empty(),
76 )?;
77
78 apply(flags, metadata, fd.as_raw_fd(), path_info, on_error)
79 }
80
81 pub fn apply_initial_flags(
82 flags: Flags,
83 metadata: &Metadata,
84 fd: RawFd,
85 on_error: &mut (dyn FnMut(Error) -> Result<(), Error> + Send),
86 ) -> Result<(), Error> {
87 let entry_flags = Flags::from_bits_truncate(metadata.stat.flags);
88 apply_chattr(
89 fd,
90 entry_flags.to_initial_chattr(),
91 flags.to_initial_chattr(),
92 )
93 .or_else(on_error)?;
94 Ok(())
95 }
96
97 pub fn apply(
98 flags: Flags,
99 metadata: &Metadata,
100 fd: RawFd,
101 path_info: &Path,
102 on_error: &mut (dyn FnMut(Error) -> Result<(), Error> + Send),
103 ) -> Result<(), Error> {
104 let c_proc_path = CString::new(format!("/proc/self/fd/{}", fd)).unwrap();
105
106 unsafe {
107 // UID and GID first, as this fails if we lose access anyway.
108 c_result!(libc::chown(
109 c_proc_path.as_ptr(),
110 metadata.stat.uid,
111 metadata.stat.gid
112 ))
113 .map(drop)
114 .or_else(allow_notsupp)
115 .map_err(|err| format_err!("failed to set ownership: {}", err))
116 .or_else(&mut *on_error)?;
117 }
118
119 let mut skip_xattrs = false;
120 apply_xattrs(flags, c_proc_path.as_ptr(), metadata, &mut skip_xattrs)
121 .or_else(&mut *on_error)?;
122 add_fcaps(flags, c_proc_path.as_ptr(), metadata, &mut skip_xattrs).or_else(&mut *on_error)?;
123 apply_acls(flags, &c_proc_path, metadata, path_info)
124 .map_err(|err| format_err!("failed to apply acls: {}", err))
125 .or_else(&mut *on_error)?;
126 apply_quota_project_id(flags, fd, metadata).or_else(&mut *on_error)?;
127
128 // Finally mode and time. We may lose access with mode, but the changing the mode also
129 // affects times.
130 if !metadata.is_symlink() {
131 c_result!(unsafe {
132 libc::chmod(c_proc_path.as_ptr(), perms_from_metadata(metadata)?.bits())
133 })
134 .map(drop)
135 .or_else(allow_notsupp)
136 .map_err(|err| format_err!("failed to change file mode: {}", err))
137 .or_else(&mut *on_error)?;
138 }
139
140 let res = c_result!(unsafe {
141 libc::utimensat(
142 libc::AT_FDCWD,
143 c_proc_path.as_ptr(),
144 timestamp_to_update_timespec(&metadata.stat.mtime).as_ptr(),
145 0,
146 )
147 });
148 match res {
149 Ok(_) => (),
150 Err(ref err) if err.is_errno(Errno::EOPNOTSUPP) => (),
151 Err(err) => {
152 on_error(format_err!(
153 "failed to restore mtime attribute on {:?}: {}",
154 path_info,
155 err
156 ))?;
157 }
158 }
159
160 if metadata.stat.flags != 0 {
161 apply_flags(flags, fd, metadata.stat.flags).or_else(&mut *on_error)?;
162 }
163
164 Ok(())
165 }
166
167 fn add_fcaps(
168 flags: Flags,
169 c_proc_path: *const libc::c_char,
170 metadata: &Metadata,
171 skip_xattrs: &mut bool,
172 ) -> Result<(), Error> {
173 if *skip_xattrs || !flags.contains(Flags::WITH_FCAPS) {
174 return Ok(());
175 }
176 let fcaps = match metadata.fcaps.as_ref() {
177 Some(fcaps) => fcaps,
178 None => return Ok(()),
179 };
180
181 c_result!(unsafe {
182 libc::setxattr(
183 c_proc_path,
184 xattr::xattr_name_fcaps().as_ptr(),
185 fcaps.data.as_ptr() as *const libc::c_void,
186 fcaps.data.len(),
187 0,
188 )
189 })
190 .map(drop)
191 .or_else(|err| allow_notsupp_remember(err, skip_xattrs))
192 .map_err(|err| format_err!("failed to apply file capabilities: {}", err))?;
193
194 Ok(())
195 }
196
197 fn apply_xattrs(
198 flags: Flags,
199 c_proc_path: *const libc::c_char,
200 metadata: &Metadata,
201 skip_xattrs: &mut bool,
202 ) -> Result<(), Error> {
203 if *skip_xattrs || !flags.contains(Flags::WITH_XATTRS) {
204 return Ok(());
205 }
206
207 for xattr in &metadata.xattrs {
208 if *skip_xattrs {
209 return Ok(());
210 }
211
212 if !xattr::is_valid_xattr_name(xattr.name()) {
213 eprintln!("skipping invalid xattr named {:?}", xattr.name());
214 continue;
215 }
216
217 c_result!(unsafe {
218 libc::setxattr(
219 c_proc_path,
220 xattr.name().as_ptr() as *const libc::c_char,
221 xattr.value().as_ptr() as *const libc::c_void,
222 xattr.value().len(),
223 0,
224 )
225 })
226 .map(drop)
227 .or_else(|err| allow_notsupp_remember(err, &mut *skip_xattrs))
228 .map_err(|err| format_err!("failed to apply extended attributes: {}", err))?;
229 }
230
231 Ok(())
232 }
233
234 fn apply_acls(
235 flags: Flags,
236 c_proc_path: &CStr,
237 metadata: &Metadata,
238 path_info: &Path,
239 ) -> Result<(), Error> {
240 if !flags.contains(Flags::WITH_ACL) || metadata.acl.is_empty() {
241 return Ok(());
242 }
243
244 let mut acl = acl::ACL::init(5)?;
245
246 // acl type access:
247 acl.add_entry_full(
248 acl::ACL_USER_OBJ,
249 None,
250 acl::mode_user_to_acl_permissions(metadata.stat.mode),
251 )?;
252
253 acl.add_entry_full(
254 acl::ACL_OTHER,
255 None,
256 acl::mode_other_to_acl_permissions(metadata.stat.mode),
257 )?;
258
259 match metadata.acl.group_obj.as_ref() {
260 Some(group_obj) => {
261 acl.add_entry_full(
262 acl::ACL_MASK,
263 None,
264 acl::mode_group_to_acl_permissions(metadata.stat.mode),
265 )?;
266 acl.add_entry_full(acl::ACL_GROUP_OBJ, None, group_obj.permissions.0)?;
267 }
268 None => {
269 let mode = acl::mode_group_to_acl_permissions(metadata.stat.mode);
270
271 acl.add_entry_full(acl::ACL_GROUP_OBJ, None, mode)?;
272
273 if !metadata.acl.users.is_empty() || !metadata.acl.groups.is_empty() {
274 eprintln!(
275 "Warning: {:?}: Missing GROUP_OBJ entry in ACL, resetting to value of MASK",
276 path_info,
277 );
278 acl.add_entry_full(acl::ACL_MASK, None, mode)?;
279 }
280 }
281 }
282
283 for user in &metadata.acl.users {
284 acl.add_entry_full(acl::ACL_USER, Some(user.uid), user.permissions.0)?;
285 }
286
287 for group in &metadata.acl.groups {
288 acl.add_entry_full(acl::ACL_GROUP, Some(group.gid), group.permissions.0)?;
289 }
290
291 if !acl.is_valid() {
292 bail!("Error while restoring ACL - ACL invalid");
293 }
294
295 acl.set_file(c_proc_path, acl::ACL_TYPE_ACCESS)?;
296 drop(acl);
297
298 // acl type default:
299 if let Some(default) = metadata.acl.default.as_ref() {
300 let mut acl = acl::ACL::init(5)?;
301
302 acl.add_entry_full(acl::ACL_USER_OBJ, None, default.user_obj_permissions.0)?;
303
304 acl.add_entry_full(acl::ACL_GROUP_OBJ, None, default.group_obj_permissions.0)?;
305
306 acl.add_entry_full(acl::ACL_OTHER, None, default.other_permissions.0)?;
307
308 if default.mask_permissions != pxar::format::acl::Permissions::NO_MASK {
309 acl.add_entry_full(acl::ACL_MASK, None, default.mask_permissions.0)?;
310 }
311
312 for user in &metadata.acl.default_users {
313 acl.add_entry_full(acl::ACL_USER, Some(user.uid), user.permissions.0)?;
314 }
315
316 for group in &metadata.acl.default_groups {
317 acl.add_entry_full(acl::ACL_GROUP, Some(group.gid), group.permissions.0)?;
318 }
319
320 if !acl.is_valid() {
321 bail!("Error while restoring ACL - ACL invalid");
322 }
323
324 acl.set_file(c_proc_path, acl::ACL_TYPE_DEFAULT)?;
325 }
326
327 Ok(())
328 }
329
330 fn apply_quota_project_id(flags: Flags, fd: RawFd, metadata: &Metadata) -> Result<(), Error> {
331 if !flags.contains(Flags::WITH_QUOTA_PROJID) {
332 return Ok(());
333 }
334
335 let projid = match metadata.quota_project_id {
336 Some(projid) => projid,
337 None => return Ok(()),
338 };
339
340 let mut fsxattr = fs::FSXAttr::default();
341 unsafe {
342 fs::fs_ioc_fsgetxattr(fd, &mut fsxattr).map_err(|err| {
343 format_err!(
344 "error while getting fsxattr to restore quota project id - {}",
345 err
346 )
347 })?;
348
349 fsxattr.fsx_projid = projid.projid as u32;
350
351 fs::fs_ioc_fssetxattr(fd, &fsxattr).map_err(|err| {
352 format_err!(
353 "error while setting fsxattr to restore quota project id - {}",
354 err
355 )
356 })?;
357 }
358
359 Ok(())
360 }
361
362 pub(crate) fn errno_is_unsupported(errno: Errno) -> bool {
363 matches!(errno, Errno::ENOTTY | Errno::ENOSYS | Errno::EBADF | Errno::EOPNOTSUPP | Errno::EINVAL)
364 }
365
366 fn apply_chattr(fd: RawFd, chattr: libc::c_long, mask: libc::c_long) -> Result<(), Error> {
367 if chattr == 0 {
368 return Ok(());
369 }
370
371 let mut fattr: libc::c_long = 0;
372 match unsafe { fs::read_attr_fd(fd, &mut fattr) } {
373 Ok(_) => (),
374 Err(nix::Error::Sys(errno)) if errno_is_unsupported(errno) => {
375 return Ok(());
376 }
377 Err(err) => bail!("failed to read file attributes: {}", err),
378 }
379
380 let attr = (chattr & mask) | (fattr & !mask);
381
382 if attr == fattr {
383 return Ok(());
384 }
385
386 match unsafe { fs::write_attr_fd(fd, &attr) } {
387 Ok(_) => Ok(()),
388 Err(nix::Error::Sys(errno)) if errno_is_unsupported(errno) => Ok(()),
389 Err(err) => bail!("failed to set file attributes: {}", err),
390 }
391 }
392
393 fn apply_flags(flags: Flags, fd: RawFd, entry_flags: u64) -> Result<(), Error> {
394 let entry_flags = Flags::from_bits_truncate(entry_flags);
395
396 apply_chattr(fd, entry_flags.to_chattr(), flags.to_chattr())?;
397
398 let fatattr = (flags & entry_flags).to_fat_attr();
399 if fatattr != 0 {
400 match unsafe { fs::write_fat_attr_fd(fd, &fatattr) } {
401 Ok(_) => (),
402 Err(nix::Error::Sys(errno)) if errno_is_unsupported(errno) => (),
403 Err(err) => bail!("failed to set file FAT attributes: {}", err),
404 }
405 }
406
407 Ok(())
408 }