]> git.proxmox.com Git - proxmox-backup.git/blob - src/pxar/tools.rs
update to pxar 0.3 to support negative timestamps
[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 /// 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 pub fn format_single_line_entry(entry: &Entry) -> String {
118 use chrono::offset::TimeZone;
119
120 let mode_string = mode_string(entry);
121
122 let meta = entry.metadata();
123 let mtime = chrono::Local.timestamp(meta.stat.mtime.secs, meta.stat.mtime.nanos);
124
125 let (size, link) = match entry.kind() {
126 EntryKind::File { size, .. } => (format!("{}", *size), String::new()),
127 EntryKind::Symlink(link) => ("0".to_string(), format!(" -> {:?}", link.as_os_str())),
128 EntryKind::Hardlink(link) => ("0".to_string(), format!(" -> {:?}", link.as_os_str())),
129 EntryKind::Device(dev) => (format!("{},{}", dev.major, dev.minor), String::new()),
130 _ => ("0".to_string(), String::new()),
131 };
132
133 format!(
134 "{} {:<13} {} {:>8} {:?}{}",
135 mode_string,
136 format!("{}/{}", meta.stat.uid, meta.stat.gid),
137 mtime.format("%Y-%m-%d %H:%M:%S"),
138 size,
139 entry.path(),
140 link,
141 )
142 }
143
144 pub fn format_multi_line_entry(entry: &Entry) -> String {
145 use chrono::offset::TimeZone;
146
147 let mode_string = mode_string(entry);
148
149 let meta = entry.metadata();
150 let mtime = chrono::Local.timestamp(meta.stat.mtime.secs, meta.stat.mtime.nanos);
151
152 let (size, link, type_name) = match entry.kind() {
153 EntryKind::File { size, .. } => (format!("{}", *size), String::new(), "file"),
154 EntryKind::Symlink(link) => (
155 "0".to_string(),
156 format!(" -> {:?}", link.as_os_str()),
157 "symlink",
158 ),
159 EntryKind::Hardlink(link) => (
160 "0".to_string(),
161 format!(" -> {:?}", link.as_os_str()),
162 "symlink",
163 ),
164 EntryKind::Device(dev) => (
165 format!("{},{}", dev.major, dev.minor),
166 String::new(),
167 if meta.stat.is_chardev() {
168 "characters pecial file"
169 } else if meta.stat.is_blockdev() {
170 "block special file"
171 } else {
172 "device"
173 },
174 ),
175 EntryKind::Socket => ("0".to_string(), String::new(), "socket"),
176 EntryKind::Fifo => ("0".to_string(), String::new(), "fifo"),
177 EntryKind::Directory => ("0".to_string(), String::new(), "directory"),
178 EntryKind::GoodbyeTable => ("0".to_string(), String::new(), "bad entry"),
179 };
180
181 let file_name = match std::str::from_utf8(entry.path().as_os_str().as_bytes()) {
182 Ok(name) => std::borrow::Cow::Borrowed(name),
183 Err(_) => std::borrow::Cow::Owned(format!("{:?}", entry.path())),
184 };
185
186 format!(
187 " File: {}{}\n \
188 Size: {:<13} Type: {}\n\
189 Access: ({:o}/{}) Uid: {:<5} Gid: {:<5}\n\
190 Modify: {}\n",
191 file_name,
192 link,
193 size,
194 type_name,
195 meta.file_mode(),
196 mode_string,
197 meta.stat.uid,
198 meta.stat.gid,
199 mtime.format("%Y-%m-%d %H:%M:%S"),
200 )
201 }