]> git.proxmox.com Git - proxmox-backup.git/blob - pbs-client/src/pxar/tools.rs
tree-wide: bump edition to 2021
[proxmox-backup.git] / pbs-client / src / pxar / tools.rs
1 //! Some common methods used within the pxar code.
2
3 use std::ffi::OsStr;
4 use std::os::unix::ffi::OsStrExt;
5 use std::path::Path;
6
7 use anyhow::{bail, format_err, Error};
8 use nix::sys::stat::Mode;
9
10 use pxar::{format::StatxTimestamp, mode, Entry, EntryKind, Metadata};
11
12 /// Get the file permissions as `nix::Mode`
13 pub fn perms_from_metadata(meta: &Metadata) -> Result<Mode, Error> {
14 let mode = meta.stat.get_permission_bits();
15 u32::try_from(mode)
16 .map_err(drop)
17 .and_then(|mode| Mode::from_bits(mode).ok_or(()))
18 .map_err(|_| format_err!("mode contains illegal bits: 0x{:x} (0o{:o})", mode, mode))
19 }
20
21 /// Make sure path is relative and not '.' or '..'.
22 pub fn assert_relative_path<S: AsRef<OsStr> + ?Sized>(path: &S) -> Result<(), Error> {
23 assert_relative_path_do(Path::new(path))
24 }
25
26 /// Make sure path is a single component and not '.' or '..'.
27 pub fn assert_single_path_component<S: AsRef<OsStr> + ?Sized>(path: &S) -> Result<(), Error> {
28 assert_single_path_component_do(Path::new(path))
29 }
30
31 fn assert_relative_path_do(path: &Path) -> Result<(), Error> {
32 if !path.is_relative() {
33 bail!("bad absolute file name in archive: {:?}", path);
34 }
35
36 Ok(())
37 }
38
39 fn assert_single_path_component_do(path: &Path) -> Result<(), Error> {
40 assert_relative_path_do(path)?;
41
42 let mut components = path.components();
43 match components.next() {
44 Some(std::path::Component::Normal(_)) => (),
45 _ => bail!("invalid path component in archive: {:?}", path),
46 }
47
48 if components.next().is_some() {
49 bail!(
50 "invalid path with multiple components in archive: {:?}",
51 path
52 );
53 }
54
55 Ok(())
56 }
57
58 #[rustfmt::skip]
59 fn symbolic_mode(c: u64, special: bool, special_x: u8, special_no_x: u8) -> [u8; 3] {
60 [
61 if 0 != c & 4 { b'r' } else { b'-' },
62 if 0 != c & 2 { b'w' } else { b'-' },
63 match (c & 1, special) {
64 (0, false) => b'-',
65 (0, true) => special_no_x,
66 (_, false) => b'x',
67 (_, true) => special_x,
68 }
69 ]
70 }
71
72 fn mode_string(entry: &Entry) -> String {
73 // https://www.gnu.org/software/coreutils/manual/html_node/What-information-is-listed.html#What-information-is-listed
74 // additionally we use:
75 // file type capital 'L' hard links
76 // a second '+' after the mode to show non-acl xattr presence
77 //
78 // Trwxrwxrwx++ uid/gid size mtime filename [-> destination]
79
80 let meta = entry.metadata();
81 let mode = meta.stat.mode;
82 let type_char = if entry.is_hardlink() {
83 'L'
84 } else {
85 match mode & mode::IFMT {
86 mode::IFREG => '-',
87 mode::IFBLK => 'b',
88 mode::IFCHR => 'c',
89 mode::IFDIR => 'd',
90 mode::IFLNK => 'l',
91 mode::IFIFO => 'p',
92 mode::IFSOCK => 's',
93 _ => '?',
94 }
95 };
96
97 let fmt_u = symbolic_mode((mode >> 6) & 7, 0 != mode & mode::ISUID, b's', b'S');
98 let fmt_g = symbolic_mode((mode >> 3) & 7, 0 != mode & mode::ISGID, b's', b'S');
99 let fmt_o = symbolic_mode(mode & 7, 0 != mode & mode::ISVTX, b't', b'T');
100
101 let has_acls = if meta.acl.is_empty() { ' ' } else { '+' };
102
103 let has_xattrs = if meta.xattrs.is_empty() { ' ' } else { '+' };
104
105 format!(
106 "{}{}{}{}{}{}",
107 type_char,
108 unsafe { std::str::from_utf8_unchecked(&fmt_u) },
109 unsafe { std::str::from_utf8_unchecked(&fmt_g) },
110 unsafe { std::str::from_utf8_unchecked(&fmt_o) },
111 has_acls,
112 has_xattrs,
113 )
114 }
115
116 fn format_mtime(mtime: &StatxTimestamp) -> String {
117 if let Ok(s) = proxmox_time::strftime_local("%Y-%m-%d %H:%M:%S", mtime.secs) {
118 return s;
119 }
120 format!("{}.{}", mtime.secs, mtime.nanos)
121 }
122
123 pub fn format_single_line_entry(entry: &Entry) -> String {
124 let mode_string = mode_string(entry);
125
126 let meta = entry.metadata();
127
128 let (size, link) = match entry.kind() {
129 EntryKind::File { size, .. } => (format!("{}", *size), String::new()),
130 EntryKind::Symlink(link) => ("0".to_string(), format!(" -> {:?}", link.as_os_str())),
131 EntryKind::Hardlink(link) => ("0".to_string(), format!(" -> {:?}", link.as_os_str())),
132 EntryKind::Device(dev) => (format!("{},{}", dev.major, dev.minor), String::new()),
133 _ => ("0".to_string(), String::new()),
134 };
135
136 let owner_string = format!("{}/{}", meta.stat.uid, meta.stat.gid);
137
138 format!(
139 "{} {:<13} {} {:>8} {:?}{}",
140 mode_string,
141 owner_string,
142 format_mtime(&meta.stat.mtime),
143 size,
144 entry.path(),
145 link,
146 )
147 }
148
149 pub fn format_multi_line_entry(entry: &Entry) -> String {
150 let mode_string = mode_string(entry);
151
152 let meta = entry.metadata();
153
154 let (size, link, type_name) = match entry.kind() {
155 EntryKind::File { size, .. } => (format!("{}", *size), String::new(), "file"),
156 EntryKind::Symlink(link) => (
157 "0".to_string(),
158 format!(" -> {:?}", link.as_os_str()),
159 "symlink",
160 ),
161 EntryKind::Hardlink(link) => (
162 "0".to_string(),
163 format!(" -> {:?}", link.as_os_str()),
164 "symlink",
165 ),
166 EntryKind::Device(dev) => (
167 format!("{},{}", dev.major, dev.minor),
168 String::new(),
169 if meta.stat.is_chardev() {
170 "characters pecial file"
171 } else if meta.stat.is_blockdev() {
172 "block special file"
173 } else {
174 "device"
175 },
176 ),
177 EntryKind::Socket => ("0".to_string(), String::new(), "socket"),
178 EntryKind::Fifo => ("0".to_string(), String::new(), "fifo"),
179 EntryKind::Directory => ("0".to_string(), String::new(), "directory"),
180 EntryKind::GoodbyeTable => ("0".to_string(), String::new(), "bad entry"),
181 };
182
183 let file_name = match std::str::from_utf8(entry.path().as_os_str().as_bytes()) {
184 Ok(name) => std::borrow::Cow::Borrowed(name),
185 Err(_) => std::borrow::Cow::Owned(format!("{:?}", entry.path())),
186 };
187
188 format!(
189 " File: {}{}\n \
190 Size: {:<13} Type: {}\n\
191 Access: ({:o}/{}) Uid: {:<5} Gid: {:<5}\n\
192 Modify: {}\n",
193 file_name,
194 link,
195 size,
196 type_name,
197 meta.file_mode(),
198 mode_string,
199 meta.stat.uid,
200 meta.stat.gid,
201 format_mtime(&meta.stat.mtime),
202 )
203 }