fm/modes/menu/
permissions.rs1use std::os::unix::fs::PermissionsExt;
2use std::sync::Arc;
3
4use anyhow::Result;
5
6use crate::common::{
7 NORMAL_PERMISSIONS_STR, SETGID_PERMISSIONS_STR, SETUID_PERMISSIONS_STR, STICKY_PERMISSIONS_STR,
8};
9use crate::io::execute_without_output;
10use crate::modes::Flagged;
11use crate::{log_info, log_line};
12
13type Mode = u32;
14
15pub struct Permissions;
17
18pub const MAX_FILE_MODE: Mode = 0o777;
20pub const MAX_SPECIAL_MODE: Mode = 0o7777;
21
22impl Permissions {
23 pub fn set_permissions_of_flagged(mode_str: &str, flagged: &Flagged) -> Result<()> {
33 log_info!("set_permissions_of_flagged mode_str {mode_str}");
34 if let Some(mode) = ModeParser::from_str(mode_str) {
35 for path in &flagged.content {
36 std::fs::set_permissions(path, std::fs::Permissions::from_mode(mode.numeric()))?;
37 }
38 log_line!("Changed permissions to {mode_str}");
39 } else if Self::validate_chmod_args(mode_str) {
40 Self::execute_chmod_for_flagged(mode_str, flagged)?;
41 }
42 Ok(())
43 }
44
45 fn validate_chmod_args(mode_str: &str) -> bool {
53 let chars: Vec<_> = mode_str.chars().collect();
54 match chars.len() {
55 3 => {
56 let (dest, action, permission) = (chars[0], chars[1], chars[2]);
57 Self::validate_chmod_3(dest, action, permission)
58 }
59 2 => {
60 let (action, permission) = (chars[0], chars[1]);
61 Self::validate_chmod_2(action, permission)
62 }
63 _ => {
64 log_info!("{mode_str} isn't a valid chmod argument. Length should be 2 or 3.");
65 false
66 }
67 }
68 }
69
70 fn validate_chmod_3(dest: char, action: char, permission: char) -> bool {
71 if !"agou".contains(dest) {
72 log_info!("{dest} isn't a valid chmod argument. The first char should be 'a', 'g', 'o' or 'u'.");
73 return false;
74 }
75 Self::validate_chmod_2(action, permission)
76 }
77
78 fn validate_chmod_2(action: char, permission: char) -> bool {
79 if !"+-".contains(action) {
80 log_info!(
81 "{action} isn't a valid chmod argument. The second char should be '+' or '-'."
82 );
83 return false;
84 }
85 if !"XrstwxT".contains(permission) {
86 log_info!("{permission} isn't a valid chmod argument. The third char should be 'X', 'r', 's', 't', 'w' or 'x' or 'T'.");
87 return false;
88 }
89 true
90 }
91
92 fn execute_chmod_for_flagged(mode_str: &str, flagged: &Flagged) -> Result<()> {
94 let flagged: Vec<_> = flagged
95 .content
96 .iter()
97 .map(|p| p.to_string_lossy().to_string())
98 .collect();
99 let flagged = flagged.join(" ");
100 let chmod_args: &str = &format!("chmod {mode_str} {flagged}");
101
102 let executable = "/usr/bin/sh";
103 let args = vec!["-c", chmod_args];
104 execute_without_output(executable, &args)?;
105 Ok(())
106 }
107}
108
109trait AsOctal<T> {
110 fn as_octal(&self) -> T;
112}
113
114impl AsOctal<u32> for str {
115 fn as_octal(&self) -> u32 {
116 u32::from_str_radix(self, 8).unwrap_or_default()
117 }
118}
119
120type IsValid = bool;
121
122pub fn parse_input_permission(mode_str: &str) -> (Arc<str>, IsValid) {
130 log_info!("parse_input_permission: {mode_str}");
131 if mode_str.chars().any(|c| c.is_alphabetic()) {
132 (Arc::from(""), true)
133 } else if mode_str.chars().all(|c| c.is_digit(8)) {
134 (permission_mode_to_str(mode_str.as_octal()), true)
135 } else {
136 (Arc::from("Unreadable mode"), false)
137 }
138}
139
140struct ModeParser(Mode);
141
142impl ModeParser {
143 const fn numeric(&self) -> Mode {
144 self.0
145 }
146
147 fn from_str(mode_str: &str) -> Option<Self> {
148 if let Some(mode) = Self::from_numeric(mode_str) {
149 return Some(mode);
150 }
151 Self::from_alphabetic(mode_str)
152 }
153
154 fn from_numeric(mode_str: &str) -> Option<Self> {
155 if let Ok(mode) = Mode::from_str_radix(mode_str, 8) {
156 if Self::is_valid_permissions(mode) {
157 return Some(Self(mode));
158 }
159 }
160 None
161 }
162
163 fn from_alphabetic(mode_str: &str) -> Option<Self> {
174 if mode_str.len() != 9 {
176 return None;
177 }
178
179 let mut mode = 0;
180 for (index, current_char) in mode_str.chars().enumerate() {
181 let Some(increment) = Self::evaluate_index_char(index, current_char) else {
182 log_info!("Invalid char in permissions '{current_char}' at position {index}");
183 return None;
184 };
185 mode += increment;
186 }
187 if Self::is_valid_permissions(mode) {
188 return Some(Self(mode));
189 }
190
191 None
192 }
193
194 fn evaluate_index_char(index: usize, current_char: char) -> Option<u32> {
197 match current_char {
198 '-' | '.' => Some(0o000),
199
200 'r' if index == 0 => Some(0o0400),
201 'w' if index == 1 => Some(0o0200),
202 'x' if index == 2 => Some(0o0100),
203 'S' if index == 2 => Some(0o4000),
204 's' if index == 2 => Some(0o4100),
205
206 'r' if index == 3 => Some(0o0040),
207 'w' if index == 4 => Some(0o0020),
208 'x' if index == 5 => Some(0o0010),
209 'S' if index == 5 => Some(0o2000),
210 's' if index == 5 => Some(0o2010),
211
212 'r' if index == 6 => Some(0o0004),
213 'w' if index == 7 => Some(0o0002),
214 'x' if index == 8 => Some(0o0001),
215 'T' if index == 8 => Some(0o1000),
216 't' if index == 8 => Some(0o1001),
217
218 _ => None,
219 }
220 }
221
222 const fn is_valid_permissions(mode: Mode) -> bool {
223 mode <= MAX_SPECIAL_MODE
224 }
225}
226
227trait ToBool {
228 fn to_bool(self) -> bool;
229}
230
231impl ToBool for u32 {
232 fn to_bool(self) -> bool {
233 (self & 1) == 1
234 }
235}
236
237#[inline]
238fn extract_setuid_flag(special: u32) -> bool {
239 (special >> 2).to_bool()
240}
241
242#[inline]
243fn extract_setgid_flag(special: u32) -> bool {
244 (special >> 1).to_bool()
245}
246
247#[inline]
248fn extract_sticky_flag(special: u32) -> bool {
249 special.to_bool()
250}
251pub fn permission_mode_to_str(mode: u32) -> Arc<str> {
253 let mode = mode & 0o7777;
254 let special = mode >> 9;
255 let owner_strs = if extract_setuid_flag(special) {
256 SETUID_PERMISSIONS_STR
257 } else {
258 NORMAL_PERMISSIONS_STR
259 };
260 let group_strs = if extract_setgid_flag(special) {
261 SETGID_PERMISSIONS_STR
262 } else {
263 NORMAL_PERMISSIONS_STR
264 };
265 let sticky_strs = if extract_sticky_flag(special) {
266 STICKY_PERMISSIONS_STR
267 } else {
268 NORMAL_PERMISSIONS_STR
269 };
270 let normal_mode = (mode & MAX_FILE_MODE) as usize;
271 let s_o = convert_octal_mode(owner_strs, normal_mode >> 6);
272 let s_g = convert_octal_mode(group_strs, (normal_mode >> 3) & 7);
273 let s_a = convert_octal_mode(sticky_strs, normal_mode & 7);
274 Arc::from([s_o, s_g, s_a].join(""))
275}
276
277fn convert_octal_mode(permission_str: [&'static str; 8], mode: usize) -> &'static str {
279 permission_str[mode]
280}