which can return varied file system I/O block size values for files.
[bug introduced in coreutils-6.0]
+ cp, mv, and install now immediately acknowledge transient errors
+ when creating copy-on-write or cloned reflink files, on supporting
+ file systems like XFS, BTRFS, APFS, etc.
+ Previously they would have tried again with other copy methods
+ which may have resulted in data corruption.
+ [bug introduced in coreutils-7.5 and enabled by default in coreutils-9.0]
+
'mv --backup=simple f d/' no longer mistakenly backs up d/f to f~.
[bug introduced in coreutils-9.1]
** Changes in behavior
cp and install now default to copy-on-write (COW) if available.
+ I.e., cp now uses --reflink=auto mode by default.
cp, install and mv now use the copy_file_range syscall if available.
Also, they use lseek+SEEK_HOLE rather than ioctl+FS_IOC_FIEMAP on sparse
}
+/* Whether the errno from FICLONE, or copy_file_range
+ indicates operation is not supported for this file or file system. */
+
+static bool
+is_CLONENOTSUP (int err)
+{
+ return err == ENOSYS || is_ENOTSUP (err)
+ || err == EINVAL || err == EBADF
+ || err == EXDEV || err == ETXTBSY;
+}
+
+
/* Copy the regular file open on SRC_FD/SRC_NAME to DST_FD/DST_NAME,
honoring the MAKE_HOLES setting and using the BUF_SIZE-byte buffer
*ABUF for temporary storage, allocating it lazily if *ABUF is null.
}
if (n_copied < 0)
{
- if (errno == ENOSYS || is_ENOTSUP (errno)
- || errno == EINVAL || errno == EBADF
- || errno == EXDEV || errno == ETXTBSY)
+ if (is_CLONENOTSUP (errno))
break;
/* copy_file_range might not be enabled in seccomp filters,
}
+/* Handle failure from FICLONE or fclonefileat.
+ Return FALSE if it's a terminal failure for this file. */
+
+static bool
+handle_clone_fail (int dst_dirfd, char const* dst_relname,
+ char const* src_name, char const* dst_name,
+ int dest_desc, bool new_dst, enum Reflink_type reflink_mode)
+{
+ /* If the clone operation is creating the destination,
+ then don't try and cater for all non transient file system errors,
+ and instead only cater for specific transient errors. */
+ bool transient_failure;
+ if (dest_desc < 0) /* currently for fclonefileat(). */
+ transient_failure = errno == EIO || errno == ENOMEM
+ || errno == ENOSPC || errno == EDQUOT;
+ else /* currently for FICLONE. */
+ transient_failure = ! is_CLONENOTSUP (errno);
+
+ if (reflink_mode == REFLINK_ALWAYS || transient_failure)
+ error (0, errno, _("failed to clone %s from %s"),
+ quoteaf_n (0, dst_name), quoteaf_n (1, src_name));
+
+ /* Remove the destination if cp --reflink=always created it
+ but cloned no data. */
+ if (new_dst /* currently not for fclonefileat(). */
+ && reflink_mode == REFLINK_ALWAYS
+ && ((! transient_failure) || 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));
+
+ if (reflink_mode == REFLINK_ALWAYS || transient_failure)
+ return false;
+
+ return true;
+}
+
+
/* Copy a regular file from SRC_NAME to DST_NAME aka DST_DIRFD+DST_RELNAME.
If the source file contains holes, copies holes and blocks of zeros
in the source file as holes in the destination file.
# ifndef CLONE_NOOWNERCOPY
# define CLONE_NOOWNERCOPY 0
# endif
- int clone_flags = x->preserve_ownership ? 0 : CLONE_NOOWNERCOPY;
+ int fc_flags = x->preserve_ownership ? 0 : CLONE_NOOWNERCOPY;
if (data_copy_required && x->reflink_mode
&& x->preserve_mode && x->preserve_timestamps
- && (x->preserve_ownership || CLONE_NOOWNERCOPY)
- && (fclonefileat (source_desc, dst_dirfd, dst_relname, clone_flags)
- == 0))
- goto close_src_desc;
+ && (x->preserve_ownership || CLONE_NOOWNERCOPY))
+ {
+ if (fclonefileat (source_desc, dst_dirfd, dst_relname, fc_flags) == 0)
+ goto close_src_desc;
+ else if (! handle_clone_fail (dst_dirfd, dst_relname, src_name,
+ dst_name,
+ -1, false /* We didn't create dst */,
+ x->reflink_mode))
+ {
+ return_val = false;
+ goto close_src_desc;
+ }
+ }
#endif
/* To allow copying xattrs on read-only files, create with u+w.
{
if (clone_file (dest_desc, source_desc) == 0)
data_copy_required = false;
- else if (x->reflink_mode == REFLINK_ALWAYS)
+ else
{
- error (0, errno, _("failed to clone %s from %s"),
- quoteaf_n (0, dst_name), quoteaf_n (1, src_name));
-
- /* 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;
+ if (! handle_clone_fail (dst_dirfd, dst_relname, src_name, dst_name,
+ dest_desc, *new_dst, x->reflink_mode))
+ {
+ return_val = false;
+ goto close_src_and_dst_desc;
+ }
}
}