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