1 //! Some common methods used within the pxar code.
3 use std
::convert
::TryFrom
;
5 use std
::os
::unix
::ffi
::OsStrExt
;
8 use anyhow
::{bail, format_err, Error}
;
9 use nix
::sys
::stat
::Mode
;
11 use pxar
::{mode, Entry, EntryKind, Metadata, format::StatxTimestamp}
;
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();
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
))
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
))
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
))
32 fn assert_relative_path_do(path
: &Path
) -> Result
<(), Error
> {
33 if !path
.is_relative() {
34 bail
!("bad absolute file name in archive: {:?}", path
);
40 fn assert_single_path_component_do(path
: &Path
) -> Result
<(), Error
> {
41 assert_relative_path_do(path
)?
;
43 let mut components
= path
.components();
44 match components
.next() {
45 Some(std
::path
::Component
::Normal(_
)) => (),
46 _
=> bail
!("invalid path component in archive: {:?}", path
),
49 if components
.next().is_some() {
51 "invalid path with multiple components in archive: {:?}",
60 fn symbolic_mode(c
: u64, special
: bool
, special_x
: u8, special_no_x
: u8) -> [u8; 3] {
62 if 0 != c
& 4 { b'r' }
else { b'-' }
,
63 if 0 != c
& 2 { b'w' }
else { b'-' }
,
64 match (c
& 1, special
) {
66 (0, true) => special_no_x
,
68 (_
, true) => special_x
,
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
79 // Trwxrwxrwx++ uid/gid size mtime filename [-> destination]
81 let meta
= entry
.metadata();
82 let mode
= meta
.stat
.mode
;
83 let type_char
= if entry
.is_hardlink() {
86 match mode
& mode
::IFMT
{
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'
);
102 let has_acls
= if meta
.acl
.is_empty() { ' ' }
else { '+' }
;
104 let has_xattrs
= if meta
.xattrs
.is_empty() { ' ' }
else { '+' }
;
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) }
,
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
) {
121 format
!("{}.{}", mtime
.secs
, mtime
.nanos
)
124 pub fn format_single_line_entry(entry
: &Entry
) -> String
{
125 let mode_string
= mode_string(entry
);
127 let meta
= entry
.metadata();
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()),
138 "{} {:<13} {} {:>8} {:?}{}",
140 format
!("{}/{}", meta
.stat
.uid
, meta
.stat
.gid
),
141 format_mtime(&meta
.stat
.mtime
),
148 pub fn format_multi_line_entry(entry
: &Entry
) -> String
{
149 let mode_string
= mode_string(entry
);
151 let meta
= entry
.metadata();
153 let (size
, link
, type_name
) = match entry
.kind() {
154 EntryKind
::File { size, .. }
=> (format
!("{}", *size
), String
::new(), "file"),
155 EntryKind
::Symlink(link
) => (
157 format
!(" -> {:?}", link
.as_os_str()),
160 EntryKind
::Hardlink(link
) => (
162 format
!(" -> {:?}", link
.as_os_str()),
165 EntryKind
::Device(dev
) => (
166 format
!("{},{}", dev
.major
, dev
.minor
),
168 if meta
.stat
.is_chardev() {
169 "characters pecial file"
170 } else if meta
.stat
.is_blockdev() {
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"),
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())),
189 Size: {:<13} Type: {}\n\
190 Access: ({:o}/{}) Uid: {:<5} Gid: {:<5}\n\
200 format_mtime(&meta
.stat
.mtime
),