@@ -7079,3 +7079,166 @@ fn test_cp_no_dereference_symlink_with_parents() {
7079
7079
. succeeds ( ) ;
7080
7080
assert_eq ! ( at. resolve_link( "x/symlink-to-directory" ) , "directory" ) ;
7081
7081
}
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