Skip to content

Commit bba1fe2

Browse files
tests/cp: add regression tests for device file handling
Add comprehensive tests to ensure cp -r correctly handles character and block devices: - test_cp_char_device: Verifies character devices are copied as device files - test_cp_block_device: Verifies block devices are copied as device files - test_cp_device_copy_contents: Ensures --copy-contents flag overrides default behavior - test_cp_device_permission_error: Tests proper error handling without permissions - test_cp_device_preserve_permissions: Verifies permission preservation These tests prevent regression of the fix for infinite copying when using cp -r with device files like /dev/urandom.
1 parent 66d4d0d commit bba1fe2

File tree

1 file changed

+163
-0
lines changed

1 file changed

+163
-0
lines changed

tests/by-util/test_cp.rs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)