@@ -269,6 +269,104 @@ struct Chmoder {
269269}
270270
271271impl Chmoder {
272+ /// Calculate the new mode based on the current mode and the chmod specification.
273+ /// Returns (`new_mode`, `naively_expected_new_mode`) for symbolic modes, or (`new_mode`, `new_mode`) for numeric/reference modes.
274+ fn calculate_new_mode ( & self , current_mode : u32 , is_dir : bool ) -> UResult < ( u32 , u32 ) > {
275+ match self . fmode {
276+ Some ( mode) => Ok ( ( mode, mode) ) ,
277+ None => {
278+ let cmode_unwrapped = self . cmode . clone ( ) . unwrap ( ) ;
279+ let mut new_mode = current_mode;
280+ let mut naively_expected_new_mode = current_mode;
281+
282+ for mode in cmode_unwrapped. split ( ',' ) {
283+ let result = if mode. chars ( ) . any ( |c| c. is_ascii_digit ( ) ) {
284+ mode:: parse_numeric ( new_mode, mode, is_dir) . map ( |v| ( v, v) )
285+ } else {
286+ mode:: parse_symbolic ( new_mode, mode, mode:: get_umask ( ) , is_dir) . map ( |m| {
287+ // calculate the new mode as if umask was 0
288+ let naive_mode =
289+ mode:: parse_symbolic ( naively_expected_new_mode, mode, 0 , is_dir)
290+ . unwrap ( ) ; // we know that mode must be valid, so this cannot fail
291+ ( m, naive_mode)
292+ } )
293+ } ;
294+
295+ match result {
296+ Ok ( ( mode, naive_mode) ) => {
297+ new_mode = mode;
298+ naively_expected_new_mode = naive_mode;
299+ }
300+ Err ( f) => {
301+ return if self . quiet {
302+ Err ( ExitCode :: new ( 1 ) )
303+ } else {
304+ Err ( USimpleError :: new ( 1 , f) )
305+ } ;
306+ }
307+ }
308+ }
309+ Ok ( ( new_mode, naively_expected_new_mode) )
310+ }
311+ }
312+ }
313+
314+ /// Report permission changes based on verbose and changes flags
315+ fn report_permission_change ( & self , file_path : & Path , old_mode : u32 , new_mode : u32 ) {
316+ if self . verbose || self . changes {
317+ let current_permissions = display_permissions_unix ( old_mode as mode_t , false ) ;
318+ let new_permissions = display_permissions_unix ( new_mode as mode_t , false ) ;
319+
320+ if new_mode != old_mode {
321+ println ! (
322+ "mode of {} changed from {:04o} ({}) to {:04o} ({})" ,
323+ file_path. quote( ) ,
324+ old_mode,
325+ current_permissions,
326+ new_mode,
327+ new_permissions
328+ ) ;
329+ } else if self . verbose {
330+ println ! (
331+ "mode of {} retained as {:04o} ({})" ,
332+ file_path. quote( ) ,
333+ old_mode,
334+ current_permissions
335+ ) ;
336+ }
337+ }
338+ }
339+
340+ /// Handle symlinks during directory traversal based on traversal mode
341+ #[ cfg( not( target_os = "linux" ) ) ]
342+ fn handle_symlink_during_traversal (
343+ & self ,
344+ path : & Path ,
345+ is_command_line_arg : bool ,
346+ ) -> UResult < ( ) > {
347+ let should_follow_symlink = match self . traverse_symlinks {
348+ TraverseSymlinks :: All => true ,
349+ TraverseSymlinks :: First => is_command_line_arg,
350+ TraverseSymlinks :: None => false ,
351+ } ;
352+
353+ if !should_follow_symlink {
354+ return self . chmod_file_internal ( path, false ) ;
355+ }
356+
357+ match fs:: metadata ( path) {
358+ Ok ( meta) if meta. is_dir ( ) => self . walk_dir_with_context ( path, false ) ,
359+ Ok ( _) => {
360+ // It's a file symlink, chmod it
361+ self . chmod_file ( path)
362+ }
363+ Err ( _) => {
364+ // Dangling symlink, chmod it without dereferencing
365+ self . chmod_file_internal ( path, false )
366+ }
367+ }
368+ }
369+
272370 fn chmod ( & self , files : & [ OsString ] ) -> UResult < ( ) > {
273371 let mut r = Ok ( ( ) ) ;
274372
@@ -371,9 +469,7 @@ impl Chmoder {
371469 if ( !file_path. is_symlink ( ) || should_follow_symlink) && file_path. is_dir ( ) {
372470 match DirFd :: open ( file_path) {
373471 Ok ( dir_fd) => {
374- r = self
375- . safe_traverse_dir ( & dir_fd, file_path, is_command_line_arg)
376- . and ( r) ;
472+ r = self . safe_traverse_dir ( & dir_fd, file_path) . and ( r) ;
377473 }
378474 Err ( err) => {
379475 // Handle permission denied errors with proper file path context
@@ -392,21 +488,10 @@ impl Chmoder {
392488 }
393489
394490 #[ cfg( target_os = "linux" ) ]
395- fn safe_traverse_dir (
396- & self ,
397- dir_fd : & DirFd ,
398- dir_path : & Path ,
399- _is_command_line_arg : bool ,
400- ) -> UResult < ( ) > {
491+ fn safe_traverse_dir ( & self , dir_fd : & DirFd , dir_path : & Path ) -> UResult < ( ) > {
401492 let mut r = Ok ( ( ) ) ;
402493
403- // Read directory entries
404- let entries = match dir_fd. read_dir ( ) {
405- Ok ( entries) => entries,
406- Err ( e) => {
407- return Err ( e. into ( ) ) ;
408- }
409- } ;
494+ let entries = dir_fd. read_dir ( ) ?;
410495
411496 for entry_name in entries {
412497 let entry_path = dir_path. join ( & entry_name) ;
@@ -489,35 +574,8 @@ impl Chmoder {
489574 entry_name : & std:: ffi:: OsStr ,
490575 current_mode : u32 ,
491576 ) -> UResult < ( ) > {
492- // Determine the new permissions to apply
493- let new_mode = match self . fmode {
494- Some ( mode) => mode,
495- None => {
496- let cmode_unwrapped = self . cmode . clone ( ) . unwrap ( ) ;
497- let mut new_mode = current_mode;
498- for mode in cmode_unwrapped. split ( ',' ) {
499- let result = if mode. chars ( ) . any ( |c| c. is_ascii_digit ( ) ) {
500- mode:: parse_numeric ( new_mode, mode, file_path. is_dir ( ) )
501- } else {
502- mode:: parse_symbolic ( new_mode, mode, mode:: get_umask ( ) , file_path. is_dir ( ) )
503- } ;
504-
505- match result {
506- Ok ( parsed_mode) => {
507- new_mode = parsed_mode;
508- }
509- Err ( f) => {
510- return if self . quiet {
511- Err ( ExitCode :: new ( 1 ) )
512- } else {
513- Err ( USimpleError :: new ( 1 , f) )
514- } ;
515- }
516- }
517- }
518- new_mode
519- }
520- } ;
577+ // Calculate the new mode using the helper method
578+ let ( new_mode, _) = self . calculate_new_mode ( current_mode, file_path. is_dir ( ) ) ?;
521579
522580 // Use safe traversal to change the mode
523581 let follow_symlinks = self . dereference ;
@@ -534,67 +592,24 @@ impl Chmoder {
534592 ) ;
535593 }
536594
537- // Report the change if verbose or changes mode
538- if self . verbose || self . changes {
539- let current_permissions = display_permissions_unix ( current_mode, false ) ;
540- let new_permissions = display_permissions_unix ( new_mode, false ) ;
541- if new_mode != current_mode {
542- if self . changes || self . verbose {
543- println ! (
544- "mode of {} changed from {} ({:04o}) to {} ({:04o})" ,
545- file_path. quote( ) ,
546- current_permissions,
547- current_mode,
548- new_permissions,
549- new_mode
550- ) ;
551- }
552- } else if self . verbose {
553- println ! (
554- "mode of {} retained as {} ({:04o})" ,
555- file_path. quote( ) ,
556- current_permissions,
557- current_mode
558- ) ;
559- }
560- }
595+ // Report the change using the helper method
596+ self . report_permission_change ( file_path, current_mode, new_mode) ;
561597
562598 Ok ( ( ) )
563599 }
564600
565601 #[ cfg( not( target_os = "linux" ) ) ]
566602 fn handle_symlink_during_recursion ( & self , path : & Path ) -> UResult < ( ) > {
567- // During recursion, determine behavior based on traversal mode
568- match self . traverse_symlinks {
569- TraverseSymlinks :: All => {
570- // Follow all symlinks during recursion
571- // Check if the symlink target is a directory, but handle dangling symlinks gracefully
572- match fs:: metadata ( path) {
573- Ok ( meta) if meta. is_dir ( ) => self . walk_dir_with_context ( path, false ) ,
574- Ok ( _) => {
575- // It's a file symlink, chmod it
576- self . chmod_file ( path)
577- }
578- Err ( _) => {
579- // Dangling symlink, chmod it without dereferencing
580- self . chmod_file_internal ( path, false )
581- }
582- }
583- }
584- TraverseSymlinks :: First | TraverseSymlinks :: None => {
585- // Don't follow symlinks encountered during recursion
586- // For these symlinks, don't dereference them even if dereference is normally true
587- self . chmod_file_internal ( path, false )
588- }
589- }
603+ // Use the common symlink handling logic
604+ self . handle_symlink_during_traversal ( path, false )
590605 }
591606
592607 fn chmod_file ( & self , file : & Path ) -> UResult < ( ) > {
593608 self . chmod_file_internal ( file, self . dereference )
594609 }
595610
596611 fn chmod_file_internal ( & self , file : & Path , dereference : bool ) -> UResult < ( ) > {
597- use uucore:: { mode :: get_umask , perms:: get_metadata} ;
612+ use uucore:: perms:: get_metadata;
598613
599614 let metadata = get_metadata ( file, dereference) ;
600615
@@ -620,45 +635,14 @@ impl Chmoder {
620635 }
621636 } ;
622637
623- // Determine the new permissions to apply
638+ // Calculate the new mode using the helper method
639+ let ( new_mode, naively_expected_new_mode) =
640+ self . calculate_new_mode ( fperm, file. is_dir ( ) ) ?;
641+
642+ // Determine how to apply the permissions
624643 match self . fmode {
625644 Some ( mode) => self . change_file ( fperm, mode, file) ?,
626645 None => {
627- let cmode_unwrapped = self . cmode . clone ( ) . unwrap ( ) ;
628- let mut new_mode = fperm;
629- let mut naively_expected_new_mode = new_mode;
630- for mode in cmode_unwrapped. split ( ',' ) {
631- let result = if mode. chars ( ) . any ( |c| c. is_ascii_digit ( ) ) {
632- mode:: parse_numeric ( new_mode, mode, file. is_dir ( ) ) . map ( |v| ( v, v) )
633- } else {
634- mode:: parse_symbolic ( new_mode, mode, get_umask ( ) , file. is_dir ( ) ) . map ( |m| {
635- // calculate the new mode as if umask was 0
636- let naive_mode = mode:: parse_symbolic (
637- naively_expected_new_mode,
638- mode,
639- 0 ,
640- file. is_dir ( ) ,
641- )
642- . unwrap ( ) ; // we know that mode must be valid, so this cannot fail
643- ( m, naive_mode)
644- } )
645- } ;
646-
647- match result {
648- Ok ( ( mode, naive_mode) ) => {
649- new_mode = mode;
650- naively_expected_new_mode = naive_mode;
651- }
652- Err ( f) => {
653- return if self . quiet {
654- Err ( ExitCode :: new ( 1 ) )
655- } else {
656- Err ( USimpleError :: new ( 1 , f) )
657- } ;
658- }
659- }
660- }
661-
662646 // Special handling for symlinks when not dereferencing
663647 if file. is_symlink ( ) && !dereference {
664648 // TODO: On most Unix systems, symlink permissions are ignored by the kernel,
@@ -690,13 +674,8 @@ impl Chmoder {
690674
691675 fn change_file ( & self , fperm : u32 , mode : u32 , file : & Path ) -> Result < ( ) , i32 > {
692676 if fperm == mode {
693- if self . verbose && !self . changes {
694- println ! (
695- "mode of {} retained as {fperm:04o} ({})" ,
696- file. quote( ) ,
697- display_permissions_unix( fperm as mode_t, false ) ,
698- ) ;
699- }
677+ // Use the helper method for consistent reporting
678+ self . report_permission_change ( file, fperm, mode) ;
700679 Ok ( ( ) )
701680 } else if let Err ( err) = fs:: set_permissions ( file, fs:: Permissions :: from_mode ( mode) ) {
702681 if !self . quiet {
@@ -712,14 +691,8 @@ impl Chmoder {
712691 }
713692 Err ( 1 )
714693 } else {
715- if self . verbose || self . changes {
716- println ! (
717- "mode of {} changed from {fperm:04o} ({}) to {mode:04o} ({})" ,
718- file. quote( ) ,
719- display_permissions_unix( fperm as mode_t, false ) ,
720- display_permissions_unix( mode as mode_t, false )
721- ) ;
722- }
694+ // Use the helper method for consistent reporting
695+ self . report_permission_change ( file, fperm, mode) ;
723696 Ok ( ( ) )
724697 }
725698 }
0 commit comments