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