coreutils/tests/by-util/test_cp.rs

5923 lines
177 KiB
Rust
Raw Normal View History

2023-08-21 08:49:27 +00:00
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs neve ROOTDIR USERDIR procfs outfile uufs xattrs
2024-05-20 06:17:17 +00:00
// spell-checker:ignore bdfl hlsl IRWXO IRWXG
2023-03-20 13:51:19 +00:00
use crate::common::util::TestScenario;
2018-09-23 00:11:13 +00:00
#[cfg(not(windows))]
use std::fs::set_permissions;
2024-11-26 09:35:13 +00:00
use std::io::Write;
#[cfg(not(windows))]
use std::os::unix::fs;
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
#[cfg(unix)]
2021-06-01 21:06:38 +00:00
use std::os::unix::fs::PermissionsExt;
#[cfg(windows)]
use std::os::windows::fs::symlink_file;
#[cfg(not(windows))]
use std::path::Path;
2023-02-13 09:00:03 +00:00
#[cfg(target_os = "linux")]
use std::path::PathBuf;
#[cfg(any(target_os = "linux", target_os = "android"))]
use filetime::FileTime;
2023-02-13 09:00:03 +00:00
#[cfg(target_os = "linux")]
use std::ffi::OsString;
#[cfg(any(target_os = "linux", target_os = "android"))]
use std::fs as std_fs;
use std::thread::sleep;
use std::time::Duration;
2020-05-28 19:54:03 +00:00
#[cfg(any(target_os = "linux", target_os = "android"))]
#[cfg(feature = "truncate")]
use crate::common::util::PATH;
2020-04-13 18:36:03 +00:00
static TEST_EXISTING_FILE: &str = "existing_file.txt";
static TEST_HELLO_WORLD_SOURCE: &str = "hello_world.txt";
static TEST_HELLO_WORLD_SOURCE_SYMLINK: &str = "hello_world.txt.link";
2020-04-13 18:36:03 +00:00
static TEST_HELLO_WORLD_DEST: &str = "copy_of_hello_world.txt";
static TEST_HELLO_WORLD_DEST_SYMLINK: &str = "copy_of_hello_world.txt.link";
2020-04-13 18:36:03 +00:00
static TEST_HOW_ARE_YOU_SOURCE: &str = "how_are_you.txt";
static TEST_HOW_ARE_YOU_DEST: &str = "hello_dir/how_are_you.txt";
static TEST_COPY_TO_FOLDER: &str = "hello_dir/";
static TEST_COPY_TO_FOLDER_FILE: &str = "hello_dir/hello_world.txt";
static TEST_COPY_FROM_FOLDER: &str = "hello_dir_with_file/";
static TEST_COPY_FROM_FOLDER_FILE: &str = "hello_dir_with_file/hello_world.txt";
2020-05-28 19:54:03 +00:00
static TEST_COPY_TO_FOLDER_NEW: &str = "hello_dir_new";
2017-08-10 23:04:25 +00:00
static TEST_COPY_TO_FOLDER_NEW_FILE: &str = "hello_dir_new/hello_world.txt";
#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
static TEST_MOUNT_COPY_FROM_FOLDER: &str = "dir_with_mount";
#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
static TEST_MOUNT_MOUNTPOINT: &str = "mount";
#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
static TEST_MOUNT_OTHER_FILESYSTEM_FILE: &str = "mount/DO_NOT_copy_me.txt";
static TEST_NONEXISTENT_FILE: &str = "nonexistent_file.txt";
#[cfg(all(
unix,
not(any(target_os = "android", target_os = "macos", target_os = "openbsd"))
))]
use crate::common::util::compare_xattrs;
/// Assert that mode, ownership, and permissions of two metadata objects match.
#[cfg(all(not(windows), not(target_os = "freebsd")))]
macro_rules! assert_metadata_eq {
($m1:expr, $m2:expr) => {{
assert_eq!($m1.mode(), $m2.mode(), "mode is different");
assert_eq!($m1.uid(), $m2.uid(), "uid is different");
assert_eq!($m1.atime(), $m2.atime(), "atime is different");
assert_eq!(
$m1.atime_nsec(),
$m2.atime_nsec(),
"atime_nsec is different"
);
assert_eq!($m1.mtime(), $m2.mtime(), "mtime is different");
assert_eq!(
$m1.mtime_nsec(),
$m2.mtime_nsec(),
"mtime_nsec is different"
);
}};
}
#[test]
fn test_cp_cp() {
let (at, mut ucmd) = at_and_ucmd!();
// Invoke our binary to make the copy.
2021-04-22 20:37:44 +00:00
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
2020-04-13 18:36:03 +00:00
.arg(TEST_HELLO_WORLD_DEST)
2021-04-22 20:37:44 +00:00
.succeeds();
// Check the content of the destination file that was copied.
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
}
#[test]
fn test_cp_existing_target() {
let (at, mut ucmd) = at_and_ucmd!();
2021-04-22 20:37:44 +00:00
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_EXISTING_FILE)
2021-04-22 20:37:44 +00:00
.succeeds();
// Check the content of the destination file
assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n");
// No backup should have been created
2023-02-13 09:00:03 +00:00
assert!(!at.file_exists(format!("{TEST_EXISTING_FILE}~")));
}
#[test]
fn test_cp_duplicate_files() {
let (at, mut ucmd) = at_and_ucmd!();
2021-04-22 20:37:44 +00:00
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_COPY_TO_FOLDER)
2021-04-22 20:37:44 +00:00
.succeeds()
.stderr_contains(format!(
"source file '{TEST_HELLO_WORLD_SOURCE}' specified more than once"
));
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
}
2024-10-31 09:55:22 +00:00
#[test]
fn test_cp_duplicate_folder() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-r")
.arg(TEST_COPY_FROM_FOLDER)
.arg(TEST_COPY_FROM_FOLDER)
.arg(TEST_COPY_TO_FOLDER)
.succeeds()
.stderr_contains(format!(
"source directory '{TEST_COPY_FROM_FOLDER}' specified more than once"
));
assert!(at.dir_exists(format!("{TEST_COPY_TO_FOLDER}/{TEST_COPY_FROM_FOLDER}").as_str()));
}
2024-10-31 09:55:22 +00:00
#[test]
fn test_cp_duplicate_files_normalized_path() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(format!("./{TEST_HELLO_WORLD_SOURCE}"))
.arg(TEST_COPY_TO_FOLDER)
.succeeds()
.stderr_contains(format!(
"source file './{TEST_HELLO_WORLD_SOURCE}' specified more than once"
));
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
}
#[test]
fn test_cp_duplicate_files_with_plain_backup() {
let (_, mut ucmd) = at_and_ucmd!();
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_COPY_TO_FOLDER)
.arg("--backup")
.fails()
// cp would skip duplicate src check and fail when it tries to overwrite the "just-created" file.
.stderr_contains(
"will not overwrite just-created 'hello_dir/hello_world.txt' with 'hello_world.txt",
);
}
#[test]
fn test_cp_duplicate_files_with_numbered_backup() {
let (at, mut ucmd) = at_and_ucmd!();
// cp would skip duplicate src check and succeeds
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_COPY_TO_FOLDER)
.arg("--backup=numbered")
.succeeds();
at.file_exists(TEST_COPY_TO_FOLDER_FILE);
at.file_exists(format!("{TEST_COPY_TO_FOLDER}.~1~"));
}
#[test]
fn test_cp_same_file() {
let (at, mut ucmd) = at_and_ucmd!();
let file = "a";
at.touch(file);
ucmd.arg(file)
.arg(file)
.fails()
.code_is(1)
.stderr_contains(format!("'{file}' and '{file}' are the same file"));
}
#[test]
fn test_cp_multiple_files_target_is_file() {
2021-04-22 20:37:44 +00:00
new_ucmd!()
2020-04-13 18:36:03 +00:00
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_EXISTING_FILE)
2021-04-22 20:37:44 +00:00
.fails()
.stderr_contains("not a directory");
}
#[test]
fn test_cp_directory_not_recursive() {
2021-04-22 20:37:44 +00:00
new_ucmd!()
2020-04-13 18:36:03 +00:00
.arg(TEST_COPY_TO_FOLDER)
.arg(TEST_HELLO_WORLD_DEST)
2021-04-22 20:37:44 +00:00
.fails()
.stderr_is(format!(
"cp: -r not specified; omitting directory '{TEST_COPY_TO_FOLDER}'\n"
));
}
#[test]
fn test_cp_multiple_files() {
let (at, mut ucmd) = at_and_ucmd!();
2021-04-22 20:37:44 +00:00
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.arg(TEST_COPY_TO_FOLDER)
2021-04-22 20:37:44 +00:00
.succeeds();
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
assert_eq!(at.read(TEST_HOW_ARE_YOU_DEST), "How are you?\n");
}
#[test]
fn test_cp_multiple_files_with_nonexistent_file() {
#[cfg(windows)]
let error_msg = "The system cannot find the file specified";
#[cfg(not(windows))]
let error_msg = format!("'{TEST_NONEXISTENT_FILE}': No such file or directory");
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_NONEXISTENT_FILE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.arg(TEST_COPY_TO_FOLDER)
.fails()
.stderr_contains(error_msg);
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
assert_eq!(at.read(TEST_HOW_ARE_YOU_DEST), "How are you?\n");
}
#[test]
fn test_cp_multiple_files_with_empty_file_name() {
#[cfg(windows)]
let error_msg = "The system cannot find the path specified";
#[cfg(not(windows))]
let error_msg = "'': No such file or directory";
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg("")
.arg(TEST_HOW_ARE_YOU_SOURCE)
.arg(TEST_COPY_TO_FOLDER)
.fails()
.stderr_contains(error_msg);
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
assert_eq!(at.read(TEST_HOW_ARE_YOU_DEST), "How are you?\n");
}
2017-06-02 11:37:31 +00:00
#[test]
// FixME: for MacOS, this has intermittent failures; track repair progress at GH:uutils/coreutils/issues/1590
2021-05-29 12:32:35 +00:00
#[cfg(not(target_os = "macos"))]
2017-06-02 11:37:31 +00:00
fn test_cp_recurse() {
2017-08-10 23:04:25 +00:00
let (at, mut ucmd) = at_and_ucmd!();
2021-04-22 20:37:44 +00:00
ucmd.arg("-r")
2017-08-10 23:04:25 +00:00
.arg(TEST_COPY_FROM_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW)
2021-04-22 20:37:44 +00:00
.succeeds();
2017-06-02 11:37:31 +00:00
// Check the content of the destination file that was copied.
2017-08-10 23:04:25 +00:00
assert_eq!(at.read(TEST_COPY_TO_FOLDER_NEW_FILE), "Hello, World!\n");
2017-06-02 11:37:31 +00:00
}
#[test]
#[cfg(not(target_os = "macos"))]
fn test_cp_recurse_several() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("-r")
.arg("-r")
2017-08-10 23:04:25 +00:00
.arg(TEST_COPY_FROM_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW)
2021-04-22 20:37:44 +00:00
.succeeds();
2017-06-02 11:37:31 +00:00
// Check the content of the destination file that was copied.
2017-08-10 23:04:25 +00:00
assert_eq!(at.read(TEST_COPY_TO_FOLDER_NEW_FILE), "Hello, World!\n");
2017-06-02 11:37:31 +00:00
}
#[test]
fn test_cp_with_dirs_t() {
let (at, mut ucmd) = at_and_ucmd!();
2021-04-22 20:37:44 +00:00
ucmd.arg("-t")
.arg(TEST_COPY_TO_FOLDER)
.arg(TEST_HELLO_WORLD_SOURCE)
2021-04-22 20:37:44 +00:00
.succeeds();
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
}
#[test]
// FixME: for MacOS, this has intermittent failures; track repair progress at GH:uutils/coreutils/issues/1590
2021-05-29 12:32:35 +00:00
#[cfg(not(target_os = "macos"))]
fn test_cp_with_dirs() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
2021-04-22 20:37:44 +00:00
scene
2020-04-13 18:36:03 +00:00
.ucmd()
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_COPY_TO_FOLDER)
2021-04-22 20:37:44 +00:00
.succeeds();
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
2021-04-22 20:37:44 +00:00
scene
2020-04-13 18:36:03 +00:00
.ucmd()
.arg(TEST_COPY_FROM_FOLDER_FILE)
.arg(TEST_HELLO_WORLD_DEST)
2021-04-22 20:37:44 +00:00
.succeeds();
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
}
#[test]
fn test_cp_arg_target_directory() {
let (at, mut ucmd) = at_and_ucmd!();
2021-04-22 20:37:44 +00:00
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg("-t")
.arg(TEST_COPY_TO_FOLDER)
2021-04-22 20:37:44 +00:00
.succeeds();
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
}
#[test]
fn test_cp_arg_no_target_directory() {
2021-04-22 20:37:44 +00:00
new_ucmd!()
2020-04-13 18:36:03 +00:00
.arg(TEST_HELLO_WORLD_SOURCE)
.arg("-v")
.arg("-T")
.arg(TEST_COPY_TO_FOLDER)
2021-04-22 20:37:44 +00:00
.fails()
.stderr_contains("cannot overwrite directory");
}
#[test]
fn test_cp_arg_no_target_directory_with_recursive() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("dir");
at.mkdir("dir2");
at.touch("dir/a");
at.touch("dir/b");
ucmd.arg("-rT").arg("dir").arg("dir2").succeeds();
assert!(at.plus("dir2").join("a").exists());
assert!(at.plus("dir2").join("b").exists());
assert!(!at.plus("dir2").join("dir").exists());
}
2022-02-02 03:26:33 +00:00
#[test]
fn test_cp_target_directory_is_file() {
new_ucmd!()
.arg("-t")
.arg(TEST_HOW_ARE_YOU_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE)
.fails()
.stderr_contains(format!("'{TEST_HOW_ARE_YOU_SOURCE}' is not a directory"));
2022-02-02 03:26:33 +00:00
}
#[test]
fn test_cp_arg_update_interactive_error() {
new_ucmd!()
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.arg("-i")
.fails()
.no_stdout();
}
2023-05-01 01:52:13 +00:00
#[test]
fn test_cp_arg_update_none() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.arg("--update=none")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n");
}
2023-05-01 01:52:13 +00:00
#[test]
fn test_cp_arg_update_none_fail() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.arg("--update=none-fail")
.fails()
2024-09-19 21:56:27 +00:00
.stderr_contains(format!("not replacing '{TEST_HOW_ARE_YOU_SOURCE}'"));
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n");
2023-05-01 01:52:13 +00:00
}
#[test]
fn test_cp_arg_update_all() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.arg("--update=all")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(
at.read(TEST_HOW_ARE_YOU_SOURCE),
at.read(TEST_HELLO_WORLD_SOURCE)
2023-05-06 17:35:44 +00:00
);
2023-05-01 01:52:13 +00:00
}
#[test]
fn test_cp_arg_update_older_dest_not_older_than_src() {
let (at, mut ucmd) = at_and_ucmd!();
2023-05-02 20:35:06 +00:00
let old = "test_cp_arg_update_dest_not_older_file1";
let new = "test_cp_arg_update_dest_not_older_file2";
let old_content = "old content\n";
let new_content = "new content\n";
2023-05-01 01:52:13 +00:00
2023-05-02 20:35:06 +00:00
at.write(old, old_content);
at.write(new, new_content);
2023-05-01 01:52:13 +00:00
ucmd.arg(old)
.arg(new)
.arg("--update=older")
.succeeds()
.no_stderr()
.no_stdout();
2023-05-06 17:35:44 +00:00
assert_eq!(at.read(new), "new content\n");
2023-05-01 01:52:13 +00:00
}
#[test]
fn test_cp_arg_update_older_dest_older_than_src() {
let (at, mut ucmd) = at_and_ucmd!();
2023-05-02 20:35:06 +00:00
let old = "test_cp_arg_update_dest_older_file1";
let new = "test_cp_arg_update_dest_older_file2";
let old_content = "old content\n";
let new_content = "new content\n";
2024-11-26 09:35:13 +00:00
let mut f = at.make_file(old);
f.write_all(old_content.as_bytes()).unwrap();
f.set_modified(std::time::UNIX_EPOCH).unwrap();
2023-05-02 20:35:06 +00:00
at.write(new, new_content);
2023-05-01 01:52:13 +00:00
ucmd.arg(new)
.arg(old)
.arg("--update=older")
.succeeds()
.no_stderr()
.no_stdout();
2023-05-06 17:35:44 +00:00
assert_eq!(at.read(old), "new content\n");
2023-05-01 01:52:13 +00:00
}
#[test]
2023-05-02 20:35:52 +00:00
fn test_cp_arg_update_short_no_overwrite() {
2023-05-01 01:52:13 +00:00
// same as --update=older
let (at, mut ucmd) = at_and_ucmd!();
2023-05-02 20:35:06 +00:00
let old = "test_cp_arg_update_short_no_overwrite_file1";
let new = "test_cp_arg_update_short_no_overwrite_file2";
let old_content = "old content\n";
let new_content = "new content\n";
2024-11-26 09:35:13 +00:00
let mut f = at.make_file(old);
f.write_all(old_content.as_bytes()).unwrap();
f.set_modified(std::time::UNIX_EPOCH).unwrap();
2023-05-01 01:52:13 +00:00
2023-05-02 20:35:06 +00:00
at.write(new, new_content);
2023-05-01 01:52:13 +00:00
ucmd.arg(old)
.arg(new)
.arg("-u")
.succeeds()
.no_stderr()
.no_stdout();
2023-05-06 17:35:44 +00:00
assert_eq!(at.read(new), "new content\n");
2023-05-01 01:52:13 +00:00
}
#[test]
2023-05-02 20:35:52 +00:00
fn test_cp_arg_update_short_overwrite() {
2023-05-01 01:52:13 +00:00
// same as --update=older
let (at, mut ucmd) = at_and_ucmd!();
2023-05-02 20:35:06 +00:00
let old = "test_cp_arg_update_short_overwrite_file1";
let new = "test_cp_arg_update_short_overwrite_file2";
let old_content = "old content\n";
let new_content = "new content\n";
2024-11-26 09:35:13 +00:00
let mut f = at.make_file(old);
f.write_all(old_content.as_bytes()).unwrap();
f.set_modified(std::time::UNIX_EPOCH).unwrap();
2023-05-01 01:52:13 +00:00
2023-05-02 20:35:06 +00:00
at.write(new, new_content);
2023-05-01 01:52:13 +00:00
ucmd.arg(new)
.arg(old)
.arg("-u")
.succeeds()
.no_stderr()
.no_stdout();
2023-05-06 17:35:44 +00:00
assert_eq!(at.read(old), "new content\n");
2023-05-01 01:52:13 +00:00
}
#[test]
fn test_cp_arg_update_none_then_all() {
// take last if multiple update args are supplied,
// update=all wins in this case
let (at, mut ucmd) = at_and_ucmd!();
let old = "test_cp_arg_update_none_then_all_file1";
let new = "test_cp_arg_update_none_then_all_file2";
let old_content = "old content\n";
let new_content = "new content\n";
2024-11-26 09:35:13 +00:00
let mut f = at.make_file(old);
f.write_all(old_content.as_bytes()).unwrap();
f.set_modified(std::time::UNIX_EPOCH).unwrap();
at.write(new, new_content);
ucmd.arg(old)
.arg(new)
.arg("--update=none")
.arg("--update=all")
.succeeds()
.no_stderr()
.no_stdout();
2023-05-06 17:35:44 +00:00
assert_eq!(at.read(new), "old content\n");
}
#[test]
fn test_cp_arg_update_all_then_none() {
// take last if multiple update args are supplied,
// update=none wins in this case
let (at, mut ucmd) = at_and_ucmd!();
let old = "test_cp_arg_update_all_then_none_file1";
let new = "test_cp_arg_update_all_then_none_file2";
let old_content = "old content\n";
let new_content = "new content\n";
2024-11-26 09:35:13 +00:00
let mut f = at.make_file(old);
f.write_all(old_content.as_bytes()).unwrap();
f.set_modified(std::time::UNIX_EPOCH).unwrap();
at.write(new, new_content);
ucmd.arg(old)
.arg(new)
.arg("--update=all")
.arg("--update=none")
.succeeds()
.no_stderr()
.no_stdout();
2023-05-06 17:35:44 +00:00
assert_eq!(at.read(new), "new content\n");
}
#[test]
fn test_cp_arg_interactive() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("a");
at.touch("b");
ucmd.args(&["-i", "a", "b"])
.pipe_in("N\n")
.fails()
.no_stdout()
.stderr_is("cp: overwrite 'b'? ");
}
#[test]
#[cfg(not(any(target_os = "android", target_os = "freebsd", target_os = "openbsd")))]
fn test_cp_arg_interactive_update_overwrite_newer() {
// -u -i won't show the prompt to validate the override or not
// Therefore, the error code will be 0
let (at, mut ucmd) = at_and_ucmd!();
at.touch("a");
at.touch("b");
ucmd.args(&["-i", "-u", "a", "b"])
.pipe_in("")
.succeeds()
.no_stdout();
// Make extra sure that closing stdin behaves identically to piping-in nothing.
let (at, mut ucmd) = at_and_ucmd!();
at.touch("a");
at.touch("b");
ucmd.args(&["-i", "-u", "a", "b"]).succeeds().no_stdout();
}
#[test]
#[cfg(not(any(target_os = "android", target_os = "freebsd")))]
fn test_cp_arg_interactive_update_overwrite_older() {
// -u -i *WILL* show the prompt to validate the override.
// Therefore, the error code depends on the prompt response.
// Option N
let (at, mut ucmd) = at_and_ucmd!();
at.touch("b");
std::thread::sleep(Duration::from_secs(1));
at.touch("a");
ucmd.args(&["-i", "-u", "a", "b"])
.pipe_in("N\n")
.fails()
.code_is(1)
.no_stdout()
.stderr_is("cp: overwrite 'b'? ");
// Option Y
let (at, mut ucmd) = at_and_ucmd!();
at.touch("b");
std::thread::sleep(Duration::from_secs(1));
at.touch("a");
ucmd.args(&["-i", "-u", "a", "b"])
.pipe_in("Y\n")
.succeeds()
.no_stdout();
}
#[test]
#[cfg(not(target_os = "android"))]
fn test_cp_arg_interactive_verbose() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("a");
at.touch("b");
ucmd.args(&["-vi", "a", "b"])
.pipe_in("N\n")
.fails()
.stderr_is("cp: overwrite 'b'? ")
.no_stdout();
}
#[test]
#[cfg(not(target_os = "android"))]
fn test_cp_arg_interactive_verbose_clobber() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("a");
at.touch("b");
ucmd.args(&["-vin", "--debug", "a", "b"])
.succeeds()
.stdout_contains("skipped 'b'");
}
#[test]
#[cfg(unix)]
fn test_cp_f_i_verbose_non_writeable_destination_y() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("a");
at.touch("b");
// Non-writeable file
at.set_mode("b", 0o0000);
ucmd.args(&["-f", "-i", "--verbose", "a", "b"])
.pipe_in("y")
.succeeds()
.stderr_is("cp: replace 'b', overriding mode 0000 (---------)? ")
.stdout_is("'a' -> 'b'\n");
}
#[test]
#[cfg(unix)]
fn test_cp_f_i_verbose_non_writeable_destination_empty() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("a");
at.touch("b");
// Non-writeable file
at.set_mode("b", 0o0000);
ucmd.args(&["-f", "-i", "--verbose", "a", "b"])
.pipe_in("")
.fails()
.stderr_only("cp: replace 'b', overriding mode 0000 (---------)? ");
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_arg_link() {
use std::os::linux::fs::MetadataExt;
let (at, mut ucmd) = at_and_ucmd!();
2021-04-22 20:37:44 +00:00
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--link")
.arg(TEST_HELLO_WORLD_DEST)
2021-04-22 20:37:44 +00:00
.succeeds();
assert_eq!(at.metadata(TEST_HELLO_WORLD_SOURCE).st_nlink(), 2);
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_arg_link_with_dest_hardlink_to_source() {
use std::os::linux::fs::MetadataExt;
let (at, mut ucmd) = at_and_ucmd!();
let file = "file";
let hardlink = "hardlink";
at.touch(file);
at.hard_link(file, hardlink);
ucmd.args(&["--link", file, hardlink]).succeeds();
assert_eq!(at.metadata(file).st_nlink(), 2);
assert!(at.file_exists(file));
assert!(at.file_exists(hardlink));
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_arg_link_with_same_file() {
use std::os::linux::fs::MetadataExt;
let (at, mut ucmd) = at_and_ucmd!();
let file = "file";
at.touch(file);
ucmd.args(&["--link", file, file]).succeeds();
assert_eq!(at.metadata(file).st_nlink(), 1);
assert!(at.file_exists(file));
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_verbose_preserved_link_to_dir() {
use std::os::linux::fs::MetadataExt;
let (at, mut ucmd) = at_and_ucmd!();
let file = "file";
let hardlink = "hardlink";
let dir = "dir";
let dst_file = "dir/file";
let dst_hardlink = "dir/hardlink";
at.touch(file);
at.hard_link(file, hardlink);
at.mkdir(dir);
ucmd.args(&["-d", "--verbose", file, hardlink, dir])
.succeeds()
.stdout_is("'file' -> 'dir/file'\n'hardlink' -> 'dir/hardlink'\n");
assert!(at.file_exists(dst_file));
assert!(at.file_exists(dst_hardlink));
assert_eq!(at.metadata(dst_file).st_nlink(), 2);
assert_eq!(at.metadata(dst_hardlink).st_nlink(), 2);
assert_eq!(
at.metadata(dst_file).st_ino(),
at.metadata(dst_hardlink).st_ino()
);
}
#[test]
fn test_cp_arg_symlink() {
let (at, mut ucmd) = at_and_ucmd!();
2021-04-22 20:37:44 +00:00
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--symbolic-link")
.arg(TEST_HELLO_WORLD_DEST)
2021-04-22 20:37:44 +00:00
.succeeds();
assert!(at.is_symlink(TEST_HELLO_WORLD_DEST));
}
#[test]
fn test_cp_arg_no_clobber() {
let (at, mut ucmd) = at_and_ucmd!();
2021-04-22 20:37:44 +00:00
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.arg("--no-clobber")
.arg("--debug")
.succeeds()
.stdout_contains("skipped 'how_are_you.txt'");
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n");
}
#[test]
fn test_cp_arg_no_clobber_inferred_arg() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.arg("--no-clob")
.arg("--debug")
.succeeds()
.stdout_contains("skipped 'how_are_you.txt'");
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "How are you?\n");
}
#[test]
fn test_cp_arg_no_clobber_twice() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
2021-04-22 20:37:44 +00:00
at.touch("source.txt");
2021-04-22 20:37:44 +00:00
scene
.ucmd()
.arg("--no-clobber")
.arg("source.txt")
.arg("dest.txt")
.arg("--debug")
2021-04-22 20:37:44 +00:00
.succeeds()
.no_stderr();
assert_eq!(at.read("source.txt"), "");
at.append("source.txt", "some-content");
2021-04-22 20:37:44 +00:00
scene
.ucmd()
.arg("--no-clobber")
.arg("source.txt")
.arg("dest.txt")
.arg("--debug")
.succeeds()
.stdout_contains("skipped 'dest.txt'");
assert_eq!(at.read("source.txt"), "some-content");
// Should be empty as the "no-clobber" should keep
// the previous version
assert_eq!(at.read("dest.txt"), "");
}
#[test]
#[cfg(not(windows))]
fn test_cp_arg_force() {
let (at, mut ucmd) = at_and_ucmd!();
// create dest without write permissions
2020-04-13 18:36:03 +00:00
let mut permissions = at
.make_file(TEST_HELLO_WORLD_DEST)
.metadata()
.unwrap()
.permissions();
permissions.set_readonly(true);
set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap();
2021-04-22 20:37:44 +00:00
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--force")
.arg(TEST_HELLO_WORLD_DEST)
2021-04-22 20:37:44 +00:00
.succeeds();
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
}
/// TODO: write a better test that differentiates --remove-destination
/// from --force. Also this test currently doesn't work on
/// Windows. This test originally checked file timestamps, which
/// proved to be unreliable per target / CI platform
#[test]
#[cfg(not(windows))]
fn test_cp_arg_remove_destination() {
let (at, mut ucmd) = at_and_ucmd!();
// create dest without write permissions
2020-04-13 18:36:03 +00:00
let mut permissions = at
.make_file(TEST_HELLO_WORLD_DEST)
.metadata()
.unwrap()
.permissions();
permissions.set_readonly(true);
set_permissions(at.plus(TEST_HELLO_WORLD_DEST), permissions).unwrap();
2021-04-22 20:37:44 +00:00
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--remove-destination")
.arg(TEST_HELLO_WORLD_DEST)
2021-04-22 20:37:44 +00:00
.succeeds();
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
}
#[test]
fn test_cp_arg_backup() {
let (at, mut ucmd) = at_and_ucmd!();
2021-04-22 20:37:44 +00:00
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.arg("-b")
.succeeds();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
assert_eq!(
at.read(&format!("{TEST_HOW_ARE_YOU_SOURCE}~")),
"How are you?\n"
);
}
2023-12-27 14:39:54 +00:00
#[test]
fn test_cp_arg_backup_with_dest_a_symlink() {
let (at, mut ucmd) = at_and_ucmd!();
let source = "source";
let source_content = "content";
let symlink = "symlink";
let original = "original";
let backup = "symlink~";
at.write(source, source_content);
at.write(original, "original");
at.symlink_file(original, symlink);
ucmd.arg("-b").arg(source).arg(symlink).succeeds();
assert!(!at.symlink_exists(symlink));
assert_eq!(source_content, at.read(symlink));
assert!(at.symlink_exists(backup));
assert_eq!(original, at.resolve_link(backup));
}
#[test]
fn test_cp_arg_backup_with_dest_a_symlink_to_source() {
let (at, mut ucmd) = at_and_ucmd!();
let source = "source";
let source_content = "content";
let symlink = "symlink";
let backup = "symlink~";
at.write(source, source_content);
at.symlink_file(source, symlink);
ucmd.arg("-b").arg(source).arg(symlink).succeeds();
assert!(!at.symlink_exists(symlink));
assert_eq!(source_content, at.read(symlink));
assert!(at.symlink_exists(backup));
assert_eq!(source, at.resolve_link(backup));
}
#[test]
fn test_cp_arg_backup_with_other_args() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.arg("-vbL")
.succeeds();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
assert_eq!(
at.read(&format!("{TEST_HOW_ARE_YOU_SOURCE}~")),
"How are you?\n"
);
}
#[test]
fn test_cp_arg_backup_arg_first() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("--backup")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
2021-04-22 20:37:44 +00:00
.succeeds();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
2020-04-13 18:36:03 +00:00
assert_eq!(
at.read(&format!("{TEST_HOW_ARE_YOU_SOURCE}~")),
2020-04-13 18:36:03 +00:00
"How are you?\n"
);
}
#[test]
fn test_cp_arg_suffix() {
let (at, mut ucmd) = at_and_ucmd!();
2021-04-22 20:37:44 +00:00
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg("-b")
.arg("--suffix")
.arg(".bak")
.arg(TEST_HOW_ARE_YOU_SOURCE)
2021-04-22 20:37:44 +00:00
.succeeds();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
2020-04-13 18:36:03 +00:00
assert_eq!(
at.read(&format!("{TEST_HOW_ARE_YOU_SOURCE}.bak")),
2020-04-13 18:36:03 +00:00
"How are you?\n"
);
}
#[test]
fn test_cp_arg_suffix_hyphen_value() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg("-b")
.arg("--suffix")
.arg("-v")
.arg(TEST_HOW_ARE_YOU_SOURCE)
.succeeds();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
assert_eq!(
at.read(&format!("{TEST_HOW_ARE_YOU_SOURCE}-v")),
"How are you?\n"
);
}
#[test]
fn test_cp_custom_backup_suffix_via_env() {
let (at, mut ucmd) = at_and_ucmd!();
let suffix = "super-suffix-of-the-century";
ucmd.arg("-b")
.env("SIMPLE_BACKUP_SUFFIX", suffix)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.succeeds()
.no_stderr();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
assert_eq!(
at.read(&format!("{TEST_HOW_ARE_YOU_SOURCE}{suffix}")),
"How are you?\n"
);
}
#[test]
fn test_cp_backup_numbered_with_t() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("--backup=t")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.succeeds()
.no_stderr();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
assert_eq!(
at.read(&format!("{TEST_HOW_ARE_YOU_SOURCE}.~1~")),
"How are you?\n"
);
}
#[test]
fn test_cp_backup_numbered() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("--backup=numbered")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.succeeds()
.no_stderr();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
assert_eq!(
at.read(&format!("{TEST_HOW_ARE_YOU_SOURCE}.~1~")),
"How are you?\n"
);
}
#[test]
fn test_cp_backup_existing() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("--backup=existing")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.succeeds()
.no_stderr();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
assert_eq!(
at.read(&format!("{TEST_HOW_ARE_YOU_SOURCE}~")),
"How are you?\n"
);
}
#[test]
fn test_cp_backup_nil() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("--backup=nil")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.succeeds()
.no_stderr();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
assert_eq!(
at.read(&format!("{TEST_HOW_ARE_YOU_SOURCE}~")),
"How are you?\n"
);
}
#[test]
fn test_cp_numbered_if_existing_backup_existing() {
let (at, mut ucmd) = at_and_ucmd!();
let existing_backup = &format!("{TEST_HOW_ARE_YOU_SOURCE}.~1~");
at.touch(existing_backup);
ucmd.arg("--backup=existing")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.succeeds()
.no_stderr();
assert!(at.file_exists(TEST_HOW_ARE_YOU_SOURCE));
assert!(at.file_exists(existing_backup));
assert_eq!(
at.read(&format!("{TEST_HOW_ARE_YOU_SOURCE}.~2~")),
"How are you?\n"
);
}
#[test]
fn test_cp_numbered_if_existing_backup_nil() {
let (at, mut ucmd) = at_and_ucmd!();
let existing_backup = &format!("{TEST_HOW_ARE_YOU_SOURCE}.~1~");
at.touch(existing_backup);
ucmd.arg("--backup=nil")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.succeeds()
.no_stderr();
assert!(at.file_exists(TEST_HOW_ARE_YOU_SOURCE));
assert!(at.file_exists(existing_backup));
assert_eq!(
at.read(&format!("{TEST_HOW_ARE_YOU_SOURCE}.~2~")),
"How are you?\n"
);
}
#[test]
fn test_cp_backup_simple() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("--backup=simple")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.succeeds()
.no_stderr();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
assert_eq!(
at.read(&format!("{TEST_HOW_ARE_YOU_SOURCE}~")),
"How are you?\n"
);
}
#[test]
fn test_cp_backup_simple_protect_source() {
let (at, mut ucmd) = at_and_ucmd!();
let source = format!("{TEST_HELLO_WORLD_SOURCE}~");
at.touch(&source);
ucmd.arg("--backup=simple")
.arg(&source)
.arg(TEST_HELLO_WORLD_SOURCE)
.fails()
.stderr_only(format!(
"cp: backing up '{TEST_HELLO_WORLD_SOURCE}' might destroy source; '{source}' not copied\n",
));
assert_eq!(at.read(TEST_HELLO_WORLD_SOURCE), "Hello, World!\n");
assert_eq!(at.read(&source), "");
}
#[test]
fn test_cp_backup_never() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("--backup=never")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.succeeds()
.no_stderr();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
assert_eq!(
at.read(&format!("{TEST_HOW_ARE_YOU_SOURCE}~")),
"How are you?\n"
);
}
#[test]
fn test_cp_backup_none() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("--backup=none")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.succeeds()
.no_stderr();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
2023-02-13 09:00:03 +00:00
assert!(!at.file_exists(format!("{TEST_HOW_ARE_YOU_SOURCE}~")));
}
#[test]
fn test_cp_backup_off() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("--backup=off")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.succeeds()
.no_stderr();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
2023-02-13 09:00:03 +00:00
assert!(!at.file_exists(format!("{TEST_HOW_ARE_YOU_SOURCE}~")));
}
#[test]
fn test_cp_backup_no_clobber_conflicting_options() {
2021-11-09 20:23:41 +00:00
new_ucmd!()
.arg("--backup")
.arg("--no-clobber")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
2021-11-09 20:23:41 +00:00
.fails()
.usage_error("options --backup and --no-clobber are mutually exclusive");
}
#[test]
fn test_cp_deref_conflicting_options() {
2021-04-22 20:37:44 +00:00
new_ucmd!()
.arg("-LP")
.arg(TEST_COPY_TO_FOLDER)
.arg(TEST_HELLO_WORLD_SOURCE)
.fails();
}
#[test]
fn test_cp_deref() {
2021-04-22 20:37:44 +00:00
let (at, mut ucmd) = at_and_ucmd!();
#[cfg(not(windows))]
let _r = fs::symlink(
TEST_HELLO_WORLD_SOURCE,
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
);
#[cfg(windows)]
let _r = symlink_file(
TEST_HELLO_WORLD_SOURCE,
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
);
//using -L option
2021-04-22 20:37:44 +00:00
ucmd.arg("-L")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE_SYMLINK)
.arg(TEST_COPY_TO_FOLDER)
2021-04-22 20:37:44 +00:00
.succeeds();
let path_to_new_symlink = at
.subdir
.join(TEST_COPY_TO_FOLDER)
.join(TEST_HELLO_WORLD_SOURCE_SYMLINK);
// unlike -P/--no-deref, we expect a file, not a link
assert!(at.file_exists(
2023-02-13 09:00:03 +00:00
path_to_new_symlink
.clone()
.into_os_string()
.into_string()
.unwrap()
));
// Check the content of the destination file that was copied.
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
let path_to_check = path_to_new_symlink.to_str().unwrap();
assert_eq!(at.read(path_to_check), "Hello, World!\n");
}
#[test]
fn test_cp_no_deref() {
2021-04-22 20:37:44 +00:00
let (at, mut ucmd) = at_and_ucmd!();
#[cfg(not(windows))]
let _r = fs::symlink(
TEST_HELLO_WORLD_SOURCE,
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
);
#[cfg(windows)]
let _r = symlink_file(
TEST_HELLO_WORLD_SOURCE,
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
);
2020-05-28 19:54:03 +00:00
//using -P option
2021-04-22 20:37:44 +00:00
ucmd.arg("-P")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_HELLO_WORLD_SOURCE_SYMLINK)
.arg(TEST_COPY_TO_FOLDER)
2021-04-22 20:37:44 +00:00
.succeeds();
let path_to_new_symlink = at
.subdir
.join(TEST_COPY_TO_FOLDER)
.join(TEST_HELLO_WORLD_SOURCE_SYMLINK);
assert!(at.is_symlink(
&path_to_new_symlink
.clone()
.into_os_string()
.into_string()
.unwrap()
));
// Check the content of the destination file that was copied.
assert_eq!(at.read(TEST_COPY_TO_FOLDER_FILE), "Hello, World!\n");
let path_to_check = path_to_new_symlink.to_str().unwrap();
assert_eq!(at.read(path_to_check), "Hello, World!\n");
}
2020-05-28 19:54:03 +00:00
#[test]
fn test_cp_no_deref_link_onto_link() {
let (at, mut ucmd) = at_and_ucmd!();
at.copy(TEST_HELLO_WORLD_SOURCE, TEST_HELLO_WORLD_DEST);
#[cfg(not(windows))]
let _r = fs::symlink(
TEST_HELLO_WORLD_SOURCE,
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
);
#[cfg(windows)]
let _r = symlink_file(
TEST_HELLO_WORLD_SOURCE,
at.subdir.join(TEST_HELLO_WORLD_SOURCE_SYMLINK),
);
#[cfg(not(windows))]
let _r = fs::symlink(
TEST_HELLO_WORLD_DEST,
at.subdir.join(TEST_HELLO_WORLD_DEST_SYMLINK),
);
#[cfg(windows)]
let _r = symlink_file(
TEST_HELLO_WORLD_DEST,
at.subdir.join(TEST_HELLO_WORLD_DEST_SYMLINK),
);
ucmd.arg("-P")
.arg(TEST_HELLO_WORLD_SOURCE_SYMLINK)
.arg(TEST_HELLO_WORLD_DEST_SYMLINK)
.succeeds();
// Ensure that the target of the destination was not modified.
assert!(!at
.symlink_metadata(TEST_HELLO_WORLD_DEST)
.file_type()
.is_symlink());
assert!(at
.symlink_metadata(TEST_HELLO_WORLD_DEST_SYMLINK)
.file_type()
.is_symlink());
assert_eq!(at.read(TEST_HELLO_WORLD_DEST_SYMLINK), "Hello, World!\n");
}
2021-03-09 10:59:26 +00:00
#[test]
fn test_cp_strip_trailing_slashes() {
let (at, mut ucmd) = at_and_ucmd!();
//using --strip-trailing-slashes option
2021-04-22 20:37:44 +00:00
ucmd.arg("--strip-trailing-slashes")
.arg(format!("{TEST_HELLO_WORLD_SOURCE}/"))
2021-03-09 10:59:26 +00:00
.arg(TEST_HELLO_WORLD_DEST)
2021-04-22 20:37:44 +00:00
.succeeds();
2021-03-09 10:59:26 +00:00
// Check the content of the destination file that was copied.
assert_eq!(at.read(TEST_HELLO_WORLD_DEST), "Hello, World!\n");
}
#[test]
fn test_cp_parents() {
let (at, mut ucmd) = at_and_ucmd!();
2021-04-22 20:37:44 +00:00
ucmd.arg("--parents")
.arg(TEST_COPY_FROM_FOLDER_FILE)
.arg(TEST_COPY_TO_FOLDER)
2021-04-22 20:37:44 +00:00
.succeeds();
assert_eq!(
at.read(&format!(
"{TEST_COPY_TO_FOLDER}/{TEST_COPY_FROM_FOLDER_FILE}"
)),
"Hello, World!\n"
);
}
#[test]
fn test_cp_parents_multiple_files() {
let (at, mut ucmd) = at_and_ucmd!();
2021-04-22 20:37:44 +00:00
ucmd.arg("--parents")
.arg(TEST_COPY_FROM_FOLDER_FILE)
.arg(TEST_HOW_ARE_YOU_SOURCE)
.arg(TEST_COPY_TO_FOLDER)
2021-04-22 20:37:44 +00:00
.succeeds();
assert_eq!(
at.read(&format!(
"{TEST_COPY_TO_FOLDER}/{TEST_COPY_FROM_FOLDER_FILE}"
)),
"Hello, World!\n"
);
assert_eq!(
at.read(&format!("{TEST_COPY_TO_FOLDER}/{TEST_HOW_ARE_YOU_SOURCE}")),
"How are you?\n"
);
}
#[test]
fn test_cp_parents_dest_not_directory() {
2021-04-22 20:37:44 +00:00
new_ucmd!()
.arg("--parents")
.arg(TEST_COPY_FROM_FOLDER_FILE)
.arg(TEST_HELLO_WORLD_DEST)
2021-04-22 20:37:44 +00:00
.fails()
.stderr_contains("with --parents, the destination must be a directory");
}
#[test]
#[cfg(not(target_os = "openbsd"))]
fn test_cp_parents_with_permissions_copy_file() {
let (at, mut ucmd) = at_and_ucmd!();
let dir = "dir";
let file = "p1/p2/file";
at.mkdir(dir);
at.mkdir_all("p1/p2");
at.touch(file);
#[cfg(unix)]
{
let p1_mode = 0o0777;
let p2_mode = 0o0711;
let file_mode = 0o0702;
at.set_mode("p1", p1_mode);
at.set_mode("p1/p2", p2_mode);
at.set_mode(file, file_mode);
}
ucmd.arg("-p")
.arg("--parents")
.arg(file)
.arg(dir)
.succeeds();
#[cfg(all(unix, not(target_os = "freebsd")))]
{
let p1_metadata = at.metadata("p1");
let p2_metadata = at.metadata("p1/p2");
let file_metadata = at.metadata(file);
assert_metadata_eq!(p1_metadata, at.metadata("dir/p1"));
assert_metadata_eq!(p2_metadata, at.metadata("dir/p1/p2"));
assert_metadata_eq!(file_metadata, at.metadata("dir/p1/p2/file"));
}
}
#[test]
#[cfg(not(target_os = "openbsd"))]
fn test_cp_parents_with_permissions_copy_dir() {
let (at, mut ucmd) = at_and_ucmd!();
let dir1 = "dir";
let dir2 = "p1/p2";
let file = "p1/p2/file";
at.mkdir(dir1);
at.mkdir_all(dir2);
at.touch(file);
#[cfg(unix)]
{
let p1_mode = 0o0777;
let p2_mode = 0o0711;
let file_mode = 0o0702;
at.set_mode("p1", p1_mode);
at.set_mode("p1/p2", p2_mode);
at.set_mode(file, file_mode);
}
ucmd.arg("-p")
.arg("--parents")
.arg("-r")
.arg(dir2)
.arg(dir1)
.succeeds();
#[cfg(all(unix, not(target_os = "freebsd")))]
{
let p1_metadata = at.metadata("p1");
let p2_metadata = at.metadata("p1/p2");
let file_metadata = at.metadata(file);
assert_metadata_eq!(p1_metadata, at.metadata("dir/p1"));
assert_metadata_eq!(p2_metadata, at.metadata("dir/p1/p2"));
assert_metadata_eq!(file_metadata, at.metadata("dir/p1/p2/file"));
}
}
#[test]
#[cfg(unix)]
fn test_cp_writable_special_file_permissions() {
new_ucmd!().arg("/dev/null").arg("/dev/zero").succeeds();
}
2021-12-19 17:19:48 +00:00
#[test]
#[cfg(unix)]
fn test_cp_issue_1665() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("/dev/null").arg("foo").succeeds();
assert!(at.file_exists("foo"));
assert_eq!(at.read("foo"), "");
}
#[test]
#[cfg(not(target_os = "openbsd"))]
fn test_cp_preserve_no_args() {
let (at, mut ucmd) = at_and_ucmd!();
let src_file = "a";
let dst_file = "b";
// Prepare the source file
at.touch(src_file);
#[cfg(unix)]
at.set_mode(src_file, 0o0500);
// Copy
ucmd.arg(src_file)
.arg(dst_file)
.arg("--preserve")
.succeeds();
#[cfg(all(unix, not(target_os = "freebsd")))]
{
// Assert that the mode, ownership, and timestamps are preserved
// NOTICE: the ownership is not modified on the src file, because that requires root permissions
let metadata_src = at.metadata(src_file);
let metadata_dst = at.metadata(dst_file);
assert_metadata_eq!(metadata_src, metadata_dst);
}
}
#[test]
#[cfg(not(target_os = "openbsd"))]
fn test_cp_preserve_no_args_before_opts() {
let (at, mut ucmd) = at_and_ucmd!();
let src_file = "a";
let dst_file = "b";
// Prepare the source file
at.touch(src_file);
#[cfg(unix)]
at.set_mode(src_file, 0o0500);
// Copy
ucmd.arg("--preserve")
.arg(src_file)
.arg(dst_file)
.succeeds();
#[cfg(all(unix, not(target_os = "freebsd")))]
{
// Assert that the mode, ownership, and timestamps are preserved
// NOTICE: the ownership is not modified on the src file, because that requires root permissions
let metadata_src = at.metadata(src_file);
let metadata_dst = at.metadata(dst_file);
assert_metadata_eq!(metadata_src, metadata_dst);
}
}
cp: require preserve only certain attributes (#4099) * cp: require preserve only certain attributes # Conflicts: # src/uu/cp/src/copydir.rs # src/uu/cp/src/cp.rs * tests/cp: preserve all and preserve xattr tests with todos * tests/cp: rename preserve tests * tests/cp: add android fail test for preserve=xattr On Android, this cp with explicit preserve of xattr must fail, because of the limitations of the filesystem setup used on Android. * cp: verify some metadata in cp preserve tests # Conflicts: # tests/by-util/test_cp.rs * cp: run test_cp_preserve_all in all OS's but only check metadata on linux * test/cp: don't expect the mode to change in explicit cp preserve * cp: attributes struct instead of enum for unified required tracking * cp: refactor preserver and handle_preserve # Conflicts: # src/uu/cp/src/cp.rs * cp: update preserve attr to max * test/cp: fix the preserve xattr test Access timestamps appear to be modified only in this test. Running the command directly does not alter the access timestamp. * cp/test: preserve all and context case * cp: fix preserve args value source * test/cp: don't check mtime on freebsd * test/cp: don't check mtime on macos * test/cp: fix freebsd deps * test/cp: support freebsd tests * cp: simplify try_set_from_string * cp: parse context attr in preserve in any case to show warning later * cp: print warnings for attribute errors if not required * cp: show SELinux warning only once * cp: show SELinux warning without error * Revert "cp: show SELinux warning without error" This reverts commit d130cf0d8c8e28ac2c903413992613241decf879. * cp: add documentation for preserve components * cp: simplify try_set_from_string * cp: EN_US "behavior" spelling for cspell
2023-01-19 12:02:06 +00:00
#[test]
fn test_cp_preserve_all() {
for argument in ["--preserve=all", "--preserve=al"] {
let (at, mut ucmd) = at_and_ucmd!();
let src_file = "a";
let dst_file = "b";
// Prepare the source file
at.touch(src_file);
#[cfg(unix)]
at.set_mode(src_file, 0o0500);
// TODO: create a destination that does not allow copying of xattr and context
// Copy
ucmd.arg(src_file).arg(dst_file).arg(argument).succeeds();
#[cfg(all(unix, not(target_os = "freebsd")))]
{
// Assert that the mode, ownership, and timestamps are preserved
// NOTICE: the ownership is not modified on the src file, because that requires root permissions
let metadata_src = at.metadata(src_file);
let metadata_dst = at.metadata(dst_file);
assert_metadata_eq!(metadata_src, metadata_dst);
}
cp: require preserve only certain attributes (#4099) * cp: require preserve only certain attributes # Conflicts: # src/uu/cp/src/copydir.rs # src/uu/cp/src/cp.rs * tests/cp: preserve all and preserve xattr tests with todos * tests/cp: rename preserve tests * tests/cp: add android fail test for preserve=xattr On Android, this cp with explicit preserve of xattr must fail, because of the limitations of the filesystem setup used on Android. * cp: verify some metadata in cp preserve tests # Conflicts: # tests/by-util/test_cp.rs * cp: run test_cp_preserve_all in all OS's but only check metadata on linux * test/cp: don't expect the mode to change in explicit cp preserve * cp: attributes struct instead of enum for unified required tracking * cp: refactor preserver and handle_preserve # Conflicts: # src/uu/cp/src/cp.rs * cp: update preserve attr to max * test/cp: fix the preserve xattr test Access timestamps appear to be modified only in this test. Running the command directly does not alter the access timestamp. * cp/test: preserve all and context case * cp: fix preserve args value source * test/cp: don't check mtime on freebsd * test/cp: don't check mtime on macos * test/cp: fix freebsd deps * test/cp: support freebsd tests * cp: simplify try_set_from_string * cp: parse context attr in preserve in any case to show warning later * cp: print warnings for attribute errors if not required * cp: show SELinux warning only once * cp: show SELinux warning without error * Revert "cp: show SELinux warning without error" This reverts commit d130cf0d8c8e28ac2c903413992613241decf879. * cp: add documentation for preserve components * cp: simplify try_set_from_string * cp: EN_US "behavior" spelling for cspell
2023-01-19 12:02:06 +00:00
}
}
#[test]
#[cfg(all(unix, not(any(target_os = "android", target_os = "openbsd"))))]
cp: require preserve only certain attributes (#4099) * cp: require preserve only certain attributes # Conflicts: # src/uu/cp/src/copydir.rs # src/uu/cp/src/cp.rs * tests/cp: preserve all and preserve xattr tests with todos * tests/cp: rename preserve tests * tests/cp: add android fail test for preserve=xattr On Android, this cp with explicit preserve of xattr must fail, because of the limitations of the filesystem setup used on Android. * cp: verify some metadata in cp preserve tests # Conflicts: # tests/by-util/test_cp.rs * cp: run test_cp_preserve_all in all OS's but only check metadata on linux * test/cp: don't expect the mode to change in explicit cp preserve * cp: attributes struct instead of enum for unified required tracking * cp: refactor preserver and handle_preserve # Conflicts: # src/uu/cp/src/cp.rs * cp: update preserve attr to max * test/cp: fix the preserve xattr test Access timestamps appear to be modified only in this test. Running the command directly does not alter the access timestamp. * cp/test: preserve all and context case * cp: fix preserve args value source * test/cp: don't check mtime on freebsd * test/cp: don't check mtime on macos * test/cp: fix freebsd deps * test/cp: support freebsd tests * cp: simplify try_set_from_string * cp: parse context attr in preserve in any case to show warning later * cp: print warnings for attribute errors if not required * cp: show SELinux warning only once * cp: show SELinux warning without error * Revert "cp: show SELinux warning without error" This reverts commit d130cf0d8c8e28ac2c903413992613241decf879. * cp: add documentation for preserve components * cp: simplify try_set_from_string * cp: EN_US "behavior" spelling for cspell
2023-01-19 12:02:06 +00:00
fn test_cp_preserve_xattr() {
let (at, mut ucmd) = at_and_ucmd!();
let src_file = "a";
let dst_file = "b";
// Prepare the source file
at.touch(src_file);
#[cfg(unix)]
at.set_mode(src_file, 0o0500);
// Sleep so that the time stats are different
sleep(Duration::from_secs(1));
// TODO: create a destination that does not allow copying of xattr and context
// Copy
ucmd.arg(src_file)
.arg(dst_file)
.arg("--preserve=xattr")
.succeeds();
// FIXME: macos copy keeps the original mtime
#[cfg(not(any(target_os = "freebsd", target_os = "macos")))]
{
// Assert that the mode, ownership, and timestamps are *NOT* preserved
// NOTICE: the ownership is not modified on the src file, because that requires root permissions
let metadata_src = at.metadata(src_file);
let metadata_dst = at.metadata(dst_file);
assert_ne!(metadata_src.mtime(), metadata_dst.mtime());
// TODO: verify access time as well. It shouldn't change, however, it does change in this test.
}
}
#[test]
#[cfg(all(target_os = "linux", not(feature = "feat_selinux")))]
fn test_cp_preserve_all_context_fails_on_non_selinux() {
new_ucmd!()
.arg(TEST_COPY_FROM_FOLDER_FILE)
.arg(TEST_HELLO_WORLD_DEST)
.arg("--preserve=all,context")
.fails();
}
#[test]
fn test_cp_preserve_link_parses() {
// TODO: Also check whether --preserve=link did the right thing!
for argument in [
"--preserve=links",
"--preserve=link",
"--preserve=li",
"--preserve=l",
] {
new_ucmd!()
.arg(argument)
.arg(TEST_COPY_FROM_FOLDER_FILE)
.arg(TEST_HELLO_WORLD_DEST)
.succeeds()
.no_output();
}
}
#[test]
fn test_cp_preserve_invalid_rejected() {
new_ucmd!()
.arg("--preserve=invalid-value")
.arg(TEST_COPY_FROM_FOLDER_FILE)
.arg(TEST_HELLO_WORLD_DEST)
.fails()
.code_is(1)
.no_stdout();
}
cp: require preserve only certain attributes (#4099) * cp: require preserve only certain attributes # Conflicts: # src/uu/cp/src/copydir.rs # src/uu/cp/src/cp.rs * tests/cp: preserve all and preserve xattr tests with todos * tests/cp: rename preserve tests * tests/cp: add android fail test for preserve=xattr On Android, this cp with explicit preserve of xattr must fail, because of the limitations of the filesystem setup used on Android. * cp: verify some metadata in cp preserve tests # Conflicts: # tests/by-util/test_cp.rs * cp: run test_cp_preserve_all in all OS's but only check metadata on linux * test/cp: don't expect the mode to change in explicit cp preserve * cp: attributes struct instead of enum for unified required tracking * cp: refactor preserver and handle_preserve # Conflicts: # src/uu/cp/src/cp.rs * cp: update preserve attr to max * test/cp: fix the preserve xattr test Access timestamps appear to be modified only in this test. Running the command directly does not alter the access timestamp. * cp/test: preserve all and context case * cp: fix preserve args value source * test/cp: don't check mtime on freebsd * test/cp: don't check mtime on macos * test/cp: fix freebsd deps * test/cp: support freebsd tests * cp: simplify try_set_from_string * cp: parse context attr in preserve in any case to show warning later * cp: print warnings for attribute errors if not required * cp: show SELinux warning only once * cp: show SELinux warning without error * Revert "cp: show SELinux warning without error" This reverts commit d130cf0d8c8e28ac2c903413992613241decf879. * cp: add documentation for preserve components * cp: simplify try_set_from_string * cp: EN_US "behavior" spelling for cspell
2023-01-19 12:02:06 +00:00
#[test]
#[cfg(target_os = "android")]
#[ignore = "disabled until fixed"] // FIXME: the test looks to .succeed on android
cp: require preserve only certain attributes (#4099) * cp: require preserve only certain attributes # Conflicts: # src/uu/cp/src/copydir.rs # src/uu/cp/src/cp.rs * tests/cp: preserve all and preserve xattr tests with todos * tests/cp: rename preserve tests * tests/cp: add android fail test for preserve=xattr On Android, this cp with explicit preserve of xattr must fail, because of the limitations of the filesystem setup used on Android. * cp: verify some metadata in cp preserve tests # Conflicts: # tests/by-util/test_cp.rs * cp: run test_cp_preserve_all in all OS's but only check metadata on linux * test/cp: don't expect the mode to change in explicit cp preserve * cp: attributes struct instead of enum for unified required tracking * cp: refactor preserver and handle_preserve # Conflicts: # src/uu/cp/src/cp.rs * cp: update preserve attr to max * test/cp: fix the preserve xattr test Access timestamps appear to be modified only in this test. Running the command directly does not alter the access timestamp. * cp/test: preserve all and context case * cp: fix preserve args value source * test/cp: don't check mtime on freebsd * test/cp: don't check mtime on macos * test/cp: fix freebsd deps * test/cp: support freebsd tests * cp: simplify try_set_from_string * cp: parse context attr in preserve in any case to show warning later * cp: print warnings for attribute errors if not required * cp: show SELinux warning only once * cp: show SELinux warning without error * Revert "cp: show SELinux warning without error" This reverts commit d130cf0d8c8e28ac2c903413992613241decf879. * cp: add documentation for preserve components * cp: simplify try_set_from_string * cp: EN_US "behavior" spelling for cspell
2023-01-19 12:02:06 +00:00
fn test_cp_preserve_xattr_fails_on_android() {
// Because of the SELinux extended attributes used on Android, trying to copy extended
// attributes has to fail in this case, since we specify `--preserve=xattr` and this puts it
// into the required attributes
new_ucmd!()
.arg(TEST_COPY_FROM_FOLDER_FILE)
.arg(TEST_HELLO_WORLD_DEST)
.arg("--preserve=xattr")
.fails();
}
#[test]
// android platform will causing stderr = cp: Permission denied (os error 13)
#[cfg(not(target_os = "android"))]
fn test_cp_preserve_links_case_1() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("a");
at.hard_link("a", "b");
at.mkdir("c");
ucmd.arg("-d").arg("a").arg("b").arg("c").succeeds();
assert!(at.dir_exists("c"));
assert!(at.plus("c").join("a").exists());
assert!(at.plus("c").join("b").exists());
#[cfg(unix)]
{
let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap();
let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap();
assert_eq!(metadata_a.ino(), metadata_b.ino());
}
}
#[test]
// android platform will causing stderr = cp: Permission denied (os error 13)
#[cfg(not(target_os = "android"))]
fn test_cp_preserve_links_case_2() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("a");
at.symlink_file("a", "b");
at.mkdir("c");
ucmd.arg("--preserve=links")
.arg("-R")
.arg("-H")
.arg("a")
.arg("b")
.arg("c")
.succeeds();
assert!(at.dir_exists("c"));
assert!(at.plus("c").join("a").exists());
assert!(at.plus("c").join("b").exists());
#[cfg(unix)]
{
let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap();
let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap();
assert_eq!(metadata_a.ino(), metadata_b.ino());
}
}
#[test]
// android platform will causing stderr = cp: Permission denied (os error 13)
#[cfg(not(target_os = "android"))]
fn test_cp_preserve_links_case_3() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("d");
at.touch("d/a");
at.symlink_file("d/a", "d/b");
ucmd.arg("--preserve=links")
.arg("-R")
.arg("-L")
.arg("d")
.arg("c")
.succeeds();
assert!(at.dir_exists("c"));
assert!(at.plus("c").join("a").exists());
assert!(at.plus("c").join("b").exists());
#[cfg(unix)]
{
let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap();
let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap();
assert_eq!(metadata_a.ino(), metadata_b.ino());
}
}
#[test]
// android platform will causing stderr = cp: Permission denied (os error 13)
#[cfg(not(target_os = "android"))]
fn test_cp_preserve_links_case_4() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("d");
at.touch("d/a");
at.hard_link("d/a", "d/b");
ucmd.arg("--preserve=links")
.arg("-R")
.arg("-L")
.arg("d")
.arg("c")
.succeeds();
assert!(at.dir_exists("c"));
assert!(at.plus("c").join("a").exists());
assert!(at.plus("c").join("b").exists());
#[cfg(unix)]
{
let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap();
let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap();
assert_eq!(metadata_a.ino(), metadata_b.ino());
}
}
#[test]
// android platform will causing stderr = cp: Permission denied (os error 13)
#[cfg(not(target_os = "android"))]
fn test_cp_preserve_links_case_5() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("d");
at.touch("d/a");
at.hard_link("d/a", "d/b");
ucmd.arg("-dR")
.arg("--no-preserve=links")
.arg("d")
.arg("c")
.succeeds();
assert!(at.dir_exists("c"));
assert!(at.plus("c").join("a").exists());
assert!(at.plus("c").join("b").exists());
#[cfg(unix)]
{
let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap();
let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap();
assert_ne!(metadata_a.ino(), metadata_b.ino());
}
}
#[test]
// android platform will causing stderr = cp: Permission denied (os error 13)
#[cfg(not(target_os = "android"))]
fn test_cp_preserve_links_case_6() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("a");
at.hard_link("a", "b");
at.mkdir("c");
ucmd.arg("-d").arg("a").arg("b").arg("c").succeeds();
assert!(at.dir_exists("c"));
assert!(at.plus("c").join("a").exists());
assert!(at.plus("c").join("b").exists());
#[cfg(unix)]
{
let metadata_a = std::fs::metadata(at.subdir.join("c").join("a")).unwrap();
let metadata_b = std::fs::metadata(at.subdir.join("c").join("b")).unwrap();
assert_eq!(metadata_a.ino(), metadata_b.ino());
}
}
#[test]
// android platform will causing stderr = cp: Permission denied (os error 13)
#[cfg(not(target_os = "android"))]
fn test_cp_preserve_links_case_7() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("src");
at.touch("src/f");
at.hard_link("src/f", "src/g");
at.mkdir("dest");
at.touch("dest/g");
ucmd.arg("-n")
.arg("--preserve=links")
.arg("--debug")
.arg("src/f")
.arg("src/g")
.arg("dest")
.succeeds()
.stdout_contains("skipped");
assert!(at.dir_exists("dest"));
assert!(at.plus("dest").join("f").exists());
assert!(at.plus("dest").join("g").exists());
}
2023-09-30 07:09:57 +00:00
#[test]
#[cfg(unix)]
fn test_cp_no_preserve_mode() {
2023-09-30 07:09:57 +00:00
use uucore::fs as uufs;
let (at, mut ucmd) = at_and_ucmd!();
at.touch("a");
at.set_mode("a", 0o731);
ucmd.arg("-a")
.arg("--no-preserve=mode")
.arg("a")
.arg("b")
.umask(0o077)
2023-09-30 07:09:57 +00:00
.succeeds();
assert!(at.file_exists("b"));
let metadata_b = std::fs::metadata(at.subdir.join("b")).unwrap();
let permission_b = uufs::display_permissions(&metadata_b, false);
assert_eq!(permission_b, "rw-------".to_string());
}
#[test]
// For now, disable the test on Windows. Symlinks aren't well support on Windows.
// It works on Unix for now and it works locally when run from a powershell
#[cfg(not(windows))]
fn test_cp_deref_folder_to_folder() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let path_to_new_symlink = at.plus(TEST_COPY_FROM_FOLDER);
at.symlink_file(
&path_to_new_symlink
.join(TEST_HELLO_WORLD_SOURCE)
.to_string_lossy(),
&path_to_new_symlink
.join(TEST_HELLO_WORLD_SOURCE_SYMLINK)
.to_string_lossy(),
);
//using -P -R option
2021-04-22 20:37:44 +00:00
scene
.ucmd()
.arg("-L")
.arg("-R")
.arg("-v")
.arg(TEST_COPY_FROM_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW)
2021-04-22 20:37:44 +00:00
.succeeds();
#[cfg(not(windows))]
{
let scene2 = TestScenario::new("ls");
let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run();
println!("ls source {}", result.stdout_str());
let path_to_new_symlink = at.subdir.join(TEST_COPY_TO_FOLDER_NEW);
let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run();
println!("ls dest {}", result.stdout_str());
}
#[cfg(windows)]
{
// No action as this test is disabled but kept in case we want to
// try to make it work in the future.
let a = Command::new("cmd").args(&["/C", "dir"]).output();
println!("output {:#?}", a);
let a = Command::new("cmd")
.args(&["/C", "dir", &at.as_string()])
.output();
println!("output {:#?}", a);
let a = Command::new("cmd")
.args(&["/C", "dir", path_to_new_symlink.to_str().unwrap()])
.output();
println!("output {:#?}", a);
let path_to_new_symlink = at.subdir.join(TEST_COPY_FROM_FOLDER);
let a = Command::new("cmd")
.args(&["/C", "dir", path_to_new_symlink.to_str().unwrap()])
.output();
println!("output {:#?}", a);
let path_to_new_symlink = at.subdir.join(TEST_COPY_TO_FOLDER_NEW);
let a = Command::new("cmd")
.args(&["/C", "dir", path_to_new_symlink.to_str().unwrap()])
.output();
println!("output {:#?}", a);
}
let path_to_new_symlink = at
.subdir
.join(TEST_COPY_TO_FOLDER_NEW)
.join(TEST_HELLO_WORLD_SOURCE_SYMLINK);
assert!(at.file_exists(
2023-02-13 09:00:03 +00:00
path_to_new_symlink
.clone()
.into_os_string()
.into_string()
.unwrap()
));
let path_to_new = at.subdir.join(TEST_COPY_TO_FOLDER_NEW_FILE);
// Check the content of the destination file that was copied.
let path_to_check = path_to_new.to_str().unwrap();
assert_eq!(at.read(path_to_check), "Hello, World!\n");
// Check the content of the symlink
let path_to_check = path_to_new_symlink.to_str().unwrap();
assert_eq!(at.read(path_to_check), "Hello, World!\n");
}
2020-05-28 19:54:03 +00:00
#[test]
// For now, disable the test on Windows. Symlinks aren't well support on Windows.
// It works on Unix for now and it works locally when run from a powershell
#[cfg(not(windows))]
fn test_cp_no_deref_folder_to_folder() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let path_to_new_symlink = at.plus(TEST_COPY_FROM_FOLDER);
2020-05-28 19:54:03 +00:00
at.symlink_file(
&path_to_new_symlink
.join(TEST_HELLO_WORLD_SOURCE)
.to_string_lossy(),
&path_to_new_symlink
.join(TEST_HELLO_WORLD_SOURCE_SYMLINK)
.to_string_lossy(),
);
2020-05-28 19:54:03 +00:00
//using -P -R option
2021-04-22 20:37:44 +00:00
scene
2020-05-28 19:54:03 +00:00
.ucmd()
.arg("-P")
.arg("-R")
.arg("-v")
.arg(TEST_COPY_FROM_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW)
2021-04-22 20:37:44 +00:00
.succeeds();
2020-05-28 19:54:03 +00:00
#[cfg(not(windows))]
{
let scene2 = TestScenario::new("ls");
let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run();
println!("ls source {}", result.stdout_str());
2020-05-28 19:54:03 +00:00
let path_to_new_symlink = at.subdir.join(TEST_COPY_TO_FOLDER_NEW);
let result = scene2.cmd("ls").arg("-al").arg(path_to_new_symlink).run();
println!("ls dest {}", result.stdout_str());
2020-05-28 19:54:03 +00:00
}
#[cfg(windows)]
{
// No action as this test is disabled but kept in case we want to
// try to make it work in the future.
let a = Command::new("cmd").args(&["/C", "dir"]).output();
println!("output {:#?}", a);
let a = Command::new("cmd")
.args(&["/C", "dir", &at.as_string()])
.output();
println!("output {:#?}", a);
let a = Command::new("cmd")
.args(&["/C", "dir", path_to_new_symlink.to_str().unwrap()])
.output();
println!("output {:#?}", a);
let path_to_new_symlink = at.subdir.join(TEST_COPY_FROM_FOLDER);
let a = Command::new("cmd")
.args(&["/C", "dir", path_to_new_symlink.to_str().unwrap()])
.output();
println!("output {:#?}", a);
let path_to_new_symlink = at.subdir.join(TEST_COPY_TO_FOLDER_NEW);
let a = Command::new("cmd")
.args(&["/C", "dir", path_to_new_symlink.to_str().unwrap()])
.output();
println!("output {:#?}", a);
}
let path_to_new_symlink = at
.subdir
.join(TEST_COPY_TO_FOLDER_NEW)
.join(TEST_HELLO_WORLD_SOURCE_SYMLINK);
assert!(at.is_symlink(
&path_to_new_symlink
.clone()
.into_os_string()
.into_string()
.unwrap()
));
let path_to_new = at.subdir.join(TEST_COPY_TO_FOLDER_NEW_FILE);
// Check the content of the destination file that was copied.
let path_to_check = path_to_new.to_str().unwrap();
assert_eq!(at.read(path_to_check), "Hello, World!\n");
// Check the content of the symlink
let path_to_check = path_to_new_symlink.to_str().unwrap();
assert_eq!(at.read(path_to_check), "Hello, World!\n");
2020-05-28 19:54:03 +00:00
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_archive() {
let (at, mut ucmd) = at_and_ucmd!();
let ts = time::OffsetDateTime::now_utc();
let previous = FileTime::from_unix_time(ts.unix_timestamp() - 3600, ts.nanosecond());
// set the file creation/modification an hour ago
filetime::set_file_times(
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
previous,
previous,
)
.unwrap();
2021-04-22 20:37:44 +00:00
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--archive")
.arg(TEST_HOW_ARE_YOU_SOURCE)
2021-04-22 20:37:44 +00:00
.succeeds();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap();
let creation = metadata.modified().unwrap();
let metadata2 = std_fs::metadata(at.subdir.join(TEST_HOW_ARE_YOU_SOURCE)).unwrap();
let creation2 = metadata2.modified().unwrap();
let scene2 = TestScenario::new("ls");
2021-04-22 20:37:44 +00:00
let result = scene2.cmd("ls").arg("-al").arg(at.subdir).succeeds();
println!("ls dest {}", result.stdout_str());
assert_eq!(creation, creation2);
}
#[test]
#[cfg(all(unix, not(target_os = "android")))]
fn test_cp_archive_recursive() {
let (at, mut ucmd) = at_and_ucmd!();
// creates
// dir/1
// dir/1.link => dir/1
// dir/2
// dir/2.link => dir/2
let file_1 = at.subdir.join(TEST_COPY_TO_FOLDER).join("1");
let file_1_link = at.subdir.join(TEST_COPY_TO_FOLDER).join("1.link");
let file_2 = at.subdir.join(TEST_COPY_TO_FOLDER).join("2");
let file_2_link = at.subdir.join(TEST_COPY_TO_FOLDER).join("2.link");
2023-02-13 09:00:03 +00:00
at.touch(file_1);
at.touch(file_2);
at.symlink_file("1", &file_1_link.to_string_lossy());
at.symlink_file("2", &file_2_link.to_string_lossy());
2021-04-22 20:37:44 +00:00
ucmd.arg("--archive")
.arg(TEST_COPY_TO_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW)
.succeeds();
let scene2 = TestScenario::new("ls");
let result = scene2
.cmd("ls")
.arg("-al")
.arg(at.subdir.join(TEST_COPY_TO_FOLDER))
.run();
println!("ls dest {}", result.stdout_str());
let result = scene2
.cmd("ls")
.arg("-al")
.arg(at.subdir.join(TEST_COPY_TO_FOLDER_NEW))
.run();
println!("ls dest {}", result.stdout_str());
2023-02-13 09:00:03 +00:00
assert!(at.file_exists(at.subdir.join(TEST_COPY_TO_FOLDER_NEW).join("1")));
assert!(at.file_exists(at.subdir.join(TEST_COPY_TO_FOLDER_NEW).join("2")));
assert!(at.is_symlink(
&at.subdir
.join(TEST_COPY_TO_FOLDER_NEW)
.join("1.link")
.to_string_lossy()
));
assert!(at.is_symlink(
&at.subdir
.join(TEST_COPY_TO_FOLDER_NEW)
.join("2.link")
.to_string_lossy()
));
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_preserve_timestamps() {
let (at, mut ucmd) = at_and_ucmd!();
let ts = time::OffsetDateTime::now_utc();
2022-04-23 19:32:35 +00:00
let previous = FileTime::from_unix_time(ts.unix_timestamp() - 3600, ts.nanosecond());
// set the file creation/modification an hour ago
filetime::set_file_times(
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
previous,
previous,
)
.unwrap();
2021-04-22 20:37:44 +00:00
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--preserve=timestamps")
.arg(TEST_HOW_ARE_YOU_SOURCE)
2021-04-22 20:37:44 +00:00
.succeeds();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap();
let creation = metadata.modified().unwrap();
let metadata2 = std_fs::metadata(at.subdir.join(TEST_HOW_ARE_YOU_SOURCE)).unwrap();
let creation2 = metadata2.modified().unwrap();
let scene2 = TestScenario::new("ls");
let result = scene2.cmd("ls").arg("-al").arg(at.subdir).run();
println!("ls dest {}", result.stdout_str());
assert_eq!(creation, creation2);
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_no_preserve_timestamps() {
let (at, mut ucmd) = at_and_ucmd!();
let ts = time::OffsetDateTime::now_utc();
2022-04-23 19:32:35 +00:00
let previous = FileTime::from_unix_time(ts.unix_timestamp() - 3600, ts.nanosecond());
// set the file creation/modification an hour ago
filetime::set_file_times(
at.plus_as_string(TEST_HELLO_WORLD_SOURCE),
previous,
previous,
)
.unwrap();
sleep(Duration::from_secs(3));
2021-04-22 20:37:44 +00:00
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.arg("--no-preserve=timestamps")
.arg(TEST_HOW_ARE_YOU_SOURCE)
2021-04-22 20:37:44 +00:00
.succeeds();
assert_eq!(at.read(TEST_HOW_ARE_YOU_SOURCE), "Hello, World!\n");
let metadata = std_fs::metadata(at.subdir.join(TEST_HELLO_WORLD_SOURCE)).unwrap();
let creation = metadata.modified().unwrap();
let metadata2 = std_fs::metadata(at.subdir.join(TEST_HOW_ARE_YOU_SOURCE)).unwrap();
let creation2 = metadata2.modified().unwrap();
let scene2 = TestScenario::new("ls");
let result = scene2.cmd("ls").arg("-al").arg(at.subdir).run();
println!("ls dest {}", result.stdout_str());
println!("creation {creation:?} / {creation2:?}");
assert_ne!(creation, creation2);
let res = creation.elapsed().unwrap() - creation2.elapsed().unwrap();
// Some margins with time check
assert!(res.as_secs() > 3595);
assert!(res.as_secs() < 3605);
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_target_file_dev_null() {
let (at, mut ucmd) = at_and_ucmd!();
let file1 = "/dev/null";
let file2 = "test_cp_target_file_file_i2";
at.touch(file2);
ucmd.arg(file1).arg(file2).succeeds().no_stderr();
assert!(at.file_exists(file2));
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android", target_os = "freebsd"))]
fn test_cp_one_file_system() {
use crate::common::util::AtPath;
use walkdir::WalkDir;
let scene = TestScenario::new(util_name!());
2023-04-20 21:00:46 +00:00
let at = &scene.fixtures;
// Test must be run as root (or with `sudo -E`)
2021-04-22 20:37:44 +00:00
if scene.cmd("whoami").run().stdout_str() != "root\n" {
return;
}
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);
2021-04-17 13:48:23 +00:00
scene
.cmd("mount")
.arg("-t")
.arg("tmpfs")
.arg("-o")
.arg("size=640k") // ought to be enough
.arg("tmpfs")
.arg(mountpoint_path)
2021-04-17 13:48:23 +00:00
.succeeds();
at_src.touch(TEST_MOUNT_OTHER_FILESYSTEM_FILE);
// Begin testing -x flag
2021-04-22 20:37:44 +00:00
scene
.ucmd()
.arg("-rx")
.arg(TEST_MOUNT_COPY_FROM_FOLDER)
.arg(TEST_COPY_TO_FOLDER_NEW)
2021-04-22 20:37:44 +00:00
.succeeds();
// Ditch the mount before the asserts
2021-04-17 13:48:23 +00:00
scene.cmd("umount").arg(mountpoint_path).succeeds();
assert!(!at_dst.file_exists(TEST_MOUNT_OTHER_FILESYSTEM_FILE));
// Check if the other files were copied from the source folder hierarchy
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!(),
}
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
fn test_cp_reflink_always() {
let (at, mut ucmd) = at_and_ucmd!();
let result = ucmd
.arg("--reflink=always")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_EXISTING_FILE)
.run();
2021-04-24 10:45:55 +00:00
if result.succeeded() {
// Check the content of the destination file
assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n");
} else {
// Older Linux versions do not support cloning.
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
fn test_cp_reflink_auto() {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg("--reflink=auto")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_EXISTING_FILE)
.succeeds();
// Check the content of the destination file
assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n");
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
fn test_cp_reflink_none() {
let (at, mut ucmd) = at_and_ucmd!();
let result = ucmd
.arg("--reflink")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_EXISTING_FILE)
.run();
if result.succeeded() {
// Check the content of the destination file
assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n");
} else {
// Older Linux versions do not support cloning.
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
fn test_cp_reflink_never() {
for argument in ["--reflink=never", "--reflink=neve", "--reflink=n"] {
let (at, mut ucmd) = at_and_ucmd!();
ucmd.arg(argument)
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_EXISTING_FILE)
.succeeds();
// Check the content of the destination file
assert_eq!(at.read(TEST_EXISTING_FILE), "Hello, World!\n");
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android", target_os = "macos"))]
fn test_cp_reflink_bad() {
let (_, mut ucmd) = at_and_ucmd!();
2021-04-24 10:45:55 +00:00
let _result = ucmd
.arg("--reflink=bad")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg(TEST_EXISTING_FILE)
.fails()
.stderr_contains("error: invalid value 'bad' for '--reflink[=<WHEN>]'");
}
2021-06-01 21:06:38 +00:00
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
2021-06-01 21:06:38 +00:00
fn test_cp_reflink_insufficient_permission() {
let (at, mut ucmd) = at_and_ucmd!();
at.make_file("unreadable")
.set_permissions(PermissionsExt::from_mode(0o000))
.unwrap();
ucmd.arg("-r")
.arg("--reflink=auto")
.arg("unreadable")
.arg(TEST_EXISTING_FILE)
.fails()
.stderr_only("cp: 'unreadable' -> 'existing_file.txt': Permission denied (os error 13)\n");
2021-06-01 21:06:38 +00:00
}
#[cfg(target_os = "linux")]
#[test]
fn test_closes_file_descriptors() {
use procfs::process::Process;
use rlimit::Resource;
let me = Process::myself().unwrap();
// The test suite runs in parallel, we have pipe, sockets
// opened by other tests.
// So, we take in account the various fd to increase the limit
let number_file_already_opened: u64 = me.fd_count().unwrap().try_into().unwrap();
let limit_fd: u64 = number_file_already_opened + 9;
// For debugging purposes:
for f in me.fd().unwrap() {
let fd = f.unwrap();
println!("{:?} {:?}", fd, fd.mode());
}
new_ucmd!()
.arg("-r")
.arg("--reflink=auto")
.arg("dir_with_10_files/")
.arg("dir_with_10_files_new/")
.limit(Resource::NOFILE, limit_fd, limit_fd)
.succeeds();
}
2021-06-17 20:26:13 +00:00
cp: Implement `--sparse` flag (#3766) * cp: Refactor `reflink`/`sparse` handling to enable `--sparse` flag `--sparse` and `--reflink` options have a lot of similarities: - They have similar options (`always`, `never`, `auto`) - Both need OS specific handling - They can be mutually exclusive Prior to this change, `sparse` was defined as `CopyMode`, but `reflink` wasn't. Given the similarities, it makes sense to handle them similarly. The idea behind this change is to move all OS specific file copy handling in the `copy_on_write_*` functions. Those function then dispatch to the correct logic depending on the arguments (at the moment, the tuple `(reflink, sparse)`). Also, move the handling of `--reflink=never` from `copy_file` to the `copy_on_write_*` functions, at the cost of a bit of code duplication, to allow `copy_on_write_*` to handle all cases (and later handle `--reflink=never` with `--sparse`). * cp: Implement `--sparse` flag This begins to address #3362 At the moment, only the `--sparse=always` logic matches the requirement form GNU cp info page, i.e. always make holes in destination when possible. Sparse copy is done by copying the source to the destination block by block (blocks being of the destination's fs block size). If the block only holds NUL bytes, we don't write to the destination. About `--sparse=auto`: according to GNU cp info page, the destination file will be made sparse if the source file is sparse as well. The next step are likely to use `lseek` with `SEEK_HOLE` detect if the source file has holes. Currently, this has the same behaviour as `--sparse=never`. This `SEEK_HOLE` logic can also be applied to `--sparse=always` to improve performance when copying sparse files. About `--sparse=never`: from my understanding, it is not guaranteed that Rust's `fs::copy` will always produce a file with no holes, as ["platform-specific behavior may change in the future"](https://doc.rust-lang.org/std/fs/fn.copy.html#platform-specific-behavior) About other platforms: - `macos`: The solution may be to use `fcntl` command `F_PUNCHHOLE`. - `windows`: I only see `FSCTL_SET_SPARSE`. This should pass the following GNU tests: - `tests/cp/sparse.sh` - `tests/cp/sparse-2.sh` - `tests/cp/sparse-extents.sh` - `tests/cp/sparse-extents-2.sh` `sparse-perf.sh` needs `--sparse=auto`, and in particular a way to skip holes in the source file. Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
2022-08-04 11:22:59 +00:00
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_cp_sparse_never_empty() {
2024-06-30 14:27:08 +00:00
const BUFFER_SIZE: usize = 4096 * 4;
cp: Implement `--sparse` flag (#3766) * cp: Refactor `reflink`/`sparse` handling to enable `--sparse` flag `--sparse` and `--reflink` options have a lot of similarities: - They have similar options (`always`, `never`, `auto`) - Both need OS specific handling - They can be mutually exclusive Prior to this change, `sparse` was defined as `CopyMode`, but `reflink` wasn't. Given the similarities, it makes sense to handle them similarly. The idea behind this change is to move all OS specific file copy handling in the `copy_on_write_*` functions. Those function then dispatch to the correct logic depending on the arguments (at the moment, the tuple `(reflink, sparse)`). Also, move the handling of `--reflink=never` from `copy_file` to the `copy_on_write_*` functions, at the cost of a bit of code duplication, to allow `copy_on_write_*` to handle all cases (and later handle `--reflink=never` with `--sparse`). * cp: Implement `--sparse` flag This begins to address #3362 At the moment, only the `--sparse=always` logic matches the requirement form GNU cp info page, i.e. always make holes in destination when possible. Sparse copy is done by copying the source to the destination block by block (blocks being of the destination's fs block size). If the block only holds NUL bytes, we don't write to the destination. About `--sparse=auto`: according to GNU cp info page, the destination file will be made sparse if the source file is sparse as well. The next step are likely to use `lseek` with `SEEK_HOLE` detect if the source file has holes. Currently, this has the same behaviour as `--sparse=never`. This `SEEK_HOLE` logic can also be applied to `--sparse=always` to improve performance when copying sparse files. About `--sparse=never`: from my understanding, it is not guaranteed that Rust's `fs::copy` will always produce a file with no holes, as ["platform-specific behavior may change in the future"](https://doc.rust-lang.org/std/fs/fn.copy.html#platform-specific-behavior) About other platforms: - `macos`: The solution may be to use `fcntl` command `F_PUNCHHOLE`. - `windows`: I only see `FSCTL_SET_SPARSE`. This should pass the following GNU tests: - `tests/cp/sparse.sh` - `tests/cp/sparse-2.sh` - `tests/cp/sparse-extents.sh` - `tests/cp/sparse-extents-2.sh` `sparse-perf.sh` needs `--sparse=auto`, and in particular a way to skip holes in the source file. Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
2022-08-04 11:22:59 +00:00
let (at, mut ucmd) = at_and_ucmd!();
let buf: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
at.make_file("src_file1");
at.write_bytes("src_file1", &buf);
ucmd.args(&["--sparse=never", "src_file1", "dst_file_non_sparse"])
.succeeds();
assert_eq!(at.read_bytes("dst_file_non_sparse"), buf);
assert_eq!(
at.metadata("dst_file_non_sparse").blocks() * 512,
buf.len() as u64
);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_cp_sparse_always_empty() {
2024-06-30 14:27:08 +00:00
const BUFFER_SIZE: usize = 4096 * 4;
for argument in ["--sparse=always", "--sparse=alway", "--sparse=al"] {
let (at, mut ucmd) = at_and_ucmd!();
cp: Implement `--sparse` flag (#3766) * cp: Refactor `reflink`/`sparse` handling to enable `--sparse` flag `--sparse` and `--reflink` options have a lot of similarities: - They have similar options (`always`, `never`, `auto`) - Both need OS specific handling - They can be mutually exclusive Prior to this change, `sparse` was defined as `CopyMode`, but `reflink` wasn't. Given the similarities, it makes sense to handle them similarly. The idea behind this change is to move all OS specific file copy handling in the `copy_on_write_*` functions. Those function then dispatch to the correct logic depending on the arguments (at the moment, the tuple `(reflink, sparse)`). Also, move the handling of `--reflink=never` from `copy_file` to the `copy_on_write_*` functions, at the cost of a bit of code duplication, to allow `copy_on_write_*` to handle all cases (and later handle `--reflink=never` with `--sparse`). * cp: Implement `--sparse` flag This begins to address #3362 At the moment, only the `--sparse=always` logic matches the requirement form GNU cp info page, i.e. always make holes in destination when possible. Sparse copy is done by copying the source to the destination block by block (blocks being of the destination's fs block size). If the block only holds NUL bytes, we don't write to the destination. About `--sparse=auto`: according to GNU cp info page, the destination file will be made sparse if the source file is sparse as well. The next step are likely to use `lseek` with `SEEK_HOLE` detect if the source file has holes. Currently, this has the same behaviour as `--sparse=never`. This `SEEK_HOLE` logic can also be applied to `--sparse=always` to improve performance when copying sparse files. About `--sparse=never`: from my understanding, it is not guaranteed that Rust's `fs::copy` will always produce a file with no holes, as ["platform-specific behavior may change in the future"](https://doc.rust-lang.org/std/fs/fn.copy.html#platform-specific-behavior) About other platforms: - `macos`: The solution may be to use `fcntl` command `F_PUNCHHOLE`. - `windows`: I only see `FSCTL_SET_SPARSE`. This should pass the following GNU tests: - `tests/cp/sparse.sh` - `tests/cp/sparse-2.sh` - `tests/cp/sparse-extents.sh` - `tests/cp/sparse-extents-2.sh` `sparse-perf.sh` needs `--sparse=auto`, and in particular a way to skip holes in the source file. Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
2022-08-04 11:22:59 +00:00
let buf: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
cp: Implement `--sparse` flag (#3766) * cp: Refactor `reflink`/`sparse` handling to enable `--sparse` flag `--sparse` and `--reflink` options have a lot of similarities: - They have similar options (`always`, `never`, `auto`) - Both need OS specific handling - They can be mutually exclusive Prior to this change, `sparse` was defined as `CopyMode`, but `reflink` wasn't. Given the similarities, it makes sense to handle them similarly. The idea behind this change is to move all OS specific file copy handling in the `copy_on_write_*` functions. Those function then dispatch to the correct logic depending on the arguments (at the moment, the tuple `(reflink, sparse)`). Also, move the handling of `--reflink=never` from `copy_file` to the `copy_on_write_*` functions, at the cost of a bit of code duplication, to allow `copy_on_write_*` to handle all cases (and later handle `--reflink=never` with `--sparse`). * cp: Implement `--sparse` flag This begins to address #3362 At the moment, only the `--sparse=always` logic matches the requirement form GNU cp info page, i.e. always make holes in destination when possible. Sparse copy is done by copying the source to the destination block by block (blocks being of the destination's fs block size). If the block only holds NUL bytes, we don't write to the destination. About `--sparse=auto`: according to GNU cp info page, the destination file will be made sparse if the source file is sparse as well. The next step are likely to use `lseek` with `SEEK_HOLE` detect if the source file has holes. Currently, this has the same behaviour as `--sparse=never`. This `SEEK_HOLE` logic can also be applied to `--sparse=always` to improve performance when copying sparse files. About `--sparse=never`: from my understanding, it is not guaranteed that Rust's `fs::copy` will always produce a file with no holes, as ["platform-specific behavior may change in the future"](https://doc.rust-lang.org/std/fs/fn.copy.html#platform-specific-behavior) About other platforms: - `macos`: The solution may be to use `fcntl` command `F_PUNCHHOLE`. - `windows`: I only see `FSCTL_SET_SPARSE`. This should pass the following GNU tests: - `tests/cp/sparse.sh` - `tests/cp/sparse-2.sh` - `tests/cp/sparse-extents.sh` - `tests/cp/sparse-extents-2.sh` `sparse-perf.sh` needs `--sparse=auto`, and in particular a way to skip holes in the source file. Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
2022-08-04 11:22:59 +00:00
at.make_file("src_file1");
at.write_bytes("src_file1", &buf);
cp: Implement `--sparse` flag (#3766) * cp: Refactor `reflink`/`sparse` handling to enable `--sparse` flag `--sparse` and `--reflink` options have a lot of similarities: - They have similar options (`always`, `never`, `auto`) - Both need OS specific handling - They can be mutually exclusive Prior to this change, `sparse` was defined as `CopyMode`, but `reflink` wasn't. Given the similarities, it makes sense to handle them similarly. The idea behind this change is to move all OS specific file copy handling in the `copy_on_write_*` functions. Those function then dispatch to the correct logic depending on the arguments (at the moment, the tuple `(reflink, sparse)`). Also, move the handling of `--reflink=never` from `copy_file` to the `copy_on_write_*` functions, at the cost of a bit of code duplication, to allow `copy_on_write_*` to handle all cases (and later handle `--reflink=never` with `--sparse`). * cp: Implement `--sparse` flag This begins to address #3362 At the moment, only the `--sparse=always` logic matches the requirement form GNU cp info page, i.e. always make holes in destination when possible. Sparse copy is done by copying the source to the destination block by block (blocks being of the destination's fs block size). If the block only holds NUL bytes, we don't write to the destination. About `--sparse=auto`: according to GNU cp info page, the destination file will be made sparse if the source file is sparse as well. The next step are likely to use `lseek` with `SEEK_HOLE` detect if the source file has holes. Currently, this has the same behaviour as `--sparse=never`. This `SEEK_HOLE` logic can also be applied to `--sparse=always` to improve performance when copying sparse files. About `--sparse=never`: from my understanding, it is not guaranteed that Rust's `fs::copy` will always produce a file with no holes, as ["platform-specific behavior may change in the future"](https://doc.rust-lang.org/std/fs/fn.copy.html#platform-specific-behavior) About other platforms: - `macos`: The solution may be to use `fcntl` command `F_PUNCHHOLE`. - `windows`: I only see `FSCTL_SET_SPARSE`. This should pass the following GNU tests: - `tests/cp/sparse.sh` - `tests/cp/sparse-2.sh` - `tests/cp/sparse-extents.sh` - `tests/cp/sparse-extents-2.sh` `sparse-perf.sh` needs `--sparse=auto`, and in particular a way to skip holes in the source file. Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
2022-08-04 11:22:59 +00:00
ucmd.args(&[argument, "src_file1", "dst_file_sparse"])
.succeeds();
cp: Implement `--sparse` flag (#3766) * cp: Refactor `reflink`/`sparse` handling to enable `--sparse` flag `--sparse` and `--reflink` options have a lot of similarities: - They have similar options (`always`, `never`, `auto`) - Both need OS specific handling - They can be mutually exclusive Prior to this change, `sparse` was defined as `CopyMode`, but `reflink` wasn't. Given the similarities, it makes sense to handle them similarly. The idea behind this change is to move all OS specific file copy handling in the `copy_on_write_*` functions. Those function then dispatch to the correct logic depending on the arguments (at the moment, the tuple `(reflink, sparse)`). Also, move the handling of `--reflink=never` from `copy_file` to the `copy_on_write_*` functions, at the cost of a bit of code duplication, to allow `copy_on_write_*` to handle all cases (and later handle `--reflink=never` with `--sparse`). * cp: Implement `--sparse` flag This begins to address #3362 At the moment, only the `--sparse=always` logic matches the requirement form GNU cp info page, i.e. always make holes in destination when possible. Sparse copy is done by copying the source to the destination block by block (blocks being of the destination's fs block size). If the block only holds NUL bytes, we don't write to the destination. About `--sparse=auto`: according to GNU cp info page, the destination file will be made sparse if the source file is sparse as well. The next step are likely to use `lseek` with `SEEK_HOLE` detect if the source file has holes. Currently, this has the same behaviour as `--sparse=never`. This `SEEK_HOLE` logic can also be applied to `--sparse=always` to improve performance when copying sparse files. About `--sparse=never`: from my understanding, it is not guaranteed that Rust's `fs::copy` will always produce a file with no holes, as ["platform-specific behavior may change in the future"](https://doc.rust-lang.org/std/fs/fn.copy.html#platform-specific-behavior) About other platforms: - `macos`: The solution may be to use `fcntl` command `F_PUNCHHOLE`. - `windows`: I only see `FSCTL_SET_SPARSE`. This should pass the following GNU tests: - `tests/cp/sparse.sh` - `tests/cp/sparse-2.sh` - `tests/cp/sparse-extents.sh` - `tests/cp/sparse-extents-2.sh` `sparse-perf.sh` needs `--sparse=auto`, and in particular a way to skip holes in the source file. Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
2022-08-04 11:22:59 +00:00
assert_eq!(at.read_bytes("dst_file_sparse"), buf);
assert_eq!(at.metadata("dst_file_sparse").blocks(), 0);
}
cp: Implement `--sparse` flag (#3766) * cp: Refactor `reflink`/`sparse` handling to enable `--sparse` flag `--sparse` and `--reflink` options have a lot of similarities: - They have similar options (`always`, `never`, `auto`) - Both need OS specific handling - They can be mutually exclusive Prior to this change, `sparse` was defined as `CopyMode`, but `reflink` wasn't. Given the similarities, it makes sense to handle them similarly. The idea behind this change is to move all OS specific file copy handling in the `copy_on_write_*` functions. Those function then dispatch to the correct logic depending on the arguments (at the moment, the tuple `(reflink, sparse)`). Also, move the handling of `--reflink=never` from `copy_file` to the `copy_on_write_*` functions, at the cost of a bit of code duplication, to allow `copy_on_write_*` to handle all cases (and later handle `--reflink=never` with `--sparse`). * cp: Implement `--sparse` flag This begins to address #3362 At the moment, only the `--sparse=always` logic matches the requirement form GNU cp info page, i.e. always make holes in destination when possible. Sparse copy is done by copying the source to the destination block by block (blocks being of the destination's fs block size). If the block only holds NUL bytes, we don't write to the destination. About `--sparse=auto`: according to GNU cp info page, the destination file will be made sparse if the source file is sparse as well. The next step are likely to use `lseek` with `SEEK_HOLE` detect if the source file has holes. Currently, this has the same behaviour as `--sparse=never`. This `SEEK_HOLE` logic can also be applied to `--sparse=always` to improve performance when copying sparse files. About `--sparse=never`: from my understanding, it is not guaranteed that Rust's `fs::copy` will always produce a file with no holes, as ["platform-specific behavior may change in the future"](https://doc.rust-lang.org/std/fs/fn.copy.html#platform-specific-behavior) About other platforms: - `macos`: The solution may be to use `fcntl` command `F_PUNCHHOLE`. - `windows`: I only see `FSCTL_SET_SPARSE`. This should pass the following GNU tests: - `tests/cp/sparse.sh` - `tests/cp/sparse-2.sh` - `tests/cp/sparse-extents.sh` - `tests/cp/sparse-extents-2.sh` `sparse-perf.sh` needs `--sparse=auto`, and in particular a way to skip holes in the source file. Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
2022-08-04 11:22:59 +00:00
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_cp_sparse_always_non_empty() {
2024-06-30 14:27:08 +00:00
const BUFFER_SIZE: usize = 4096 * 16 + 3;
cp: Implement `--sparse` flag (#3766) * cp: Refactor `reflink`/`sparse` handling to enable `--sparse` flag `--sparse` and `--reflink` options have a lot of similarities: - They have similar options (`always`, `never`, `auto`) - Both need OS specific handling - They can be mutually exclusive Prior to this change, `sparse` was defined as `CopyMode`, but `reflink` wasn't. Given the similarities, it makes sense to handle them similarly. The idea behind this change is to move all OS specific file copy handling in the `copy_on_write_*` functions. Those function then dispatch to the correct logic depending on the arguments (at the moment, the tuple `(reflink, sparse)`). Also, move the handling of `--reflink=never` from `copy_file` to the `copy_on_write_*` functions, at the cost of a bit of code duplication, to allow `copy_on_write_*` to handle all cases (and later handle `--reflink=never` with `--sparse`). * cp: Implement `--sparse` flag This begins to address #3362 At the moment, only the `--sparse=always` logic matches the requirement form GNU cp info page, i.e. always make holes in destination when possible. Sparse copy is done by copying the source to the destination block by block (blocks being of the destination's fs block size). If the block only holds NUL bytes, we don't write to the destination. About `--sparse=auto`: according to GNU cp info page, the destination file will be made sparse if the source file is sparse as well. The next step are likely to use `lseek` with `SEEK_HOLE` detect if the source file has holes. Currently, this has the same behaviour as `--sparse=never`. This `SEEK_HOLE` logic can also be applied to `--sparse=always` to improve performance when copying sparse files. About `--sparse=never`: from my understanding, it is not guaranteed that Rust's `fs::copy` will always produce a file with no holes, as ["platform-specific behavior may change in the future"](https://doc.rust-lang.org/std/fs/fn.copy.html#platform-specific-behavior) About other platforms: - `macos`: The solution may be to use `fcntl` command `F_PUNCHHOLE`. - `windows`: I only see `FSCTL_SET_SPARSE`. This should pass the following GNU tests: - `tests/cp/sparse.sh` - `tests/cp/sparse-2.sh` - `tests/cp/sparse-extents.sh` - `tests/cp/sparse-extents-2.sh` `sparse-perf.sh` needs `--sparse=auto`, and in particular a way to skip holes in the source file. Co-authored-by: Sylvestre Ledru <sylvestre@debian.org>
2022-08-04 11:22:59 +00:00
let (at, mut ucmd) = at_and_ucmd!();
let mut buf: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
let blocks_to_touch = [buf.len() / 3, 2 * (buf.len() / 3)];
for i in blocks_to_touch {
buf[i] = b'x';
}
at.make_file("src_file1");
at.write_bytes("src_file1", &buf);
ucmd.args(&["--sparse=always", "src_file1", "dst_file_sparse"])
.succeeds();
let touched_block_count =
blocks_to_touch.len() as u64 * at.metadata("dst_file_sparse").blksize() / 512;
assert_eq!(at.read_bytes("dst_file_sparse"), buf);
assert_eq!(at.metadata("dst_file_sparse").blocks(), touched_block_count);
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_cp_sparse_invalid_option() {
let (at, mut ucmd) = at_and_ucmd!();
at.make_file("src_file1");
ucmd.args(&["--sparse=invalid", "src_file1", "dst_file"])
.fails();
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_cp_sparse_always_reflink_always() {
let (at, mut ucmd) = at_and_ucmd!();
at.make_file("src_file1");
ucmd.args(&[
"--sparse=always",
"--reflink=always",
"src_file1",
"dst_file",
])
.fails();
}
#[cfg(any(target_os = "linux", target_os = "android"))]
#[test]
fn test_cp_sparse_never_reflink_always() {
let (at, mut ucmd) = at_and_ucmd!();
at.make_file("src_file1");
ucmd.args(&[
"--sparse=never",
"--reflink=always",
"src_file1",
"dst_file",
])
.fails();
}
cp: truncate destination when `--reflink` is set (#3759) * cp: truncate destination when `--reflink` is set This is needed in order to allow overriding an existing file. ``` $ truncate -s 512M /tmp/disk.img $ mkfs.btrfs /tmp/disk.img [...] $ mkdir /tmp/disk $ sudo mount /tmp/disk.img /tmp/disk $ sudo chown $(id -u):$(id -g) -R /tmp/disk $ for i in $(seq 0 8192); do echo -ne 'a' >>/tmp/disk/src1; done $ echo "success" >/tmp/disk/src2 $ $ # GNU ls supports overriding files created with `--reflink` $ cp --reflink=always /tmp/disk/src1 /tmp/disk/dst1 $ cp --reflink=always /tmp/disk/src2 /tmp/disk/dst1 $ cat /tmp/disk/dst1 success $ $ # Now testing with uutils $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.25s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.26s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2` cp: failed to clone "/tmp/disk/src2" from "/tmp/disk/dst2": Invalid argument (os error 22) $ cat /tmp/disk/dst2 [lots of 'a'] $ $ # With truncate(true) $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 7.98s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 0.27s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3` $ cat /tmp/disk/dst3 success ```
2022-08-04 06:50:19 +00:00
#[cfg(any(target_os = "linux", target_os = "android"))]
2022-09-29 14:22:03 +00:00
#[cfg(feature = "truncate")]
cp: truncate destination when `--reflink` is set (#3759) * cp: truncate destination when `--reflink` is set This is needed in order to allow overriding an existing file. ``` $ truncate -s 512M /tmp/disk.img $ mkfs.btrfs /tmp/disk.img [...] $ mkdir /tmp/disk $ sudo mount /tmp/disk.img /tmp/disk $ sudo chown $(id -u):$(id -g) -R /tmp/disk $ for i in $(seq 0 8192); do echo -ne 'a' >>/tmp/disk/src1; done $ echo "success" >/tmp/disk/src2 $ $ # GNU ls supports overriding files created with `--reflink` $ cp --reflink=always /tmp/disk/src1 /tmp/disk/dst1 $ cp --reflink=always /tmp/disk/src2 /tmp/disk/dst1 $ cat /tmp/disk/dst1 success $ $ # Now testing with uutils $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.25s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.26s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2` cp: failed to clone "/tmp/disk/src2" from "/tmp/disk/dst2": Invalid argument (os error 22) $ cat /tmp/disk/dst2 [lots of 'a'] $ $ # With truncate(true) $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 7.98s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 0.27s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3` $ cat /tmp/disk/dst3 success ```
2022-08-04 06:50:19 +00:00
#[test]
fn test_cp_reflink_always_override() {
const DISK: &str = "disk.img";
const ROOTDIR: &str = "disk_root/";
const USERDIR: &str = "dir/";
const MOUNTPOINT: &str = "mountpoint/";
2024-06-30 14:27:08 +00:00
let scene = TestScenario::new(util_name!());
cp: truncate destination when `--reflink` is set (#3759) * cp: truncate destination when `--reflink` is set This is needed in order to allow overriding an existing file. ``` $ truncate -s 512M /tmp/disk.img $ mkfs.btrfs /tmp/disk.img [...] $ mkdir /tmp/disk $ sudo mount /tmp/disk.img /tmp/disk $ sudo chown $(id -u):$(id -g) -R /tmp/disk $ for i in $(seq 0 8192); do echo -ne 'a' >>/tmp/disk/src1; done $ echo "success" >/tmp/disk/src2 $ $ # GNU ls supports overriding files created with `--reflink` $ cp --reflink=always /tmp/disk/src1 /tmp/disk/dst1 $ cp --reflink=always /tmp/disk/src2 /tmp/disk/dst1 $ cat /tmp/disk/dst1 success $ $ # Now testing with uutils $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.25s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.26s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2` cp: failed to clone "/tmp/disk/src2" from "/tmp/disk/dst2": Invalid argument (os error 22) $ cat /tmp/disk/dst2 [lots of 'a'] $ $ # With truncate(true) $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 7.98s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 0.27s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3` $ cat /tmp/disk/dst3 success ```
2022-08-04 06:50:19 +00:00
2023-08-21 06:11:38 +00:00
let src1_path: &str = &[MOUNTPOINT, USERDIR, "src1"].concat();
let src2_path: &str = &[MOUNTPOINT, USERDIR, "src2"].concat();
let dst_path: &str = &[MOUNTPOINT, USERDIR, "dst"].concat();
cp: truncate destination when `--reflink` is set (#3759) * cp: truncate destination when `--reflink` is set This is needed in order to allow overriding an existing file. ``` $ truncate -s 512M /tmp/disk.img $ mkfs.btrfs /tmp/disk.img [...] $ mkdir /tmp/disk $ sudo mount /tmp/disk.img /tmp/disk $ sudo chown $(id -u):$(id -g) -R /tmp/disk $ for i in $(seq 0 8192); do echo -ne 'a' >>/tmp/disk/src1; done $ echo "success" >/tmp/disk/src2 $ $ # GNU ls supports overriding files created with `--reflink` $ cp --reflink=always /tmp/disk/src1 /tmp/disk/dst1 $ cp --reflink=always /tmp/disk/src2 /tmp/disk/dst1 $ cat /tmp/disk/dst1 success $ $ # Now testing with uutils $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.25s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.26s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2` cp: failed to clone "/tmp/disk/src2" from "/tmp/disk/dst2": Invalid argument (os error 22) $ cat /tmp/disk/dst2 [lots of 'a'] $ $ # With truncate(true) $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 7.98s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 0.27s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3` $ cat /tmp/disk/dst3 success ```
2022-08-04 06:50:19 +00:00
scene.fixtures.mkdir(ROOTDIR);
2023-08-21 06:11:38 +00:00
scene.fixtures.mkdir([ROOTDIR, USERDIR].concat());
cp: truncate destination when `--reflink` is set (#3759) * cp: truncate destination when `--reflink` is set This is needed in order to allow overriding an existing file. ``` $ truncate -s 512M /tmp/disk.img $ mkfs.btrfs /tmp/disk.img [...] $ mkdir /tmp/disk $ sudo mount /tmp/disk.img /tmp/disk $ sudo chown $(id -u):$(id -g) -R /tmp/disk $ for i in $(seq 0 8192); do echo -ne 'a' >>/tmp/disk/src1; done $ echo "success" >/tmp/disk/src2 $ $ # GNU ls supports overriding files created with `--reflink` $ cp --reflink=always /tmp/disk/src1 /tmp/disk/dst1 $ cp --reflink=always /tmp/disk/src2 /tmp/disk/dst1 $ cat /tmp/disk/dst1 success $ $ # Now testing with uutils $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.25s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.26s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2` cp: failed to clone "/tmp/disk/src2" from "/tmp/disk/dst2": Invalid argument (os error 22) $ cat /tmp/disk/dst2 [lots of 'a'] $ $ # With truncate(true) $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 7.98s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 0.27s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3` $ cat /tmp/disk/dst3 success ```
2022-08-04 06:50:19 +00:00
// Setup:
// Because neither `mkfs.btrfs` not btrfs `mount` options allow us to have a mountpoint owned
// by a non-root user, we want the following directory structure:
//
// uid | path
// ---------------------------
// user | .
// root | └── mountpoint
// user | └── dir
// user | ├── src1
// user | └── src2
scene
.ccmd("truncate")
.args(&["-s", "128M", DISK])
.succeeds();
if !scene
.cmd("env")
.env("PATH", PATH)
cp: truncate destination when `--reflink` is set (#3759) * cp: truncate destination when `--reflink` is set This is needed in order to allow overriding an existing file. ``` $ truncate -s 512M /tmp/disk.img $ mkfs.btrfs /tmp/disk.img [...] $ mkdir /tmp/disk $ sudo mount /tmp/disk.img /tmp/disk $ sudo chown $(id -u):$(id -g) -R /tmp/disk $ for i in $(seq 0 8192); do echo -ne 'a' >>/tmp/disk/src1; done $ echo "success" >/tmp/disk/src2 $ $ # GNU ls supports overriding files created with `--reflink` $ cp --reflink=always /tmp/disk/src1 /tmp/disk/dst1 $ cp --reflink=always /tmp/disk/src2 /tmp/disk/dst1 $ cat /tmp/disk/dst1 success $ $ # Now testing with uutils $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.25s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.26s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2` cp: failed to clone "/tmp/disk/src2" from "/tmp/disk/dst2": Invalid argument (os error 22) $ cat /tmp/disk/dst2 [lots of 'a'] $ $ # With truncate(true) $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 7.98s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 0.27s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3` $ cat /tmp/disk/dst3 success ```
2022-08-04 06:50:19 +00:00
.args(&["mkfs.btrfs", "--rootdir", ROOTDIR, DISK])
.run()
.succeeded()
{
print!("Test skipped; couldn't make btrfs disk image");
return;
}
scene.fixtures.mkdir(MOUNTPOINT);
let mount = scene
.cmd("sudo")
.env("PATH", PATH)
cp: truncate destination when `--reflink` is set (#3759) * cp: truncate destination when `--reflink` is set This is needed in order to allow overriding an existing file. ``` $ truncate -s 512M /tmp/disk.img $ mkfs.btrfs /tmp/disk.img [...] $ mkdir /tmp/disk $ sudo mount /tmp/disk.img /tmp/disk $ sudo chown $(id -u):$(id -g) -R /tmp/disk $ for i in $(seq 0 8192); do echo -ne 'a' >>/tmp/disk/src1; done $ echo "success" >/tmp/disk/src2 $ $ # GNU ls supports overriding files created with `--reflink` $ cp --reflink=always /tmp/disk/src1 /tmp/disk/dst1 $ cp --reflink=always /tmp/disk/src2 /tmp/disk/dst1 $ cat /tmp/disk/dst1 success $ $ # Now testing with uutils $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.25s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.26s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2` cp: failed to clone "/tmp/disk/src2" from "/tmp/disk/dst2": Invalid argument (os error 22) $ cat /tmp/disk/dst2 [lots of 'a'] $ $ # With truncate(true) $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 7.98s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 0.27s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3` $ cat /tmp/disk/dst3 success ```
2022-08-04 06:50:19 +00:00
.args(&["-E", "--non-interactive", "mount", DISK, MOUNTPOINT])
.run();
if !mount.succeeded() {
print!("Test skipped; requires root user");
return;
}
scene.fixtures.make_file(src1_path);
scene.fixtures.write_bytes(src1_path, &[0x64; 8192]);
scene.fixtures.make_file(src2_path);
scene.fixtures.write(src2_path, "other data");
scene
.ucmd()
.args(&["--reflink=always", src1_path, dst_path])
.succeeds();
scene
.ucmd()
.args(&["--reflink=always", src2_path, dst_path])
.succeeds();
scene
.cmd("sudo")
.env("PATH", PATH)
cp: truncate destination when `--reflink` is set (#3759) * cp: truncate destination when `--reflink` is set This is needed in order to allow overriding an existing file. ``` $ truncate -s 512M /tmp/disk.img $ mkfs.btrfs /tmp/disk.img [...] $ mkdir /tmp/disk $ sudo mount /tmp/disk.img /tmp/disk $ sudo chown $(id -u):$(id -g) -R /tmp/disk $ for i in $(seq 0 8192); do echo -ne 'a' >>/tmp/disk/src1; done $ echo "success" >/tmp/disk/src2 $ $ # GNU ls supports overriding files created with `--reflink` $ cp --reflink=always /tmp/disk/src1 /tmp/disk/dst1 $ cp --reflink=always /tmp/disk/src2 /tmp/disk/dst1 $ cat /tmp/disk/dst1 success $ $ # Now testing with uutils $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.25s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst2` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2 Finished dev [unoptimized + debuginfo] target(s) in 0.26s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst2` cp: failed to clone "/tmp/disk/src2" from "/tmp/disk/dst2": Invalid argument (os error 22) $ cat /tmp/disk/dst2 [lots of 'a'] $ $ # With truncate(true) $ cargo run cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 7.98s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src1 /tmp/disk/dst3` $ cargo run cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3 Finished dev [unoptimized + debuginfo] target(s) in 0.27s Running `target/debug/coreutils cp --reflink=always /tmp/disk/src2 /tmp/disk/dst3` $ cat /tmp/disk/dst3 success ```
2022-08-04 06:50:19 +00:00
.args(&["-E", "--non-interactive", "umount", MOUNTPOINT])
.succeeds();
}
2021-06-17 20:26:13 +00:00
#[test]
fn test_copy_dir_symlink() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("dir");
at.symlink_dir("dir", "dir-link");
ucmd.args(&["-r", "dir-link", "copy"]).succeeds();
assert_eq!(at.resolve_link("copy"), "dir");
}
#[test]
#[cfg(not(target_os = "freebsd"))] // FIXME: fix this test for FreeBSD
2022-09-29 14:22:03 +00:00
#[cfg(feature = "ln")]
2021-06-17 20:26:13 +00:00
fn test_copy_dir_with_symlinks() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("dir");
at.make_file("dir/file");
TestScenario::new("ln")
.ucmd()
.arg("-sr")
.arg(at.subdir.join("dir/file"))
.arg(at.subdir.join("dir/file-link"))
.succeeds();
ucmd.args(&["-r", "dir", "copy"]).succeeds();
assert_eq!(at.resolve_link("copy/file-link"), "file");
}
#[test]
#[cfg(not(windows))]
fn test_copy_symlink_force() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("file");
at.symlink_file("file", "file-link");
at.touch("copy");
ucmd.args(&["file-link", "copy", "-f", "--no-dereference"])
.succeeds();
assert_eq!(at.resolve_link("copy"), "file");
}
#[test]
#[cfg(unix)]
#[cfg(not(target_os = "openbsd"))]
fn test_no_preserve_mode() {
use std::os::unix::prelude::MetadataExt;
const PERMS_ALL: u32 = if cfg!(target_os = "freebsd") {
// Only the superuser can set the sticky bit on a file.
0o6777
} else {
0o7777
};
let (at, mut ucmd) = at_and_ucmd!();
at.touch("file");
set_permissions(at.plus("file"), PermissionsExt::from_mode(PERMS_ALL)).unwrap();
let umask: u16 = 0o022;
ucmd.arg("file")
.arg("dest")
2024-06-30 14:27:08 +00:00
.umask(libc::mode_t::from(umask))
.succeeds()
.no_stderr()
.no_stdout();
// remove sticky bit, setuid and setgid bit; apply umask
2024-06-30 14:27:08 +00:00
let expected_perms = PERMS_ALL & !0o7000 & u32::from(!umask);
assert_eq!(
at.plus("dest").metadata().unwrap().mode() & 0o7777,
expected_perms
);
}
#[test]
#[cfg(unix)]
#[cfg(not(target_os = "openbsd"))]
fn test_preserve_mode() {
use std::os::unix::prelude::MetadataExt;
const PERMS_ALL: u32 = if cfg!(target_os = "freebsd") {
// Only the superuser can set the sticky bit on a file.
0o6777
} else {
0o7777
};
let (at, mut ucmd) = at_and_ucmd!();
at.touch("file");
set_permissions(at.plus("file"), PermissionsExt::from_mode(PERMS_ALL)).unwrap();
ucmd.arg("file")
.arg("dest")
.arg("-p")
.succeeds()
.no_stderr()
.no_stdout();
assert_eq!(
at.plus("dest").metadata().unwrap().mode() & 0o7777,
PERMS_ALL
);
}
2021-09-03 12:56:35 +00:00
#[test]
fn test_canonicalize_symlink() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("dir");
at.touch("dir/file");
at.relative_symlink_file("../dir/file", "dir/file-ln");
2021-09-03 12:56:35 +00:00
ucmd.arg("dir/file-ln")
.arg(".")
.succeeds()
.no_stderr()
.no_stdout();
}
#[test]
fn test_copy_through_just_created_symlink() {
2022-04-02 08:47:37 +00:00
for create_t in [true, false] {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("a");
at.mkdir("b");
at.mkdir("c");
at.relative_symlink_file("../t", "a/1");
at.write("b/1", "hello");
if create_t {
at.write("t", "world");
}
ucmd.arg("--no-dereference")
.arg("a/1")
.arg("b/1")
.arg("c")
.fails()
.stderr_only(if cfg!(not(target_os = "windows")) {
"cp: will not copy 'b/1' through just-created symlink 'c/1'\n"
} else {
"cp: will not copy 'b/1' through just-created symlink 'c\\1'\n"
});
if create_t {
assert_eq!(at.read("a/1"), "world");
}
}
}
#[test]
fn test_copy_through_dangling_symlink() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("file");
at.symlink_file("nonexistent", "target");
ucmd.arg("file")
.arg("target")
.fails()
.stderr_only("cp: not writing through dangling symlink 'target'\n");
}
#[test]
fn test_copy_through_dangling_symlink_posixly_correct() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("file");
at.symlink_file("nonexistent", "target");
ucmd.arg("file")
.arg("target")
.env("POSIXLY_CORRECT", "1")
.succeeds();
}
#[test]
fn test_copy_through_dangling_symlink_no_dereference() {
let (at, mut ucmd) = at_and_ucmd!();
at.symlink_file("no-such-file", "dangle");
ucmd.arg("-P")
.arg("dangle")
.arg("d2")
.succeeds()
.no_stderr()
.no_stdout();
}
#[test]
fn test_cp_symlink_overwrite_detection() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.mkdir("good");
at.mkdir("tmp");
at.write("README", "file1");
at.write("good/README", "file2");
at.symlink_file("tmp/foo", "tmp/README");
at.touch("tmp/foo");
ts.ucmd()
.arg("README")
.arg("good/README")
.arg("tmp")
.fails()
.stderr_only(if cfg!(target_os = "windows") {
"cp: will not copy 'good/README' through just-created symlink 'tmp\\README'\n"
} else if cfg!(target_os = "macos") {
"cp: will not overwrite just-created 'tmp/README' with 'good/README'\n"
} else {
"cp: will not copy 'good/README' through just-created symlink 'tmp/README'\n"
});
let contents = at.read("tmp/foo");
// None of the files seem to be copied in macos
if cfg!(not(target_os = "macos")) {
assert_eq!(contents, "file1");
}
}
#[test]
fn test_cp_dangling_symlink_inside_directory() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.mkdir("good");
at.mkdir("tmp");
at.write("README", "file1");
at.write("good/README", "file2");
at.symlink_file("foo", "tmp/README");
ts.ucmd()
.arg("README")
.arg("good/README")
.arg("tmp")
.fails()
.stderr_only( if cfg!(target_os="windows") {
"cp: not writing through dangling symlink 'tmp\\README'\ncp: not writing through dangling symlink 'tmp\\README'\n"
} else {
"cp: not writing through dangling symlink 'tmp/README'\ncp: not writing through dangling symlink 'tmp/README'\n"
} );
}
/// Test for copying a dangling symbolic link and its permissions.
#[cfg(not(any(target_os = "freebsd", target_os = "openbsd")))] // FIXME: fix this test for FreeBSD/OpenBSD
#[test]
fn test_copy_through_dangling_symlink_no_dereference_permissions() {
let (at, mut ucmd) = at_and_ucmd!();
// target name link name
at.symlink_file("no-such-file", "dangle");
// to check if access time and modification time didn't change
sleep(Duration::from_millis(5000));
// don't dereference the link
// | copy permissions, too
// | | from the link
// | | | to new file d2
// | | | |
// V V V V
ucmd.args(&["-P", "-p", "dangle", "d2"])
.succeeds()
.no_stderr()
.no_stdout();
assert!(at.symlink_exists("d2"), "symlink wasn't created");
// `-p` means `--preserve=mode,ownership,timestamps`
#[cfg(all(unix, not(target_os = "freebsd")))]
{
let metadata1 = at.symlink_metadata("dangle");
let metadata2 = at.symlink_metadata("d2");
assert_metadata_eq!(metadata1, metadata2);
}
}
#[test]
fn test_copy_through_dangling_symlink_no_dereference_2() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("file");
at.symlink_file("nonexistent", "target");
ucmd.args(&["-P", "file", "target"])
.fails()
.stderr_only("cp: not writing through dangling symlink 'target'\n");
}
/// Test that copy through a dangling symbolic link fails, even with --force.
#[test]
#[cfg(not(windows))]
fn test_copy_through_dangling_symlink_force() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("src");
at.symlink_file("no-such-file", "dest");
ucmd.args(&["--force", "src", "dest"])
.fails()
.stderr_only("cp: not writing through dangling symlink 'dest'\n");
assert!(!at.file_exists("dest"));
}
#[test]
#[cfg(unix)]
fn test_cp_archive_on_nonexistent_file() {
new_ucmd!()
.arg("-a")
.arg(TEST_NONEXISTENT_FILE)
.arg(TEST_EXISTING_FILE)
.fails()
.stderr_only(
"cp: cannot stat 'nonexistent_file.txt': No such file or directory (os error 2)\n",
);
}
2022-02-02 06:47:30 +00:00
#[test]
#[cfg(not(target_os = "android"))]
2022-02-02 06:47:30 +00:00
fn test_cp_link_backup() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("file2");
ucmd.arg("-l")
.arg("-b")
.arg(TEST_HELLO_WORLD_SOURCE)
.arg("file2")
.succeeds();
assert!(at.file_exists("file2~"));
assert_eq!(at.read("file2"), "Hello, World!\n");
}
2022-02-20 09:31:13 +00:00
#[test]
#[cfg(unix)]
fn test_cp_fifo() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkfifo("fifo");
ucmd.arg("-r")
.arg("fifo")
.arg("fifo2")
.succeeds()
.no_stderr()
.no_stdout();
assert!(at.is_fifo("fifo2"));
}
#[test]
fn test_dir_recursive_copy() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir_all("parent1/child");
at.mkdir_all("parent2/child1/child2/child3");
// case-1: copy parent1 -> parent1: should fail
scene
.ucmd()
.arg("-R")
.arg("parent1")
.arg("parent1")
.fails()
.stderr_contains("cannot copy a directory");
// case-2: copy parent1 -> parent1/child should fail
scene
.ucmd()
.arg("-R")
.arg("parent1")
.arg("parent1/child")
.fails()
.stderr_contains("cannot copy a directory");
// case-3: copy parent1/child -> parent2 should pass
scene
.ucmd()
.arg("-R")
.arg("parent1/child")
.arg("parent2")
.succeeds();
// case-4: copy parent2/child1/ -> parent2/child1/child2/child3
scene
.ucmd()
.arg("-R")
.arg("parent2/child1/")
.arg("parent2/child1/child2/child3")
.fails()
.stderr_contains("cannot copy a directory");
}
#[test]
fn test_cp_dir_vs_file() {
new_ucmd!()
.arg("-R")
.arg(TEST_COPY_FROM_FOLDER)
.arg(TEST_EXISTING_FILE)
.fails()
.stderr_only("cp: cannot overwrite non-directory with directory\n");
}
#[test]
fn test_cp_overriding_arguments() {
let s = TestScenario::new(util_name!());
s.fixtures.touch("file1");
2022-04-02 08:47:37 +00:00
for (arg1, arg2) in [
#[cfg(not(windows))]
("--remove-destination", "--force"),
#[cfg(not(windows))]
("--force", "--remove-destination"),
("--interactive", "--no-clobber"),
("--link", "--symbolic-link"),
#[cfg(not(target_os = "android"))]
("--symbolic-link", "--link"),
("--dereference", "--no-dereference"),
("--no-dereference", "--dereference"),
] {
s.ucmd()
.arg(arg1)
.arg(arg2)
.arg("file1")
.arg("file2")
.succeeds();
s.fixtures.remove("file2");
}
}
#[test]
fn test_copy_no_dereference_1() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("a");
at.mkdir("b");
at.write("a/foo", "bar");
at.relative_symlink_file("../a/foo", "b/foo");
ucmd.args(&["-P", "a/foo", "b"]).fails();
}
#[test]
fn test_abuse_existing() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("a");
at.mkdir("b");
at.mkdir("c");
at.relative_symlink_file("../t", "a/1");
at.write("b/1", "hello");
at.relative_symlink_file("../t", "c/1");
at.write("t", "i");
ucmd.args(&["-dR", "a/1", "b/1", "c"])
.fails()
.stderr_contains(format!(
"will not copy 'b/1' through just-created symlink 'c{}1'",
if cfg!(windows) { "\\" } else { "/" }
));
assert_eq!(at.read("t"), "i");
}
#[test]
fn test_copy_same_symlink_no_dereference() {
let (at, mut ucmd) = at_and_ucmd!();
at.relative_symlink_file("t", "a");
at.relative_symlink_file("t", "b");
at.touch("t");
ucmd.args(&["-d", "a", "b"]).succeeds();
}
#[test]
fn test_copy_same_symlink_no_dereference_dangling() {
let (at, mut ucmd) = at_and_ucmd!();
at.relative_symlink_file("t", "a");
at.relative_symlink_file("t", "b");
ucmd.args(&["-d", "a", "b"]).succeeds();
}
2022-07-17 20:03:29 +00:00
// TODO: enable for Android, when #3477 solved
#[cfg(not(any(windows, target_os = "android", target_os = "openbsd")))]
#[test]
fn test_cp_parents_2_dirs() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir_all("a/b/c");
at.mkdir("d");
ucmd.args(&["-a", "--parents", "a/b/c", "d"])
.succeeds()
.no_stderr()
.no_stdout();
assert!(at.dir_exists("d/a/b/c"));
}
2022-07-17 20:03:29 +00:00
#[test]
fn test_cp_parents_2() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir_all("a/b");
at.touch("a/b/c");
at.mkdir("d");
#[cfg(not(windows))]
let expected_stdout = "a -> d/a\na/b -> d/a/b\n'a/b/c' -> 'd/a/b/c'\n";
#[cfg(windows)]
let expected_stdout = "a -> d\\a\na/b -> d\\a/b\n'a/b/c' -> 'd\\a/b/c'\n";
ucmd.args(&["--verbose", "--parents", "a/b/c", "d"])
2022-07-17 20:03:29 +00:00
.succeeds()
.stdout_only(expected_stdout);
2022-07-17 20:03:29 +00:00
assert!(at.file_exists("d/a/b/c"));
}
#[test]
fn test_cp_parents_2_link() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir_all("a/b");
at.touch("a/b/c");
at.mkdir("d");
at.relative_symlink_file("b", "a/link");
#[cfg(not(windows))]
let expected_stdout = "a -> d/a\na/link -> d/a/link\n'a/link/c' -> 'd/a/link/c'\n";
#[cfg(windows)]
let expected_stdout = "a -> d\\a\na/link -> d\\a/link\n'a/link/c' -> 'd\\a/link/c'\n";
ucmd.args(&["--verbose", "--parents", "a/link/c", "d"])
2022-07-17 20:03:29 +00:00
.succeeds()
.stdout_only(expected_stdout);
assert!(at.dir_exists("d/a/link"));
assert!(!at.symlink_exists("d/a/link"));
2022-07-17 20:03:29 +00:00
assert!(at.file_exists("d/a/link/c"));
}
#[test]
fn test_cp_parents_2_dir() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir_all("a/b/c");
at.mkdir("d");
#[cfg(not(windows))]
let expected_stdout = "a -> d/a\na/b -> d/a/b\n'a/b/c' -> 'd/a/b/c'\n";
#[cfg(windows)]
let expected_stdout = "a -> d\\a\na/b -> d\\a/b\n'a/b/c' -> 'd\\a/b\\c'\n";
ucmd.args(&["--verbose", "-r", "--parents", "a/b/c", "d"])
.succeeds()
.stdout_only(expected_stdout);
assert!(at.dir_exists("d/a/b/c"));
}
#[test]
fn test_cp_parents_2_deep_dir() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir_all("a/b/c");
at.mkdir_all("d/e");
#[cfg(not(windows))]
let expected_stdout = "a -> d/e/a\na/b -> d/e/a/b\n'a/b/c' -> 'd/e/a/b/c'\n";
#[cfg(windows)]
let expected_stdout = "a -> d/e\\a\na/b -> d/e\\a/b\n'a/b/c' -> 'd/e\\a/b\\c'\n";
ucmd.args(&["--verbose", "-r", "--parents", "a/b/c", "d/e"])
.succeeds()
.stdout_only(expected_stdout);
assert!(at.dir_exists("d/e/a/b/c"));
}
#[test]
fn test_cp_copy_symlink_contents_recursive() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("src-dir");
at.mkdir("dest-dir");
at.write("f", "f");
at.relative_symlink_file("f", "slink");
at.relative_symlink_file("no-file", &path_concat!("src-dir", "slink"));
ucmd.args(&["-H", "-R", "slink", "src-dir", "dest-dir"])
.succeeds();
assert!(at.dir_exists("src-dir"));
assert!(at.dir_exists("dest-dir"));
assert!(at.dir_exists(&path_concat!("dest-dir", "src-dir")));
let regular_file = path_concat!("dest-dir", "slink");
assert!(!at.symlink_exists(&regular_file) && at.file_exists(&regular_file));
assert_eq!(at.read(&regular_file), "f");
}
#[test]
fn test_cp_mode_symlink() {
for from in ["file", "slink", "slink2"] {
let (at, mut ucmd) = at_and_ucmd!();
at.write("file", "f");
at.relative_symlink_file("file", "slink");
at.relative_symlink_file("slink", "slink2");
ucmd.args(&["-s", "-L", from, "z"]).succeeds();
assert!(at.symlink_exists("z"));
assert_eq!(at.read_symlink("z"), from);
}
}
// Android doesn't allow creating hard links
#[cfg(not(target_os = "android"))]
#[test]
fn test_cp_mode_hardlink() {
for from in ["file", "slink", "slink2"] {
let (at, mut ucmd) = at_and_ucmd!();
at.write("file", "f");
at.relative_symlink_file("file", "slink");
at.relative_symlink_file("slink", "slink2");
ucmd.args(&["--link", "-L", from, "z"]).succeeds();
assert!(at.file_exists("z") && !at.symlink_exists("z"));
assert_eq!(at.read("z"), "f");
// checking that it's the same hard link
at.append("z", "g");
assert_eq!(at.read("file"), "fg");
}
}
// Android doesn't allow creating hard links
#[cfg(not(target_os = "android"))]
#[test]
fn test_cp_mode_hardlink_no_dereference() {
let (at, mut ucmd) = at_and_ucmd!();
at.write("file", "f");
at.relative_symlink_file("file", "slink");
at.relative_symlink_file("slink", "slink2");
ucmd.args(&["--link", "-P", "slink2", "z"]).succeeds();
assert!(at.symlink_exists("z"));
assert_eq!(at.read_symlink("z"), "slink");
}
#[cfg(not(any(windows, target_os = "android")))]
#[test]
fn test_remove_destination_with_destination_being_a_hardlink_to_source() {
let (at, mut ucmd) = at_and_ucmd!();
let file = "file";
let hardlink = "hardlink";
at.touch(file);
at.hard_link(file, hardlink);
ucmd.args(&["--remove-destination", file, hardlink])
.succeeds();
assert_eq!("", at.resolve_link(hardlink));
assert!(at.file_exists(file));
assert!(at.file_exists(hardlink));
}
#[test]
fn test_remove_destination_with_destination_being_a_symlink_to_source() {
let (at, mut ucmd) = at_and_ucmd!();
let file = "file";
let symlink = "symlink";
at.touch(file);
at.symlink_file(file, symlink);
ucmd.args(&["--remove-destination", file, symlink])
.succeeds();
assert!(!at.symlink_exists(symlink));
assert!(at.file_exists(file));
assert!(at.file_exists(symlink));
}
#[test]
fn test_remove_destination_symbolic_link_loop() {
let (at, mut ucmd) = at_and_ucmd!();
at.symlink_file("loop", "loop");
at.plus("loop");
at.touch("f");
ucmd.args(&["--remove-destination", "f", "loop"])
.succeeds()
.no_stdout()
.no_stderr();
assert!(at.file_exists("loop"));
}
#[test]
#[cfg(not(windows))]
fn test_cp_symbolic_link_loop() {
let (at, mut ucmd) = at_and_ucmd!();
at.symlink_file("loop", "loop");
at.plus("loop");
at.touch("f");
ucmd.args(&["-f", "f", "loop"])
.succeeds()
.no_stdout()
.no_stderr();
assert!(at.file_exists("loop"));
}
/// Test that copying a directory to itself is disallowed.
#[test]
fn test_copy_directory_to_itself_disallowed() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("d");
#[cfg(not(windows))]
let expected = "cp: cannot copy a directory, 'd', into itself, 'd/d'\n";
#[cfg(windows)]
let expected = "cp: cannot copy a directory, 'd', into itself, 'd\\d'\n";
ucmd.args(&["-R", "d", "d"]).fails().stderr_only(expected);
}
/// Test that copying a nested directory to itself is disallowed.
#[test]
fn test_copy_nested_directory_to_itself_disallowed() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("a");
at.mkdir("a/b");
at.mkdir("a/b/c");
#[cfg(not(windows))]
let expected = "cp: cannot copy a directory, 'a/b', into itself, 'a/b/c/b'\n";
#[cfg(windows)]
let expected = "cp: cannot copy a directory, 'a/b', into itself, 'a/b/c\\b'\n";
ucmd.args(&["-R", "a/b", "a/b/c"])
.fails()
.stderr_only(expected);
}
/// Test for preserving permissions when copying a directory.
#[cfg(all(not(windows), not(target_os = "freebsd"), not(target_os = "openbsd")))]
#[test]
fn test_copy_dir_preserve_permissions() {
// Create a directory that has some non-default permissions.
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("d1");
at.set_mode("d1", 0o0500);
// Copy the directory, preserving those permissions.
//
// preserve permissions (mode, ownership, timestamps)
// | copy directories recursively
// | | from this source directory
// | | | to this destination
// | | | |
// V V V V
ucmd.args(&["-p", "-R", "d1", "d2"])
.succeeds()
.no_stderr()
.no_stdout();
assert!(at.dir_exists("d2"));
// Assert that the permissions are preserved.
let metadata1 = at.metadata("d1");
let metadata2 = at.metadata("d2");
assert_metadata_eq!(metadata1, metadata2);
}
/// Test for preserving permissions when copying a directory, even in
/// the face of an inaccessible file in that directory.
#[cfg(all(not(windows), not(target_os = "freebsd"), not(target_os = "openbsd")))]
#[test]
fn test_copy_dir_preserve_permissions_inaccessible_file() {
// Create a directory that has some non-default permissions and
// contains an inaccessible file.
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("d1");
at.touch("d1/f");
at.set_mode("d1/f", 0);
at.set_mode("d1", 0o0500);
// Copy the directory, preserving those permissions. There should
// be an error message that the file `d1/f` is inaccessible.
//
// preserve permissions (mode, ownership, timestamps)
// | copy directories recursively
// | | from this source directory
// | | | to this destination
// | | | |
// V V V V
ucmd.args(&["-p", "-R", "d1", "d2"])
.fails()
.code_is(1)
.stderr_only("cp: cannot open 'd1/f' for reading: permission denied\n");
assert!(at.dir_exists("d2"));
assert!(!at.file_exists("d2/f"));
// Assert that the permissions are preserved.
let metadata1 = at.metadata("d1");
let metadata2 = at.metadata("d2");
assert_metadata_eq!(metadata1, metadata2);
}
/// Test that copying file to itself with backup fails.
#[test]
fn test_same_file_backup() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("f");
ucmd.args(&["--backup", "f", "f"])
.fails()
.stderr_only("cp: 'f' and 'f' are the same file\n");
assert!(!at.file_exists("f~"));
}
/// Test that copying file to itself with forced backup succeeds.
#[cfg(not(windows))]
#[test]
fn test_same_file_force() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("f");
ucmd.args(&["--force", "f", "f"])
.fails()
.stderr_only("cp: 'f' and 'f' are the same file\n");
assert!(!at.file_exists("f~"));
}
/// Test that copying file to itself with forced backup succeeds.
#[cfg(not(windows))]
#[test]
fn test_same_file_force_backup() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("f");
ucmd.args(&["--force", "--backup", "f", "f"])
.succeeds()
.no_stdout()
.no_stderr();
assert!(at.file_exists("f~"));
}
/// Test for copying the contents of a FIFO as opposed to the FIFO object itself.
#[cfg(all(unix, not(target_os = "freebsd"), not(target_os = "openbsd")))]
#[test]
fn test_copy_contents_fifo() {
// TODO this test should work on FreeBSD, but the command was
// causing an error:
//
// cp: 'fifo' -> 'outfile': the source path is neither a regular file nor a symlink to a regular file
//
// the underlying `std::fs:copy` doesn't support copying fifo on freeBSD
let scenario = TestScenario::new(util_name!());
let at = &scenario.fixtures;
// Start the `cp` process, reading the contents of `fifo` and
// writing to regular file `outfile`.
at.mkfifo("fifo");
let mut ucmd = scenario.ucmd();
let child = ucmd
.args(&["--copy-contents", "fifo", "outfile"])
.run_no_wait();
// Write some bytes to the `fifo`. We expect these bytes to get
// copied through to `outfile`.
std::fs::write(at.plus("fifo"), "foo").unwrap();
// At this point the child process should have terminated
// successfully with no output. The `outfile` should have the
// contents of `fifo` copied into it.
child.wait().unwrap().no_stdout().no_stderr().success();
assert_eq!(at.read("outfile"), "foo");
}
#[cfg(target_os = "linux")]
#[test]
fn test_reflink_never_sparse_always() {
let (at, mut ucmd) = at_and_ucmd!();
// Create a file and make it a large sparse file.
//
// On common Linux filesystems, setting the length to one megabyte
// should cause the file to become a sparse file, but it depends
// on the system.
std::fs::File::create(at.plus("src"))
.unwrap()
.set_len(1024 * 1024)
.unwrap();
ucmd.args(&["--reflink=never", "--sparse=always", "src", "dest"])
.succeeds()
.no_stdout()
.no_stderr();
at.file_exists("dest");
let src_metadata = std::fs::metadata(at.plus("src")).unwrap();
let dest_metadata = std::fs::metadata(at.plus("dest")).unwrap();
assert_eq!(src_metadata.blocks(), dest_metadata.blocks());
assert_eq!(dest_metadata.len(), 1024 * 1024);
}
/// Test for preserving attributes of a hard link in a directory.
#[test]
#[cfg(not(any(target_os = "android", target_os = "openbsd")))]
fn test_preserve_hardlink_attributes_in_directory() {
let (at, mut ucmd) = at_and_ucmd!();
// The source directory tree.
at.mkdir("src");
at.touch("src/f");
at.hard_link("src/f", "src/link");
// The destination directory tree.
//
// The file `f` already exists, but the `link` does not.
at.mkdir_all("dest/src");
at.touch("dest/src/f");
ucmd.args(&["-a", "src", "dest"]).succeeds().no_output();
// The hard link should now appear in the destination directory tree.
//
// A hard link should have the same inode as the target file.
at.file_exists("dest/src/link");
2022-12-04 04:50:39 +00:00
#[cfg(all(unix, not(target_os = "freebsd")))]
assert_eq!(
at.metadata("dest/src/f").ino(),
at.metadata("dest/src/link").ino()
);
}
#[test]
#[cfg(not(any(windows, target_os = "android")))]
fn test_hard_link_file() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("src");
at.touch("dest");
ucmd.args(&["-f", "--link", "src", "dest"])
.succeeds()
.no_output();
#[cfg(all(unix, not(target_os = "freebsd")))]
assert_eq!(at.metadata("src").ino(), at.metadata("dest").ino());
}
#[test]
#[cfg(not(windows))]
fn test_symbolic_link_file() {
let (at, mut ucmd) = at_and_ucmd!();
at.touch("src");
at.touch("dest");
ucmd.args(&["-f", "--symbolic-link", "src", "dest"])
.succeeds()
.no_output();
assert_eq!(
std::fs::read_link(at.plus("dest")).unwrap(),
Path::new("src")
);
}
#[test]
fn test_src_base_dot() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.mkdir("x");
at.mkdir("y");
ts.ucmd()
.current_dir(at.plus("y"))
.args(&["--verbose", "-r", "../x/.", "."])
.succeeds()
.no_stderr()
.no_stdout();
assert!(!at.dir_exists("y/x"));
}
2023-02-13 09:00:03 +00:00
#[cfg(target_os = "linux")]
fn non_utf8_name(suffix: &str) -> OsString {
use std::os::unix::ffi::OsStringExt;
let mut name = OsString::from_vec(vec![0xff, 0xff]);
name.push(suffix);
name
}
#[cfg(target_os = "linux")]
#[test]
fn test_non_utf8_src() {
let (at, mut ucmd) = at_and_ucmd!();
let src = non_utf8_name("src");
std::fs::File::create(at.plus(&src)).unwrap();
ucmd.args(&[src, "dest".into()])
.succeeds()
.no_stderr()
.no_stdout();
assert!(at.file_exists("dest"));
}
#[cfg(target_os = "linux")]
#[test]
fn test_non_utf8_dest() {
let (at, mut ucmd) = at_and_ucmd!();
let dest = non_utf8_name("dest");
ucmd.args(&[TEST_HELLO_WORLD_SOURCE.as_ref(), &*dest])
.succeeds()
.no_stderr()
.no_stdout();
assert!(at.file_exists(dest));
}
#[cfg(target_os = "linux")]
#[test]
fn test_non_utf8_target() {
let (at, mut ucmd) = at_and_ucmd!();
let dest = non_utf8_name("dest");
at.mkdir(&dest);
ucmd.args(&["-t".as_ref(), &*dest, TEST_HELLO_WORLD_SOURCE.as_ref()])
.succeeds()
.no_stderr()
.no_stdout();
let mut copied_file = PathBuf::from(dest);
copied_file.push(TEST_HELLO_WORLD_SOURCE);
assert!(at.file_exists(copied_file));
}
2023-03-05 15:22:21 +00:00
#[test]
#[cfg(not(windows))]
fn test_cp_archive_on_directory_ending_dot() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("dir1");
at.mkdir("dir2");
at.touch("dir1/file");
ucmd.args(&["-a", "dir1/.", "dir2"]).succeeds();
assert!(at.file_exists("dir2/file"));
}
2023-05-22 21:03:54 +00:00
#[test]
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
2023-05-22 21:03:54 +00:00
fn test_cp_debug_default() {
2024-04-30 14:22:49 +00:00
#[cfg(target_os = "macos")]
let expected = "copy offload: unknown, reflink: unsupported, sparse detection: unsupported";
#[cfg(target_os = "linux")]
let expected = "copy offload: unknown, reflink: unsupported, sparse detection: no";
#[cfg(windows)]
let expected = "copy offload: unsupported, reflink: unsupported, sparse detection: unsupported";
2023-05-22 21:03:54 +00:00
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("a")
.arg("b")
.succeeds()
.stdout_contains(expected);
2023-05-22 21:03:54 +00:00
}
#[test]
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
2023-05-22 21:03:54 +00:00
fn test_cp_debug_multiple_default() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
let dir = "dir";
at.touch("a");
at.touch("b");
at.mkdir(dir);
2024-04-30 14:22:49 +00:00
2023-05-22 21:03:54 +00:00
let result = ts
.ucmd()
.arg("--debug")
.arg("a")
.arg("b")
.arg(dir)
.succeeds();
#[cfg(target_os = "macos")]
2024-04-30 14:22:49 +00:00
let expected = "copy offload: unknown, reflink: unsupported, sparse detection: unsupported";
2023-05-22 21:03:54 +00:00
#[cfg(target_os = "linux")]
2024-04-30 14:22:49 +00:00
let expected = "copy offload: unknown, reflink: unsupported, sparse detection: no";
#[cfg(windows)]
let expected = "copy offload: unsupported, reflink: unsupported, sparse detection: unsupported";
2023-05-22 21:03:54 +00:00
2024-04-30 14:22:49 +00:00
// two files, two occurrences
assert_eq!(result.stdout_str().matches(expected).count(), 2);
2023-05-22 21:03:54 +00:00
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_debug_sparse_reflink() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
2024-04-30 14:22:49 +00:00
ts.ucmd()
2023-05-22 21:03:54 +00:00
.arg("--debug")
.arg("--sparse=always")
.arg("--reflink=never")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: no, sparse detection: zeros");
2023-05-22 21:03:54 +00:00
}
#[test]
fn test_cp_debug_no_update() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
at.touch("b");
ts.ucmd()
.arg("--debug")
.arg("--update=none")
.arg("a")
.arg("b")
.succeeds()
.stdout_contains("skipped 'b'");
}
2023-05-22 21:03:54 +00:00
#[test]
#[cfg(target_os = "linux")]
fn test_cp_debug_sparse_always() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
2024-04-30 14:22:49 +00:00
ts.ucmd()
2023-05-22 21:03:54 +00:00
.arg("--debug")
.arg("--sparse=always")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: unsupported, sparse detection: zeros");
2023-05-22 21:03:54 +00:00
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_debug_sparse_never() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
2024-04-30 14:22:49 +00:00
ts.ucmd()
2023-05-22 21:03:54 +00:00
.arg("--debug")
.arg("--sparse=never")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: no, sparse detection: no");
2023-05-22 21:03:54 +00:00
}
#[test]
fn test_cp_debug_sparse_auto() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
ts.ucmd()
2023-05-22 21:03:54 +00:00
.arg("--debug")
.arg("--sparse=auto")
.arg("a")
.arg("b")
.succeeds();
#[cfg(any(target_os = "linux", target_os = "macos"))]
2023-05-22 21:03:54 +00:00
{
2024-04-30 14:22:49 +00:00
#[cfg(target_os = "macos")]
let expected = "copy offload: unknown, reflink: unsupported, sparse detection: unsupported";
#[cfg(target_os = "linux")]
let expected = "copy offload: unknown, reflink: unsupported, sparse detection: no";
ts.ucmd()
.arg("--debug")
.arg("--sparse=auto")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains(expected);
2023-05-22 21:03:54 +00:00
}
}
#[test]
#[cfg(any(target_os = "linux", target_os = "macos"))]
2023-05-22 21:03:54 +00:00
fn test_cp_debug_reflink_auto() {
2024-04-30 14:22:49 +00:00
#[cfg(target_os = "macos")]
let expected = "copy offload: unknown, reflink: unsupported, sparse detection: unsupported";
#[cfg(target_os = "linux")]
let expected = "copy offload: unknown, reflink: unsupported, sparse detection: no";
2023-05-22 21:03:54 +00:00
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
2024-04-30 14:22:49 +00:00
ts.ucmd()
2023-05-22 21:03:54 +00:00
.arg("--debug")
.arg("--reflink=auto")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains(expected);
2023-05-22 21:03:54 +00:00
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_debug_sparse_always_reflink_auto() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
2024-04-30 14:22:49 +00:00
ts.ucmd()
2023-05-22 21:03:54 +00:00
.arg("--debug")
.arg("--sparse=always")
.arg("--reflink=auto")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: unsupported, sparse detection: zeros");
2023-05-22 21:03:54 +00:00
}
#[test]
fn test_cp_only_source_no_target() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("a")
.fails()
.stderr_contains("missing destination file operand after \"a\"");
}
#[test]
fn test_cp_dest_no_permissions() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("valid.txt");
at.touch("invalid_perms.txt");
at.set_readonly("invalid_perms.txt");
ts.ucmd()
.args(&["valid.txt", "invalid_perms.txt"])
.fails()
.stderr_contains("invalid_perms.txt")
.stderr_contains("denied");
}
2023-10-26 14:18:48 +00:00
#[test]
#[cfg(all(unix, not(target_os = "freebsd")))]
fn test_cp_attributes_only() {
let (at, mut ucmd) = at_and_ucmd!();
let a = "file_a";
let b = "file_b";
let mode_a = 0o0500;
let mode_b = 0o0777;
at.write(a, "a");
at.write(b, "b");
at.set_mode(a, mode_a);
at.set_mode(b, mode_b);
let mode_a = at.metadata(a).mode();
let mode_b = at.metadata(b).mode();
// --attributes-only doesn't do anything without other attribute preservation flags
ucmd.arg("--attributes-only")
.arg(a)
.arg(b)
.succeeds()
.no_output();
assert_eq!("a", at.read(a));
assert_eq!("b", at.read(b));
assert_eq!(mode_a, at.metadata(a).mode());
assert_eq!(mode_b, at.metadata(b).mode());
}
#[test]
fn test_cp_seen_file() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.mkdir("a");
at.mkdir("b");
at.mkdir("c");
at.write("a/f", "a");
at.write("b/f", "b");
2023-12-24 14:18:31 +00:00
let result = ts.ucmd().arg("a/f").arg("b/f").arg("c").fails();
#[cfg(not(unix))]
assert!(result
.stderr_str()
.contains("will not overwrite just-created 'c\\f' with 'b/f'"));
#[cfg(unix)]
assert!(result
.stderr_str()
.contains("will not overwrite just-created 'c/f' with 'b/f'"));
assert!(at.plus("c").join("f").exists());
ts.ucmd()
.arg("--backup=numbered")
.arg("a/f")
.arg("b/f")
.arg("c")
.succeeds();
assert!(at.plus("c").join("f").exists());
assert!(at.plus("c").join("f.~1~").exists());
}
2024-01-03 23:41:54 +00:00
#[test]
fn test_cp_path_ends_with_terminator() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.mkdir("a");
ts.ucmd().arg("-r").arg("-T").arg("a").arg("e/").succeeds();
}
#[test]
fn test_cp_no_such() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("b");
ts.ucmd()
.arg("b")
.arg("no-such/")
.fails()
.stderr_is("cp: 'no-such/' is not a directory\n");
}
#[cfg(all(
unix,
not(any(target_os = "android", target_os = "macos", target_os = "openbsd"))
))]
#[test]
fn test_acl_preserve() {
use std::process::Command;
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let path1 = "a";
let path2 = "b";
let file = "a/file";
let file_target = "b/file";
at.mkdir(path1);
at.mkdir(path2);
at.touch(file);
let path = at.plus_as_string(file);
// calling the command directly. xattr requires some dev packages to be installed
// and it adds a complex dependency just for a test
match Command::new("setfacl")
2024-03-09 21:30:22 +00:00
.args(["-m", "group::rwx", path1])
.status()
.map(|status| status.code())
{
Ok(Some(0)) => {}
Ok(_) => {
println!("test skipped: setfacl failed");
return;
}
Err(e) => {
2024-09-19 21:56:27 +00:00
println!("test skipped: setfacl failed with {e}");
return;
}
}
scene.ucmd().args(&["-p", &path, path2]).succeeds();
assert!(compare_xattrs(&file, &file_target));
}
2024-04-30 14:22:49 +00:00
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.write("a", "hello");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: no, sparse detection: SEEK_HOLE");
2024-04-30 14:22:49 +00:00
let src_file_metadata = std::fs::metadata(at.plus("a")).unwrap();
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
assert_eq!(src_file_metadata.blocks(), dst_file_metadata.blocks());
}
2024-04-30 14:22:49 +00:00
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_empty_file_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: unknown, reflink: no, sparse detection: SEEK_HOLE");
2024-04-30 14:22:49 +00:00
let src_file_metadata = std::fs::metadata(at.plus("a")).unwrap();
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
assert_eq!(src_file_metadata.blocks(), dst_file_metadata.blocks());
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_default_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
at.append_bytes("a", "hello".as_bytes());
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("a")
.arg("b")
.succeeds()
.stdout_contains("copy offload: yes, reflink: unsupported, sparse detection: SEEK_HOLE");
let src_file_metadata = std::fs::metadata(at.plus("a")).unwrap();
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
assert_eq!(src_file_metadata.blocks(), dst_file_metadata.blocks());
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_default_less_than_512_bytes() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.write_bytes("a", "hello".as_bytes());
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(400).unwrap();
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--reflink=auto")
.arg("--sparse=auto")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: yes, reflink: unsupported, sparse detection: no");
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_default_without_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.write_bytes("a", "hello".as_bytes());
let filler_bytes = [0_u8; 10000];
at.append_bytes("a", &filler_bytes);
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("a")
.arg("b")
.succeeds()
.stdout_contains("copy offload: yes, reflink: unsupported, sparse detection: no");
}
2024-04-30 14:22:49 +00:00
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_default_empty_file_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("a")
.arg("b")
.succeeds()
.stdout_contains(
"copy offload: unknown, reflink: unsupported, sparse detection: SEEK_HOLE",
);
2024-04-30 14:22:49 +00:00
let src_file_metadata = std::fs::metadata(at.plus("a")).unwrap();
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
assert_eq!(src_file_metadata.blocks(), dst_file_metadata.blocks());
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_sparse_always_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.write("a", "hello");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("--sparse=always")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: no, sparse detection: SEEK_HOLE + zeros");
let src_file_metadata = std::fs::metadata(at.plus("a")).unwrap();
2024-04-30 14:22:49 +00:00
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
assert_eq!(src_file_metadata.blocks(), dst_file_metadata.blocks());
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_sparse_always_without_hole() {
let ts = TestScenario::new(util_name!());
let empty_bytes = [0_u8; 10000];
let at = &ts.fixtures;
at.write("a", "hello");
at.append_bytes("a", &empty_bytes);
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("--sparse=always")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: no, sparse detection: zeros");
2024-04-30 14:22:49 +00:00
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
assert_eq!(
dst_file_metadata.blocks(),
dst_file_metadata.blksize() / 512
);
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_sparse_always_empty_file_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("--sparse=always")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: unknown, reflink: no, sparse detection: SEEK_HOLE");
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_default_virtual_file() {
// This file has existed at least since 2008, so we assume that it is present on "all" Linux kernels.
// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-profiling
use std::os::unix::prelude::MetadataExt;
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
ts.ucmd().arg("/sys/kernel/profiling").arg("b").succeeds();
let dest_size = std::fs::metadata(at.plus("b"))
.expect("Metadata of copied file cannot be read")
.size();
2024-04-30 14:22:49 +00:00
assert!(dest_size > 0);
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_auto_sparse_always_non_sparse_file_with_long_zero_sequence() {
let ts = TestScenario::new(util_name!());
let buf: Vec<u8> = vec![0; 4096 * 4];
let at = &ts.fixtures;
at.touch("a");
at.append_bytes("a", &buf);
at.append_bytes("a", "hello".as_bytes());
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--sparse=always")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: unsupported, sparse detection: zeros");
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
2024-04-30 14:22:49 +00:00
assert_eq!(
dst_file_metadata.blocks(),
dst_file_metadata.blksize() / 512
);
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_debug_sparse_never_empty_sparse_file() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--sparse=never")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: no, sparse detection: no");
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_sparse_always_non_sparse_file_with_long_zero_sequence() {
let ts = TestScenario::new(util_name!());
let buf: Vec<u8> = vec![0; 4096 * 4];
let at = &ts.fixtures;
at.touch("a");
at.append_bytes("a", &buf);
at.append_bytes("a", "hello".as_bytes());
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("--sparse=always")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: no, sparse detection: zeros");
let dst_file_metadata = std::fs::metadata(at.plus("b")).unwrap();
2024-04-30 14:22:49 +00:00
assert_eq!(
dst_file_metadata.blocks(),
dst_file_metadata.blksize() / 512
);
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_debug_sparse_always_sparse_virtual_file() {
// This file has existed at least since 2008, so we assume that it is present on "all" Linux kernels.
// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-profiling
let ts = TestScenario::new(util_name!());
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--sparse=always")
.arg("/sys/kernel/profiling")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains(
"copy offload: avoided, reflink: unsupported, sparse detection: SEEK_HOLE + zeros",
);
}
2024-04-30 14:22:49 +00:00
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_less_than_512_bytes() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.write_bytes("a", "hello".as_bytes());
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(400).unwrap();
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: no, sparse detection: no");
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_sparse_never_empty_file_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("--sparse=never")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: unknown, reflink: no, sparse detection: SEEK_HOLE");
}
2024-04-30 14:22:49 +00:00
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_file_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
at.append_bytes("a", "hello".as_bytes());
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("--sparse=never")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: no, sparse detection: SEEK_HOLE");
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_sparse_never_less_than_512_bytes() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.write_bytes("a", "hello".as_bytes());
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(400).unwrap();
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--reflink=auto")
.arg("--sparse=never")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: no, sparse detection: no");
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_sparse_never_without_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.write_bytes("a", "hello".as_bytes());
let filler_bytes = [0_u8; 10000];
at.append_bytes("a", &filler_bytes);
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--reflink=auto")
.arg("--sparse=never")
.arg("--debug")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: no, sparse detection: no");
}
2024-04-30 14:22:49 +00:00
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_sparse_never_empty_file_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--reflink=auto")
.arg("--sparse=never")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: unknown, reflink: no, sparse detection: SEEK_HOLE");
}
2024-04-30 14:22:49 +00:00
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_sparse_never_file_with_hole() {
let ts = TestScenario::new(util_name!());
let at = &ts.fixtures;
at.touch("a");
let f = std::fs::OpenOptions::new()
.write(true)
.open(at.plus("a"))
.unwrap();
f.set_len(10000).unwrap();
at.append_bytes("a", "hello".as_bytes());
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--reflink=auto")
.arg("--sparse=never")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: no, sparse detection: SEEK_HOLE");
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_debug_default_sparse_virtual_file() {
// This file has existed at least since 2008, so we assume that it is present on "all" Linux kernels.
// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-profiling
let ts = TestScenario::new(util_name!());
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("/sys/kernel/profiling")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains(
"copy offload: unsupported, reflink: unsupported, sparse detection: SEEK_HOLE",
);
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_debug_sparse_never_zero_sized_virtual_file() {
let ts = TestScenario::new(util_name!());
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--sparse=never")
.arg("/proc/version")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: no, sparse detection: no");
}
#[test]
#[cfg(target_os = "linux")]
fn test_cp_debug_default_zero_sized_virtual_file() {
let ts = TestScenario::new(util_name!());
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("/proc/version")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: unsupported, reflink: unsupported, sparse detection: no");
}
#[test]
#[cfg(any(target_os = "linux", target_os = "android"))]
fn test_cp_debug_reflink_never_without_hole() {
let ts = TestScenario::new(util_name!());
let filler_bytes = [0_u8; 1000];
let at = &ts.fixtures;
at.write("a", "hello");
at.append_bytes("a", &filler_bytes);
2024-04-30 14:22:49 +00:00
ts.ucmd()
.arg("--debug")
.arg("--reflink=never")
.arg("a")
.arg("b")
2024-04-30 14:22:49 +00:00
.succeeds()
.stdout_contains("copy offload: avoided, reflink: no, sparse detection: no");
}
#[test]
fn test_cp_force_remove_destination_attributes_only_with_symlink() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write("file1", "1");
at.write("file2", "2");
at.symlink_file("file1", "sym1");
scene
.ucmd()
.args(&[
"-a",
"--remove-destination",
"--attributes-only",
"sym1",
"file2",
])
.succeeds();
assert!(
at.symlink_exists("file2"),
"file2 is not a symbolic link as expected"
);
assert_eq!(
at.read("file1"),
at.read("file2"),
"Contents of file1 and file2 do not match"
);
}
#[test]
fn test_cp_no_dereference_attributes_only_with_symlink() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write("file1", "1");
at.write("file2", "2");
at.write("file2.exp", "2");
at.symlink_file("file1", "sym1");
let result = scene
.ucmd()
.args(&["--no-dereference", "--attributes-only", "sym1", "file2"])
.fails();
assert_eq!(result.code(), 1, "cp command did not fail");
assert_eq!(
at.read("file2"),
at.read("file2.exp"),
"file2 content does not match expected"
);
}
#[cfg(all(unix, not(target_os = "android")))]
#[cfg(test)]
/// contains the test for cp when the source and destination points to the same file
mod same_file {
use crate::common::util::TestScenario;
const FILE_NAME: &str = "foo";
const SYMLINK_NAME: &str = "symlink";
const CONTENTS: &str = "abcd";
// the following tests tries to copy a file to the symlink of the same file with
// various options
#[test]
fn test_same_file_from_file_to_symlink() {
for option in ["-d", "-f", "-df"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
scene
.ucmd()
.args(&[option, FILE_NAME, SYMLINK_NAME])
.fails()
.stderr_contains("'foo' and 'symlink' are the same file");
assert!(at.symlink_exists(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_file_to_symlink_with_rem_option() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
scene
.ucmd()
.args(&["--rem", FILE_NAME, SYMLINK_NAME])
.succeeds();
assert!(at.file_exists(SYMLINK_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
#[test]
fn test_same_file_from_file_to_symlink_with_backup_option() {
for option in ["-b", "-bd", "-bf", "-bdf"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let backup = "symlink~";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
scene
.ucmd()
.args(&[option, FILE_NAME, SYMLINK_NAME])
.succeeds();
assert!(at.symlink_exists(backup));
assert_eq!(FILE_NAME, at.resolve_link(backup));
assert!(at.file_exists(SYMLINK_NAME));
assert_eq!(at.read(SYMLINK_NAME), CONTENTS,);
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_file_to_symlink_with_link_option() {
for option in ["-l", "-dl"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
scene
.ucmd()
.args(&[option, FILE_NAME, SYMLINK_NAME])
.fails()
.stderr_contains("cp: cannot create hard link 'symlink' to 'foo'");
assert!(at.symlink_exists(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_file_to_symlink_with_options_link_and_force() {
for option in ["-fl", "-dfl"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
scene
.ucmd()
.args(&[option, FILE_NAME, SYMLINK_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.file_exists(SYMLINK_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_file_to_symlink_with_options_backup_and_link() {
for option in ["-bl", "-bdl", "-bfl", "-bdfl"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let backup = "symlink~";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
scene
.ucmd()
.args(&[option, FILE_NAME, SYMLINK_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.file_exists(SYMLINK_NAME));
assert!(at.symlink_exists(backup));
assert_eq!(FILE_NAME, at.resolve_link(backup));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_file_to_symlink_with_options_symlink() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
scene
.ucmd()
.args(&["-s", FILE_NAME, SYMLINK_NAME])
.fails()
.stderr_contains("cp: cannot create symlink 'symlink' to 'foo'");
assert!(at.file_exists(FILE_NAME));
assert!(at.symlink_exists(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
#[test]
fn test_same_file_from_file_to_symlink_with_options_symlink_and_force() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
scene
.ucmd()
.args(&["-sf", FILE_NAME, SYMLINK_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.symlink_exists(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
// the following tests tries to copy a symlink to the file that symlink points to with
// various options
#[test]
fn test_same_file_from_symlink_to_file() {
for option in ["-d", "-f", "-df", "--rem"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
scene
.ucmd()
.args(&[option, SYMLINK_NAME, FILE_NAME])
.fails()
.stderr_contains("'symlink' and 'foo' are the same file");
assert!(at.file_exists(FILE_NAME));
assert!(at.symlink_exists(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_symlink_to_file_with_option_backup() {
for option in ["-b", "-bf"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
scene
.ucmd()
.args(&[option, SYMLINK_NAME, FILE_NAME])
.fails()
.stderr_contains("'symlink' and 'foo' are the same file");
assert!(at.file_exists(FILE_NAME));
assert!(at.symlink_exists(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_symlink_to_file_with_option_backup_without_deref() {
for option in ["-bd", "-bdf"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let backup = "foo~";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
scene
.ucmd()
.args(&[option, SYMLINK_NAME, FILE_NAME])
.succeeds();
assert!(at.file_exists(backup));
assert!(at.symlink_exists(FILE_NAME));
// this doesn't makes sense but this is how gnu does it
assert_eq!(FILE_NAME, at.resolve_link(FILE_NAME));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert_eq!(at.read(backup), CONTENTS,);
}
}
#[test]
fn test_same_file_from_symlink_to_file_with_options_link() {
for option in ["-l", "-dl", "-fl", "-bl", "-bfl"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
scene
.ucmd()
.args(&[option, SYMLINK_NAME, FILE_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.symlink_exists(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_symlink_to_file_with_option_symlink() {
for option in ["-s", "-sf"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
scene
.ucmd()
.args(&[option, SYMLINK_NAME, FILE_NAME])
.fails()
.stderr_contains("'symlink' and 'foo' are the same file");
assert!(at.file_exists(FILE_NAME));
assert!(at.symlink_exists(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
// the following tests tries to copy a file to the same file with various options
#[test]
fn test_same_file_from_file_to_file() {
for option in ["-d", "-f", "-df", "--rem"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write(FILE_NAME, CONTENTS);
scene
.ucmd()
.args(&[option, FILE_NAME, FILE_NAME])
.fails()
.stderr_contains("'foo' and 'foo' are the same file");
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_file_to_file_with_backup() {
for option in ["-b", "-bd"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write(FILE_NAME, CONTENTS);
scene
.ucmd()
.args(&[option, FILE_NAME, FILE_NAME])
.fails()
.stderr_contains("'foo' and 'foo' are the same file");
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_file_to_file_with_options_backup_and_no_deref() {
for option in ["-bf", "-bdf"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let backup = "foo~";
at.write(FILE_NAME, CONTENTS);
scene
.ucmd()
.args(&[option, FILE_NAME, FILE_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.file_exists(backup));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
assert_eq!(at.read(backup), CONTENTS,);
}
}
#[test]
fn test_same_file_from_file_to_file_with_options_link() {
for option in ["-l", "-dl", "-fl", "-dfl", "-bl", "-bdl"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let backup = "foo~";
at.write(FILE_NAME, CONTENTS);
scene
.ucmd()
.args(&[option, FILE_NAME, FILE_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(!at.file_exists(backup));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_file_to_file_with_options_link_and_backup_and_force() {
for option in ["-bfl", "-bdfl"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let backup = "foo~";
at.write(FILE_NAME, CONTENTS);
scene
.ucmd()
.args(&[option, FILE_NAME, FILE_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.file_exists(backup));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
assert_eq!(at.read(backup), CONTENTS,);
}
}
#[test]
fn test_same_file_from_file_to_file_with_options_symlink() {
for option in ["-s", "-sf"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write(FILE_NAME, CONTENTS);
scene
.ucmd()
.args(&[option, FILE_NAME, FILE_NAME])
.fails()
.stderr_contains("'foo' and 'foo' are the same file");
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
// the following tests tries to copy a symlink that points to a file to a symlink
// that points to the same file with various options
#[test]
fn test_same_file_from_symlink_to_symlink_with_option_no_deref() {
for option in ["-d", "-df"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let symlink1 = "sl1";
let symlink2 = "sl2";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, symlink1);
at.symlink_file(FILE_NAME, symlink2);
scene.ucmd().args(&[option, symlink1, symlink2]).succeeds();
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
assert_eq!(FILE_NAME, at.resolve_link(symlink1));
assert_eq!(FILE_NAME, at.resolve_link(symlink2));
}
}
#[test]
fn test_same_file_from_symlink_to_symlink_with_option_force() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let symlink1 = "sl1";
let symlink2 = "sl2";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, symlink1);
at.symlink_file(FILE_NAME, symlink2);
scene
.ucmd()
.args(&["-f", symlink1, symlink2])
.fails()
.stderr_contains("'sl1' and 'sl2' are the same file");
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
assert_eq!(FILE_NAME, at.resolve_link(symlink1));
assert_eq!(FILE_NAME, at.resolve_link(symlink2));
}
#[test]
fn test_same_file_from_symlink_to_symlink_with_option_rem() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let symlink1 = "sl1";
let symlink2 = "sl2";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, symlink1);
at.symlink_file(FILE_NAME, symlink2);
scene.ucmd().args(&["--rem", symlink1, symlink2]).succeeds();
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
assert_eq!(FILE_NAME, at.resolve_link(symlink1));
assert!(at.file_exists(symlink2));
assert_eq!(at.read(symlink2), CONTENTS,);
}
#[test]
fn test_same_file_from_symlink_to_symlink_with_option_backup() {
for option in ["-b", "-bf"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let symlink1 = "sl1";
let symlink2 = "sl2";
let backup = "sl2~";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, symlink1);
at.symlink_file(FILE_NAME, symlink2);
scene.ucmd().args(&[option, symlink1, symlink2]).succeeds();
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
assert_eq!(FILE_NAME, at.resolve_link(symlink1));
assert!(at.file_exists(symlink2));
assert_eq!(at.read(symlink2), CONTENTS,);
assert_eq!(FILE_NAME, at.resolve_link(backup));
}
}
#[test]
fn test_same_file_from_symlink_to_symlink_with_option_backup_and_no_deref() {
for option in ["-bd", "-bdf"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let symlink1 = "sl1";
let symlink2 = "sl2";
let backup = "sl2~";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, symlink1);
at.symlink_file(FILE_NAME, symlink2);
scene.ucmd().args(&[option, symlink1, symlink2]).succeeds();
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
assert_eq!(FILE_NAME, at.resolve_link(symlink1));
assert_eq!(FILE_NAME, at.resolve_link(symlink2));
assert_eq!(FILE_NAME, at.resolve_link(backup));
}
}
#[test]
fn test_same_file_from_symlink_to_symlink_with_option_link() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let symlink1 = "sl1";
let symlink2 = "sl2";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, symlink1);
at.symlink_file(FILE_NAME, symlink2);
scene
.ucmd()
.args(&["-l", symlink1, symlink2])
.fails()
.stderr_contains("cannot create hard link 'sl2' to 'sl1'");
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
assert_eq!(FILE_NAME, at.resolve_link(symlink1));
assert_eq!(FILE_NAME, at.resolve_link(symlink2));
}
#[test]
fn test_same_file_from_symlink_to_symlink_with_option_force_link() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let symlink1 = "sl1";
let symlink2 = "sl2";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, symlink1);
at.symlink_file(FILE_NAME, symlink2);
scene.ucmd().args(&["-fl", symlink1, symlink2]).succeeds();
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
assert_eq!(FILE_NAME, at.resolve_link(symlink1));
assert!(at.file_exists(symlink2));
assert_eq!(at.read(symlink2), CONTENTS,);
}
#[test]
fn test_same_file_from_symlink_to_symlink_with_option_backup_and_link() {
for option in ["-bl", "-bfl"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let symlink1 = "sl1";
let symlink2 = "sl2";
let backup = "sl2~";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, symlink1);
at.symlink_file(FILE_NAME, symlink2);
scene.ucmd().args(&[option, symlink1, symlink2]).succeeds();
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
assert_eq!(FILE_NAME, at.resolve_link(symlink1));
assert!(at.file_exists(symlink2));
assert_eq!(at.read(symlink2), CONTENTS,);
assert_eq!(FILE_NAME, at.resolve_link(backup));
}
}
#[test]
fn test_same_file_from_symlink_to_symlink_with_option_symlink() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let symlink1 = "sl1";
let symlink2 = "sl2";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, symlink1);
at.symlink_file(FILE_NAME, symlink2);
scene
.ucmd()
.args(&["-s", symlink1, symlink2])
.fails()
.stderr_contains("cannot create symlink 'sl2' to 'sl1'");
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
assert_eq!(FILE_NAME, at.resolve_link(symlink1));
assert_eq!(FILE_NAME, at.resolve_link(symlink2));
}
#[test]
fn test_same_file_from_symlink_to_symlink_with_option_symlink_and_force() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let symlink1 = "sl1";
let symlink2 = "sl2";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, symlink1);
at.symlink_file(FILE_NAME, symlink2);
scene.ucmd().args(&["-sf", symlink1, symlink2]).succeeds();
assert!(at.file_exists(FILE_NAME));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
assert_eq!(FILE_NAME, at.resolve_link(symlink1));
assert_eq!(symlink1, at.resolve_link(symlink2));
}
// the following tests tries to copy file to a hardlink of the same file with
// various options
#[test]
fn test_same_file_from_file_to_hardlink() {
for option in ["-d", "-f", "-df"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let hardlink = "hardlink";
at.write(FILE_NAME, CONTENTS);
at.hard_link(FILE_NAME, hardlink);
scene
.ucmd()
.args(&[option, FILE_NAME, hardlink])
.fails()
.stderr_contains("'foo' and 'hardlink' are the same file");
assert!(at.file_exists(FILE_NAME));
assert!(at.file_exists(hardlink));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_file_to_hardlink_with_option_rem() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let hardlink = "hardlink";
at.write(FILE_NAME, CONTENTS);
at.hard_link(FILE_NAME, hardlink);
scene
.ucmd()
.args(&["--rem", FILE_NAME, hardlink])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.file_exists(hardlink));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
#[test]
fn test_same_file_from_file_to_hardlink_with_option_backup() {
for option in ["-b", "-bd", "-bf", "-bdf"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let hardlink = "hardlink";
let backup = "hardlink~";
at.write(FILE_NAME, CONTENTS);
at.hard_link(FILE_NAME, hardlink);
scene.ucmd().args(&[option, FILE_NAME, hardlink]).succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.file_exists(hardlink));
assert!(at.file_exists(backup));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_file_to_hardlink_with_option_link() {
for option in ["-l", "-dl", "-fl", "-dfl", "-bl", "-bdl", "-bfl", "-bdfl"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let hardlink = "hardlink";
at.write(FILE_NAME, CONTENTS);
at.hard_link(FILE_NAME, hardlink);
scene.ucmd().args(&[option, FILE_NAME, hardlink]).succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.file_exists(hardlink));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_file_to_hardlink_with_option_symlink() {
for option in ["-s", "-sf"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let hardlink = "hardlink";
at.write(FILE_NAME, CONTENTS);
at.hard_link(FILE_NAME, hardlink);
scene
.ucmd()
.args(&[option, FILE_NAME, hardlink])
.fails()
.stderr_contains("'foo' and 'hardlink' are the same file");
assert!(at.file_exists(FILE_NAME));
assert!(at.file_exists(hardlink));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
// the following tests tries to copy symlink to a hardlink of the same symlink with
// various options
#[test]
fn test_same_file_from_hard_link_of_symlink_to_symlink() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let hardlink_to_symlink = "hlsl";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
at.hard_link(SYMLINK_NAME, hardlink_to_symlink);
scene
.ucmd()
.args(&[hardlink_to_symlink, SYMLINK_NAME])
.fails()
.stderr_contains("cp: 'hlsl' and 'symlink' are the same file");
assert!(at.file_exists(FILE_NAME));
assert!(at.symlink_exists(SYMLINK_NAME));
assert!(at.symlink_exists(hardlink_to_symlink));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
#[test]
fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_force() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let hardlink_to_symlink = "hlsl";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
at.hard_link(SYMLINK_NAME, hardlink_to_symlink);
scene
.ucmd()
.args(&["-f", hardlink_to_symlink, SYMLINK_NAME])
.fails()
.stderr_contains("cp: 'hlsl' and 'symlink' are the same file");
assert!(at.file_exists(FILE_NAME));
assert!(at.symlink_exists(SYMLINK_NAME));
assert!(at.symlink_exists(hardlink_to_symlink));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
#[test]
fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_no_deref() {
for option in ["-d", "-df"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let hardlink_to_symlink = "hlsl";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
at.hard_link(SYMLINK_NAME, hardlink_to_symlink);
scene
.ucmd()
.args(&[option, hardlink_to_symlink, SYMLINK_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.symlink_exists(SYMLINK_NAME));
assert!(at.symlink_exists(hardlink_to_symlink));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_rem() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let hardlink_to_symlink = "hlsl";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
at.hard_link(SYMLINK_NAME, hardlink_to_symlink);
scene
.ucmd()
.args(&["--rem", hardlink_to_symlink, SYMLINK_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.file_exists(SYMLINK_NAME));
assert!(!at.symlink_exists(SYMLINK_NAME));
assert!(at.symlink_exists(hardlink_to_symlink));
assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
assert_eq!(at.read(SYMLINK_NAME), CONTENTS,);
}
#[test]
fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_backup() {
for option in ["-b", "-bf"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let backup = "symlink~";
let hardlink_to_symlink = "hlsl";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
at.hard_link(SYMLINK_NAME, hardlink_to_symlink);
scene
.ucmd()
.args(&[option, hardlink_to_symlink, SYMLINK_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.file_exists(SYMLINK_NAME));
assert!(!at.symlink_exists(SYMLINK_NAME));
assert!(at.symlink_exists(hardlink_to_symlink));
assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink));
assert!(at.symlink_exists(backup));
assert_eq!(FILE_NAME, at.resolve_link(backup));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
assert_eq!(at.read(SYMLINK_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_backup_and_no_deref() {
for option in ["-bd", "-bdf"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let backup = "symlink~";
let hardlink_to_symlink = "hlsl";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
at.hard_link(SYMLINK_NAME, hardlink_to_symlink);
scene
.ucmd()
.args(&[option, hardlink_to_symlink, SYMLINK_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.symlink_exists(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert!(at.symlink_exists(hardlink_to_symlink));
assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink));
assert!(at.symlink_exists(backup));
assert_eq!(FILE_NAME, at.resolve_link(backup));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_link() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let hardlink_to_symlink = "hlsl";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
at.hard_link(SYMLINK_NAME, hardlink_to_symlink);
scene
.ucmd()
.args(&["-l", hardlink_to_symlink, SYMLINK_NAME])
.fails()
.stderr_contains("cannot create hard link 'symlink' to 'hlsl'");
assert!(at.file_exists(FILE_NAME));
assert!(at.symlink_exists(SYMLINK_NAME));
assert!(at.symlink_exists(hardlink_to_symlink));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
#[test]
fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_link_and_no_deref() {
for option in ["-dl", "-dfl"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let hardlink_to_symlink = "hlsl";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
at.hard_link(SYMLINK_NAME, hardlink_to_symlink);
scene
.ucmd()
.args(&[option, hardlink_to_symlink, SYMLINK_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.symlink_exists(SYMLINK_NAME));
assert!(at.symlink_exists(hardlink_to_symlink));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_link_and_force() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let hardlink_to_symlink = "hlsl";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
at.hard_link(SYMLINK_NAME, hardlink_to_symlink);
scene
.ucmd()
.args(&["-fl", hardlink_to_symlink, SYMLINK_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.file_exists(SYMLINK_NAME));
assert!(!at.symlink_exists(SYMLINK_NAME));
assert!(at.symlink_exists(hardlink_to_symlink));
assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
#[test]
fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_link_and_backup() {
for option in ["-bl", "-bfl"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let backup = "symlink~";
let hardlink_to_symlink = "hlsl";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
at.hard_link(SYMLINK_NAME, hardlink_to_symlink);
scene
.ucmd()
.args(&[option, hardlink_to_symlink, SYMLINK_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.file_exists(SYMLINK_NAME));
assert!(!at.symlink_exists(SYMLINK_NAME));
assert!(at.symlink_exists(hardlink_to_symlink));
assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink));
assert!(at.symlink_exists(backup));
assert_eq!(FILE_NAME, at.resolve_link(backup));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_hard_link_of_symlink_to_symlink_with_options_backup_link_no_deref() {
for option in ["-bdl", "-bdfl"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let hardlink_to_symlink = "hlsl";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
at.hard_link(SYMLINK_NAME, hardlink_to_symlink);
scene
.ucmd()
.args(&[option, hardlink_to_symlink, SYMLINK_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.symlink_exists(SYMLINK_NAME));
assert!(at.symlink_exists(hardlink_to_symlink));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
#[test]
fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_symlink() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let hardlink_to_symlink = "hlsl";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
at.hard_link(SYMLINK_NAME, hardlink_to_symlink);
scene
.ucmd()
.args(&["-s", hardlink_to_symlink, SYMLINK_NAME])
.fails()
.stderr_contains("cannot create symlink 'symlink' to 'hlsl'");
assert!(at.file_exists(FILE_NAME));
assert!(at.symlink_exists(SYMLINK_NAME));
assert!(at.symlink_exists(hardlink_to_symlink));
assert_eq!(FILE_NAME, at.resolve_link(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
#[test]
fn test_same_file_from_hard_link_of_symlink_to_symlink_with_option_symlink_and_force() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
let hardlink_to_symlink = "hlsl";
at.write(FILE_NAME, CONTENTS);
at.symlink_file(FILE_NAME, SYMLINK_NAME);
at.hard_link(SYMLINK_NAME, hardlink_to_symlink);
scene
.ucmd()
.args(&["-sf", hardlink_to_symlink, SYMLINK_NAME])
.succeeds();
assert!(at.file_exists(FILE_NAME));
assert!(at.symlink_exists(SYMLINK_NAME));
assert!(at.symlink_exists(hardlink_to_symlink));
assert_eq!(hardlink_to_symlink, at.resolve_link(SYMLINK_NAME));
assert_eq!(FILE_NAME, at.resolve_link(hardlink_to_symlink));
assert_eq!(at.read(FILE_NAME), CONTENTS,);
}
}
// the following tests are for how the cp should behave when the source is a symlink
// and link option is given
#[cfg(all(unix, not(target_os = "android")))]
mod link_deref {
use crate::common::util::{AtPath, TestScenario};
use std::os::unix::fs::MetadataExt;
const FILE: &str = "file";
const FILE_LINK: &str = "file_link";
const DIR: &str = "dir";
const DIR_LINK: &str = "dir_link";
const DANG_LINK: &str = "dang_link";
const DST: &str = "dst";
fn setup_link_deref_tests(source: &str, at: &AtPath) {
match source {
FILE_LINK => {
at.touch(FILE);
at.symlink_file(FILE, FILE_LINK);
}
DIR_LINK => {
at.mkdir(DIR);
at.symlink_dir(DIR, DIR_LINK);
}
DANG_LINK => at.symlink_file("nowhere", DANG_LINK),
_ => {}
}
}
// cp --link shouldn't deref source if -P is given
#[test]
fn test_cp_symlink_as_source_with_link_and_no_deref() {
for src in [FILE_LINK, DIR_LINK, DANG_LINK] {
for r in [false, true] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
setup_link_deref_tests(src, at);
let mut args = vec!["--link", "-P", src, DST];
if r {
args.push("-R");
};
scene.ucmd().args(&args).succeeds().no_stderr();
at.is_symlink(DST);
let src_ino = at.symlink_metadata(src).ino();
let dest_ino = at.symlink_metadata(DST).ino();
assert_eq!(src_ino, dest_ino);
}
}
}
// Dereferencing should fail for dangling symlink.
#[test]
fn test_cp_dang_link_as_source_with_link() {
for option in ["", "-L", "-H"] {
for r in [false, true] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
setup_link_deref_tests(DANG_LINK, at);
let mut args = vec!["--link", DANG_LINK, DST];
if r {
args.push("-R");
};
if !option.is_empty() {
args.push(option);
}
scene
.ucmd()
.args(&args)
.fails()
.stderr_contains("No such file or directory");
}
}
}
// Dereferencing should fail for the 'dir_link' without -R.
#[test]
fn test_cp_dir_link_as_source_with_link() {
for option in ["", "-L", "-H"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
setup_link_deref_tests(DIR_LINK, at);
let mut args = vec!["--link", DIR_LINK, DST];
if !option.is_empty() {
args.push(option);
}
scene
.ucmd()
.args(&args)
.fails()
.stderr_contains("cp: -r not specified; omitting directory");
}
}
// cp --link -R 'dir_link' should create a new directory.
#[test]
fn test_cp_dir_link_as_source_with_link_and_r() {
for option in ["", "-L", "-H"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
setup_link_deref_tests(DIR_LINK, at);
let mut args = vec!["--link", "-R", DIR_LINK, DST];
if !option.is_empty() {
args.push(option);
}
scene.ucmd().args(&args).succeeds();
at.dir_exists(DST);
}
}
//cp --link 'file_link' should create a hard link to the target.
#[test]
fn test_cp_file_link_as_source_with_link() {
for option in ["", "-L", "-H"] {
for r in [false, true] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
setup_link_deref_tests(FILE_LINK, at);
let mut args = vec!["--link", "-R", FILE_LINK, DST];
if !option.is_empty() {
args.push(option);
}
if r {
args.push("-R");
}
scene.ucmd().args(&args).succeeds();
at.file_exists(DST);
let src_ino = at.symlink_metadata(FILE).ino();
let dest_ino = at.symlink_metadata(DST).ino();
assert_eq!(src_ino, dest_ino);
}
}
}
}
2024-05-20 06:17:17 +00:00
// The cp command might create directories with excessively permissive permissions temporarily,
// which could be problematic if we aim to preserve ownership or mode. For example, when
// copying a directory, the destination directory could temporarily be setgid on some filesystems.
// This temporary setgid status could grant access to other users who share the same group
// ownership as the newly created directory.To mitigate this issue, when creating a directory we
// disable these excessive permissions.
#[test]
#[cfg(unix)]
#[cfg(not(target_os = "openbsd"))]
2024-05-20 06:17:17 +00:00
fn test_dir_perm_race_with_preserve_mode_and_ownership() {
const SRC_DIR: &str = "src";
const DEST_DIR: &str = "dest";
const FIFO: &str = "src/fifo";
for attr in ["mode", "ownership"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir(SRC_DIR);
at.mkdir(DEST_DIR);
at.set_mode(SRC_DIR, 0o775);
at.set_mode(DEST_DIR, 0o2775);
at.mkfifo(FIFO);
let child = scene
.ucmd()
.args(&[
2024-09-19 21:56:27 +00:00
format!("--preserve={attr}").as_str(),
2024-05-20 06:17:17 +00:00
"-R",
"--copy-contents",
"--parents",
SRC_DIR,
DEST_DIR,
])
// make sure permissions weren't disabled because of umask.
.umask(0)
.run_no_wait();
// while cp wait for fifo we could check the dirs created by cp
let timeout = Duration::from_secs(10);
let start_time = std::time::Instant::now();
// wait for cp to create dirs
loop {
2024-06-30 14:27:08 +00:00
assert!(
start_time.elapsed() < timeout,
"timed out: cp took too long to create destination directory"
);
2024-09-19 21:56:27 +00:00
if at.dir_exists(&format!("{DEST_DIR}/{SRC_DIR}")) {
2024-05-20 06:17:17 +00:00
break;
}
std::thread::sleep(Duration::from_millis(100));
}
2024-09-19 21:56:27 +00:00
let mode = at.metadata(&format!("{DEST_DIR}/{SRC_DIR}")).mode();
2024-06-30 14:27:08 +00:00
#[allow(clippy::unnecessary_cast, clippy::cast_lossless)]
2024-05-20 06:17:17 +00:00
let mask = if attr == "mode" {
libc::S_IWGRP | libc::S_IWOTH
} else {
libc::S_IRWXG | libc::S_IRWXO
} as u32;
assert_eq!(
(mode & mask),
0,
2024-09-19 21:56:27 +00:00
"unwanted permissions are present - {attr}"
2024-05-20 06:17:17 +00:00
);
at.write(FIFO, "done");
child.wait().unwrap().succeeded();
}
}
#[test]
// when -d and -a are overridden with --preserve or --no-preserve make sure that it only
// overrides attributes not other flags like -r or --no_deref implied in -a and -d.
fn test_preserve_attrs_overriding_1() {
const FILE: &str = "file";
const SYMLINK: &str = "symlink";
const DEST: &str = "dest";
for f in ["-d", "-a"] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.make_file(FILE);
at.symlink_file(FILE, SYMLINK);
scene
.ucmd()
.args(&[f, "--no-preserve=all", SYMLINK, DEST])
.succeeds();
at.symlink_exists(DEST);
}
}
#[test]
#[cfg(all(unix, not(target_os = "android")))]
fn test_preserve_attrs_overriding_2() {
const FILE1: &str = "file1";
const FILE2: &str = "file2";
const FOLDER: &str = "folder";
const DEST: &str = "dest";
for mut args in [
// All of the following to args should tell cp to preserve mode and
// timestamp, but not the link.
vec!["-r", "--preserve=mode,link,timestamp", "--no-preserve=link"],
vec![
"-r",
"--preserve=mode",
"--preserve=link",
"--preserve=timestamp",
"--no-preserve=link",
],
vec![
"-r",
"--preserve=mode,link",
"--no-preserve=link",
"--preserve=timestamp",
],
vec!["-a", "--no-preserve=link"],
vec!["-r", "--preserve", "--no-preserve=link"],
] {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir(FOLDER);
2024-09-19 21:56:27 +00:00
at.make_file(&format!("{FOLDER}/{FILE1}"));
at.set_mode(&format!("{FOLDER}/{FILE1}"), 0o775);
at.hard_link(&format!("{FOLDER}/{FILE1}"), &format!("{FOLDER}/{FILE2}"));
args.append(&mut vec![FOLDER, DEST]);
2024-09-19 21:56:27 +00:00
let src_file1_metadata = at.metadata(&format!("{FOLDER}/{FILE1}"));
scene.ucmd().args(&args).succeeds();
at.dir_exists(DEST);
2024-09-19 21:56:27 +00:00
let dest_file1_metadata = at.metadata(&format!("{DEST}/{FILE1}"));
let dest_file2_metadata = at.metadata(&format!("{DEST}/{FILE2}"));
assert_eq!(
src_file1_metadata.modified().unwrap(),
dest_file1_metadata.modified().unwrap()
);
assert_eq!(src_file1_metadata.mode(), dest_file1_metadata.mode());
assert_ne!(dest_file1_metadata.ino(), dest_file2_metadata.ino());
}
}
2024-06-17 09:16:26 +00:00
/// Test the behavior of preserving permissions when copying through a symlink
#[test]
#[cfg(unix)]
fn test_cp_symlink_permissions() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.touch("a");
at.set_mode("a", 0o700);
at.symlink_file("a", "symlink");
at.mkdir("dest");
scene
.ucmd()
.args(&["--preserve", "symlink", "dest"])
.succeeds();
let dest_dir_metadata = at.metadata("dest/symlink");
let src_dir_metadata = at.metadata("a");
assert_eq!(
src_dir_metadata.permissions().mode(),
dest_dir_metadata.permissions().mode()
);
}
/// Test the behavior of preserving permissions of parents when copying through a symlink
#[test]
#[cfg(unix)]
fn test_cp_parents_symlink_permissions_file() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir("a");
at.touch("a/file");
at.set_mode("a", 0o700);
at.symlink_dir("a", "symlink");
at.mkdir("dest");
scene
.ucmd()
.args(&["--parents", "-a", "symlink/file", "dest"])
.succeeds();
let dest_dir_metadata = at.metadata("dest/symlink");
let src_dir_metadata = at.metadata("a");
assert_eq!(
src_dir_metadata.permissions().mode(),
dest_dir_metadata.permissions().mode()
);
}
/// Test the behavior of preserving permissions of parents when copying through
/// a symlink when source is a dir.
#[test]
#[cfg(unix)]
fn test_cp_parents_symlink_permissions_dir() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir_all("a/b");
at.set_mode("a", 0o755); // Set mode for the actual directory
at.symlink_dir("a", "symlink");
at.mkdir("dest");
scene
.ucmd()
.args(&["--parents", "-a", "symlink/b", "dest"])
.succeeds();
let dest_dir_metadata = at.metadata("dest/symlink");
let src_dir_metadata = at.metadata("a");
assert_eq!(
src_dir_metadata.permissions().mode(),
dest_dir_metadata.permissions().mode()
);
}
/// Test the behavior of copying a file to a destination with parents using absolute paths.
#[cfg(unix)]
#[test]
fn test_cp_parents_absolute_path() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.mkdir_all("a/b");
at.touch("a/b/f");
at.mkdir("dest");
let src = format!("{}/a/b/f", at.root_dir_resolved());
scene
.ucmd()
.args(&["--parents", src.as_str(), "dest"])
.succeeds();
let res = format!("dest{}/a/b/f", at.root_dir_resolved());
at.file_exists(res);
}
#[test]
fn test_copy_symlink_overwrite() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("a");
at.mkdir("b");
at.mkdir("c");
at.write("t", "hello");
at.relative_symlink_file("../t", "a/1");
at.relative_symlink_file("../t", "b/1");
ucmd.arg("--no-dereference")
.arg("a/1")
.arg("b/1")
.arg("c")
.fails()
.stderr_only(if cfg!(not(target_os = "windows")) {
"cp: will not overwrite just-created 'c/1' with 'b/1'\n"
} else {
"cp: will not overwrite just-created 'c\\1' with 'b/1'\n"
});
}
#[test]
fn test_symlink_mode_overwrite() {
let (at, mut ucmd) = at_and_ucmd!();
at.mkdir("a");
at.mkdir("b");
at.write("a/t", "hello");
at.write("b/t", "hello");
if cfg!(not(target_os = "windows")) {
ucmd.arg("-s")
.arg("a/t")
.arg("b/t")
.arg(".")
.fails()
.stderr_only("cp: will not overwrite just-created './t' with 'b/t'\n");
assert_eq!(at.read("./t"), "hello");
} else {
ucmd.arg("-s")
.arg("a\\t")
.arg("b\\t")
.arg(".")
.fails()
.stderr_only("cp: will not overwrite just-created '.\\t' with 'b\\t'\n");
assert_eq!(at.read(".\\t"), "hello");
}
}
// make sure that cp backup dest symlink before removing it.
#[test]
fn test_cp_with_options_backup_and_rem_when_dest_is_symlink() {
let scene = TestScenario::new(util_name!());
let at = &scene.fixtures;
at.write("file", "xyz");
at.mkdir("inner_dir");
at.write("inner_dir/inner_file", "abc");
at.relative_symlink_file("inner_file", "inner_dir/sl");
scene
.ucmd()
.args(&["-b", "--rem", "file", "inner_dir/sl"])
.succeeds();
assert!(at.file_exists("inner_dir/inner_file"));
assert_eq!(at.read("inner_dir/inner_file"), "abc");
assert!(at.symlink_exists("inner_dir/sl~"));
assert!(!at.symlink_exists("inner_dir/sl"));
assert_eq!(at.read("inner_dir/sl"), "xyz");
}
#[test]
fn test_cp_single_file() {
let (_at, mut ucmd) = at_and_ucmd!();
ucmd.arg(TEST_HELLO_WORLD_SOURCE)
.fails()
.code_is(1)
.stderr_contains("missing destination file");
}
#[test]
fn test_cp_no_file() {
let (_at, mut ucmd) = at_and_ucmd!();
ucmd.fails()
.code_is(1)
.stderr_contains("error: the following required arguments were not provided:");
}