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