@@ -269,6 +269,117 @@ 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+ if self . changes || self . verbose {
322+ println ! (
323+ "mode of {} changed from {:04o} ({}) to {:04o} ({})" ,
324+ file_path. quote( ) ,
325+ old_mode,
326+ current_permissions,
327+ new_mode,
328+ new_permissions
329+ ) ;
330+ }
331+ } else if self . verbose {
332+ println ! (
333+ "mode of {} retained as {:04o} ({})" ,
334+ file_path. quote( ) ,
335+ old_mode,
336+ current_permissions
337+ ) ;
338+ }
339+ }
340+ }
341+
342+ /// Handle symlinks during directory traversal based on traversal mode
343+ #[ cfg( not( target_os = "linux" ) ) ]
344+ fn handle_symlink_during_traversal (
345+ & self ,
346+ path : & Path ,
347+ is_command_line_arg : bool ,
348+ ) -> UResult < ( ) > {
349+ match self . traverse_symlinks {
350+ TraverseSymlinks :: All => {
351+ // Follow all symlinks during traversal
352+ match fs:: metadata ( path) {
353+ Ok ( meta) if meta. is_dir ( ) => self . walk_dir_with_context ( path, false ) ,
354+ Ok ( _) => {
355+ // It's a file symlink, chmod it
356+ self . chmod_file ( path)
357+ }
358+ Err ( _) => {
359+ // Dangling symlink, chmod it without dereferencing
360+ self . chmod_file_internal ( path, false )
361+ }
362+ }
363+ }
364+ TraverseSymlinks :: First => {
365+ if is_command_line_arg {
366+ // Follow symlinks that are command line args
367+ match fs:: metadata ( path) {
368+ Ok ( meta) if meta. is_dir ( ) => self . walk_dir_with_context ( path, false ) ,
369+ Ok ( _) => self . chmod_file ( path) ,
370+ Err ( _) => self . chmod_file_internal ( path, false ) ,
371+ }
372+ } else {
373+ // Don't follow symlinks encountered during recursion
374+ self . chmod_file_internal ( path, false )
375+ }
376+ }
377+ TraverseSymlinks :: None => {
378+ // Don't follow symlinks
379+ self . chmod_file_internal ( path, false )
380+ }
381+ }
382+ }
272383 fn chmod ( & self , files : & [ OsString ] ) -> UResult < ( ) > {
273384 let mut r = Ok ( ( ) ) ;
274385
@@ -489,35 +600,8 @@ impl Chmoder {
489600 entry_name : & std:: ffi:: OsStr ,
490601 current_mode : u32 ,
491602 ) -> 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- } ;
603+ // Calculate the new mode using the helper method
604+ let ( new_mode, _) = self . calculate_new_mode ( current_mode, file_path. is_dir ( ) ) ?;
521605
522606 // Use safe traversal to change the mode
523607 let follow_symlinks = self . dereference ;
@@ -534,67 +618,24 @@ impl Chmoder {
534618 ) ;
535619 }
536620
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- }
621+ // Report the change using the helper method
622+ self . report_permission_change ( file_path, current_mode, new_mode) ;
561623
562624 Ok ( ( ) )
563625 }
564626
565627 #[ cfg( not( target_os = "linux" ) ) ]
566628 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- }
629+ // Use the common symlink handling logic
630+ self . handle_symlink_during_traversal ( path, false )
590631 }
591632
592633 fn chmod_file ( & self , file : & Path ) -> UResult < ( ) > {
593634 self . chmod_file_internal ( file, self . dereference )
594635 }
595636
596637 fn chmod_file_internal ( & self , file : & Path , dereference : bool ) -> UResult < ( ) > {
597- use uucore:: { mode :: get_umask , perms:: get_metadata} ;
638+ use uucore:: perms:: get_metadata;
598639
599640 let metadata = get_metadata ( file, dereference) ;
600641
@@ -620,45 +661,14 @@ impl Chmoder {
620661 }
621662 } ;
622663
623- // Determine the new permissions to apply
664+ // Calculate the new mode using the helper method
665+ let ( new_mode, naively_expected_new_mode) =
666+ self . calculate_new_mode ( fperm, file. is_dir ( ) ) ?;
667+
668+ // Determine how to apply the permissions
624669 match self . fmode {
625670 Some ( mode) => self . change_file ( fperm, mode, file) ?,
626671 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-
662672 // Special handling for symlinks when not dereferencing
663673 if file. is_symlink ( ) && !dereference {
664674 // TODO: On most Unix systems, symlink permissions are ignored by the kernel,
@@ -690,13 +700,8 @@ impl Chmoder {
690700
691701 fn change_file ( & self , fperm : u32 , mode : u32 , file : & Path ) -> Result < ( ) , i32 > {
692702 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- }
703+ // Use the helper method for consistent reporting
704+ self . report_permission_change ( file, fperm, mode) ;
700705 Ok ( ( ) )
701706 } else if let Err ( err) = fs:: set_permissions ( file, fs:: Permissions :: from_mode ( mode) ) {
702707 if !self . quiet {
@@ -712,14 +717,8 @@ impl Chmoder {
712717 }
713718 Err ( 1 )
714719 } 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- }
720+ // Use the helper method for consistent reporting
721+ self . report_permission_change ( file, fperm, mode) ;
723722 Ok ( ( ) )
724723 }
725724 }
0 commit comments