@@ -16,6 +16,7 @@ use uucore::fs::display_permissions_unix;
1616use uucore:: libc:: mode_t;
1717#[ cfg( not( windows) ) ]
1818use uucore:: mode;
19+ use uucore:: perms:: { configure_symlink_and_recursion, TraverseSymlinks } ;
1920use uucore:: { format_usage, help_about, help_section, help_usage, show, show_error} ;
2021
2122const ABOUT : & str = help_about ! ( "chmod.md" ) ;
@@ -99,7 +100,6 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
99100 let quiet = matches. get_flag ( options:: QUIET ) ;
100101 let verbose = matches. get_flag ( options:: VERBOSE ) ;
101102 let preserve_root = matches. get_flag ( options:: PRESERVE_ROOT ) ;
102- let recursive = matches. get_flag ( options:: RECURSIVE ) ;
103103 let fmode = match matches. get_one :: < String > ( options:: REFERENCE ) {
104104 Some ( fref) => match fs:: metadata ( fref) {
105105 Ok ( meta) => Some ( meta. mode ( ) & 0o7777 ) ,
@@ -138,6 +138,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
138138 return Err ( UUsageError :: new ( 1 , "missing operand" . to_string ( ) ) ) ;
139139 }
140140
141+ let ( recursive, dereference, traverse_symlinks) = configure_symlink_and_recursion ( & matches) ?;
142+
141143 let chmoder = Chmoder {
142144 changes,
143145 quiet,
@@ -146,6 +148,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
146148 recursive,
147149 fmode,
148150 cmode,
151+ traverse_symlinks,
152+ dereference,
149153 } ;
150154
151155 chmoder. chmod ( & files)
@@ -237,6 +241,8 @@ struct Chmoder {
237241 recursive : bool ,
238242 fmode : Option < u32 > ,
239243 cmode : Option < String > ,
244+ traverse_symlinks : TraverseSymlinks ,
245+ dereference : bool ,
240246}
241247
242248impl Chmoder {
@@ -248,12 +254,19 @@ impl Chmoder {
248254 let file = Path :: new ( filename) ;
249255 if !file. exists ( ) {
250256 if file. is_symlink ( ) {
257+ if !self . dereference && !self . recursive {
258+ // The file is a symlink and we should not follow it
259+ // Don't try to change the mode of the symlink itself
260+ continue ;
261+ }
251262 if !self . quiet {
252263 show ! ( USimpleError :: new(
253264 1 ,
254265 format!( "cannot operate on dangling symlink {}" , filename. quote( ) ) ,
255266 ) ) ;
267+ set_exit_code ( 1 ) ;
256268 }
269+
257270 if self . verbose {
258271 println ! (
259272 "failed to change mode of {} from 0000 (---------) to 1500 (r-x-----T)" ,
@@ -273,6 +286,11 @@ impl Chmoder {
273286 // So we set the exit code, because it hasn't been set yet if `self.quiet` is true.
274287 set_exit_code ( 1 ) ;
275288 continue ;
289+ } else if !self . dereference && file. is_symlink ( ) {
290+ // The file is a symlink and we should not follow it
291+ // chmod 755 --no-dereference a/link
292+ // should not change the permissions in this case
293+ continue ;
276294 }
277295 if self . recursive && self . preserve_root && filename == "/" {
278296 return Err ( USimpleError :: new (
@@ -294,11 +312,23 @@ impl Chmoder {
294312
295313 fn walk_dir ( & self , file_path : & Path ) -> UResult < ( ) > {
296314 let mut r = self . chmod_file ( file_path) ;
297- if !file_path. is_symlink ( ) && file_path. is_dir ( ) {
315+ // Determine whether to traverse symlinks based on `self.traverse_symlinks`
316+ let should_follow_symlink = match self . traverse_symlinks {
317+ TraverseSymlinks :: All => true ,
318+ TraverseSymlinks :: First => {
319+ file_path == file_path. canonicalize ( ) . unwrap_or ( file_path. to_path_buf ( ) )
320+ }
321+ TraverseSymlinks :: None => false ,
322+ } ;
323+
324+ // If the path is a directory (or we should follow symlinks), recurse into it
325+ if ( !file_path. is_symlink ( ) || should_follow_symlink) && file_path. is_dir ( ) {
298326 for dir_entry in file_path. read_dir ( ) ? {
299327 let path = dir_entry?. path ( ) ;
300328 if !path. is_symlink ( ) {
301329 r = self . walk_dir ( path. as_path ( ) ) ;
330+ } else if should_follow_symlink {
331+ r = self . chmod_file ( path. as_path ( ) ) . and ( r) ;
302332 }
303333 }
304334 }
@@ -314,19 +344,22 @@ impl Chmoder {
314344 }
315345 #[ cfg( unix) ]
316346 fn chmod_file ( & self , file : & Path ) -> UResult < ( ) > {
317- use uucore:: mode:: get_umask;
347+ use uucore:: { mode:: get_umask, perms:: get_metadata} ;
348+
349+ let metadata = get_metadata ( file, self . dereference ) ;
318350
319- let fperm = match fs :: metadata ( file ) {
351+ let fperm = match metadata {
320352 Ok ( meta) => meta. mode ( ) & 0o7777 ,
321353 Err ( err) => {
322- if file. is_symlink ( ) {
354+ // Handle dangling symlinks or other errors
355+ if file. is_symlink ( ) && !self . dereference {
323356 if self . verbose {
324357 println ! (
325358 "neither symbolic link {} nor referent has been changed" ,
326359 file. quote( )
327360 ) ;
328361 }
329- return Ok ( ( ) ) ;
362+ return Ok ( ( ) ) ; // Skip dangling symlinks
330363 } else if err. kind ( ) == std:: io:: ErrorKind :: PermissionDenied {
331364 // These two filenames would normally be conditionally
332365 // quoted, but GNU's tests expect them to always be quoted
@@ -339,6 +372,8 @@ impl Chmoder {
339372 }
340373 }
341374 } ;
375+
376+ // Determine the new permissions to apply
342377 match self . fmode {
343378 Some ( mode) => self . change_file ( fperm, mode, file) ?,
344379 None => {
0 commit comments