@@ -944,10 +944,8 @@ fn test_chmod_dangling_symlink_recursive_combos() {
944944 at. symlink_file ( dangling_target, symlink) ;
945945
946946 let mut ucmd = scene. ucmd ( ) ;
947- for f in & flags {
948- ucmd. arg ( f) ;
949- }
950- ucmd. arg ( "u+x" )
947+ ucmd. args ( & flags)
948+ . arg ( "u+x" )
951949 . umask ( 0o022 )
952950 . arg ( symlink)
953951 . fails ( )
@@ -1002,10 +1000,8 @@ fn test_chmod_traverse_symlink_combo() {
10021000 set_permissions ( at. plus ( target) , Permissions :: from_mode ( 0o664 ) ) . unwrap ( ) ;
10031001
10041002 let mut ucmd = scene. ucmd ( ) ;
1005- for f in & flags {
1006- ucmd. arg ( f) ;
1007- }
1008- ucmd. arg ( "u+x" )
1003+ ucmd. args ( & flags)
1004+ . arg ( "u+x" )
10091005 . umask ( 0o022 )
10101006 . arg ( directory)
10111007 . succeeds ( )
@@ -1027,3 +1023,163 @@ fn test_chmod_traverse_symlink_combo() {
10271023 ) ;
10281024 }
10291025}
1026+
1027+ #[ test]
1028+ fn test_chmod_recursive_symlink_to_directory_command_line ( ) {
1029+ // Test behavior when the symlink itself is a command-line argument
1030+ let scenarios = [
1031+ ( vec ! [ "-R" ] , true ) , // Default behavior (-H): follow symlinks that are command line args
1032+ ( vec ! [ "-R" , "-H" ] , true ) , // Explicit -H: follow symlinks that are command line args
1033+ ( vec ! [ "-R" , "-L" ] , true ) , // -L: follow all symlinks
1034+ ( vec ! [ "-R" , "-P" ] , false ) , // -P: never follow symlinks
1035+ ] ;
1036+
1037+ for ( flags, should_follow_symlink_dir) in scenarios {
1038+ let scene = TestScenario :: new ( util_name ! ( ) ) ;
1039+ let at = & scene. fixtures ;
1040+
1041+ let target_dir = "target_dir" ;
1042+ let symlink_to_dir = "link_dir" ;
1043+ let file_in_target = "file_in_target" ;
1044+
1045+ at. mkdir ( target_dir) ;
1046+ at. touch ( format ! ( "{target_dir}/{file_in_target}" ) ) ;
1047+ at. symlink_dir ( target_dir, symlink_to_dir) ;
1048+
1049+ set_permissions (
1050+ at. plus ( format ! ( "{target_dir}/{file_in_target}" ) ) ,
1051+ Permissions :: from_mode ( 0o644 ) ,
1052+ )
1053+ . unwrap ( ) ;
1054+
1055+ let mut ucmd = scene. ucmd ( ) ;
1056+ ucmd. args ( & flags)
1057+ . arg ( "go-rwx" )
1058+ . arg ( symlink_to_dir) // The symlink itself is the command-line argument
1059+ . succeeds ( )
1060+ . no_stderr ( ) ;
1061+
1062+ let actual_file_perms = at
1063+ . metadata ( & format ! ( "{target_dir}/{file_in_target}" ) )
1064+ . permissions ( )
1065+ . mode ( ) ;
1066+
1067+ if should_follow_symlink_dir {
1068+ // When following symlinks, the file inside the target directory should have its permissions changed
1069+ assert_eq ! (
1070+ actual_file_perms, 0o100_600 ,
1071+ "For flags {flags:?}, expected file perms when following symlinks = 600, got = {actual_file_perms:o}" ,
1072+ ) ;
1073+ } else {
1074+ // When not following symlinks, the file inside the target directory should be unchanged
1075+ assert_eq ! (
1076+ actual_file_perms, 0o100_644 ,
1077+ "For flags {flags:?}, expected file perms when not following symlinks = 644, got = {actual_file_perms:o}" ,
1078+ ) ;
1079+ }
1080+ }
1081+ }
1082+
1083+ #[ test]
1084+ fn test_chmod_recursive_symlink_during_traversal ( ) {
1085+ // Test behavior when symlinks are encountered during directory traversal
1086+ let scenarios = [
1087+ ( vec ! [ "-R" ] , false ) , // Default behavior (-H): don't follow symlinks encountered during traversal
1088+ ( vec ! [ "-R" , "-H" ] , false ) , // Explicit -H: don't follow symlinks encountered during traversal
1089+ ( vec ! [ "-R" , "-L" ] , true ) , // -L: follow all symlinks including those found during traversal
1090+ ( vec ! [ "-R" , "-P" ] , false ) , // -P: never follow symlinks
1091+ ] ;
1092+
1093+ for ( flags, should_follow_symlink_dir) in scenarios {
1094+ let scene = TestScenario :: new ( util_name ! ( ) ) ;
1095+ let at = & scene. fixtures ;
1096+
1097+ let directory = "dir" ;
1098+ let target_dir = "target_dir" ;
1099+ let symlink_to_dir = "link_dir" ;
1100+ let file_in_target = "file_in_target" ;
1101+
1102+ at. mkdir ( directory) ;
1103+ at. mkdir ( target_dir) ;
1104+ at. touch ( format ! ( "{target_dir}/{file_in_target}" ) ) ;
1105+ at. symlink_dir ( target_dir, & format ! ( "{directory}/{symlink_to_dir}" ) ) ;
1106+
1107+ set_permissions (
1108+ at. plus ( format ! ( "{target_dir}/{file_in_target}" ) ) ,
1109+ Permissions :: from_mode ( 0o644 ) ,
1110+ )
1111+ . unwrap ( ) ;
1112+
1113+ let mut ucmd = scene. ucmd ( ) ;
1114+ ucmd. args ( & flags)
1115+ . arg ( "go-rwx" )
1116+ . arg ( directory) // The directory is the command-line argument
1117+ . succeeds ( )
1118+ . no_stderr ( ) ;
1119+
1120+ let actual_file_perms = at
1121+ . metadata ( & format ! ( "{target_dir}/{file_in_target}" ) )
1122+ . permissions ( )
1123+ . mode ( ) ;
1124+
1125+ if should_follow_symlink_dir {
1126+ // When following symlinks, the file inside the target directory should have its permissions changed
1127+ assert_eq ! (
1128+ actual_file_perms, 0o100_600 ,
1129+ "For flags {flags:?}, expected file perms when following symlinks = 600, got = {actual_file_perms:o}" ,
1130+ ) ;
1131+ } else {
1132+ // When not following symlinks, the file inside the target directory should be unchanged
1133+ assert_eq ! (
1134+ actual_file_perms, 0o100_644 ,
1135+ "For flags {flags:?}, expected file perms when not following symlinks = 644, got = {actual_file_perms:o}" ,
1136+ ) ;
1137+ }
1138+ }
1139+ }
1140+
1141+ #[ test]
1142+ fn test_chmod_recursive_symlink_combinations ( ) {
1143+ let scene = TestScenario :: new ( util_name ! ( ) ) ;
1144+ let at = & scene. fixtures ;
1145+
1146+ let directory = "dir" ;
1147+ let target_dir = "target_dir" ;
1148+ let target_file = "target_file" ;
1149+ let symlink_to_dir = "link_dir" ;
1150+ let symlink_to_file = "link_file" ;
1151+ let file_in_target = "file" ;
1152+
1153+ at. mkdir ( directory) ;
1154+ at. mkdir ( target_dir) ;
1155+ at. touch ( target_file) ;
1156+ at. touch ( format ! ( "{target_dir}/{file_in_target}" ) ) ;
1157+ at. symlink_dir ( target_dir, & format ! ( "{directory}/{symlink_to_dir}" ) ) ;
1158+ at. symlink_file ( target_file, & format ! ( "{directory}/{symlink_to_file}" ) ) ;
1159+
1160+ set_permissions ( at. plus ( target_file) , Permissions :: from_mode ( 0o644 ) ) . unwrap ( ) ;
1161+ set_permissions (
1162+ at. plus ( format ! ( "{target_dir}/{file_in_target}" ) ) ,
1163+ Permissions :: from_mode ( 0o644 ) ,
1164+ )
1165+ . unwrap ( ) ;
1166+
1167+ // Test with -R -L (follow all symlinks)
1168+ scene
1169+ . ucmd ( )
1170+ . arg ( "-R" )
1171+ . arg ( "-L" )
1172+ . arg ( "go-rwx" )
1173+ . arg ( directory)
1174+ . succeeds ( )
1175+ . no_stderr ( ) ;
1176+
1177+ // Both target file and file in target directory should have permissions changed
1178+ assert_eq ! ( at. metadata( target_file) . permissions( ) . mode( ) , 0o100_600 ) ;
1179+ assert_eq ! (
1180+ at. metadata( & format!( "{target_dir}/{file_in_target}" ) )
1181+ . permissions( )
1182+ . mode( ) ,
1183+ 0o100_600
1184+ ) ;
1185+ }
0 commit comments