@@ -9,12 +9,25 @@ use uucore::mode;
99use uucore:: translate;
1010
1111/// 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).
1213pub fn parse ( mode_string : & str , considering_dir : bool , umask : u32 ) -> Result < u32 , String > {
13- if mode_string. chars ( ) . any ( |c| c. is_ascii_digit ( ) ) {
14- mode:: parse_numeric ( 0 , mode_string, considering_dir)
15- } else {
16- mode:: parse_symbolic ( 0 , mode_string, umask, considering_dir)
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+ } ;
1728 }
29+
30+ Ok ( current_mode)
1831}
1932
2033/// chmod a file or directory on UNIX.
@@ -42,3 +55,128 @@ pub fn chmod(path: &Path, mode: u32) -> Result<(), ()> {
4255 // chmod on Windows only sets the readonly flag, which isn't even honored on directories
4356 Ok ( ( ) )
4457}
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+ }
0 commit comments