Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -383,8 +383,26 @@ esac
dnl Check for file cloning support
case $host_os in
darwin*) AC_CHECK_HEADERS([sys/clonefile.h]) ;; # clonefile(2) (macOS 10.12+)
freebsd*) AC_CHECK_FUNCS(copy_file_range) ;; # copy_file_range(2) (FreeBSD 13+)
solaris*) AC_CHECK_FUNCS(reflink) ;; # reflink(3C) (Solaris 11.3+)
freebsd*) # copy_file_range(2) (FreeBSD 13+)
AC_CHECK_FUNCS(copy_file_range)
AC_MSG_CHECKING([for COPY_FILE_RANGE_CLONE])
AC_EGREP_CPP([COPY_FILE_RANGE_CLONE_IS_DEFINED],
[
#include <unistd.h>
#ifdef COPY_FILE_RANGE_CLONE
COPY_FILE_RANGE_CLONE_IS_DEFINED
#endif
],
[
have_copy_file_range_clone=yes
AC_DEFINE(HAVE_COPY_FILE_RANGE_CLONE, 1, [Define if COPY_FILE_RANGE_CLONE is supported on FreeBSD])
AC_MSG_RESULT(yes)
],
[
AC_MSG_RESULT(no)
])
;;
linux*) # FICLONERANGE (Linux 4.5+)
AC_CHECK_HEADERS([linux/fs.h])
AC_MSG_CHECKING([for FICLONERANGE])
Expand All @@ -403,11 +421,13 @@ linux*) # FICLONERANGE (Linux 4.5+)
[
AC_MSG_RESULT(no)
])
# copy_file_range(2) (Linux): glibc declares it with off64_t *, so require a 64-bit off_t
AS_IF([test "x$ac_cv_sizeof_off_t" = x8], [AC_CHECK_FUNCS(copy_file_range)])
;;
esac

if test "x$have_ficlonerange" = xyes || \
test "x$ac_cv_func_copy_file_range" = xyes; then
test "x$have_copy_file_range_clone" = xyes; then
AC_DEFINE([HAVE_FILE_CLONING_BY_RANGE], [1], [Define if system can clone files by range])
fi

Expand Down
151 changes: 85 additions & 66 deletions lib/vfs/vfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,16 @@
#ifdef HAVE_FICLONERANGE
#include <linux/fs.h> // FICLONERANGE
#include <sys/ioctl.h> // ioctl()
#elif defined(HAVE_COPY_FILE_RANGE)
#include <unistd.h> // COPY_FILE_RANGE_CLONE
#endif

#if defined(HAVE_COPY_FILE_RANGE)

#include <unistd.h> // copy_file_range(), COPY_FILE_RANGE_CLONE

#ifdef __linux__
#include <sys/utsname.h> // uname()
#endif
Comment thread
tuffnatty marked this conversation as resolved.

#elif defined(HAVE_SYS_CLONEFILE_H)
#include <sys/clonefile.h> // CLONE_NOOWNERCOPY
#elif defined(HAVE_REFLINK)
Expand Down Expand Up @@ -210,6 +218,31 @@ vfs_test_current_dir (const vfs_path_t *vpath)
&& my_stat.st_ino == my_stat2.st_ino && my_stat.st_dev == my_stat2.st_dev);
}

/* --------------------------------------------------------------------------------------------- */

#ifdef HAVE_COPY_FILE_RANGE
/* Return true if copy_file_range(2) works well. To stay on the safe side, this is false on Linux
* kernels before 5.19 and on FreeBSD builds before 15.0 which has introduced
* COPY_FILE_RANGE_CLONE. */
static gboolean
vfs_copy_file_range_works (void)
{
#if defined(__FreeBSD__) && defined(COPY_FILE_RANGE_CLONE)
return TRUE;
#elif defined(__linux__)
static int copy_file_range_works_cache = -1;
struct utsname name;

if (copy_file_range_works_cache < 0)
copy_file_range_works_cache =
uname (&name) == 0 && 0 <= str_verscmp (name.release, "5.19.0");
return (gboolean) copy_file_range_works_cache;
#else
return FALSE;
#endif
}
#endif

/* --------------------------------------------------------------------------------------------- */
/*** public functions ****************************************************************************/
/* --------------------------------------------------------------------------------------------- */
Expand Down Expand Up @@ -719,78 +752,64 @@ vfs_preallocate (int dest_vfs_fd, off_t src_fsize, off_t dest_fsize)

/* --------------------------------------------------------------------------------------------- */

int
vfs_clone_file (int dest_vfs_fd, int src_vfs_fd)
{
#ifdef HAVE_FILE_CLONING_BY_RANGE
void *dest_fd = NULL;
void *src_fd = NULL;
struct vfs_class *dest_class;
struct vfs_class *src_class;
off_t in_offset, out_offset;
#if defined(FICLONERANGE)
ssize_t
mc_copy_file_range_ficlonerange (int src_fd, off_t *src_offset, int dest_fd, off_t *dest_offset,
size_t len)
{
int rc;
struct file_clone_range fcr = {
.src_fd = src_fd,
.src_offset = *src_offset,
.src_length = (len == SSIZE_MAX) ? 0 : len,
.dest_offset = *dest_offset,
};
off_t n_copied;

rc = ioctl (dest_fd, FICLONERANGE, &fcr);
if (rc == -1)
return rc;

n_copied = (len == SSIZE_MAX) ? lseek (src_fd, 0, SEEK_END) - *src_offset : (off_t) len;
*src_offset += n_copied;
*dest_offset += n_copied;
return n_copied;
}
#endif

dest_class = vfs_class_find_by_handle (dest_vfs_fd, &dest_fd);
if ((dest_class->flags & VFSF_LOCAL) == 0)
{
errno = ENOTSUP;
return (-1);
}
if (dest_fd == NULL)
{
errno = EBADF;
return (-1);
}
/* --------------------------------------------------------------------------------------------- */

src_class = vfs_class_find_by_handle (src_vfs_fd, &src_fd);
if ((src_class->flags & VFSF_LOCAL) == 0)
#if defined(HAVE_COPY_FILE_RANGE)
ssize_t
mc_copy_file_range_native (int src_fd, off_t *src_offset, int dest_fd, off_t *dest_offset,
size_t len)
{
if (!vfs_copy_file_range_works ())
{
errno = ENOTSUP;
return (-1);
return -1;
Comment thread
tuffnatty marked this conversation as resolved.
}
if (src_fd == NULL)
{
errno = EBADF;
return (-1);
}

in_offset = mc_lseek (src_vfs_fd, 0, SEEK_CUR);
if (in_offset < 0)
return (-1);
out_offset = mc_lseek (dest_vfs_fd, 0, SEEK_CUR);
if (out_offset < 0)
return (-1);

#if defined(FICLONERANGE)
{
struct file_clone_range fcr = {
.src_fd = *(int *) src_fd,
.src_offset = in_offset,
.src_length = 0,
.dest_offset = out_offset,
};

return ioctl (*(int *) dest_fd, FICLONERANGE, &fcr);
}
#elif defined(COPY_FILE_RANGE_CLONE)
{
ssize_t result;

do
{
result = copy_file_range (*(int *) src_fd, &in_offset, *(int *) dest_fd, &out_offset,
SSIZE_MAX, COPY_FILE_RANGE_CLONE);
}
while (result > 0);
return result;
}
#endif

#if defined(COPY_FILE_RANGE_CLONE)
return copy_file_range (src_fd, src_offset, dest_fd, dest_offset, len, COPY_FILE_RANGE_CLONE);
#else
(void) dest_vfs_fd;
(void) src_vfs_fd;
errno = ENOTSUP;
return (-1);
return copy_file_range (src_fd, src_offset, dest_fd, dest_offset, len, 0);
#endif
}
#endif

/* --------------------------------------------------------------------------------------------- */

gboolean
vfs_cloning_supported (const vfs_path_t *src_vpath, const vfs_path_t *dst_vpath)
{
if (!vfs_file_is_local (src_vpath))
return FALSE;
if (vfs_file_is_local (dst_vpath))
return TRUE;
if ((vfs_file_class_flags (dst_vpath) & VFSF_USETMP) != 0)
return TRUE;
return FALSE;
}
Comment on lines +803 to +813

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already added to my corrections batch, see unit test.

Only localfs stores an int * as the handle's fsinfo, for ftpfs the write handle is usually the data socket. Suggest limiting cloning to local files, i.e. dropping the USETMP part.

Suggested change
gboolean
vfs_cloning_supported (vfs_path_t *src_vpath, vfs_path_t *dst_vpath)
{
if (!vfs_file_is_local (src_vpath))
return FALSE;
if (vfs_file_is_local (dst_vpath))
return TRUE;
if ((vfs_file_class_flags (dst_vpath) & VFSF_USETMP) != 0)
return TRUE;
return FALSE;
}
gboolean
vfs_cloning_supported (const vfs_path_t *src_vpath, const vfs_path_t *dst_vpath)
{
return vfs_file_is_local (src_vpath) && vfs_file_is_local (dst_vpath);
}


/* --------------------------------------------------------------------------------------------- */
Expand Down
11 changes: 10 additions & 1 deletion lib/vfs/vfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,9 +302,18 @@ char *vfs_get_cwd (void);

int vfs_preallocate (int dest_desc, off_t src_fsize, off_t dest_fsize);

int vfs_clone_file (int dest_vfs_fd, int src_vfs_fd);
int vfs_clone_file_by_path (const vfs_path_t *dest_vpath, const vfs_path_t *src_vpath,
gboolean preserve_uidgid);
gboolean vfs_cloning_supported (const vfs_path_t *src_vpath, const vfs_path_t *dst_vpath);

#if defined(HAVE_FICLONERANGE)
ssize_t mc_copy_file_range_ficlonerange (int src_fd, off_t *src_offset, int dest_fd,
off_t *dest_offset, size_t len);
#endif
#if defined(HAVE_COPY_FILE_RANGE)
ssize_t mc_copy_file_range_native (int src_fd, off_t *src_offset, int dest_fd, off_t *dest_offset,
size_t len);
#endif

/**
* Interface functions described in interface.c
Expand Down
Loading
Loading