From e6c5a05027c8adc7c9b64c527c2f6f90dff18749 Mon Sep 17 00:00:00 2001 From: Pierre Marsais Date: Sat, 30 Jul 2022 18:00:34 +0100 Subject: [PATCH] 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 ``` --- src/uu/cp/src/cp.rs | 2 +- tests/by-util/test_cp.rs | 48 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/uu/cp/src/cp.rs b/src/uu/cp/src/cp.rs index b609657fbd9..5df0ae206ac 100644 --- a/src/uu/cp/src/cp.rs +++ b/src/uu/cp/src/cp.rs @@ -1535,7 +1535,7 @@ fn copy_on_write_linux( let src_file = File::open(source).context(context)?; let dst_file = OpenOptions::new() .write(true) - .truncate(false) + .truncate(true) .create(true) .open(dest) .context(context)?; diff --git a/tests/by-util/test_cp.rs b/tests/by-util/test_cp.rs index 321f38990ae..39052ff88bf 100644 --- a/tests/by-util/test_cp.rs +++ b/tests/by-util/test_cp.rs @@ -1,4 +1,4 @@ -// spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob +// spell-checker:ignore (flags) reflink (fs) tmpfs (linux) rlimit Rlim NOFILE clob btrfs use crate::common::util::*; #[cfg(not(windows))] @@ -1388,6 +1388,52 @@ fn test_closes_file_descriptors() { .succeeds(); } +#[cfg(any(target_os = "linux", target_os = "android"))] +#[test] +fn test_cp_reflink_always_override() { + let scene = TestScenario::new(util_name!()); + + // Test must be run as root (or with `sudo -E`) + if scene.cmd("whoami").run().stdout_str() != "root\n" { + return; + } + + const DISK: &str = "disk.img"; + const MOUNTPOINT: &str = "mount/"; + let src1_path: &str = &vec![MOUNTPOINT, "src1"].concat(); + let src2_path: &str = &vec![MOUNTPOINT, "src2"].concat(); + let dst_path: &str = &vec![MOUNTPOINT, "dst"].concat(); + + scene + .ccmd("truncate") + .args(&["-s", "128M", DISK]) + .succeeds(); + + scene.cmd("mkfs.btrfs").args(&[DISK]).succeeds(); + + scene.fixtures.mkdir(MOUNTPOINT); + + scene.cmd("mount").args(&[DISK, MOUNTPOINT]).succeeds(); + + 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("umount").args(&[MOUNTPOINT]).succeeds(); +} + #[test] fn test_copy_dir_symlink() { let (at, mut ucmd) = at_and_ucmd!();