diff --git a/src/main.c b/src/main.c index b0cc68d9..0aa4da14 100644 --- a/src/main.c +++ b/src/main.c @@ -1,7 +1,7 @@ /* This file is part of rmw -Copyright (C) 2012-2023 Andy Alt (arch_stanton5995@proton.me) +Copyright (C) 2012-2025 Andy Alt (arch_stanton5995@proton.me) Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md This program is free software: you can redistribute it and/or modify @@ -262,7 +262,6 @@ list_waste_folders(st_waste *waste_head) return; } - static int remove_to_waste(const int argc, char *const argv[], @@ -412,12 +411,26 @@ damage of 5000 hp. You feel satisfied.\n")); int r_result = 0; if (cli_user_options->want_dry_run == false) { - if ((waste_curr->dev_num != st_target.dev_num) && !S_ISDIR(st_file_arg.st_mode)) + int save_errno = errno; + const char *src = argv[file_arg]; + const char *dst = st_target.waste_dest_name; + + if (waste_curr->dev_num != st_target.dev_num) { - int save_errno = errno; - r_result = - do_btrfs_clone(argv[file_arg], st_target.waste_dest_name, - &save_errno); + /* cross-device: different device numbers */ + if (!S_ISDIR(st_file_arg.st_mode)) + { + /* attempt btrfs clone if not a directory */ + r_result = do_btrfs_clone(src, dst, &save_errno); + errno = save_errno; + } + else + { + /* directory on different device: call /bin/mv safely */ + r_result = safe_mv_via_exec(src, dst, &save_errno); + errno = save_errno; + } + if (r_result != 0) { if (save_errno == EXDEV) @@ -426,12 +439,16 @@ damage of 5000 hp. You feel satisfied.\n")); continue; } else + { exit(EXIT_FAILURE); + } } } else { - r_result = rename(argv[file_arg], st_target.waste_dest_name); + /* same device: simple rename */ + r_result = rename(src, dst); + /* rename sets errno on failure */ } } diff --git a/src/restore.c b/src/restore.c index 56a1a1e4..5752b006 100644 --- a/src/restore.c +++ b/src/restore.c @@ -1,7 +1,7 @@ /* This file is part of rmw -Copyright (C) 2012-2023 Andy Alt (arch_stanton5995@proton.me) +Copyright (C) 2012-2025 Andy Alt (arch_stanton5995@proton.me) Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md This program is free software: you can redistribute it and/or modify @@ -63,6 +63,67 @@ get_waste_parent(char *waste_parent, const char *src) return; } +static int +move_back(const char *src, const char *dest, bool want_dry_run) +{ + int rename_res = 0; + int save_errno = 0; + int clone_errno = 0; + + if (want_dry_run) + return 0; + + rename_res = rename(src, dest); + if (rename_res == 0) + return 0; /* success */ + + /* rename failed; preserve errno immediately */ + save_errno = errno; + + struct stat st_src; + + /* rename already failed and save_errno == errno from rename() */ + if (save_errno == EXDEV) + { + /* get file type without following symlinks */ + if (lstat(src, &st_src) != 0) + { + /* cannot stat. restore rename's errno and fail */ + errno = save_errno; + return -1; + } + + if (S_ISDIR(st_src.st_mode)) + { + /* directory on different device -> execv mv */ + int mv_ret = safe_mv_via_exec(src, dest, &clone_errno); + if (mv_ret == 0) + { + errno = 0; + return 0; + } + errno = clone_errno ? clone_errno : save_errno; + return -1; + } + else + { + /* regular file on different device -> try btrfs clone */ + int clone_res = do_btrfs_clone(src, dest, &clone_errno); + if (clone_res == 0) + { + errno = 0; + return 0; + } + errno = clone_errno ? clone_errno : save_errno; + return -1; + } + } + + /* other rename error */ + errno = save_errno; + return -1; +} + int restore(const char *src, st_time *st_time_var, @@ -171,16 +232,10 @@ Duplicate filename at destination - appending time string...\n")); return p_state_parent; } - int rename_res = 0; - int save_errno = errno; - if (cli_user_options->want_dry_run == false) - { - rename_res = rename(src, dest); - if (errno == EXDEV) - rename_res = do_btrfs_clone(src, dest, &save_errno); - } + int res = move_back(src, dest, cli_user_options->want_dry_run); + - if (!rename_res) + if (!res) { printf("+'%s' -> '%s'\n", src, dest); @@ -199,7 +254,7 @@ Duplicate filename at destination - appending time string...\n")); else { msg_err_rename(src, dest); - return rename_res; + return res; } } else diff --git a/src/utils.c b/src/utils.c index 03a342e8..3c9c4f3d 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,7 +1,7 @@ /* This file is part of rmw -Copyright (C) 2012-2023 Andy Alt (arch_stanton5995@proton.me) +Copyright (C) 2012-2025 Andy Alt (arch_stanton5995@proton.me) Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md This program is free software: you can redistribute it and/or modify @@ -21,6 +21,8 @@ along with this program. If not, see . #ifndef INC_GLOBALS_H #define INC_GLOBALS_H +#include + #include "globals.h" #endif @@ -495,6 +497,67 @@ count_chars(const char c, const char *str) } +/* returns 0 on success, non-zero on failure. + On error sets *out_errno when out_errno != NULL. */ +int +safe_mv_via_exec(const char *src, const char *dst, int *out_errno) +{ + pid_t pid; + int status; + int saved_errno = 0; + + pid = fork(); + if (pid < 0) + { + saved_errno = errno; + if (out_errno) + *out_errno = saved_errno; + return -1; + } + + if (pid == 0) + { + /* child: exec /bin/mv directly (argv[0] is "mv") */ + char *const argv_mv[] = { "mv", (char *) src, (char *) dst, NULL }; + execv("/bin/mv", argv_mv); + /* if execv fails, set errno and exit with a distinct code */ + _exit(127); + } + + /* parent: wait for child */ + if (waitpid(pid, &status, 0) < 0) + { + saved_errno = errno; + if (out_errno) + *out_errno = saved_errno; + return -1; + } + + if (WIFEXITED(status)) + { + int code = WEXITSTATUS(status); + if (code == 0) + { + if (out_errno) + *out_errno = 0; + return 0; + } + /* child program returned nonzero. we cannot see its errno. + map common mv exit codes to errno conservatively. */ + saved_errno = EIO; + if (out_errno) + *out_errno = saved_errno; + return code; + } + + /* child was terminated by signal */ + saved_errno = EINTR; + if (out_errno) + *out_errno = saved_errno; + return -1; +} + + /////////////////////////////////////////////////////////////////////// #ifdef TEST_LIB diff --git a/src/utils.h b/src/utils.h index 947a725a..66356f70 100644 --- a/src/utils.h +++ b/src/utils.h @@ -1,7 +1,7 @@ /* This file is part of rmw -Copyright (C) 2012-2024 Andy Alt (arch_stanton5995@proton.me) +Copyright (C) 2012-2025 Andy Alt (arch_stanton5995@proton.me) Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md This program is free software: you can redistribute it and/or modify @@ -64,4 +64,6 @@ bool is_dir_f(const char *pathname); int count_chars(const char c, const char *str); +int safe_mv_via_exec(const char *src, const char *dst, int *out_errno); + #endif diff --git a/test/conf/btrfs_img.testrc b/test/conf/btrfs_img.testrc index 907943c8..92fa7f16 100644 --- a/test/conf/btrfs_img.testrc +++ b/test/conf/btrfs_img.testrc @@ -1,5 +1,4 @@ #waste = /tmp/rmw-loop/.Trash-$UID -waste = /tmp/rmw-loop/three/Waste waste = /tmp/rmw-loop/@two/Waste waste = /tmp/rmw-loop/trashlink WASTE = $HOME/.local/share/Waste diff --git a/test/test_btrfs_clone.sh b/test/test_btrfs_clone.sh index 4f791c44..91e8ce1e 100755 --- a/test/test_btrfs_clone.sh +++ b/test/test_btrfs_clone.sh @@ -28,20 +28,22 @@ fi cd "$BTRFS_IMAGE_MOUNTPOINT" RMW_TEST_CMD_STRING="$BIN_DIR/rmw -c ${MESON_SOURCE_ROOT}/test/conf/btrfs_img.testrc" -SUBVOLUME_USED="/tmp/rmw-loop/three" +SUBVOLUME_USED="/tmp/rmw-loop/@two" WASTE_USED="$SUBVOLUME_USED/Waste" if [ -d "$WASTE_USED" ]; then rm -rf "$WASTE_USED" fi -TEST_DIR="$SUBVOLUME_USED/test_dir" +TEST_DIR="$BTRFS_IMAGE_MOUNTPOINT/test_dir" if [ -d "$TEST_DIR" ]; then rm -rf "$TEST_DIR" fi mkdir "$TEST_DIR" -$RMW_TEST_CMD_STRING -v "$TEST_DIR" +$RMW_TEST_CMD_STRING "$TEST_DIR" + +$RMW_TEST_CMD_STRING -u touch foo $RMW_TEST_CMD_STRING foo