>From 9c60f6f603563c335737102fc54609b837f5d02a Mon Sep 17 00:00:00 2001 From: Paul Eggert Date: Wed, 6 Jul 2016 12:44:46 +0200 Subject: [PATCH] copy-file now uses GNU/Linux file cloning >From a suggestion by Kieran Colford (see Bug#23904). * configure.ac: Check for linux/fs.h. * src/fileio.c [HAVE_LINUX_FS_H]: Include sys/ioctl.h and linux/fs.h. (clone_file_range): New function. (Fcopy_file): Use it. --- configure.ac | 1 + src/fileio.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/configure.ac b/configure.ac index aaddfcd..1798fa3 100644 --- a/configure.ac +++ b/configure.ac @@ -1636,6 +1636,7 @@ AC_DEFUN dnl checks for header files AC_CHECK_HEADERS_ONCE( + linux/fs.h malloc.h sys/systeminfo.h sys/sysinfo.h diff --git a/src/fileio.c b/src/fileio.c index b1f9d3c..f980247 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -52,6 +52,11 @@ along with GNU Emacs. If not, see . */ #include "region-cache.h" #include "frame.h" +#ifdef HAVE_LINUX_FS_H +# include +# include +#endif + #ifdef WINDOWSNT #define NOMINMAX 1 #include @@ -1828,6 +1833,23 @@ barf_or_query_if_file_exists (Lisp_Object absname, bool known_to_exist, } } +/* Copy data to DEST from SOURCE if possible, starting at byte offset + OFF and continuing for LENGTH bytes. Return true if OK. */ +static bool +clone_file_range (int dest, int source, off_t off, off_t length) +{ +#ifdef FICLONERANGE + /* FIXME: Is the ioctl interruptible? If so, what happens if the + ioctl is interrupted by a signal? Might it partially copy the + range? */ + struct file_clone_range range = { src_fd: source, src_offset: off, + src_length: length, dest_offset: off }; + if (ioctl (dest, FICLONERANGE, &range) == 0) + return true; +#endif + return false; +} + DEFUN ("copy-file", Fcopy_file, Scopy_file, 2, 6, "fCopy file: \nGCopy %s to file: \np\nP", doc: /* Copy FILE to NEWNAME. Both args must be strings. @@ -1974,7 +1996,7 @@ permissions. */) record_unwind_protect_int (close_file_unwind, ofd); - off_t oldsize = 0, newsize = 0; + off_t oldsize = 0, newsize; if (already_exists) { @@ -1990,18 +2012,32 @@ permissions. */) immediate_quit = 1; QUIT; - while (true) - { - char buf[MAX_ALLOCA]; - ptrdiff_t n = emacs_read (ifd, buf, sizeof buf); - if (n < 0) - report_file_error ("Read error", file); - if (n == 0) - break; - if (emacs_write_sig (ofd, buf, n) != n) - report_file_error ("Write error", newname); - newsize += n; - } + + off_t insize = st.st_size; + /* FIXME: is 1 GiB a good number? */ + off_t max_clone_chunk_size = 1 << 30; + off_t chunk_size = min (insize, max_clone_chunk_size); + + if (clone_file_range (ofd, ifd, 0, chunk_size)) + for (newsize = chunk_size; newsize < insize; newsize += chunk_size) + { + chunk_size = min (insize - newsize, max_clone_chunk_size); + if (! clone_file_range (ofd, ifd, newsize, chunk_size)) + report_file_error ("Clone error", file); + } + else + for (newsize = 0; ; newsize += chunk_size) + { + char buf[MAX_ALLOCA]; + ptrdiff_t n = emacs_read (ifd, buf, sizeof buf); + if (n < 0) + report_file_error ("Read error", file); + if (n == 0) + break; + if (emacs_write_sig (ofd, buf, n) != n) + report_file_error ("Write error", newname); + chunk_size = n; + } /* Truncate any existing output file after writing the data. This is more likely to work than truncation before writing, if the -- 2.5.5