Skip to main content

f00_format/
perms.rs

1use f00_core::{Entry, EntryKind, IndicatorStyle};
2
3/// Render a 10-char permission string similar to `ls -l`.
4///
5/// On Unix uses mode bits; elsewhere falls back to a simplified string.
6pub fn format_permissions(entry: &Entry) -> String {
7    #[cfg(unix)]
8    {
9        format_unix(entry)
10    }
11    #[cfg(not(unix))]
12    {
13        format_fallback(entry)
14    }
15}
16
17#[cfg(unix)]
18fn format_unix(entry: &Entry) -> String {
19    let mode = entry.mode;
20    let file_type = match entry.kind {
21        EntryKind::Directory => 'd',
22        EntryKind::Symlink => 'l',
23        EntryKind::File => '-',
24        EntryKind::Other => match mode & 0o170000 {
25            0o140000 => 's',
26            0o010000 => 'p',
27            0o060000 => 'b',
28            0o020000 => 'c',
29            _ => '-',
30        },
31    };
32
33    let mut s = String::with_capacity(10);
34    s.push(file_type);
35    // owner
36    s.push(if mode & 0o400 != 0 { 'r' } else { '-' });
37    s.push(if mode & 0o200 != 0 { 'w' } else { '-' });
38    s.push(suid_bit(mode, 0o100, 0o4000));
39    // group
40    s.push(if mode & 0o040 != 0 { 'r' } else { '-' });
41    s.push(if mode & 0o020 != 0 { 'w' } else { '-' });
42    s.push(sgid_bit(mode, 0o010, 0o2000));
43    // other
44    s.push(if mode & 0o004 != 0 { 'r' } else { '-' });
45    s.push(if mode & 0o002 != 0 { 'w' } else { '-' });
46    s.push(sticky_bit(mode, 0o001, 0o1000));
47    s
48}
49
50#[cfg(unix)]
51fn suid_bit(mode: u32, exec: u32, special: u32) -> char {
52    match (mode & exec != 0, mode & special != 0) {
53        (true, true) => 's',
54        (false, true) => 'S',
55        (true, false) => 'x',
56        (false, false) => '-',
57    }
58}
59
60#[cfg(unix)]
61fn sgid_bit(mode: u32, exec: u32, special: u32) -> char {
62    suid_bit(mode, exec, special)
63}
64
65#[cfg(unix)]
66fn sticky_bit(mode: u32, exec: u32, special: u32) -> char {
67    match (mode & exec != 0, mode & special != 0) {
68        (true, true) => 't',
69        (false, true) => 'T',
70        (true, false) => 'x',
71        (false, false) => '-',
72    }
73}
74
75#[cfg(not(unix))]
76fn format_fallback(entry: &Entry) -> String {
77    let t = match entry.kind {
78        EntryKind::Directory => 'd',
79        EntryKind::Symlink => 'l',
80        EntryKind::File => '-',
81        EntryKind::Other => '-',
82    };
83    let w = if entry.readonly { '-' } else { 'w' };
84    format!("{t}r{w}xr{w}xr{w}x")
85}
86
87/// Classification suffix like `ls -F` / `-p` / `--file-type`.
88pub fn classify_suffix(entry: &Entry, style: IndicatorStyle) -> &'static str {
89    if matches!(style, IndicatorStyle::None) || entry.is_dir_header {
90        return "";
91    }
92    match entry.kind {
93        EntryKind::Directory => "/",
94        EntryKind::Symlink => {
95            if matches!(style, IndicatorStyle::Slash) {
96                ""
97            } else {
98                "@"
99            }
100        }
101        EntryKind::File if is_executable(entry) => {
102            if matches!(style, IndicatorStyle::Classify) {
103                "*"
104            } else {
105                ""
106            }
107        }
108        EntryKind::Other => {
109            #[cfg(unix)]
110            {
111                match entry.mode & 0o170000 {
112                    0o010000 if !matches!(style, IndicatorStyle::Slash) => "|", // fifo
113                    0o140000 if !matches!(style, IndicatorStyle::Slash) => "=", // socket
114                    _ => "",
115                }
116            }
117            #[cfg(not(unix))]
118            {
119                ""
120            }
121        }
122        _ => "",
123    }
124}
125
126/// Backward-compatible helper used by older call sites.
127pub fn classify_suffix_bool(entry: &Entry, enabled: bool) -> &'static str {
128    classify_suffix(
129        entry,
130        if enabled {
131            IndicatorStyle::Classify
132        } else {
133            IndicatorStyle::None
134        },
135    )
136}
137
138fn is_executable(entry: &Entry) -> bool {
139    #[cfg(unix)]
140    {
141        entry.mode & 0o111 != 0
142    }
143    #[cfg(not(unix))]
144    {
145        let _ = entry;
146        false
147    }
148}