@@ -2864,3 +2864,145 @@ fn test_mv_xattr_enotsup_silent() {
28642864 std:: fs:: remove_file ( "/dev/shm/mv_test" ) . ok ( ) ;
28652865 }
28662866}
2867+
2868+ /// Test that symlinks inside directories are preserved during cross-device moves
2869+ /// (not expanded into full copies of their targets)
2870+ #[ test]
2871+ #[ cfg( target_os = "linux" ) ]
2872+ fn test_mv_cross_device_symlink_preserved ( ) {
2873+ use std:: fs;
2874+ use std:: os:: unix:: fs:: symlink;
2875+ use tempfile:: TempDir ;
2876+
2877+ let scene = TestScenario :: new ( util_name ! ( ) ) ;
2878+ let at = & scene. fixtures ;
2879+
2880+ // Create a directory with a symlink to /etc inside
2881+ at. mkdir ( "src_dir" ) ;
2882+ at. write ( "src_dir/local.txt" , "local content" ) ;
2883+ symlink ( "/etc" , at. plus ( "src_dir/etc_link" ) ) . expect ( "Failed to create symlink" ) ;
2884+
2885+ assert ! ( at. is_symlink( "src_dir/etc_link" ) ) ;
2886+
2887+ // Force cross-filesystem move using /dev/shm (tmpfs)
2888+ let target_dir =
2889+ TempDir :: new_in ( "/dev/shm/" ) . expect ( "Unable to create temp directory in /dev/shm" ) ;
2890+ let target_path = target_dir. path ( ) . join ( "dst_dir" ) ;
2891+
2892+ scene
2893+ . ucmd ( )
2894+ . arg ( "src_dir" )
2895+ . arg ( target_path. to_str ( ) . unwrap ( ) )
2896+ . succeeds ( )
2897+ . no_stderr ( ) ;
2898+
2899+ assert ! ( !at. dir_exists( "src_dir" ) ) ;
2900+
2901+ // Verify the symlink was preserved (not expanded)
2902+ let moved_symlink = target_path. join ( "etc_link" ) ;
2903+ assert ! (
2904+ moved_symlink. is_symlink( ) ,
2905+ "etc_link should still be a symlink after cross-device move"
2906+ ) ;
2907+ assert_eq ! (
2908+ fs:: read_link( & moved_symlink) . expect( "Failed to read symlink" ) ,
2909+ Path :: new( "/etc" ) ,
2910+ "symlink should still point to /etc"
2911+ ) ;
2912+
2913+ assert ! ( target_path. join( "local.txt" ) . exists( ) ) ;
2914+ }
2915+
2916+ /// Test that broken/dangling symlinks are preserved during cross-device moves
2917+ #[ test]
2918+ #[ cfg( target_os = "linux" ) ]
2919+ fn test_mv_cross_device_broken_symlink_preserved ( ) {
2920+ use std:: fs;
2921+ use std:: os:: unix:: fs:: symlink;
2922+ use tempfile:: TempDir ;
2923+
2924+ let scene = TestScenario :: new ( util_name ! ( ) ) ;
2925+ let at = & scene. fixtures ;
2926+
2927+ // Create a directory with a broken symlink inside
2928+ at. mkdir ( "src_dir" ) ;
2929+ symlink ( "/nonexistent/path" , at. plus ( "src_dir/broken_link" ) )
2930+ . expect ( "Failed to create broken symlink" ) ;
2931+
2932+ assert ! ( at. is_symlink( "src_dir/broken_link" ) ) ;
2933+ assert ! ( !at. file_exists( "src_dir/broken_link" ) ) ;
2934+
2935+ // Force cross-filesystem move using /dev/shm (tmpfs)
2936+ let target_dir =
2937+ TempDir :: new_in ( "/dev/shm/" ) . expect ( "Unable to create temp directory in /dev/shm" ) ;
2938+ let target_path = target_dir. path ( ) . join ( "dst_dir" ) ;
2939+
2940+ scene
2941+ . ucmd ( )
2942+ . arg ( "src_dir" )
2943+ . arg ( target_path. to_str ( ) . unwrap ( ) )
2944+ . succeeds ( )
2945+ . no_stderr ( ) ;
2946+
2947+ assert ! ( !at. dir_exists( "src_dir" ) ) ;
2948+
2949+ let moved_symlink = target_path. join ( "broken_link" ) ;
2950+ assert ! (
2951+ moved_symlink. is_symlink( ) ,
2952+ "broken_link should still be a symlink after cross-device move"
2953+ ) ;
2954+ assert_eq ! (
2955+ fs:: read_link( & moved_symlink) . expect( "Failed to read broken symlink" ) ,
2956+ Path :: new( "/nonexistent/path" ) ,
2957+ "broken symlink should still point to its original (nonexistent) target"
2958+ ) ;
2959+ }
2960+
2961+ /// Test that symlinks to regular files are preserved during cross-device moves
2962+ #[ test]
2963+ #[ cfg( target_os = "linux" ) ]
2964+ fn test_mv_cross_device_file_symlink_preserved ( ) {
2965+ use std:: fs;
2966+ use std:: os:: unix:: fs:: symlink;
2967+ use tempfile:: TempDir ;
2968+
2969+ let scene = TestScenario :: new ( util_name ! ( ) ) ;
2970+ let at = & scene. fixtures ;
2971+
2972+ // Create a directory with a file and a symlink to it
2973+ at. mkdir ( "src_dir" ) ;
2974+ at. write ( "src_dir/target.txt" , "target content" ) ;
2975+ symlink ( at. plus ( "src_dir/target.txt" ) , at. plus ( "src_dir/file_link" ) )
2976+ . expect ( "Failed to create file symlink" ) ;
2977+
2978+ assert ! ( at. is_symlink( "src_dir/file_link" ) ) ;
2979+
2980+ // Force cross-filesystem move using /dev/shm (tmpfs)
2981+ let target_dir =
2982+ TempDir :: new_in ( "/dev/shm/" ) . expect ( "Unable to create temp directory in /dev/shm" ) ;
2983+ let target_path = target_dir. path ( ) . join ( "dst_dir" ) ;
2984+
2985+ scene
2986+ . ucmd ( )
2987+ . arg ( "src_dir" )
2988+ . arg ( target_path. to_str ( ) . unwrap ( ) )
2989+ . succeeds ( )
2990+ . no_stderr ( ) ;
2991+
2992+ assert ! ( !at. dir_exists( "src_dir" ) ) ;
2993+
2994+ // Verify the symlink was preserved (not expanded)
2995+ let moved_symlink = target_path. join ( "file_link" ) ;
2996+ assert ! (
2997+ moved_symlink. is_symlink( ) ,
2998+ "file_link should still be a symlink after cross-device move"
2999+ ) ;
3000+
3001+ // Verify the target file was also moved
3002+ let moved_target = target_path. join ( "target.txt" ) ;
3003+ assert ! ( moved_target. exists( ) ) ;
3004+ assert_eq ! (
3005+ fs:: read_to_string( & moved_target) . expect( "Failed to read target file" ) ,
3006+ "target content"
3007+ ) ;
3008+ }
0 commit comments