Skip to content

Commit 51a41a2

Browse files
tuffnattyzyv
authored andcommitted
Ticket #5082: add file cloning support for FreeBSD, macOS and Solaris
Signed-off-by: Phil Krylov <phil@krylov.eu> Signed-off-by: Yury V. Zaytsev <yury@shurup.com>
1 parent b6012a2 commit 51a41a2

12 files changed

Lines changed: 468 additions & 29 deletions

File tree

configure.ac

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,12 +380,56 @@ 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+
have_ficlonerange=yes
397+
AC_DEFINE(HAVE_FICLONERANGE, 1, [Define if FICLONERANGE is supported on Linux])
398+
AC_MSG_RESULT(yes)
399+
],
400+
[
401+
AC_MSG_RESULT(no)
402+
])
387403
esac
388404

405+
dnl Check for copy_file_range(2) support (FreeBSD 13+)
406+
case $host_os in
407+
freebsd*)
408+
AC_CHECK_FUNCS(copy_file_range)
409+
esac
410+
411+
dnl Check for reflink(3C) support (Solaris 11.3+)
412+
case $host_os in
413+
solaris*)
414+
AC_CHECK_FUNCS(reflink)
415+
esac
416+
417+
dnl Check for clonefile(2) support (macOS 10.12+)
418+
case $host_os in
419+
darwin*)
420+
AC_CHECK_HEADERS([sys/clonefile.h])
421+
esac
422+
423+
if test "x$have_ficlonerange" = xyes || \
424+
test "x$ac_cv_func_copy_file_range" = xyes; then
425+
AC_DEFINE([HAVE_FILE_CLONING_BY_RANGE], [1], [Define if system can clone files by range])
426+
fi
427+
428+
if test "x$ac_cv_header_sys_clonefile_h" = xyes || \
429+
test "x$ac_cv_func_reflink" = xyes; then
430+
AC_DEFINE([HAVE_FILE_CLONING_BY_PATH], [1], [Define if system can clone files by path])
431+
fi
432+
389433
dnl Check if the OS is supported by the console saver.
390434
cons_saver=""
391435
case $host_os in

lib/global.c

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,7 @@
4646

4747
/*** global variables ****************************************************************************/
4848

49-
mc_global_t mc_global =
50-
{
49+
mc_global_t mc_global = {
5150
.mc_version = MC_CURRENT_VERSION,
5251

5352
.mc_run_mode = MC_RUN_FULL,
@@ -70,28 +69,26 @@ mc_global_t mc_global =
7069
.we_are_background = FALSE,
7170
#endif
7271

73-
.widget =
74-
{
72+
.widget = {
7573
.confirm_history_cleanup = TRUE,
7674
.show_all_if_ambiguous = FALSE,
77-
.is_right = FALSE
75+
.is_right = FALSE,
7876
},
7977

8078
.shell = NULL,
8179

82-
.tty =
83-
{
80+
.tty = {
8481
.skin = NULL,
8582
.shadows = TRUE,
8683
.setup_color_string = NULL,
8784
.term_color_string = NULL,
8885
.color_terminal_string = NULL,
86+
8987
#ifndef LINUX_CONS_SAVER_C
9088
.console_flag = '\0',
9189
#endif
9290

9391
.use_subshell = SUBSHELL_USE,
94-
9592
#ifdef ENABLE_SUBSHELL
9693
.subshell_pty = 0,
9794
#endif
@@ -102,14 +99,14 @@ mc_global_t mc_global =
10299
.disable_colors = FALSE,
103100
.ugly_line_drawing = FALSE,
104101
.old_mouse = FALSE,
105-
.alternate_plus_minus = FALSE
102+
.alternate_plus_minus = FALSE,
106103
},
107104

108-
.vfs =
109-
{
105+
.vfs = {
110106
.cd_symlinks = TRUE,
111107
.preallocate_space = FALSE,
112-
}
108+
.file_cloning = TRUE,
109+
},
113110

114111
};
115112

lib/global.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,8 @@ typedef struct
220220
// Preallocate space before file copying
221221
gboolean preallocate_space;
222222

223+
// Use COW file cloning on supported filesystems
224+
gboolean file_cloning;
223225
} vfs;
224226
} mc_global_t;
225227

lib/util.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,9 @@ MC_MOCKABLE sighandler_t my_signal (int signum, sighandler_t handler);
223223
MC_MOCKABLE int my_sigaction (int signum, const struct sigaction *act, struct sigaction *oldact);
224224
MC_MOCKABLE pid_t my_fork (void);
225225
MC_MOCKABLE int my_execvp (const char *file, char *const argv[]);
226+
#ifdef HAVE_SYS_CLONEFILE_H
227+
MC_MOCKABLE int my_clonefile (const char *src, const char *dst, uint32_t flags);
228+
#endif
226229
MC_MOCKABLE char *my_get_current_dir (void);
227230

228231
// Process spawning

lib/utilunix.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@
5656
#include <sys/wait.h>
5757
#include <pwd.h>
5858
#include <grp.h>
59+
#ifdef HAVE_SYS_CLONEFILE_H
60+
#include <sys/clonefile.h>
61+
#endif
5962

6063
#include "lib/global.h"
6164

@@ -389,6 +392,19 @@ my_execvp (const char *file, char *const argv[])
389392
return execvp (file, argv);
390393
}
391394

395+
#ifdef HAVE_SYS_CLONEFILE_H
396+
/* --------------------------------------------------------------------------------------------- */
397+
/**
398+
* Wrapper for clonefile() system call on macOS.
399+
*/
400+
401+
int
402+
my_clonefile (const char *src, const char *dst, uint32_t flags)
403+
{
404+
return clonefile (src, dst, flags);
405+
}
406+
#endif
407+
392408
/* --------------------------------------------------------------------------------------------- */
393409
/**
394410
* Wrapper for g_get_current_dir() library function.

lib/vfs/vfs.c

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,13 +46,15 @@
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
49+
#ifdef HAVE_FICLONERANGE
50+
#include <linux/fs.h> // FICLONERANGE
51+
#include <sys/ioctl.h> // ioctl()
52+
#elif defined(HAVE_COPY_FILE_RANGE)
53+
#include <unistd.h> // COPY_FILE_RANGE_CLONE
54+
#elif defined(HAVE_SYS_CLONEFILE_H)
55+
#include <sys/clonefile.h> // CLONE_NOOWNERCOPY
56+
#elif defined(HAVE_REFLINK)
57+
#include <unistd.h> // reflink()
5658
#endif
5759

5860
#include "lib/global.h"
@@ -720,11 +722,12 @@ vfs_preallocate (int dest_vfs_fd, off_t src_fsize, off_t dest_fsize)
720722
int
721723
vfs_clone_file (int dest_vfs_fd, int src_vfs_fd)
722724
{
723-
#ifdef FICLONE
725+
#ifdef HAVE_FILE_CLONING_BY_RANGE
724726
void *dest_fd = NULL;
725727
void *src_fd = NULL;
726728
struct vfs_class *dest_class;
727729
struct vfs_class *src_class;
730+
off_t in_offset, out_offset;
728731

729732
dest_class = vfs_class_find_by_handle (dest_vfs_fd, &dest_fd);
730733
if ((dest_class->flags & VFSF_LOCAL) == 0)
@@ -750,7 +753,38 @@ vfs_clone_file (int dest_vfs_fd, int src_vfs_fd)
750753
return (-1);
751754
}
752755

753-
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 = {
766+
.src_fd = *(int *) src_fd,
767+
.src_offset = in_offset,
768+
.src_length = 0,
769+
.dest_offset = out_offset,
770+
};
771+
772+
return ioctl (*(int *) dest_fd, FICLONERANGE, &fcr);
773+
}
774+
#elif defined(COPY_FILE_RANGE_CLONE)
775+
{
776+
ssize_t result;
777+
778+
do
779+
{
780+
result = copy_file_range (*(int *) src_fd, &in_offset, *(int *) dest_fd, &out_offset,
781+
SSIZE_MAX, COPY_FILE_RANGE_CLONE);
782+
}
783+
while (result > 0);
784+
return result;
785+
}
786+
#endif
787+
754788
#else
755789
(void) dest_vfs_fd;
756790
(void) src_vfs_fd;
@@ -760,3 +794,40 @@ vfs_clone_file (int dest_vfs_fd, int src_vfs_fd)
760794
}
761795

762796
/* --------------------------------------------------------------------------------------------- */
797+
798+
int
799+
vfs_clone_file_by_path (const vfs_path_t *dest_vpath, const vfs_path_t *src_vpath,
800+
gboolean preserve_uidgid)
801+
{
802+
#ifdef HAVE_FILE_CLONING_BY_PATH
803+
const char *src_path;
804+
const char *dest_path;
805+
806+
if (!vfs_file_is_local (dest_vpath) || !vfs_file_is_local (src_vpath))
807+
{
808+
errno = ENOTSUP;
809+
return (-1);
810+
}
811+
812+
src_path = vfs_path_get_last_path_str (src_vpath);
813+
dest_path = vfs_path_get_last_path_str (dest_vpath);
814+
815+
#if defined(HAVE_SYS_CLONEFILE_H)
816+
#ifndef CLONE_NOOWNERCOPY // macOS 10.13+
817+
#define CLONE_NOOWNERCOPY 0
818+
#endif
819+
return my_clonefile (src_path, dest_path, preserve_uidgid ? 0 : CLONE_NOOWNERCOPY);
820+
#elif defined(HAVE_REFLINK)
821+
return reflink (src_path, dest_path, preserve_uidgid);
822+
#endif
823+
824+
#else
825+
(void) dest_vpath;
826+
(void) src_vpath;
827+
(void) preserve_uidgid;
828+
errno = ENOTSUP;
829+
return (-1);
830+
#endif
831+
}
832+
833+
/* --------------------------------------------------------------------------------------------- */

lib/vfs/vfs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,8 @@ char *vfs_get_cwd (void);
303303
int vfs_preallocate (int dest_desc, off_t src_fsize, off_t dest_fsize);
304304

305305
int vfs_clone_file (int dest_vfs_fd, int src_vfs_fd);
306+
int vfs_clone_file_by_path (const vfs_path_t *dest_vpath, const vfs_path_t *src_vpath,
307+
gboolean preserve_uidgid);
306308

307309
/**
308310
* Interface functions described in interface.c

src/filemanager/boxes.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -569,6 +569,7 @@ configure_box (void)
569569
QUICK_CHECKBOX (_ ("Mkdi&r autoname"), &auto_fill_mkdir_name, NULL),
570570
QUICK_CHECKBOX (_ ("&Preallocate space"), &mc_global.vfs.preallocate_space,
571571
NULL),
572+
QUICK_CHECKBOX (_ ("Use COW file cloning"), &mc_global.vfs.file_cloning, NULL),
572573
QUICK_STOP_GROUPBOX,
573574
QUICK_START_GROUPBOX (_ ("Esc key mode")),
574575
QUICK_CHECKBOX (_ ("S&ingle press"), &old_esc_mode, &configure_old_esc_mode_id),
@@ -619,17 +620,22 @@ configure_box (void)
619620
g_snprintf (time_out, sizeof (time_out), "%d", old_esc_mode_timeout);
620621

621622
#ifndef USE_INTERNAL_EDIT
622-
quick_widgets[17].state = WST_DISABLED;
623+
quick_widgets[18].state = WST_DISABLED;
623624
#endif
624625

625626
if (!old_esc_mode)
626-
quick_widgets[10].state = WST_DISABLED;
627+
quick_widgets[11].state = WST_DISABLED;
627628

628629
#ifndef HAVE_POSIX_FALLOCATE
629630
mc_global.vfs.preallocate_space = FALSE;
630631
quick_widgets[6].state = WST_DISABLED;
631632
#endif
632633

634+
#if !defined(HAVE_FILE_CLONING_BY_RANGE) && !defined(HAVE_FILE_CLONING_BY_PATH)
635+
mc_global.vfs.file_cloning = FALSE;
636+
quick_widgets[7].state = WST_DISABLED;
637+
#endif
638+
633639
if (quick_dialog (&qdlg) == B_ENTER)
634640
{
635641
if (time_out_new[0] == '\0')

0 commit comments

Comments
 (0)