1 //! Some common methods used within the pxar code.
4 use std
::os
::unix
::ffi
::OsStrExt
;
7 use anyhow
::{bail, format_err, Error}
;
8 use nix
::sys
::stat
::Mode
;
10 use pxar
::{format::StatxTimestamp, mode, Entry, EntryKind, Metadata}
;
12 /// Get the file permissions as `nix::Mode`
13 pub fn perms_from_metadata(meta
: &Metadata
) -> Result
<Mode
, Error
> {
14 let mode
= meta
.stat
.get_permission_bits();
17 .and_then(|mode
| Mode
::from_bits(mode
).ok_or(()))
18 .map_err(|_
| format_err
!("mode contains illegal bits: 0x{:x} (0o{:o})", mode
, mode
))
21 /// Make sure path is relative and not '.' or '..'.
22 pub fn assert_relative_path
<S
: AsRef
<OsStr
> + ?Sized
>(path
: &S
) -> Result
<(), Error
> {
23 assert_relative_path_do(Path
::new(path
))
26 /// Make sure path is a single component and not '.' or '..'.
27 pub fn assert_single_path_component
<S
: AsRef
<OsStr
> + ?Sized
>(path
: &S
) -> Result
<(), Error
> {
28 assert_single_path_component_do(Path
::new(path
))
31 fn assert_relative_path_do(path
: &Path
) -> Result
<(), Error
> {
32 if !path
.is_relative() {
33 bail
!("bad absolute file name in archive: {:?}", path
);
39 fn assert_single_path_component_do(path
: &Path
) -> Result
<(), Error
> {
40 assert_relative_path_do(path
)?
;
42 let mut components
= path
.components();
43 match components
.next() {
44 Some(std
::path
::Component
::Normal(_
)) => (),
45 _
=> bail
!("invalid path component in archive: {:?}", path
),
48 if components
.next().is_some() {
50 "invalid path with multiple components in archive: {:?}",
59 fn symbolic_mode(c
: u64, special
: bool
, special_x
: u8, special_no_x
: u8) -> [u8; 3] {
61 if 0 != c
& 4 { b'r' }
else { b'-' }
,
62 if 0 != c
& 2 { b'w' }
else { b'-' }
,
63 match (c
& 1, special
) {
65 (0, true) => special_no_x
,
67 (_
, true) => special_x
,
72 fn mode_string(entry
: &Entry
) -> String
{
73 // https://www.gnu.org/software/coreutils/manual/html_node/What-information-is-listed.html#What-information-is-listed
74 // additionally we use:
75 // file type capital 'L' hard links
76 // a second '+' after the mode to show non-acl xattr presence
78 // Trwxrwxrwx++ uid/gid size mtime filename [-> destination]
80 let meta
= entry
.metadata();
81 let mode
= meta
.stat
.mode
;
82 let type_char
= if entry
.is_hardlink() {
85 match mode
& mode
::IFMT
{
97 let fmt_u
= symbolic_mode((mode
>> 6) & 7, 0 != mode
& mode
::ISUID
, b's'
, b'S'
);
98 let fmt_g
= symbolic_mode((mode
>> 3) & 7, 0 != mode
& mode
::ISGID
, b's'
, b'S'
);
99 let fmt_o
= symbolic_mode(mode
& 7, 0 != mode
& mode
::ISVTX
, b't'
, b'T'
);
101 let has_acls
= if meta
.acl
.is_empty() { ' ' }
else { '+' }
;
103 let has_xattrs
= if meta
.xattrs
.is_empty() { ' ' }
else { '+' }
;
108 unsafe { std::str::from_utf8_unchecked(&fmt_u) }
,
109 unsafe { std::str::from_utf8_unchecked(&fmt_g) }
,
110 unsafe { std::str::from_utf8_unchecked(&fmt_o) }
,
116 fn format_mtime(mtime
: &StatxTimestamp
) -> String
{
117 if let Ok(s
) = proxmox_time
::strftime_local("%Y-%m-%d %H:%M:%S", mtime
.secs
) {
120 format
!("{}.{}", mtime
.secs
, mtime
.nanos
)
123 pub fn format_single_line_entry(entry
: &Entry
) -> String
{
124 let mode_string
= mode_string(entry
);
126 let meta
= entry
.metadata();
128 let (size
, link
) = match entry
.kind() {
129 EntryKind
::File { size, .. }
=> (format
!("{}", *size
), String
::new()),
130 EntryKind
::Symlink(link
) => ("0".to_string(), format
!(" -> {:?}", link
.as_os_str())),
131 EntryKind
::Hardlink(link
) => ("0".to_string(), format
!(" -> {:?}", link
.as_os_str())),
132 EntryKind
::Device(dev
) => (format
!("{},{}", dev
.major
, dev
.minor
), String
::new()),
133 _
=> ("0".to_string(), String
::new()),
136 let owner_string
= format
!("{}/{}", meta
.stat
.uid
, meta
.stat
.gid
);
139 "{} {:<13} {} {:>8} {:?}{}",
142 format_mtime(&meta
.stat
.mtime
),
149 pub fn format_multi_line_entry(entry
: &Entry
) -> String
{
150 let mode_string
= mode_string(entry
);
152 let meta
= entry
.metadata();
154 let (size
, link
, type_name
) = match entry
.kind() {
155 EntryKind
::File { size, .. }
=> (format
!("{}", *size
), String
::new(), "file"),
156 EntryKind
::Symlink(link
) => (
158 format
!(" -> {:?}", link
.as_os_str()),
161 EntryKind
::Hardlink(link
) => (
163 format
!(" -> {:?}", link
.as_os_str()),
166 EntryKind
::Device(dev
) => (
167 format
!("{},{}", dev
.major
, dev
.minor
),
169 if meta
.stat
.is_chardev() {
170 "characters pecial file"
171 } else if meta
.stat
.is_blockdev() {
177 EntryKind
::Socket
=> ("0".to_string(), String
::new(), "socket"),
178 EntryKind
::Fifo
=> ("0".to_string(), String
::new(), "fifo"),
179 EntryKind
::Directory
=> ("0".to_string(), String
::new(), "directory"),
180 EntryKind
::GoodbyeTable
=> ("0".to_string(), String
::new(), "bad entry"),
183 let file_name
= match std
::str::from_utf8(entry
.path().as_os_str().as_bytes()) {
184 Ok(name
) => std
::borrow
::Cow
::Borrowed(name
),
185 Err(_
) => std
::borrow
::Cow
::Owned(format
!("{:?}", entry
.path())),
190 Size: {:<13} Type: {}\n\
191 Access: ({:o}/{}) Uid: {:<5} Gid: {:<5}\n\
201 format_mtime(&meta
.stat
.mtime
),