diff --git a/configure.ac b/configure.ac index 5a5155f9e0..755e4a75e3 100644 --- a/configure.ac +++ b/configure.ac @@ -380,12 +380,42 @@ case $host_os in AC_DEFINE([PTY_ZEROREAD], [1], [read(1) can return 0 for a non-closed fd]) esac -dnl Check linux/fs.h for FICLONE to support BTRFS's file clone operation +dnl Check for file cloning support case $host_os in -linux*) +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+) +linux*) # FICLONERANGE (Linux 4.5+) AC_CHECK_HEADERS([linux/fs.h]) + AC_MSG_CHECKING([for FICLONERANGE]) + AC_EGREP_CPP([FICLONERANGE_IS_DEFINED], + [ + #include + #ifdef FICLONERANGE + FICLONERANGE_IS_DEFINED + #endif + ], + [ + have_ficlonerange=yes + AC_DEFINE(HAVE_FICLONERANGE, 1, [Define if FICLONERANGE is supported on Linux]) + AC_MSG_RESULT(yes) + ], + [ + AC_MSG_RESULT(no) + ]) + ;; esac +if test "x$have_ficlonerange" = xyes || \ + test "x$ac_cv_func_copy_file_range" = xyes; then + AC_DEFINE([HAVE_FILE_CLONING_BY_RANGE], [1], [Define if system can clone files by range]) +fi + +if test "x$ac_cv_header_sys_clonefile_h" = xyes || \ + test "x$ac_cv_func_reflink" = xyes; then + AC_DEFINE([HAVE_FILE_CLONING_BY_PATH], [1], [Define if system can clone files by path]) +fi + dnl Check if the OS is supported by the console saver. cons_saver="" case $host_os in diff --git a/doc/man/es/mc.1.in b/doc/man/es/mc.1.in index 97bca92314..8076e192fa 100644 --- a/doc/man/es/mc.1.in +++ b/doc/man/es/mc.1.in @@ -1123,6 +1123,11 @@ Si la opción .I Reservar espacio (preallocate_space) está activada, Midnight Commander intentará preasignar espacio para todo el archivo de destino antes de copiarlo. +Si la opción +.I Usar clonación de archivos COW +está habilitada (configuración predeterminada), Midnight Commander intenta usar +la clonación de datos mediante copia en escritura en los sistemas de archivos +locales compatibles. Durante el proceso de copia, podemos pulsar .IR Ctrl\-c " o " Esc para anular la operación. Para más detalles sobre la máscara de origen @@ -1756,6 +1761,11 @@ directorio actual en el panbel activo. Está deshabilitado por defecto. Antes de comenzar una copia reserva espacio para el archivo destino completo. Por defecto está desactivado. .PP +.I Usar clonación de archivos COW. +Intentar utilizar la clonación de datos copy\-on\-write en sistemas de archivos +locales compatibles durante las operaciones de copia y movimiento. +Por defecto, está activa. +.PP .B Tecla de Escape. .PP Midnight Commander utiliza la tecla ESC como prefijo para ciertas teclas. diff --git a/doc/man/it/mc.1.in b/doc/man/it/mc.1.in index 7d6bf53377..8a04b211de 100644 --- a/doc/man/it/mc.1.in +++ b/doc/man/it/mc.1.in @@ -938,6 +938,10 @@ Se l'opzione .I Prealloca spazio (preallocate_space) è attiva, Midnight Commander tenterà di preallocare spazio per l'intero file di destinazione prima di copiarlo. +Se l'opzione +.I Usa clonazione file COW +è abilitata (impostazione predefinita), Midnight Commander tenta di utilizzare +la clonazione dei dati copy\-on\-write sui file system locali supportati. Durante il processo di copia, è possibile premere C\-c o ESC per abortire l'operazione. Per maggiori dettagli sulla maschera sorgente (che sarà normalmente * o ^\e(.*\e)$ a seconda @@ -1571,6 +1575,11 @@ Prealloca spazio per l'intero file di destinazione, se possibile, prima dell'operazione di copia. Disabilitata per impostazione predefinita. .PP +.I Usa clonazione file COW. +Tenta di utilizzare la clonazione dati copy\-on\-write sui file system locali +supportati durante le operazioni di copia e spostamento. +Abilitata per impostazione predefinita. +.PP .B Modalità tasto Esc Per impostazione predefinita, Midnight Commander interpreta il tasto Esc come un prefisso. diff --git a/doc/man/mc.1.in b/doc/man/mc.1.in index 1f304c9aa0..69285dd5f7 100644 --- a/doc/man/mc.1.in +++ b/doc/man/mc.1.in @@ -1073,6 +1073,10 @@ If the .I Preallocate space (preallocate_space) option is on, Midnight Commander will try to preallocate space for the whole destination file before copying. +If +.I Use COW file cloning +is enabled (default setting), Midnight Commander attempts to use copy\-on\-write +data cloning on supported local filesystems. During the copy process, you can press C\-c or Esc to abort the operation. For details about source mask (which will be usually either * or ^\e(.*\e)$ depending on setting of Use shell patterns) and possible wildcards in the @@ -1758,6 +1762,11 @@ Disabled by default. Preallocate space for whole target file, if possible, before copy operation. Disabled by default. .PP +.I Use COW file cloning. +Attempt to use copy\-on\-write data cloning on supported local filesystems during +copy and move operations. +Enabled by default. +.PP .B Esc key mode. .PP By default, Midnight Commander treats the Esc key as a key prefix. diff --git a/doc/man/pl/mc.1.in b/doc/man/pl/mc.1.in index baec614f71..59211b6240 100644 --- a/doc/man/pl/mc.1.in +++ b/doc/man/pl/mc.1.in @@ -777,6 +777,9 @@ Jeśli opcja .I Przydzielanie miejsca z wyprzedzeniem (preallocate_space) jest włączona, Midnight Commander spróbuje wstępnie przydzielić miejsce na cały plik docelowy przed kopiowaniem. +Jeśli opcja Używaj klonowania plików COW jest włączona (ustawienie domyślne), +Midnight Commander próbuje użyć klonowania danych typu "kopiuj przy zapisie” +w obsługiwanych lokalnych systemach plików. Podczas procesu kopiowania możesz go w każdej chwili przerwać wciskając C\-c lub Esc. Żeby dowiedzieć się czegoś więcej na temat jokerów w ścieżce źródłowej (którymi najczęściej będą * lub ^\e(.*\e)$) i innych możliwych określeń w @@ -1338,6 +1341,11 @@ Domyślnie wyłączone. Z wyprzedzeniem przydzielić miejsce dla całego pliku docelowego, jeśli to możliwe, przed operacją kopiowania. Domyślnie wyłączone. .PP +.I Używaj klonowania plików COW (Use COW file cloning). +Podczas operacji kopiowania i przenoszenia spróbuj użyć klonowania danych z +kopiowaniem przy zapisie na obsługiwanych lokalnych systemach plików. +Domyślnie włączone. +.PP .B Tryb klawisza Esc (Esc key mode). .PP Domyślnie Midnight Commander traktuje klawisz Esc jako prefiks. diff --git a/doc/man/ru/mc.1.in b/doc/man/ru/mc.1.in index fedabdd828..a2d43496ab 100644 --- a/doc/man/ru/mc.1.in +++ b/doc/man/ru/mc.1.in @@ -1117,6 +1117,10 @@ F13, встроенная программа просмотра не выпол .I Предвыделять место (preallocate_space), Midnight Commander будет пытаться перед копированием каждого файла зарезервировать место под файл назначения. +Если включена опция +.I Использовать COW\-клонирование файлов +(значение по умолчанию), Midnight Commander будет пытаться использовать +клонирование данных copy\-on\-write в поддерживаемых локальных файловых системах. О том, как задать шаблон для имен копируемых файлов (обычно это "*" или "^\e(.*\e)$", в зависимости от установки опции .IR "Образцы в стиле shell" , @@ -1945,6 +1949,11 @@ mc на экране. Если возможно, предварительно выделять место под весь копируемый файл. По умолчанию выключено. .PP +.IR "Использовать COW\-клонирование файлов" . +Пытаться использовать клонирование данных copy\-on\-write в поддерживаемых +локальных файловых системах при операциях копирования и перемещения. +По умолчанию включено. +.PP .B Клавиша Esc .PP По умолчанию Midnight Commander трактует нажатие на клавишу Esc как diff --git a/doc/man/sr/mc.1.in b/doc/man/sr/mc.1.in index c826da54f7..1c02c94345 100644 --- a/doc/man/sr/mc.1.in +++ b/doc/man/sr/mc.1.in @@ -1032,6 +1032,10 @@ F13, прегледач ће бити покренут без икаквог ф .I Унапред додели простор (preallocate_space) укључена, Midnight Commander ће покушати да претходно додели простор за целу одредишну датотеку пре копирања. +Ако је омогућено +.I Користи клонирање COW датотека +(подразумевано подешавање), Midnight Commander покушава да користи клонирање +података копирањем приликом писања на подржаним локалним фајл системима. Током процеса копирања, можете притиснути C\-c или Esc да бисте прекинули операцију. За детаље о изворној маски (која је обично * или ^\e(.*\e)$, у зависности од опције „Користи обрасце љуске“) и могућим џокерским @@ -1683,6 +1687,11 @@ L Листа садржај компримоване архиве врсте tar Уколико је то могуће, пре сваке операције копирања унапред додељује простор за целу одредишну датотеку. Подразумевано деактивирано. .PP +.I Користите клонирање COW датотека. +Покушати користити клонирање података copy\-on\-write на подржаним локалним +датотечним системима током операција копирања и премештања. +Подразумевано је активирана. +.PP .B Режим тастера Esc. .PP Подразумевано Поноћни наредник третира тастер Esc као тастерски префикс. Зато би diff --git a/lib/global.c b/lib/global.c index c5cfc1a362..29c13ebaa1 100644 --- a/lib/global.c +++ b/lib/global.c @@ -46,8 +46,7 @@ /*** global variables ****************************************************************************/ -mc_global_t mc_global = -{ +mc_global_t mc_global = { .mc_version = MC_CURRENT_VERSION, .mc_run_mode = MC_RUN_FULL, @@ -70,28 +69,26 @@ mc_global_t mc_global = .we_are_background = FALSE, #endif - .widget = - { + .widget = { .confirm_history_cleanup = TRUE, .show_all_if_ambiguous = FALSE, - .is_right = FALSE + .is_right = FALSE, }, .shell = NULL, - .tty = - { + .tty = { .skin = NULL, .shadows = TRUE, .setup_color_string = NULL, .term_color_string = NULL, .color_terminal_string = NULL, + #ifndef LINUX_CONS_SAVER_C .console_flag = '\0', #endif .use_subshell = SUBSHELL_USE, - #ifdef ENABLE_SUBSHELL .subshell_pty = 0, #endif @@ -102,14 +99,14 @@ mc_global_t mc_global = .disable_colors = FALSE, .ugly_line_drawing = FALSE, .old_mouse = FALSE, - .alternate_plus_minus = FALSE + .alternate_plus_minus = FALSE, }, - .vfs = - { + .vfs = { .cd_symlinks = TRUE, .preallocate_space = FALSE, - } + .file_cloning = TRUE, + }, }; diff --git a/lib/global.h b/lib/global.h index 6f7e4bad95..81abaf7864 100644 --- a/lib/global.h +++ b/lib/global.h @@ -220,6 +220,8 @@ typedef struct // Preallocate space before file copying gboolean preallocate_space; + // Use COW file cloning on supported filesystems + gboolean file_cloning; } vfs; } mc_global_t; diff --git a/lib/util.h b/lib/util.h index 224e62a213..496988f6c9 100644 --- a/lib/util.h +++ b/lib/util.h @@ -223,6 +223,9 @@ MC_MOCKABLE sighandler_t my_signal (int signum, sighandler_t handler); MC_MOCKABLE int my_sigaction (int signum, const struct sigaction *act, struct sigaction *oldact); MC_MOCKABLE pid_t my_fork (void); MC_MOCKABLE int my_execvp (const char *file, char *const argv[]); +#ifdef HAVE_SYS_CLONEFILE_H +MC_MOCKABLE int my_clonefile (const char *src, const char *dst, uint32_t flags); +#endif MC_MOCKABLE char *my_get_current_dir (void); // Process spawning diff --git a/lib/utilunix.c b/lib/utilunix.c index c3149ee874..8ab8f61fc3 100644 --- a/lib/utilunix.c +++ b/lib/utilunix.c @@ -56,6 +56,9 @@ #include #include #include +#ifdef HAVE_SYS_CLONEFILE_H +#include +#endif #include "lib/global.h" @@ -389,6 +392,19 @@ my_execvp (const char *file, char *const argv[]) return execvp (file, argv); } +#ifdef HAVE_SYS_CLONEFILE_H +/* --------------------------------------------------------------------------------------------- */ +/** + * Wrapper for clonefile() system call on macOS. + */ + +int +my_clonefile (const char *src, const char *dst, uint32_t flags) +{ + return clonefile (src, dst, flags); +} +#endif + /* --------------------------------------------------------------------------------------------- */ /** * Wrapper for g_get_current_dir() library function. diff --git a/lib/vfs/vfs.c b/lib/vfs/vfs.c index 2c978895e1..ed537deaf5 100644 --- a/lib/vfs/vfs.c +++ b/lib/vfs/vfs.c @@ -46,13 +46,15 @@ #include #include -#ifdef __linux__ -#ifdef HAVE_LINUX_FS_H -#include -#endif -#ifdef HAVE_SYS_IOCTL_H -#include -#endif +#ifdef HAVE_FICLONERANGE +#include // FICLONERANGE +#include // ioctl() +#elif defined(HAVE_COPY_FILE_RANGE) +#include // COPY_FILE_RANGE_CLONE +#elif defined(HAVE_SYS_CLONEFILE_H) +#include // CLONE_NOOWNERCOPY +#elif defined(HAVE_REFLINK) +#include // reflink() #endif #include "lib/global.h" @@ -720,11 +722,12 @@ 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 FICLONE +#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; dest_class = vfs_class_find_by_handle (dest_vfs_fd, &dest_fd); if ((dest_class->flags & VFSF_LOCAL) == 0) @@ -750,7 +753,38 @@ vfs_clone_file (int dest_vfs_fd, int src_vfs_fd) return (-1); } - return ioctl (*(int *) dest_fd, FICLONE, *(int *) src_fd); + 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 + #else (void) dest_vfs_fd; (void) src_vfs_fd; @@ -760,3 +794,40 @@ 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) +{ +#ifdef HAVE_FILE_CLONING_BY_PATH + const char *src_path; + const char *dest_path; + + if (!vfs_file_is_local (dest_vpath) || !vfs_file_is_local (src_vpath)) + { + errno = ENOTSUP; + return (-1); + } + + src_path = vfs_path_get_last_path_str (src_vpath); + dest_path = vfs_path_get_last_path_str (dest_vpath); + +#if defined(HAVE_SYS_CLONEFILE_H) +#ifndef CLONE_NOOWNERCOPY // macOS 10.13+ +#define CLONE_NOOWNERCOPY 0 +#endif + return my_clonefile (src_path, dest_path, preserve_uidgid ? 0 : CLONE_NOOWNERCOPY); +#elif defined(HAVE_REFLINK) + return reflink (src_path, dest_path, preserve_uidgid); +#endif + +#else + (void) dest_vpath; + (void) src_vpath; + (void) preserve_uidgid; + errno = ENOTSUP; + return (-1); +#endif +} + +/* --------------------------------------------------------------------------------------------- */ diff --git a/lib/vfs/vfs.h b/lib/vfs/vfs.h index 5497e06dc7..6ce271e386 100644 --- a/lib/vfs/vfs.h +++ b/lib/vfs/vfs.h @@ -303,6 +303,8 @@ 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); /** * Interface functions described in interface.c diff --git a/src/filemanager/boxes.c b/src/filemanager/boxes.c index 01a2b588f8..be09c241fe 100644 --- a/src/filemanager/boxes.c +++ b/src/filemanager/boxes.c @@ -569,6 +569,7 @@ configure_box (void) QUICK_CHECKBOX (_ ("Mkdi&r autoname"), &auto_fill_mkdir_name, NULL), QUICK_CHECKBOX (_ ("&Preallocate space"), &mc_global.vfs.preallocate_space, NULL), + QUICK_CHECKBOX (_ ("Use COW file cloning"), &mc_global.vfs.file_cloning, NULL), QUICK_STOP_GROUPBOX, QUICK_START_GROUPBOX (_ ("Esc key mode")), QUICK_CHECKBOX (_ ("S&ingle press"), &old_esc_mode, &configure_old_esc_mode_id), @@ -619,17 +620,22 @@ configure_box (void) g_snprintf (time_out, sizeof (time_out), "%d", old_esc_mode_timeout); #ifndef USE_INTERNAL_EDIT - quick_widgets[17].state = WST_DISABLED; + quick_widgets[18].state = WST_DISABLED; #endif if (!old_esc_mode) - quick_widgets[10].state = WST_DISABLED; + quick_widgets[11].state = WST_DISABLED; #ifndef HAVE_POSIX_FALLOCATE mc_global.vfs.preallocate_space = FALSE; quick_widgets[6].state = WST_DISABLED; #endif +#if !defined(HAVE_FILE_CLONING_BY_RANGE) && !defined(HAVE_FILE_CLONING_BY_PATH) + mc_global.vfs.file_cloning = FALSE; + quick_widgets[7].state = WST_DISABLED; +#endif + if (quick_dialog (&qdlg) == B_ENTER) { if (time_out_new[0] == '\0') diff --git a/src/filemanager/file.c b/src/filemanager/file.c index dae7e77e59..398bfa85f1 100644 --- a/src/filemanager/file.c +++ b/src/filemanager/file.c @@ -2590,14 +2590,47 @@ copy_file_file (file_op_context_t *ctx, const char *src_path, const char *dst_pa src_gid = src_stat.st_gid; file_size = src_stat.st_size; +#ifdef HAVE_FILE_CLONING_BY_PATH + // On macOS 10.12+ and Solaris 11.4+, the syscalls for file cloning respond for creation of the + // destination file, so try them before mc_open to avoid handling various races later. + // Full file cloning is not supported in append and reget modes. + if (mc_global.vfs.file_cloning && !(dst_exists && ctx->do_append)) + { + // Destination file must not exist before the cloning syscall + if (dst_exists) + { + if (!try_remove_file (ctx, dst_vpath, &return_status)) + goto ret; + dst_exists = FALSE; + } + // Passing preserve_uidgid to the syscall to reduce racing + if (vfs_clone_file_by_path (dst_vpath, src_vpath, ctx->preserve_uidgid) == 0) + { + dst_status = DEST_FULL; + return_status = FILE_CONT; + goto ret; + } + } +#endif + open_flags = O_WRONLY; if (!dst_exists) open_flags |= O_CREAT | O_EXCL; else if (ctx->do_append) +#ifdef HAVE_FILE_CLONING_BY_RANGE + // FICLONERANGE on Linux and copy_file_range(2) on FreeBSD support block-aligned ranges for + // cloning, but for not in O_APPEND mode. Use O_WRONLY + mc_lseek instead as we don't care + // about atomicity in our use cases. + open_flags |= mc_global.vfs.file_cloning ? O_WRONLY : O_APPEND; +#else open_flags |= O_APPEND; +#endif else open_flags |= O_CREAT | O_TRUNC; +#ifdef HAVE_FILE_CLONING_BY_RANGE +open_dest: +#endif while ((dest_desc = mc_open (dst_vpath, open_flags, src_mode)) < 0) { if (errno != EEXIST) @@ -2624,13 +2657,28 @@ copy_file_file (file_op_context_t *ctx, const char *src_path, const char *dst_pa appending = ctx->do_append; ctx->do_append = FALSE; - // Try clone the file first. - if (vfs_clone_file (dest_desc, src_desc) == 0) +#ifdef HAVE_FILE_CLONING_BY_RANGE + // Try clone the file first, but not if the file is in O_APPEND mode + if (mc_global.vfs.file_cloning && (open_flags & O_APPEND) == 0) { - dst_status = DEST_FULL; - return_status = FILE_CONT; - goto ret; + if ((appending ? mc_lseek (dest_desc, 0, SEEK_END) >= 0 : TRUE) + && vfs_clone_file (dest_desc, src_desc) == 0) + { + dst_status = DEST_FULL; + return_status = FILE_CONT; + goto ret; + } + if (appending && (open_flags & O_APPEND) == 0) + { + // Cloning append has failed, resort to normal append + ctx->do_append = TRUE; + mc_close (dest_desc); + dst_status = DEST_NONE; + open_flags = (open_flags & ~O_WRONLY) | O_APPEND; + goto open_dest; + } } +#endif // Find out the optimal buffer size. while (mc_fstat (dest_desc, &dst_stat) != 0) diff --git a/src/setup.c b/src/setup.c index aaa32bd6ad..b4ad17ce50 100644 --- a/src/setup.c +++ b/src/setup.c @@ -365,6 +365,7 @@ static const struct #ifdef ENABLE_EXT2FS_ATTR { "copymove_persistent_ext2_attr", ©move_persistent_ext2_attr }, #endif + { "file_cloning", &mc_global.vfs.file_cloning }, { NULL, NULL, diff --git a/tests/lib/vfs/Makefile.am b/tests/lib/vfs/Makefile.am index 892614fa8b..90bd55cbbc 100644 --- a/tests/lib/vfs/Makefile.am +++ b/tests/lib/vfs/Makefile.am @@ -29,6 +29,7 @@ TESTS = \ relative_cd \ tempdir \ vfs_adjust_stat \ + vfs_clone_file \ vfs_parse_ls_lga \ vfs_path_from_str_flags \ vfs_path_string_convert \ @@ -72,6 +73,9 @@ tempdir_SOURCES = \ vfs_adjust_stat_SOURCES = \ vfs_adjust_stat.c +vfs_clone_file_SOURCES = \ + vfs_clone_file.c + vfs_get_encoding_SOURCES = \ vfs_get_encoding.c diff --git a/tests/lib/vfs/vfs_clone_file.c b/tests/lib/vfs/vfs_clone_file.c new file mode 100644 index 0000000000..98eed3aea7 --- /dev/null +++ b/tests/lib/vfs/vfs_clone_file.c @@ -0,0 +1,245 @@ +/* + lib/vfs - test vfs_clone_file() functionality + + Copyright (C) 2026 + Free Software Foundation, Inc. + + Written by: + Phil Krylov , 2026 + + This file is part of the Midnight Commander. + + The Midnight Commander is free software: you can redistribute it + and/or modify it under the terms of the GNU General Public License as + published by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + The Midnight Commander is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +#define TEST_SUITE_NAME "/lib/vfs" + +#include "tests/mctest.h" + +#include +#include + +#ifdef HAVE_FICLONERANGE +#include // FICLONERANGE +#include // ioctl() +#elif defined(HAVE_COPY_FILE_RANGE) +#include // copy_file_range() +#elif defined(HAVE_REFLINK) +#include // reflink() +#elif defined(HAVE_SYS_CLONEFILE_H) +#include // clonefile() +#endif + +#include "lib/strutil.h" +#include "lib/util.h" +#include "src/vfs/local/local.c" + +/* --------------------------------------------------------------------------------------------- */ + +static int clone_syscall__call_count = 0; +static gboolean clone_syscall__call_arguments_are_proper = FALSE; + +static const char test_filename1[] = "mctestclone1.tst"; +static const char test_filename2[] = "mctestclone2.tst"; + +#ifdef HAVE_COPY_FILE_RANGE +/* @Mock */ +ssize_t +copy_file_range (int infd, off_t *inoffp, int outfd, off_t *outoffp, size_t len, unsigned int flags) +{ + (void) infd; + (void) inoffp; + (void) outfd; + (void) outoffp; + (void) len; + + clone_syscall__call_count++; + clone_syscall__call_arguments_are_proper = (flags == COPY_FILE_RANGE_CLONE); + + return -1; +} +#endif + +#ifdef HAVE_FICLONERANGE +#ifdef __GLIBC__ +/* @Mock */ +int +ioctl (int fd, unsigned long request, ...) +#else // POSIX, musl +/* @Mock */ +int +ioctl (int fd, int request, ...) +#endif +{ + (void) fd; + + clone_syscall__call_count++; + clone_syscall__call_arguments_are_proper = (request == FICLONERANGE); + return -1; +} +#endif + +#ifdef HAVE_SYS_CLONEFILE_H +/* @Mock */ +int +my_clonefile (const char *src, const char *dst, uint32_t flags) +{ + (void) src; + (void) dst; + + clone_syscall__call_count++; + clone_syscall__call_arguments_are_proper = (flags == 0); + return -1; +} +#endif + +#ifdef HAVE_REFLINK +/* @Mock */ +int +reflink (const char *src, const char *dst, int preserve) +{ + (void) src; + (void) dst; + + clone_syscall__call_count++; + clone_syscall__call_arguments_are_proper = (preserve != 0); + return -1; +} +#endif + +/* --------------------------------------------------------------------------------------------- */ + +/* @Before */ +static void +setup (void) +{ + str_init_strings (NULL); + + vfs_init (); + vfs_init_localfs (); + vfs_setup_work_dir (); +} + +/* --------------------------------------------------------------------------------------------- */ + +/* @After */ +static void +teardown (void) +{ + vfs_shut (); + str_uninit_strings (); +} + +/* --------------------------------------------------------------------------------------------- */ +extern GPtrArray *vfs_openfiles; +static void +prepare_files (vfs_path_t **vpath1, vfs_path_t **vpath2) +{ + unlink (test_filename1); // remove a possible leftover from a previous run + g_file_set_contents (test_filename1, "test", sizeof ("test") - 1, NULL); + unlink (test_filename2); // remove a possible leftover from a previous run + + *vpath1 = vfs_path_from_str (test_filename1); + *vpath2 = vfs_path_from_str (test_filename2); +} + +static void +cleanup_files (vfs_path_t *vpath1, vfs_path_t *vpath2) +{ + vfs_path_free (vpath1, TRUE); + vfs_path_free (vpath2, TRUE); + unlink (test_filename1); + unlink (test_filename2); +} + +/* @Test */ +START_TEST (test_vfs_clone_file) +{ + vfs_path_t *vpath1; + vfs_path_t *vpath2; + int fdin; + int fdout; + + // given + clone_syscall__call_count = 0; + clone_syscall__call_arguments_are_proper = FALSE; + prepare_files (&vpath1, &vpath2); + fdin = mc_open (vpath1, O_RDONLY | O_BINARY); + fdout = mc_open (vpath2, O_CREAT | O_WRONLY | O_TRUNC | O_BINARY, 0600); + + // when + vfs_clone_file (fdout, fdin); + + // then +#ifdef HAVE_FILE_CLONING_BY_RANGE + ck_assert (clone_syscall__call_count > 0); + ck_assert (clone_syscall__call_arguments_are_proper); +#else + ck_assert (errno == ENOTSUP); +#endif + + // cleanup + mc_close (fdout); + mc_close (fdin); + cleanup_files (vpath1, vpath2); +} +END_TEST + +/* --------------------------------------------------------------------------------------------- */ + +/* @Test */ +START_TEST (test_vfs_clone_file_by_path) +{ + vfs_path_t *vpath1; + vfs_path_t *vpath2; + + // given + clone_syscall__call_count = 0; + clone_syscall__call_arguments_are_proper = FALSE; + prepare_files (&vpath1, &vpath2); + + // when + vfs_clone_file_by_path (vpath1, vpath2, TRUE); + + // then +#ifdef HAVE_FILE_CLONING_BY_PATH + ck_assert (clone_syscall__call_count > 0); + ck_assert (clone_syscall__call_arguments_are_proper); +#else + ck_assert (errno == ENOTSUP); +#endif + + // cleanup + cleanup_files (vpath1, vpath2); +} +END_TEST + +/* --------------------------------------------------------------------------------------------- */ + +int +main (void) +{ + TCase *tc_core; + + tc_core = tcase_create ("Core"); + + tcase_add_checked_fixture (tc_core, setup, teardown); + + // Add new tests here: *************** + tcase_add_test (tc_core, test_vfs_clone_file); + tcase_add_test (tc_core, test_vfs_clone_file_by_path); + // *********************************** + + return mctest_run_all (tc_core); +}