]>
Commit | Line | Data |
---|---|---|
c443f58b WB |
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 | ||
7158b304 | 11 | use pxar::{mode, Entry, EntryKind, Metadata, format::StatxTimestamp}; |
c443f58b WB |
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 | ||
7eacdc76 WB |
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 | ||
c443f58b WB |
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 | ||
7eacdc76 WB |
37 | Ok(()) |
38 | } | |
39 | ||
40 | fn assert_single_path_component_do(path: &Path) -> Result<(), Error> { | |
41 | assert_relative_path_do(path)?; | |
42 | ||
c443f58b WB |
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 | ||
7158b304 | 117 | fn format_mtime(mtime: &StatxTimestamp) -> String { |
6a7be83e DM |
118 | if let Ok(s) = proxmox::tools::time::strftime_local("%Y-%m-%d %H:%M:%S", mtime.secs) { |
119 | return s; | |
7158b304 | 120 | } |
6a7be83e | 121 | format!("{}.{}", mtime.secs, mtime.nanos) |
7158b304 FG |
122 | } |
123 | ||
124 | pub fn format_single_line_entry(entry: &Entry) -> String { | |
c443f58b WB |
125 | let mode_string = mode_string(entry); |
126 | ||
127 | let meta = entry.metadata(); | |
c443f58b WB |
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), | |
7158b304 | 141 | format_mtime(&meta.stat.mtime), |
c443f58b WB |
142 | size, |
143 | entry.path(), | |
144 | link, | |
145 | ) | |
146 | } | |
147 | ||
148 | pub fn format_multi_line_entry(entry: &Entry) -> String { | |
c443f58b WB |
149 | let mode_string = mode_string(entry); |
150 | ||
151 | let meta = entry.metadata(); | |
c443f58b WB |
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, | |
7158b304 | 200 | format_mtime(&meta.stat.mtime), |
c443f58b WB |
201 | ) |
202 | } |