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