@@ -7079,3 +7079,166 @@ fn test_cp_no_dereference_symlink_with_parents() {
70797079 . succeeds ( ) ;
70807080 assert_eq ! ( at. resolve_link( "x/symlink-to-directory" ) , "directory" ) ;
70817081}
7082+
7083+ /// Test for copying character device files with -r flag.
7084+ /// This ensures that cp -r creates device files instead of copying content,
7085+ /// preventing infinite loops when copying devices like /dev/urandom.
7086+ #[ cfg( unix) ]
7087+ #[ test]
7088+ fn test_cp_char_device ( ) {
7089+ use uutests:: util:: run_ucmd_as_root;
7090+
7091+ let scenario = TestScenario :: new ( util_name ! ( ) ) ;
7092+ let at = & scenario. fixtures ;
7093+
7094+ // Create a character device using mknod (requires root)
7095+ // Using major=1, minor=9 (same as /dev/urandom for consistency)
7096+ if let Ok ( result) = run_ucmd_as_root (
7097+ & TestScenario :: new ( "mknod" ) ,
7098+ & [ "test_char_dev" , "c" , "1" , "9" ] ,
7099+ ) {
7100+ result. success ( ) ;
7101+
7102+ // Test copying the character device with -r
7103+ let mut ucmd = scenario. ucmd ( ) ;
7104+ ucmd. arg ( "-r" )
7105+ . arg ( "test_char_dev" )
7106+ . arg ( "copied_char_dev" )
7107+ . succeeds ( )
7108+ . no_stderr ( ) ;
7109+
7110+ // Verify the copied file is also a character device
7111+ assert ! ( at. is_char_device( "copied_char_dev" ) ) ;
7112+
7113+ // Verify device numbers match
7114+ let orig_metadata = std:: fs:: metadata ( at. plus ( "test_char_dev" ) ) . unwrap ( ) ;
7115+ let copy_metadata = std:: fs:: metadata ( at. plus ( "copied_char_dev" ) ) . unwrap ( ) ;
7116+ assert_eq ! ( orig_metadata. rdev( ) , copy_metadata. rdev( ) ) ;
7117+ } else {
7118+ println ! ( "Test skipped; creating character devices requires root privileges" ) ;
7119+ }
7120+ }
7121+
7122+ /// Test for copying block device files with -r flag.
7123+ #[ cfg( unix) ]
7124+ #[ test]
7125+ fn test_cp_block_device ( ) {
7126+ use uutests:: util:: run_ucmd_as_root;
7127+
7128+ let scenario = TestScenario :: new ( util_name ! ( ) ) ;
7129+ let at = & scenario. fixtures ;
7130+
7131+ // Create a block device using mknod (requires root)
7132+ // Using major=7, minor=0 (similar to loop devices)
7133+ if let Ok ( result) = run_ucmd_as_root (
7134+ & TestScenario :: new ( "mknod" ) ,
7135+ & [ "test_block_dev" , "b" , "7" , "0" ] ,
7136+ ) {
7137+ result. success ( ) ;
7138+
7139+ // Test copying the block device with -r
7140+ let mut ucmd = scenario. ucmd ( ) ;
7141+ ucmd. arg ( "-r" )
7142+ . arg ( "test_block_dev" )
7143+ . arg ( "copied_block_dev" )
7144+ . succeeds ( )
7145+ . no_stderr ( ) ;
7146+
7147+ // Verify the copied file is a block device
7148+ // Note: we need a helper method for this, let's check metadata directly
7149+ let copy_metadata = std:: fs:: metadata ( at. plus ( "copied_block_dev" ) ) . unwrap ( ) ;
7150+ assert ! ( copy_metadata. file_type( ) . is_block_device( ) ) ;
7151+
7152+ // Verify device numbers match
7153+ let orig_metadata = std:: fs:: metadata ( at. plus ( "test_block_dev" ) ) . unwrap ( ) ;
7154+ assert_eq ! ( orig_metadata. rdev( ) , copy_metadata. rdev( ) ) ;
7155+ } else {
7156+ println ! ( "Test skipped; creating block devices requires root privileges" ) ;
7157+ }
7158+ }
7159+
7160+ /// Test that cp with --copy-contents still copies device content instead of creating device files.
7161+ /// This verifies the --copy-contents flag overrides the default device file creation behavior.
7162+ #[ cfg( unix) ]
7163+ #[ test]
7164+ fn test_cp_device_copy_contents ( ) {
7165+ use uutests:: util:: run_ucmd_as_root;
7166+
7167+ let scenario = TestScenario :: new ( util_name ! ( ) ) ;
7168+
7169+ // Create a character device using mknod (requires root)
7170+ if let Ok ( result) = run_ucmd_as_root (
7171+ & TestScenario :: new ( "mknod" ) ,
7172+ & [ "test_char_dev" , "c" , "1" , "8" ] , // Using /dev/random major/minor for safety
7173+ ) {
7174+ result. success ( ) ;
7175+
7176+ // Test copying with --copy-contents flag
7177+ // This should attempt to copy content, not create a device file
7178+ // We expect this to succeed and create a regular file (even if empty)
7179+ let mut ucmd = scenario. ucmd ( ) ;
7180+ ucmd. arg ( "-r" )
7181+ . arg ( "--copy-contents" )
7182+ . arg ( "test_char_dev" )
7183+ . arg ( "copied_content" )
7184+ . succeeds ( ) ;
7185+
7186+ // The result should be a regular file, not a character device
7187+ let copy_metadata = std:: fs:: metadata ( scenario. fixtures . plus ( "copied_content" ) ) . unwrap ( ) ;
7188+ assert ! ( copy_metadata. file_type( ) . is_file( ) ) ;
7189+ assert ! ( !copy_metadata. file_type( ) . is_char_device( ) ) ;
7190+ } else {
7191+ println ! ( "Test skipped; creating character devices requires root privileges" ) ;
7192+ }
7193+ }
7194+
7195+ /// Test error handling when trying to copy devices without sufficient permissions.
7196+ #[ cfg( unix) ]
7197+ #[ test]
7198+ fn test_cp_device_permission_error ( ) {
7199+ let scenario = TestScenario :: new ( util_name ! ( ) ) ;
7200+
7201+ // Try to copy a system device file to a location where we can't create device files
7202+ // This should show our proper error message
7203+ scenario. ucmd ( )
7204+ . arg ( "-r" )
7205+ . arg ( "/dev/null" )
7206+ . arg ( "/tmp/test_null_copy" )
7207+ . fails ( )
7208+ . stderr_contains ( "cannot create character device" ) ;
7209+ }
7210+
7211+ /// Test that copying devices preserves permissions when possible.
7212+ #[ cfg( unix) ]
7213+ #[ test]
7214+ fn test_cp_device_preserve_permissions ( ) {
7215+ use uutests:: util:: run_ucmd_as_root;
7216+
7217+ let scenario = TestScenario :: new ( util_name ! ( ) ) ;
7218+ let at = & scenario. fixtures ;
7219+
7220+ if let Ok ( result) = run_ucmd_as_root (
7221+ & TestScenario :: new ( "mknod" ) ,
7222+ & [ "test_char_dev" , "c" , "1" , "9" ] ,
7223+ ) {
7224+ result. success ( ) ;
7225+
7226+ // Set specific permissions on the source device
7227+ at. set_mode ( "test_char_dev" , 0o640 ) ;
7228+
7229+ // Copy with permission preservation
7230+ let mut ucmd = scenario. ucmd ( ) ;
7231+ ucmd. arg ( "-r" )
7232+ . arg ( "--preserve=mode" )
7233+ . arg ( "test_char_dev" )
7234+ . arg ( "copied_char_dev" )
7235+ . succeeds ( ) ;
7236+
7237+ // Check that permissions are preserved
7238+ let copy_metadata = std:: fs:: metadata ( at. plus ( "copied_char_dev" ) ) . unwrap ( ) ;
7239+ let permissions = copy_metadata. permissions ( ) . mode ( ) & 0o777 ;
7240+ assert_eq ! ( permissions, 0o640 ) ;
7241+ } else {
7242+ println ! ( "Test skipped; creating character devices requires root privileges" ) ;
7243+ }
7244+ }
0 commit comments