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