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}
;
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 fn assert_relative_path_do(path
: &Path
) -> Result
<(), Error
> {
28 if !path
.is_relative() {
29 bail
!("bad absolute file name in archive: {:?}", path
);
32 let mut components
= path
.components();
33 match components
.next() {
34 Some(std
::path
::Component
::Normal(_
)) => (),
35 _
=> bail
!("invalid path component in archive: {:?}", path
),
38 if components
.next().is_some() {
40 "invalid path with multiple components in archive: {:?}",
49 fn symbolic_mode(c
: u64, special
: bool
, special_x
: u8, special_no_x
: u8) -> [u8; 3] {
51 if 0 != c
& 4 { b'r' }
else { b'-' }
,
52 if 0 != c
& 2 { b'w' }
else { b'-' }
,
53 match (c
& 1, special
) {
55 (0, true) => special_no_x
,
57 (_
, true) => special_x
,
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
68 // Trwxrwxrwx++ uid/gid size mtime filename [-> destination]
70 let meta
= entry
.metadata();
71 let mode
= meta
.stat
.mode
;
72 let type_char
= if entry
.is_hardlink() {
75 match mode
& mode
::IFMT
{
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'
);
91 let has_acls
= if meta
.acl
.is_empty() { ' ' }
else { '+' }
;
93 let has_xattrs
= if meta
.xattrs
.is_empty() { ' ' }
else { '+' }
;
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) }
,
106 pub fn format_single_line_entry(entry
: &Entry
) -> String
{
107 use chrono
::offset
::TimeZone
;
109 let mode_string
= mode_string(entry
);
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());
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()),
124 "{} {:<13} {} {:>8} {:?}{}",
126 format
!("{}/{}", meta
.stat
.uid
, meta
.stat
.gid
),
127 mtime
.format("%Y-%m-%d %H:%M:%S"),
134 pub fn format_multi_line_entry(entry
: &Entry
) -> String
{
135 use chrono
::offset
::TimeZone
;
137 let mode_string
= mode_string(entry
);
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());
143 let (size
, link
, type_name
) = match entry
.kind() {
144 EntryKind
::File { size, .. }
=> (format
!("{}", *size
), String
::new(), "file"),
145 EntryKind
::Symlink(link
) => (
147 format
!(" -> {:?}", link
.as_os_str()),
150 EntryKind
::Hardlink(link
) => (
152 format
!(" -> {:?}", link
.as_os_str()),
155 EntryKind
::Device(dev
) => (
156 format
!("{},{}", dev
.major
, dev
.minor
),
158 if meta
.stat
.is_chardev() {
159 "characters pecial file"
160 } else if meta
.stat
.is_blockdev() {
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"),
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())),
179 Size: {:<13} Type: {}\n\
180 Access: ({:o}/{}) Uid: {:<5} Gid: {:<5}\n\
190 mtime
.format("%Y-%m-%d %H:%M:%S"),