]> git.proxmox.com Git - proxmox-backup.git/blob - src/pxar/tools.rs
switch to external pxar and fuse crates
[proxmox-backup.git] / 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};
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 fn assert_relative_path_do(path: &Path) -> Result<(), Error> {
28 if !path.is_relative() {
29 bail!("bad absolute file name in archive: {:?}", path);
30 }
31
32 let mut components = path.components();
33 match components.next() {
34 Some(std::path::Component::Normal(_)) => (),
35 _ => bail!("invalid path component in archive: {:?}", path),
36 }
37
38 if components.next().is_some() {
39 bail!(
40 "invalid path with multiple components in archive: {:?}",
41 path
42 );
43 }
44
45 Ok(())
46 }
47
48 #[rustfmt::skip]
49 fn symbolic_mode(c: u64, special: bool, special_x: u8, special_no_x: u8) -> [u8; 3] {
50 [
51 if 0 != c & 4 { b'r' } else { b'-' },
52 if 0 != c & 2 { b'w' } else { b'-' },
53 match (c & 1, special) {
54 (0, false) => b'-',
55 (0, true) => special_no_x,
56 (_, false) => b'x',
57 (_, true) => special_x,
58 }
59 ]
60 }
61
62 fn mode_string(entry: &Entry) -> String {
63 // https://www.gnu.org/software/coreutils/manual/html_node/What-information-is-listed.html#What-information-is-listed
64 // additionally we use:
65 // file type capital 'L' hard links
66 // a second '+' after the mode to show non-acl xattr presence
67 //
68 // Trwxrwxrwx++ uid/gid size mtime filename [-> destination]
69
70 let meta = entry.metadata();
71 let mode = meta.stat.mode;
72 let type_char = if entry.is_hardlink() {
73 'L'
74 } else {
75 match mode & mode::IFMT {
76 mode::IFREG => '-',
77 mode::IFBLK => 'b',
78 mode::IFCHR => 'c',
79 mode::IFDIR => 'd',
80 mode::IFLNK => 'l',
81 mode::IFIFO => 'p',
82 mode::IFSOCK => 's',
83 _ => '?',
84 }
85 };
86
87 let fmt_u = symbolic_mode((mode >> 6) & 7, 0 != mode & mode::ISUID, b's', b'S');
88 let fmt_g = symbolic_mode((mode >> 3) & 7, 0 != mode & mode::ISGID, b's', b'S');
89 let fmt_o = symbolic_mode((mode >> 3) & 7, 0 != mode & mode::ISVTX, b't', b'T');
90
91 let has_acls = if meta.acl.is_empty() { ' ' } else { '+' };
92
93 let has_xattrs = if meta.xattrs.is_empty() { ' ' } else { '+' };
94
95 format!(
96 "{}{}{}{}{}{}",
97 type_char,
98 unsafe { std::str::from_utf8_unchecked(&fmt_u) },
99 unsafe { std::str::from_utf8_unchecked(&fmt_g) },
100 unsafe { std::str::from_utf8_unchecked(&fmt_o) },
101 has_acls,
102 has_xattrs,
103 )
104 }
105
106 pub fn format_single_line_entry(entry: &Entry) -> String {
107 use chrono::offset::TimeZone;
108
109 let mode_string = mode_string(entry);
110
111 let meta = entry.metadata();
112 let mtime = meta.mtime_as_duration();
113 let mtime = chrono::Local.timestamp(mtime.as_secs() as i64, mtime.subsec_nanos());
114
115 let (size, link) = match entry.kind() {
116 EntryKind::File { size, .. } => (format!("{}", *size), String::new()),
117 EntryKind::Symlink(link) => ("0".to_string(), format!(" -> {:?}", link.as_os_str())),
118 EntryKind::Hardlink(link) => ("0".to_string(), format!(" -> {:?}", link.as_os_str())),
119 EntryKind::Device(dev) => (format!("{},{}", dev.major, dev.minor), String::new()),
120 _ => ("0".to_string(), String::new()),
121 };
122
123 format!(
124 "{} {:<13} {} {:>8} {:?}{}",
125 mode_string,
126 format!("{}/{}", meta.stat.uid, meta.stat.gid),
127 mtime.format("%Y-%m-%d %H:%M:%S"),
128 size,
129 entry.path(),
130 link,
131 )
132 }
133
134 pub fn format_multi_line_entry(entry: &Entry) -> String {
135 use chrono::offset::TimeZone;
136
137 let mode_string = mode_string(entry);
138
139 let meta = entry.metadata();
140 let mtime = meta.mtime_as_duration();
141 let mtime = chrono::Local.timestamp(mtime.as_secs() as i64, mtime.subsec_nanos());
142
143 let (size, link, type_name) = match entry.kind() {
144 EntryKind::File { size, .. } => (format!("{}", *size), String::new(), "file"),
145 EntryKind::Symlink(link) => (
146 "0".to_string(),
147 format!(" -> {:?}", link.as_os_str()),
148 "symlink",
149 ),
150 EntryKind::Hardlink(link) => (
151 "0".to_string(),
152 format!(" -> {:?}", link.as_os_str()),
153 "symlink",
154 ),
155 EntryKind::Device(dev) => (
156 format!("{},{}", dev.major, dev.minor),
157 String::new(),
158 if meta.stat.is_chardev() {
159 "characters pecial file"
160 } else if meta.stat.is_blockdev() {
161 "block special file"
162 } else {
163 "device"
164 },
165 ),
166 EntryKind::Socket => ("0".to_string(), String::new(), "socket"),
167 EntryKind::Fifo => ("0".to_string(), String::new(), "fifo"),
168 EntryKind::Directory => ("0".to_string(), String::new(), "directory"),
169 EntryKind::GoodbyeTable => ("0".to_string(), String::new(), "bad entry"),
170 };
171
172 let file_name = match std::str::from_utf8(entry.path().as_os_str().as_bytes()) {
173 Ok(name) => std::borrow::Cow::Borrowed(name),
174 Err(_) => std::borrow::Cow::Owned(format!("{:?}", entry.path())),
175 };
176
177 format!(
178 " File: {}{}\n \
179 Size: {:<13} Type: {}\n\
180 Access: ({:o}/{}) Uid: {:<5} Gid: {:<5}\n\
181 Modify: {}\n",
182 file_name,
183 link,
184 size,
185 type_name,
186 meta.file_mode(),
187 mode_string,
188 meta.stat.uid,
189 meta.stat.gid,
190 mtime.format("%Y-%m-%d %H:%M:%S"),
191 )
192 }