]>
Commit | Line | Data |
---|---|---|
c443f58b | 1 | use std::ffi::{CStr, CString}; |
c443f58b | 2 | use std::os::unix::io::{AsRawFd, FromRawFd, RawFd}; |
c443f58b WB |
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 | ||
4482f3fe | 11 | use proxmox::c_result; |
c443f58b WB |
12 | use proxmox::sys::error::SysError; |
13 | use proxmox::tools::fd::RawFdNum; | |
c443f58b | 14 | |
c443f58b | 15 | use crate::pxar::tools::perms_from_metadata; |
4482f3fe | 16 | use crate::pxar::Flags; |
c443f58b WB |
17 | use crate::tools::{acl, fs, xattr}; |
18 | ||
19 | // | |
20 | // utility functions | |
21 | // | |
22 | ||
c443f58b WB |
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 | ||
f6c6e09a | 40 | fn 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 | ||
60 | pub 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 |
76 | pub 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 | 86 | pub 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 | ||
146 | fn 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 | ||
176 | fn 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 | 213 | fn 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 |
298 | fn 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 | |
330 | pub(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 | ||
337 | fn 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 | ||
359 | fn 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 | } |