1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-28 11:37:44 +00:00

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
```
This commit is contained in:
Pierre Marsais 2022-08-04 07:50:19 +01:00 committed by GitHub
parent aed0a5ce0f
commit 90a9829287
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 80 additions and 2 deletions

View file

@ -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)?;

View file

@ -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 ROOTDIR USERDIR
use crate::common::util::*;
#[cfg(not(windows))]
@ -1388,6 +1388,84 @@ 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!());
const DISK: &str = "disk.img";
const ROOTDIR: &str = "disk_root/";
const USERDIR: &str = "dir/";
const MOUNTPOINT: &str = "mountpoint/";
let src1_path: &str = &vec![MOUNTPOINT, USERDIR, "src1"].concat();
let src2_path: &str = &vec![MOUNTPOINT, USERDIR, "src2"].concat();
let dst_path: &str = &vec![MOUNTPOINT, USERDIR, "dst"].concat();
scene.fixtures.mkdir(ROOTDIR);
scene.fixtures.mkdir(&vec![ROOTDIR, USERDIR].concat());
// 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_keepenv("env")
.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_keepenv("sudo")
.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_keepenv("sudo")
.args(&["-E", "--non-interactive", "umount", MOUNTPOINT])
.succeeds();
}
#[test]
fn test_copy_dir_symlink() {
let (at, mut ucmd) = at_and_ucmd!();