diff --git a/Cargo.toml b/Cargo.toml index 9b55abe5c..bf11e66fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -347,6 +347,7 @@ tempfile = "= 3.1.0" time = "0.1" unindent = "0.1" uucore = { version=">=0.0.7", package="uucore", path="src/uucore", features=["entries"] } +walkdir = "2.2" [target.'cfg(unix)'.dev-dependencies] rust-users = { version="0.10", package="users" } diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index 0922af241..01ad4a8aa 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -431,6 +431,10 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .arg(Arg::with_name(OPT_NO_DEREFERENCE_PRESERVE_LINKS) .short("d") .help("same as --no-dereference --preserve=links")) + .arg(Arg::with_name(OPT_ONE_FILE_SYSTEM) + .short("x") + .long(OPT_ONE_FILE_SYSTEM) + .help("stay on this file system")) // TODO: implement the following args .arg(Arg::with_name(OPT_COPY_CONTENTS) @@ -442,10 +446,6 @@ pub fn uumain(args: impl uucore::Args) -> i32 { .takes_value(true) .value_name("WHEN") .help("NotImplemented: control creation of sparse files. See below")) - .arg(Arg::with_name(OPT_ONE_FILE_SYSTEM) - .short("x") - .long(OPT_ONE_FILE_SYSTEM) - .help("NotImplemented: stay on this file system")) .arg(Arg::with_name(OPT_CONTEXT) .long(OPT_CONTEXT) .takes_value(true) @@ -563,6 +563,7 @@ impl Options { let not_implemented_opts = vec![ OPT_COPY_CONTENTS, OPT_SPARSE, + #[cfg(not(any(windows, unix)))] OPT_ONE_FILE_SYSTEM, OPT_CONTEXT, #[cfg(windows)] @@ -937,7 +938,7 @@ fn copy_directory(root: &Path, target: &Target, options: &Options) -> CopyResult #[cfg(any(windows, target_os = "redox"))] let mut hard_links: Vec<(String, u64)> = vec![]; - for path in WalkDir::new(root) { + for path in WalkDir::new(root).same_file_system(options.one_file_system) { let p = or_continue!(path); let is_symlink = fs::symlink_metadata(p.path())?.file_type().is_symlink(); let path = if (options.no_dereference || options.dereference) && is_symlink { diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index b96bd4e29..a00ed2fd2 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -31,6 +31,12 @@ static TEST_COPY_FROM_FOLDER: &str = "hello_dir_with_file/"; static TEST_COPY_FROM_FOLDER_FILE: &str = "hello_dir_with_file/hello_world.txt"; static TEST_COPY_TO_FOLDER_NEW: &str = "hello_dir_new"; static TEST_COPY_TO_FOLDER_NEW_FILE: &str = "hello_dir_new/hello_world.txt"; +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +static TEST_MOUNT_COPY_FROM_FOLDER: &str = "dir_with_mount"; +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +static TEST_MOUNT_MOUNTPOINT: &str = "mount"; +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +static TEST_MOUNT_OTHER_FILESYSTEM_FILE: &str = "mount/DO_NOT_copy_me.txt"; #[test] fn test_cp_cp() { @@ -1001,3 +1007,70 @@ fn test_cp_target_file_dev_null() { assert!(at.file_exists(file2)); } + +#[test] +#[cfg(any(target_os = "linux", target_os = "freebsd"))] +fn test_cp_one_file_system() { + use crate::common::util::AtPath; + use walkdir::WalkDir; + + let scene = TestScenario::new(util_name!()); + + // Test must be run as root (or with `sudo -E`) + if scene.cmd("whoami").run().stdout != "root\n" { + return; + } + + let at = scene.fixtures.clone(); + let at_src = AtPath::new(&at.plus(TEST_MOUNT_COPY_FROM_FOLDER)); + let at_dst = AtPath::new(&at.plus(TEST_COPY_TO_FOLDER_NEW)); + + // Prepare the mount + at_src.mkdir(TEST_MOUNT_MOUNTPOINT); + let mountpoint_path = &at_src.plus_as_string(TEST_MOUNT_MOUNTPOINT); + + let _r = scene + .cmd("mount") + .arg("-t") + .arg("tmpfs") + .arg("-o") + .arg("size=640k") // ought to be enough + .arg("tmpfs") + .arg(mountpoint_path) + .run(); + assert!(_r.code == Some(0), _r.stderr); + + at_src.touch(TEST_MOUNT_OTHER_FILESYSTEM_FILE); + + // Begin testing -x flag + let result = scene + .ucmd() + .arg("-rx") + .arg(TEST_MOUNT_COPY_FROM_FOLDER) + .arg(TEST_COPY_TO_FOLDER_NEW) + .run(); + + // Ditch the mount before the asserts + let _r = scene.cmd("umount").arg(mountpoint_path).run(); + assert!(_r.code == Some(0), _r.stderr); + + assert!(result.success); + assert!(!at_dst.file_exists(TEST_MOUNT_OTHER_FILESYSTEM_FILE)); + // Check if the other files were copied from the source folder hirerarchy + for entry in WalkDir::new(at_src.as_string()) { + let entry = entry.unwrap(); + let relative_src = entry + .path() + .strip_prefix(at_src.as_string()) + .unwrap() + .to_str() + .unwrap(); + + let ft = entry.file_type(); + match (ft.is_dir(), ft.is_file(), ft.is_symlink()) { + (true, _, _) => assert!(at_dst.dir_exists(relative_src)), + (_, true, _) => assert!(at_dst.file_exists(relative_src)), + (_, _, _) => panic!(), + } + } +} diff --git a/tests/fixtures/cp/dir_with_mount/copy_me.txt b/tests/fixtures/cp/dir_with_mount/copy_me.txt new file mode 100644 index 000000000..e69de29bb diff --git a/tests/fixtures/cp/dir_with_mount/copy_me/copy_me.txt b/tests/fixtures/cp/dir_with_mount/copy_me/copy_me.txt new file mode 100644 index 000000000..e69de29bb