From: Paul Eggert Date: Wed, 6 Jul 2022 19:29:12 +0000 (-0500) Subject: cp: don’t remove nonempty cloned dest X-Git-Tag: v9.2~176 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8da1edffff5a5932fd77afd84fb42cc5e24b9922;p=thirdparty%2Fcoreutils.git cp: don’t remove nonempty cloned dest This follows up on comments by Pádraig Brady (bug#56391). * src/copy.c (copy_reg): When --reflink=always removes a file due to an FICLONE failure, do not remove a nonempty file. --- diff --git a/NEWS b/NEWS index a3a55541e0..b4e3cf83ad 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,9 @@ GNU coreutils NEWS -*- outline -*- ** Changes in behavior + 'cp --reflink=always A B' no longer leaves behind a newly created + empty file B merely because copy-on-write clones are not supported. + 'ls -v' and 'sort -V' go back to sorting ".0" before ".A", reverting to the behavior in coreutils-9.0 and earlier. This behavior is now documented. diff --git a/src/copy.c b/src/copy.c index eaed148b4f..e465271efa 100644 --- a/src/copy.c +++ b/src/copy.c @@ -1279,9 +1279,17 @@ copy_reg (char const *src_name, char const *dst_name, { error (0, errno, _("failed to clone %s from %s"), quoteaf_n (0, dst_name), quoteaf_n (1, src_name)); - if (*new_dst && unlinkat (dst_dirfd, dst_relname, 0) != 0 - && errno != ENOENT) + + /* Remove the destination if cp --reflink=always created it + but cloned no data. If clone_file failed with + EOPNOTSUPP, EXDEV or EINVAL no data were copied so do not + go to the expense of lseeking. */ + if (*new_dst + && (is_ENOTSUP (errno) || errno == EXDEV || errno == EINVAL + || lseek (dest_desc, 0, SEEK_END) == 0) + && unlinkat (dst_dirfd, dst_relname, 0) != 0 && errno != ENOENT) error (0, errno, _("cannot remove %s"), quoteaf (dst_name)); + return_val = false; goto close_src_and_dst_desc; }