Skip to content

Commit

Permalink
cp: truncate destination when --reflink is set
Browse files Browse the repository at this point in the history
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
```
  • Loading branch information
pimzero committed Aug 1, 2022
1 parent 38679f1 commit e6c5a05
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 2 deletions.
2 changes: 1 addition & 1 deletion src/uu/cp/src/cp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down
48 changes: 47 additions & 1 deletion tests/by-util/test_cp.rs
Original file line number Diff line number Diff line change
@@ -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))]
Expand Down Expand Up @@ -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!();
Expand Down

0 comments on commit e6c5a05

Please sign in to comment.