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