Skip to content

Commit 4e653b5

Browse files
committed
move mode parsing and tests from install to uucore
1 parent 11e77c7 commit 4e653b5

File tree

3 files changed

+142
-153
lines changed

3 files changed

+142
-153
lines changed

src/uu/install/src/install.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ fn behavior(matches: &ArgMatches) -> UResult<Behavior> {
338338

339339
let specified_mode: Option<u32> = if matches.contains_id(OPT_MODE) {
340340
let x = matches.get_one::<String>(OPT_MODE).ok_or(1)?;
341-
Some(mode::parse(x, considering_dir, 0).map_err(|err| {
341+
Some(uucore::mode::parse(x, considering_dir, 0).map_err(|err| {
342342
show_error!(
343343
"{}",
344344
translate!("install-error-invalid-mode", "error" => err)

src/uu/install/src/mode.rs

Lines changed: 0 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,8 @@
44
// file that was distributed with this source code.
55
use std::fs;
66
use std::path::Path;
7-
#[cfg(not(windows))]
8-
use uucore::mode;
97
use uucore::translate;
108

11-
/// Takes a user-supplied string and tries to parse to u16 mode bitmask.
12-
/// Supports comma-separated mode strings like "ug+rwX,o+rX" (same as chmod).
13-
pub fn parse(mode_string: &str, considering_dir: bool, umask: u32) -> Result<u32, String> {
14-
// Split by commas and process each mode part sequentially
15-
let mut current_mode: u32 = 0;
16-
17-
for mode_part in mode_string.split(',') {
18-
let mode_part = mode_part.trim();
19-
if mode_part.is_empty() {
20-
continue;
21-
}
22-
23-
current_mode = if mode_part.chars().any(|c| c.is_ascii_digit()) {
24-
mode::parse_numeric(current_mode, mode_part, considering_dir)?
25-
} else {
26-
mode::parse_symbolic(current_mode, mode_part, umask, considering_dir)?
27-
};
28-
}
29-
30-
Ok(current_mode)
31-
}
32-
339
/// chmod a file or directory on UNIX.
3410
///
3511
/// Adapted from mkdir.rs. Handles own error printing.
@@ -55,128 +31,3 @@ pub fn chmod(path: &Path, mode: u32) -> Result<(), ()> {
5531
// chmod on Windows only sets the readonly flag, which isn't even honored on directories
5632
Ok(())
5733
}
58-
59-
#[cfg(test)]
60-
#[cfg(not(windows))]
61-
mod tests {
62-
use super::parse;
63-
64-
#[test]
65-
fn test_parse_numeric_mode() {
66-
// Simple numeric mode
67-
assert_eq!(parse("644", false, 0).unwrap(), 0o644);
68-
assert_eq!(parse("755", false, 0).unwrap(), 0o755);
69-
assert_eq!(parse("777", false, 0).unwrap(), 0o777);
70-
assert_eq!(parse("600", false, 0).unwrap(), 0o600);
71-
}
72-
73-
#[test]
74-
fn test_parse_numeric_mode_with_operator() {
75-
// Numeric mode with + operator
76-
assert_eq!(parse("+100", false, 0).unwrap(), 0o100);
77-
assert_eq!(parse("+644", false, 0).unwrap(), 0o644);
78-
79-
// Numeric mode with - operator (starting from 0, so nothing to remove)
80-
assert_eq!(parse("-4", false, 0).unwrap(), 0);
81-
// But if we first set a mode, then remove bits
82-
assert_eq!(parse("644,-4", false, 0).unwrap(), 0o640);
83-
}
84-
85-
#[test]
86-
fn test_parse_symbolic_mode() {
87-
// Simple symbolic modes
88-
assert_eq!(parse("u+x", false, 0).unwrap(), 0o100);
89-
assert_eq!(parse("g+w", false, 0).unwrap(), 0o020);
90-
assert_eq!(parse("o+r", false, 0).unwrap(), 0o004);
91-
assert_eq!(parse("a+x", false, 0).unwrap(), 0o111);
92-
}
93-
94-
#[test]
95-
fn test_parse_symbolic_mode_multiple_permissions() {
96-
// Multiple permissions in one mode
97-
assert_eq!(parse("u+rw", false, 0).unwrap(), 0o600);
98-
assert_eq!(parse("ug+rwx", false, 0).unwrap(), 0o770);
99-
assert_eq!(parse("a+rwx", false, 0).unwrap(), 0o777);
100-
}
101-
102-
#[test]
103-
fn test_parse_comma_separated_modes() {
104-
// Comma-separated mode strings (as mentioned in the doc comment)
105-
assert_eq!(parse("ug+rwX,o+rX", false, 0).unwrap(), 0o664);
106-
assert_eq!(parse("u+rwx,g+rx,o+r", false, 0).unwrap(), 0o754);
107-
assert_eq!(parse("u+w,g+w,o+w", false, 0).unwrap(), 0o222);
108-
}
109-
110-
#[test]
111-
fn test_parse_comma_separated_with_spaces() {
112-
// Comma-separated with spaces (should be trimmed)
113-
assert_eq!(parse("u+rw, g+rw, o+r", false, 0).unwrap(), 0o664);
114-
assert_eq!(parse(" u+x , g+x ", false, 0).unwrap(), 0o110);
115-
}
116-
117-
#[test]
118-
fn test_parse_mixed_numeric_and_symbolic() {
119-
// Mix of numeric and symbolic modes
120-
assert_eq!(parse("644,u+x", false, 0).unwrap(), 0o744);
121-
assert_eq!(parse("u+rw,755", false, 0).unwrap(), 0o755);
122-
}
123-
124-
#[test]
125-
fn test_parse_empty_string() {
126-
// Empty string should return 0
127-
assert_eq!(parse("", false, 0).unwrap(), 0);
128-
assert_eq!(parse(" ", false, 0).unwrap(), 0);
129-
assert_eq!(parse(",,", false, 0).unwrap(), 0);
130-
}
131-
132-
#[test]
133-
fn test_parse_with_umask() {
134-
// Test with umask (affects symbolic modes when no level is specified)
135-
let umask = 0o022;
136-
assert_eq!(parse("+w", false, umask).unwrap(), 0o200);
137-
// The umask should be respected for symbolic modes without explicit level
138-
}
139-
140-
#[test]
141-
fn test_parse_considering_dir() {
142-
// Test directory vs file mode differences
143-
// For directories, X (capital X) should add execute permission
144-
assert_eq!(parse("a+X", true, 0).unwrap(), 0o111);
145-
// For files without execute, X should not add execute
146-
assert_eq!(parse("a+X", false, 0).unwrap(), 0o000);
147-
148-
// Numeric modes for directories preserve setuid/setgid bits
149-
assert_eq!(parse("755", true, 0).unwrap(), 0o755);
150-
}
151-
152-
#[test]
153-
fn test_parse_invalid_modes() {
154-
// Invalid numeric mode (too large)
155-
assert!(parse("10000", false, 0).is_err());
156-
157-
// Invalid operator
158-
assert!(parse("u*rw", false, 0).is_err());
159-
160-
// Invalid symbolic mode
161-
assert!(parse("invalid", false, 0).is_err());
162-
}
163-
164-
#[test]
165-
fn test_parse_complex_combinations() {
166-
// Complex real-world examples
167-
assert_eq!(parse("u=rwx,g=rx,o=r", false, 0).unwrap(), 0o754);
168-
// To test removal, we need to first set permissions, then remove them
169-
assert_eq!(parse("644,a-w", false, 0).unwrap(), 0o444);
170-
assert_eq!(parse("644,g-r", false, 0).unwrap(), 0o604);
171-
}
172-
173-
#[test]
174-
fn test_parse_sequential_application() {
175-
// Test that comma-separated modes are applied sequentially
176-
// First set to 644, then add execute for user
177-
assert_eq!(parse("644,u+x", false, 0).unwrap(), 0o744);
178-
179-
// First add user write, then set to 755 (should override)
180-
assert_eq!(parse("u+w,755", false, 0).unwrap(), 0o755);
181-
}
182-
}

src/uucore/src/lib/features/mode.rs

Lines changed: 141 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,28 @@ fn parse_change(mode: &str, fperm: u32, considering_dir: bool) -> (u32, usize) {
137137
(srwx, pos)
138138
}
139139

140+
/// Takes a user-supplied string and tries to parse to u16 mode bitmask.
141+
/// Supports comma-separated mode strings like "ug+rwX,o+rX" (same as chmod).
142+
pub fn parse(mode_string: &str, considering_dir: bool, umask: u32) -> Result<u32, String> {
143+
// Split by commas and process each mode part sequentially
144+
let mut current_mode: u32 = 0;
145+
146+
for mode_part in mode_string.split(',') {
147+
let mode_part = mode_part.trim();
148+
if mode_part.is_empty() {
149+
continue;
150+
}
151+
152+
current_mode = if mode_part.chars().any(|c| c.is_ascii_digit()) {
153+
parse_numeric(current_mode, mode_part, considering_dir)?
154+
} else {
155+
parse_symbolic(current_mode, mode_part, umask, considering_dir)?
156+
};
157+
}
158+
159+
Ok(current_mode)
160+
}
161+
140162
#[allow(clippy::unnecessary_cast)]
141163
pub fn parse_mode(mode: &str) -> Result<mode_t, String> {
142164
let mut new_mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32;
@@ -178,7 +200,9 @@ pub fn get_umask() -> u32 {
178200
}
179201

180202
#[cfg(test)]
181-
mod test {
203+
mod tests {
204+
205+
use super::parse;
182206

183207
#[test]
184208
fn symbolic_modes() {
@@ -199,7 +223,121 @@ mod test {
199223
}
200224

201225
#[test]
202-
fn multiple_modes() {
203-
assert_eq!(super::parse_mode("+100,+010").unwrap(), 0o776);
226+
fn test_parse_numeric_mode() {
227+
// Simple numeric mode
228+
assert_eq!(parse("644", false, 0).unwrap(), 0o644);
229+
assert_eq!(parse("755", false, 0).unwrap(), 0o755);
230+
assert_eq!(parse("777", false, 0).unwrap(), 0o777);
231+
assert_eq!(parse("600", false, 0).unwrap(), 0o600);
232+
}
233+
234+
#[test]
235+
fn test_parse_numeric_mode_with_operator() {
236+
// Numeric mode with + operator
237+
assert_eq!(parse("+100", false, 0).unwrap(), 0o100);
238+
assert_eq!(parse("+644", false, 0).unwrap(), 0o644);
239+
240+
// Numeric mode with - operator (starting from 0, so nothing to remove)
241+
assert_eq!(parse("-4", false, 0).unwrap(), 0);
242+
// But if we first set a mode, then remove bits
243+
assert_eq!(parse("644,-4", false, 0).unwrap(), 0o640);
244+
}
245+
246+
#[test]
247+
fn test_parse_symbolic_mode() {
248+
// Simple symbolic modes
249+
assert_eq!(parse("u+x", false, 0).unwrap(), 0o100);
250+
assert_eq!(parse("g+w", false, 0).unwrap(), 0o020);
251+
assert_eq!(parse("o+r", false, 0).unwrap(), 0o004);
252+
assert_eq!(parse("a+x", false, 0).unwrap(), 0o111);
253+
}
254+
255+
#[test]
256+
fn test_parse_symbolic_mode_multiple_permissions() {
257+
// Multiple permissions in one mode
258+
assert_eq!(parse("u+rw", false, 0).unwrap(), 0o600);
259+
assert_eq!(parse("ug+rwx", false, 0).unwrap(), 0o770);
260+
assert_eq!(parse("a+rwx", false, 0).unwrap(), 0o777);
261+
}
262+
263+
#[test]
264+
fn test_parse_comma_separated_modes() {
265+
// Comma-separated mode strings (as mentioned in the doc comment)
266+
assert_eq!(parse("ug+rwX,o+rX", false, 0).unwrap(), 0o664);
267+
assert_eq!(parse("u+rwx,g+rx,o+r", false, 0).unwrap(), 0o754);
268+
assert_eq!(parse("u+w,g+w,o+w", false, 0).unwrap(), 0o222);
269+
}
270+
271+
#[test]
272+
fn test_parse_comma_separated_with_spaces() {
273+
// Comma-separated with spaces (should be trimmed)
274+
assert_eq!(parse("u+rw, g+rw, o+r", false, 0).unwrap(), 0o664);
275+
assert_eq!(parse(" u+x , g+x ", false, 0).unwrap(), 0o110);
276+
}
277+
278+
#[test]
279+
fn test_parse_mixed_numeric_and_symbolic() {
280+
// Mix of numeric and symbolic modes
281+
assert_eq!(parse("644,u+x", false, 0).unwrap(), 0o744);
282+
assert_eq!(parse("u+rw,755", false, 0).unwrap(), 0o755);
283+
}
284+
285+
#[test]
286+
fn test_parse_empty_string() {
287+
// Empty string should return 0
288+
assert_eq!(parse("", false, 0).unwrap(), 0);
289+
assert_eq!(parse(" ", false, 0).unwrap(), 0);
290+
assert_eq!(parse(",,", false, 0).unwrap(), 0);
291+
}
292+
293+
#[test]
294+
fn test_parse_with_umask() {
295+
// Test with umask (affects symbolic modes when no level is specified)
296+
let umask = 0o022;
297+
assert_eq!(parse("+w", false, umask).unwrap(), 0o200);
298+
// The umask should be respected for symbolic modes without explicit level
299+
}
300+
301+
#[test]
302+
fn test_parse_considering_dir() {
303+
// Test directory vs file mode differences
304+
// For directories, X (capital X) should add execute permission
305+
assert_eq!(parse("a+X", true, 0).unwrap(), 0o111);
306+
// For files without execute, X should not add execute
307+
assert_eq!(parse("a+X", false, 0).unwrap(), 0o000);
308+
309+
// Numeric modes for directories preserve setuid/setgid bits
310+
assert_eq!(parse("755", true, 0).unwrap(), 0o755);
311+
}
312+
313+
#[test]
314+
fn test_parse_invalid_modes() {
315+
// Invalid numeric mode (too large)
316+
assert!(parse("10000", false, 0).is_err());
317+
318+
// Invalid operator
319+
assert!(parse("u*rw", false, 0).is_err());
320+
321+
// Invalid symbolic mode
322+
assert!(parse("invalid", false, 0).is_err());
323+
}
324+
325+
#[test]
326+
fn test_parse_complex_combinations() {
327+
// Complex real-world examples
328+
assert_eq!(parse("u=rwx,g=rx,o=r", false, 0).unwrap(), 0o754);
329+
// To test removal, we need to first set permissions, then remove them
330+
assert_eq!(parse("644,a-w", false, 0).unwrap(), 0o444);
331+
assert_eq!(parse("644,g-r", false, 0).unwrap(), 0o604);
332+
}
333+
334+
#[test]
335+
fn test_parse_sequential_application() {
336+
// Test that comma-separated modes are applied sequentially
337+
// First set to 644, then add execute for user
338+
assert_eq!(parse("644,u+x", false, 0).unwrap(), 0o744);
339+
340+
// First add user write, then set to 755 (should override)
341+
assert_eq!(parse("u+w,755", false, 0).unwrap(), 0o755);
204342
}
205343
}

0 commit comments

Comments
 (0)