mirror of
https://github.com/RGBCube/uutils-coreutils
synced 2025-07-28 19:47:45 +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:
parent
aed0a5ce0f
commit
90a9829287
2 changed files with 80 additions and 2 deletions
|
@ -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)?;
|
||||
|
|
|
@ -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!();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue