Skip to content

Commit 7c62885

Browse files
authored
Merge pull request #9578 from martinkunkel2/mknod-multiple-modes
uucore: mode parsing: support comma-separated modes
2 parents f30509b + 63dbffa commit 7c62885

File tree

8 files changed

+190
-204
lines changed

8 files changed

+190
-204
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/uu/mkdir/src/mkdir.rs

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,11 @@ fn get_mode(_matches: &ArgMatches) -> Result<u32, String> {
5757
#[cfg(not(windows))]
5858
fn get_mode(matches: &ArgMatches) -> Result<u32, String> {
5959
// Not tested on Windows
60-
let mut new_mode = DEFAULT_PERM;
61-
6260
if let Some(m) = matches.get_one::<String>(options::MODE) {
63-
for mode in m.split(',') {
64-
if mode.chars().any(|c| c.is_ascii_digit()) {
65-
new_mode = mode::parse_numeric(new_mode, m, true)?;
66-
} else {
67-
new_mode = mode::parse_symbolic(new_mode, mode, mode::get_umask(), true)?;
68-
}
69-
}
70-
Ok(new_mode)
61+
mode::parse_chmod(DEFAULT_PERM, m, true, mode::get_umask())
7162
} else {
7263
// If no mode argument is specified return the mode derived from umask
73-
Ok(!mode::get_umask() & 0o0777)
64+
Ok(!mode::get_umask() & DEFAULT_PERM)
7465
}
7566
}
7667

src/uu/mkfifo/src/mkfifo.rs

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -119,19 +119,11 @@ pub fn uu_app() -> Command {
119119

120120
fn calculate_mode(mode_option: Option<&String>) -> Result<u32, String> {
121121
let umask = uucore::mode::get_umask();
122-
let mut mode = 0o666; // Default mode for FIFOs
122+
let mode = 0o666; // Default mode for FIFOs
123123

124124
if let Some(m) = mode_option {
125-
if m.chars().any(|c| c.is_ascii_digit()) {
126-
mode = uucore::mode::parse_numeric(mode, m, false)?;
127-
} else {
128-
for item in m.split(',') {
129-
mode = uucore::mode::parse_symbolic(mode, item, umask, false)?;
130-
}
131-
}
125+
uucore::mode::parse_chmod(mode, m, false, umask)
132126
} else {
133-
mode &= !umask; // Apply umask if no mode is specified
127+
Ok(mode & !umask) // Apply umask if no mode is specified
134128
}
135-
136-
Ok(mode)
137129
}

src/uu/mknod/src/mknod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,10 @@ pub fn uu_app() -> Command {
225225
)
226226
}
227227

228+
#[allow(clippy::unnecessary_cast)]
228229
fn parse_mode(str_mode: &str) -> Result<mode_t, String> {
229-
uucore::mode::parse_mode(str_mode)
230+
let default_mode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) as u32;
231+
uucore::mode::parse_chmod(default_mode, str_mode, true, uucore::mode::get_umask())
230232
.map_err(|e| {
231233
translate!(
232234
"mknod-error-invalid-mode",
@@ -237,7 +239,7 @@ fn parse_mode(str_mode: &str) -> Result<mode_t, String> {
237239
if mode > 0o777 {
238240
Err(translate!("mknod-error-mode-permission-bits-only"))
239241
} else {
240-
Ok(mode)
242+
Ok(mode as mode_t)
241243
}
242244
})
243245
}

0 commit comments

Comments
 (0)