xaizek / vifm (License: GPLv2+) (since 2018-12-07)
Vifm is a file manager with curses interface, which provides Vi[m]-like environment for managing objects within file systems, extended with some useful ideas from mutt.
Commit 4f18017ba9ef0241ec2aef86fcb29ec04dafc913

Implement deep file copying in iop_cp()
This applies only to copying individual files using this function,
directory handling is done by ior_cp().
Author: xaizek
Author date (UTC): 2026-01-24 11:41
Committer name: xaizek
Committer date (UTC): 2026-02-19 09:45
Parent(s): ab7b39f0802b96988484f64ce45628002b10ccca
Signing key: 99DC5E4DB05F6BE2
Tree: 3dc107bbc4c56c1629975351538f25cb57450c20
File Lines added Lines deleted
src/io/ioc.h 2 0
src/io/iop.c 8 5
tests/iop/cp.c 57 0
File src/io/ioc.h changed (mode: 100644) (index c7611146d..47346366c)
... ... struct io_args_t
125 125 unsigned int fast_file_cloning : 1; unsigned int fast_file_cloning : 1;
126 126 /* Whether to call fdatasync() periodically. */ /* Whether to call fdatasync() periodically. */
127 127 unsigned int data_sync : 1; unsigned int data_sync : 1;
128 /* Deep link copying (copy the target instead of linking to it). */
129 unsigned int deep_copying : 1;
128 130 }; };
129 131 } }
130 132 arg4; arg4;
File src/io/iop.c changed (mode: 100644) (index 402f44f70..358679c42)
... ... iop_cp_internal(io_args_t *args)
298 298 const char *const src = args->arg1.src; const char *const src = args->arg1.src;
299 299 const char *const dst = args->arg2.dst; const char *const dst = args->arg2.dst;
300 300 const IoCrs crs = args->arg3.crs; const IoCrs crs = args->arg3.crs;
301 const int deep_copying = args->arg4.deep_copying;
301 302 const io_confirm confirm = args->confirm; const io_confirm confirm = args->confirm;
302 303 struct stat st; struct stat st;
303 304
 
... ... iop_cp_internal(io_args_t *args)
318 319 DWORD flags; DWORD flags;
319 320 wchar_t *utf16_src, *utf16_dst; wchar_t *utf16_src, *utf16_dst;
320 321
321 flags = COPY_FILE_COPY_SYMLINK;
322 flags = (deep_copying ? 0 : COPY_FILE_COPY_SYMLINK);
322 323 if(crs == IO_CRS_FAIL) if(crs == IO_CRS_FAIL)
323 324 { {
324 325 flags |= COPY_FILE_FAIL_IF_EXISTS; flags |= COPY_FILE_FAIL_IF_EXISTS;
 
... ... iop_cp_internal(io_args_t *args)
359 360 } }
360 361 #endif #endif
361 362
362 /* Create symbolic link rather than copying file it points to. This check
363 * should go before directory check as is_dir() resolves symbolic links. */
364 if(is_symlink(src))
363 /* Optionally create a symbolic link rather than copying file it points to. A
364 * broken symlink is always copied as a symlink. This check should go before
365 * directory check as is_dir() resolves symbolic links. */
366 if((!deep_copying && is_symlink(src)) ||
367 (deep_copying && is_symlink(src) && !path_exists(src, DEREF)))
365 368 { {
366 369 char link_target[PATH_MAX + 1]; char link_target[PATH_MAX + 1];
367 370
 
... ... iop_cp_internal(io_args_t *args)
630 633 error = 1; error = 1;
631 634 } }
632 635
633 if(error == 0 && os_lstat(src, &src_st) == 0)
636 if(error == 0 && (deep_copying ? os_stat : os_lstat)(src, &src_st) == 0)
634 637 { {
635 638 error = os_chmod(dst, src_st.st_mode & 07777); error = os_chmod(dst, src_st.st_mode & 07777);
636 639 if(error != 0) if(error != 0)
File tests/iop/cp.c changed (mode: 100644) (index 0ead9827a..2985555da)
17 17 #include "../../src/compat/os.h" #include "../../src/compat/os.h"
18 18 #include "../../src/io/iop.h" #include "../../src/io/iop.h"
19 19 #include "../../src/utils/fs.h" #include "../../src/utils/fs.h"
20 #include "../../src/utils/macros.h"
20 21
21 22 #include "utils.h" #include "utils.h"
22 23
 
... ... TEST(file_symlink_copy_is_symlink, IF(not_windows))
337 338 delete_test_file(SANDBOX_PATH "/sym-link-copy"); delete_test_file(SANDBOX_PATH "/sym-link-copy");
338 339 } }
339 340
341 TEST(file_symlink_copy_is_a_file, IF(not_windows))
342 {
343 char file_contents[] = "file_symlink_copy_is_a_file";
344 const char *file_lines[] = { file_contents };
345
346 make_file(SANDBOX_PATH "/target", file_contents);
347
348 /* This is to help verify that file mode is copied. */
349 assert_success(os_chmod(SANDBOX_PATH "/target", 0600));
350
351 {
352 char path[PATH_MAX + 1];
353 io_args_t args = {
354 .arg1.path = path,
355 .arg2.target = SANDBOX_PATH "/sym-link",
356 };
357 ioe_errlst_init(&args.result.errors);
358
359 make_abs_path(path, sizeof(path), SANDBOX_PATH, "target", NULL);
360 assert_int_equal(IO_RES_SUCCEEDED, iop_ln(&args));
361
362 assert_int_equal(0, args.result.errors.error_count);
363 }
364
365 assert_true(is_symlink(SANDBOX_PATH "/sym-link"));
366
367 {
368 io_args_t args = {
369 .arg1.src = SANDBOX_PATH "/sym-link",
370 .arg2.dst = SANDBOX_PATH "/sym-link-copy",
371 .arg4.deep_copying = 1,
372 };
373 ioe_errlst_init(&args.result.errors);
374
375 assert_int_equal(IO_RES_SUCCEEDED, iop_cp(&args));
376
377 assert_int_equal(0, args.result.errors.error_count);
378 }
379
380 assert_true(is_symlink(SANDBOX_PATH "/sym-link"));
381 assert_true(!is_symlink(SANDBOX_PATH "/sym-link-copy"));
382
383 /* Check the contents is copied. */
384 file_is(SANDBOX_PATH "/sym-link-copy", file_lines, ARRAY_LEN(file_lines));
385
386 /* Check that file mode is copied. */
387 struct stat src, dst;
388 assert_success(os_stat(SANDBOX_PATH "/sym-link", &src));
389 assert_success(os_lstat(SANDBOX_PATH "/sym-link-copy", &dst));
390 assert_int_equal(src.st_mode & 0777, dst.st_mode & 0777);
391
392 delete_test_file(SANDBOX_PATH "/target");
393 delete_test_file(SANDBOX_PATH "/sym-link");
394 delete_test_file(SANDBOX_PATH "/sym-link-copy");
395 }
396
340 397 TEST(timestamps_are_preserved) TEST(timestamps_are_preserved)
341 398 { {
342 399 struct stat src; struct stat src;
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/vifm

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/vifm

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master