Skip to content

Commit e52f20c

Browse files
committed
Add support for file cloning in append/reget modes on Linux and FreeBSD
Signed-off-by: Phil Krylov <phil@krylov.eu>
1 parent b945bdd commit e52f20c

5 files changed

Lines changed: 84 additions & 40 deletions

File tree

configure.ac

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,10 +380,25 @@ case $host_os in
380380
AC_DEFINE([PTY_ZEROREAD], [1], [read(1) can return 0 for a non-closed fd])
381381
esac
382382

383-
dnl Check linux/fs.h for FICLONE to support BTRFS's file clone operation
383+
dnl Check linux/fs.h for FICLONERANGE to support file clone operation
384384
case $host_os in
385385
linux*)
386386
AC_CHECK_HEADERS([linux/fs.h])
387+
AC_MSG_CHECKING([for FICLONERANGE])
388+
AC_EGREP_CPP([FICLONERANGE_IS_DEFINED],
389+
[
390+
#include <linux/fs.h>
391+
#ifdef FICLONERANGE
392+
FICLONERANGE_IS_DEFINED
393+
#endif
394+
],
395+
[
396+
AC_DEFINE(HAVE_FICLONERANGE, 1, [Define if FICLONERANGE is supported on Linux])
397+
AC_MSG_RESULT(yes)
398+
],
399+
[
400+
AC_MSG_RESULT(no)
401+
])
387402
esac
388403

389404
dnl Check for copy_file_range(2) support (FreeBSD 13+)

lib/vfs/vfs.c

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,14 @@
4646
#include <errno.h>
4747
#include <stdlib.h>
4848

49-
#ifdef __linux__
50-
#ifdef HAVE_LINUX_FS_H
51-
#include <linux/fs.h>
52-
#endif
53-
#ifdef HAVE_SYS_IOCTL_H
54-
#include <sys/ioctl.h>
55-
#endif
56-
#elif defined(__FreeBSD__) && defined(HAVE_COPY_FILE_RANGE)
49+
#ifdef HAVE_FICLONERANGE
50+
#include <linux/fs.h> // FICLONERANGE
51+
#include <sys/ioctl.h> // ioctl()
52+
#elif defined(HAVE_COPY_FILE_RANGE)
5753
#include <unistd.h> // COPY_FILE_RANGE_CLONE
58-
#elif defined(__APPLE__) && defined(HAVE_SYS_CLONEFILE_H)
54+
#elif defined(HAVE_SYS_CLONEFILE_H)
5955
#include <sys/clonefile.h> // CLONE_NOOWNERCOPY
60-
#elif defined(__sun) && defined(HAVE_REFLINK)
56+
#elif defined(HAVE_REFLINK)
6157
#include <unistd.h> // reflink()
6258
#endif
6359

@@ -726,11 +722,12 @@ vfs_preallocate (int dest_vfs_fd, off_t src_fsize, off_t dest_fsize)
726722
int
727723
vfs_clone_file (int dest_vfs_fd, int src_vfs_fd)
728724
{
729-
#if defined(FICLONE) || defined(COPY_FILE_RANGE_CLONE)
725+
#if defined(FICLONERANGE) || defined(COPY_FILE_RANGE_CLONE)
730726
void *dest_fd = NULL;
731727
void *src_fd = NULL;
732728
struct vfs_class *dest_class;
733729
struct vfs_class *src_class;
730+
off_t in_offset, out_offset;
734731

735732
dest_class = vfs_class_find_by_handle (dest_vfs_fd, &dest_fd);
736733
if ((dest_class->flags & VFSF_LOCAL) == 0)
@@ -756,12 +753,26 @@ vfs_clone_file (int dest_vfs_fd, int src_vfs_fd)
756753
return (-1);
757754
}
758755

759-
#if defined(FICLONE)
760-
return ioctl (*(int *) dest_fd, FICLONE, *(int *) src_fd);
756+
in_offset = mc_lseek (src_vfs_fd, 0, SEEK_CUR);
757+
if (in_offset < 0)
758+
return (-1);
759+
out_offset = mc_lseek (dest_vfs_fd, 0, SEEK_CUR);
760+
if (out_offset < 0)
761+
return (-1);
762+
763+
#if defined(FICLONERANGE)
764+
{
765+
struct file_clone_range fcr = { .src_fd = *(int *) src_fd,
766+
.src_offset = in_offset,
767+
.src_length = 0,
768+
.dest_offset = out_offset };
769+
770+
return ioctl (*(int *) dest_fd, FICLONERANGE, &fcr);
771+
}
761772
#elif defined(COPY_FILE_RANGE_CLONE)
762773
{
763-
off_t in_offset = 0, out_offset = 0;
764774
ssize_t result;
775+
765776
do
766777
{
767778
result = copy_file_range (*(int *) src_fd, &in_offset, *(int *) dest_fd, &out_offset,

src/filemanager/boxes.c

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,6 @@
3838
#include <string.h>
3939
#include <sys/types.h>
4040
#include <sys/stat.h>
41-
#ifdef HAVE_SYS_IOCTL_H
42-
#include <sys/ioctl.h> // FICLONE
43-
#endif
4441

4542
#include "lib/global.h"
4643

@@ -634,7 +631,8 @@ configure_box (void)
634631
quick_widgets[6].state = WST_DISABLED;
635632
#endif
636633

637-
#if !defined(FICLONE) && !defined(HAVE_COPY_FILE_RANGE) && !defined(HAVE_SYS_CLONEFILE_H) && !defined(HAVE_REFLINK)
634+
#if !defined(HAVE_FICLONERANGE) && !defined(HAVE_COPY_FILE_RANGE) \
635+
&& !defined(HAVE_SYS_CLONEFILE_H) && !defined(HAVE_REFLINK)
638636
mc_global.vfs.file_cloning = FALSE;
639637
quick_widgets[7].state = WST_DISABLED;
640638
#endif

src/filemanager/file.c

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,6 @@
5959
#include <sys/types.h>
6060
#include <sys/stat.h>
6161
#include <unistd.h>
62-
#ifdef HAVE_SYS_IOCTL_H
63-
#include <sys/ioctl.h> // FICLONE
64-
#endif
6562

6663
#include "lib/global.h"
6764
#include "lib/tty/tty.h"
@@ -2593,7 +2590,7 @@ copy_file_file (file_op_context_t *ctx, const char *src_path, const char *dst_pa
25932590
src_gid = src_stat.st_gid;
25942591
file_size = src_stat.st_size;
25952592

2596-
#if defined(__APPLE__) || defined(__sun)
2593+
#if defined(HAVE_SYS_CLONEFILE_H) || defined(HAVE_REFLINK)
25972594
// On macOS 10.12+ and Solaris 11.4+, the syscalls for file cloning respond for creation of the
25982595
// destination file, so try them before mc_open to avoid handling various races later.
25992596
// Full file cloning is not supported in append and reget modes.
@@ -2620,10 +2617,20 @@ copy_file_file (file_op_context_t *ctx, const char *src_path, const char *dst_pa
26202617
if (!dst_exists)
26212618
open_flags |= O_CREAT | O_EXCL;
26222619
else if (ctx->do_append)
2620+
#if defined(HAVE_FICLONERANGE) || defined(HAVE_COPY_FILE_RANGE)
2621+
// FICLONERANGE on Linux and copy_file_range(2) on FreeBSD support block-aligned ranges for
2622+
// cloning, but for not in O_APPEND mode. Use O_WRONLY + mc_lseek instead as we don't care
2623+
// about atomicity in our use cases.
2624+
open_flags |= mc_global.vfs.file_cloning ? O_WRONLY : O_APPEND;
2625+
#else
26232626
open_flags |= O_APPEND;
2627+
#endif
26242628
else
26252629
open_flags |= O_CREAT | O_TRUNC;
26262630

2631+
#if defined(HAVE_FICLONERANGE) || defined(HAVE_COPY_FILE_RANGE)
2632+
open_dest:
2633+
#endif
26272634
while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0)
26282635
{
26292636
if (errno != EEXIST)
@@ -2650,13 +2657,26 @@ copy_file_file (file_op_context_t *ctx, const char *src_path, const char *dst_pa
26502657
appending = ctx->do_append;
26512658
ctx->do_append = FALSE;
26522659

2653-
#if defined(FICLONE) || defined(HAVE_COPY_FILE_RANGE)
2654-
// Try clone the file first. It's not supported in append mode
2655-
if (mc_global.vfs.file_cloning && !appending && vfs_clone_file (dest_desc, src_desc) == 0)
2660+
#if defined(HAVE_FICLONERANGE) || defined(HAVE_COPY_FILE_RANGE)
2661+
// Try clone the file first, but not if the file is in O_APPEND mode
2662+
if (mc_global.vfs.file_cloning && !(open_flags & O_APPEND))
26562663
{
2657-
dst_status = DEST_FULL;
2658-
return_status = FILE_CONT;
2659-
goto ret;
2664+
if ((appending ? mc_lseek (dest_desc, 0, SEEK_END) >= 0 : TRUE)
2665+
&& vfs_clone_file (dest_desc, src_desc) == 0)
2666+
{
2667+
dst_status = DEST_FULL;
2668+
return_status = FILE_CONT;
2669+
goto ret;
2670+
}
2671+
else if (appending && !(open_flags & O_APPEND))
2672+
{
2673+
// Cloning append has failed, resort to normal append
2674+
ctx->do_append = TRUE;
2675+
mc_close (dest_desc);
2676+
dst_status = DEST_NONE;
2677+
open_flags = (open_flags & ~O_WRONLY) | O_APPEND;
2678+
goto open_dest;
2679+
}
26602680
}
26612681
#endif
26622682

tests/lib/vfs/vfs_clone_file.c

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@
3030
#include <stdarg.h>
3131
#include <stdlib.h>
3232

33-
#ifdef __linux__
34-
#include <linux/fs.h> // FICLONE
33+
#ifdef HAVE_FICLONERANGE
34+
#include <linux/fs.h> // FICLONERANGE
3535
#include <sys/ioctl.h> // ioctl()
36-
#elif defined(__FreeBSD__) && defined(HAVE_COPY_FILE_RANGE)
36+
#elif defined(HAVE_COPY_FILE_RANGE)
3737
#include <unistd.h> // copy_file_range()
38-
#elif defined(__sun) && defined(HAVE_REFLINK)
38+
#elif defined(HAVE_REFLINK)
3939
#include <unistd.h> // reflink()
40-
#elif defined(__APPLE__) && defined(HAVE_SYS_CLONEFILE_H)
40+
#elif defined(HAVE_SYS_CLONEFILE_H)
4141
#include <sys/clonefile.h> // clonefile()
4242
#endif
4343

@@ -53,7 +53,7 @@ static gboolean clone_syscall__call_arguments_are_proper = FALSE;
5353
static const char test_filename1[] = "mctestclone1.tst";
5454
static const char test_filename2[] = "mctestclone2.tst";
5555

56-
#ifdef __FreeBSD__
56+
#ifdef HAVE_COPY_FILE_RANGE
5757
/* @Mock */
5858
ssize_t
5959
copy_file_range (int infd, off_t *inoffp, int outfd, off_t *outoffp, size_t len, unsigned int flags)
@@ -71,7 +71,7 @@ copy_file_range (int infd, off_t *inoffp, int outfd, off_t *outoffp, size_t len,
7171
}
7272
#endif
7373

74-
#ifdef __linux__
74+
#ifdef HAVE_FICLONERANGE
7575
#ifdef __GLIBC__
7676
/* @Mock */
7777
int
@@ -85,12 +85,12 @@ ioctl (int fd, int request, ...)
8585
(void) fd;
8686

8787
clone_syscall__call_count++;
88-
clone_syscall__call_arguments_are_proper = (request == FICLONE);
88+
clone_syscall__call_arguments_are_proper = (request == FICLONERANGE);
8989
return -1;
9090
}
9191
#endif
9292

93-
#if defined(HAVE_SYS_CLONEFILE_H)
93+
#ifdef HAVE_SYS_CLONEFILE_H
9494
/* @Mock */
9595
int
9696
my_clonefile (const char *src, const char *dst, uint32_t flags)
@@ -104,7 +104,7 @@ my_clonefile (const char *src, const char *dst, uint32_t flags)
104104
}
105105
#endif
106106

107-
#if defined(HAVE_REFLINK)
107+
#ifdef HAVE_REFLINK
108108
/* @Mock */
109109
int
110110
reflink (const char *src, const char *dst, int preserve)
@@ -182,7 +182,7 @@ START_TEST (test_vfs_clone_file)
182182
vfs_clone_file (fdout, fdin);
183183

184184
// then
185-
#if defined(FICLONE) || defined(HAVE_COPY_FILE_RANGE)
185+
#if defined(HAVE_FICLONERANGE) || defined(HAVE_COPY_FILE_RANGE)
186186
ck_assert (clone_syscall__call_count > 0);
187187
ck_assert (clone_syscall__call_arguments_are_proper);
188188
#else

0 commit comments

Comments
 (0)