diff --git a/doc/changes/12074.md b/doc/changes/12074.md new file mode 100644 index 00000000000..18dc8fc8179 --- /dev/null +++ b/doc/changes/12074.md @@ -0,0 +1,2 @@ +- Use copy-on-write (COW) when copying files on filesystems that support it + (Btrfs, ZFS, XFS, etc), under Linux. (#12074, fixes #12071, @nojb) \ No newline at end of file diff --git a/otherlibs/stdune/src/copyfile_stubs.c b/otherlibs/stdune/src/copyfile_stubs.c index 4ab47f35c19..07ae342b16a 100644 --- a/otherlibs/stdune/src/copyfile_stubs.c +++ b/otherlibs/stdune/src/copyfile_stubs.c @@ -63,7 +63,10 @@ CAMLprim value stdune_sendfile(value v_in, value v_out, value v_size) { #include #include -#include +#include +#include +#include +#include #define FD_val(value) Int_val(value) @@ -73,7 +76,7 @@ CAMLprim value stdune_copyfile(value v_from, value v_to) { caml_failwith("copyfile: only on macos"); } -static int dune_sendfile(int in, int out, size_t length) { +static ssize_t dune_sendfile(int in, int out, size_t length) { ssize_t ret; while (length > 0) { ret = sendfile(out, in, NULL, length); @@ -85,12 +88,48 @@ static int dune_sendfile(int in, int out, size_t length) { return length; } +typedef ssize_t (*copy_file_range_t)(int, loff_t*, int, loff_t*, size_t, unsigned int); + +static copy_file_range_t copy_file_range_fn = NULL; + +static ssize_t dune_copy_file_range(int in, int out, size_t length) { + ssize_t ret; + while (length > 0) { + ret = copy_file_range_fn(in, NULL, out, NULL, length, 0); + if (ret < 0) { + return dune_sendfile(in, out, length); + } + length = length - ret; + } + return length; +} + +static int kernel_version(void) { + struct utsname uts; + int major, minor, patch; + + if (uname(&uts) < 0) + return -1; + + if (sscanf(uts.release, "%d.%d.%d", &major, &minor, &patch) != 3) + return -1; + + return KERNEL_VERSION(major, minor, patch); +} + CAMLprim value stdune_sendfile(value v_in, value v_out, value v_size) { + static ssize_t (*dune_copyfile)(int, int, size_t) = NULL; + if (dune_copyfile == NULL) { + if (kernel_version() < KERNEL_VERSION(5, 19, 0) || + (copy_file_range_fn = (copy_file_range_t)dlsym(NULL, "copy_file_range")) == NULL) { + dune_copyfile = &dune_sendfile; + } else { + dune_copyfile = &dune_copy_file_range; + } + } CAMLparam3(v_in, v_out, v_size); caml_release_runtime_system(); - /* TODO Use copy_file_range once we have a good mechanism to test for its - * existence */ - int ret = dune_sendfile(FD_val(v_in), FD_val(v_out), Long_val(v_size)); + ssize_t ret = dune_copyfile(FD_val(v_in), FD_val(v_out), Long_val(v_size)); caml_acquire_runtime_system(); if (ret < 0) { uerror("sendfile", Nothing);