Skip to content

Commit f40b360

Browse files
committed
Handle cross-subvolume moves safely using execv mv fallback
Fixes #497
1 parent a6b56fc commit f40b360

File tree

6 files changed

+163
-25
lines changed

6 files changed

+163
-25
lines changed

src/main.c

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This file is part of rmw<https://theimpossibleastronaut.github.io/rmw-website/>
33
4-
Copyright (C) 2012-2023 Andy Alt (arch_stanton5995@proton.me)
4+
Copyright (C) 2012-2025 Andy Alt (arch_stanton5995@proton.me)
55
Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md
66
77
This program is free software: you can redistribute it and/or modify
@@ -262,7 +262,6 @@ list_waste_folders(st_waste *waste_head)
262262
return;
263263
}
264264

265-
266265
static int
267266
remove_to_waste(const int argc,
268267
char *const argv[],
@@ -412,12 +411,26 @@ damage of 5000 hp. You feel satisfied.\n"));
412411
int r_result = 0;
413412
if (cli_user_options->want_dry_run == false)
414413
{
415-
if ((waste_curr->dev_num != st_target.dev_num) && !S_ISDIR(st_file_arg.st_mode))
414+
int save_errno = errno;
415+
const char *src = argv[file_arg];
416+
const char *dst = st_target.waste_dest_name;
417+
418+
if (waste_curr->dev_num != st_target.dev_num)
416419
{
417-
int save_errno = errno;
418-
r_result =
419-
do_btrfs_clone(argv[file_arg], st_target.waste_dest_name,
420-
&save_errno);
420+
/* cross-device: different device numbers */
421+
if (!S_ISDIR(st_file_arg.st_mode))
422+
{
423+
/* attempt btrfs clone if not a directory */
424+
r_result = do_btrfs_clone(src, dst, &save_errno);
425+
errno = save_errno;
426+
}
427+
else
428+
{
429+
/* directory on different device: call /bin/mv safely */
430+
r_result = safe_mv_via_exec(src, dst, &save_errno);
431+
errno = save_errno;
432+
}
433+
421434
if (r_result != 0)
422435
{
423436
if (save_errno == EXDEV)
@@ -426,12 +439,16 @@ damage of 5000 hp. You feel satisfied.\n"));
426439
continue;
427440
}
428441
else
442+
{
429443
exit(EXIT_FAILURE);
444+
}
430445
}
431446
}
432447
else
433448
{
434-
r_result = rename(argv[file_arg], st_target.waste_dest_name);
449+
/* same device: simple rename */
450+
r_result = rename(src, dst);
451+
/* rename sets errno on failure */
435452
}
436453
}
437454

src/restore.c

Lines changed: 66 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This file is part of rmw<https://theimpossibleastronaut.github.io/rmw-website/>
33
4-
Copyright (C) 2012-2023 Andy Alt (arch_stanton5995@proton.me)
4+
Copyright (C) 2012-2025 Andy Alt (arch_stanton5995@proton.me)
55
Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md
66
77
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)
6363
return;
6464
}
6565

66+
static int
67+
move_back(const char *src, const char *dest, bool want_dry_run)
68+
{
69+
int rename_res = 0;
70+
int save_errno = 0;
71+
int clone_errno = 0;
72+
73+
if (want_dry_run)
74+
return 0;
75+
76+
rename_res = rename(src, dest);
77+
if (rename_res == 0)
78+
return 0; /* success */
79+
80+
/* rename failed; preserve errno immediately */
81+
save_errno = errno;
82+
83+
struct stat st_src;
84+
85+
/* rename already failed and save_errno == errno from rename() */
86+
if (save_errno == EXDEV)
87+
{
88+
/* get file type without following symlinks */
89+
if (lstat(src, &st_src) != 0)
90+
{
91+
/* cannot stat. restore rename's errno and fail */
92+
errno = save_errno;
93+
return -1;
94+
}
95+
96+
if (S_ISDIR(st_src.st_mode))
97+
{
98+
/* directory on different device -> execv mv */
99+
int mv_ret = safe_mv_via_exec(src, dest, &clone_errno);
100+
if (mv_ret == 0)
101+
{
102+
errno = 0;
103+
return 0;
104+
}
105+
errno = clone_errno ? clone_errno : save_errno;
106+
return -1;
107+
}
108+
else
109+
{
110+
/* regular file on different device -> try btrfs clone */
111+
int clone_res = do_btrfs_clone(src, dest, &clone_errno);
112+
if (clone_res == 0)
113+
{
114+
errno = 0;
115+
return 0;
116+
}
117+
errno = clone_errno ? clone_errno : save_errno;
118+
return -1;
119+
}
120+
}
121+
122+
/* other rename error */
123+
errno = save_errno;
124+
return -1;
125+
}
126+
66127

67128
int
68129
restore(const char *src, st_time *st_time_var,
@@ -171,16 +232,10 @@ Duplicate filename at destination - appending time string...\n"));
171232
return p_state_parent;
172233
}
173234

174-
int rename_res = 0;
175-
int save_errno = errno;
176-
if (cli_user_options->want_dry_run == false)
177-
{
178-
rename_res = rename(src, dest);
179-
if (errno == EXDEV)
180-
rename_res = do_btrfs_clone(src, dest, &save_errno);
181-
}
235+
int res = move_back(src, dest, cli_user_options->want_dry_run);
236+
182237

183-
if (!rename_res)
238+
if (!res)
184239
{
185240
printf("+'%s' -> '%s'\n", src, dest);
186241

@@ -199,7 +254,7 @@ Duplicate filename at destination - appending time string...\n"));
199254
else
200255
{
201256
msg_err_rename(src, dest);
202-
return rename_res;
257+
return res;
203258
}
204259
}
205260
else

src/utils.c

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This file is part of rmw<https://theimpossibleastronaut.github.io/rmw-website/>
33
4-
Copyright (C) 2012-2023 Andy Alt (arch_stanton5995@proton.me)
4+
Copyright (C) 2012-2025 Andy Alt (arch_stanton5995@proton.me)
55
Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md
66
77
This program is free software: you can redistribute it and/or modify
@@ -21,6 +21,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
2121
#ifndef INC_GLOBALS_H
2222
#define INC_GLOBALS_H
2323

24+
#include <sys/wait.h>
25+
2426
#include "globals.h"
2527
#endif
2628

@@ -495,6 +497,67 @@ count_chars(const char c, const char *str)
495497
}
496498

497499

500+
/* returns 0 on success, non-zero on failure.
501+
On error sets *out_errno when out_errno != NULL. */
502+
int
503+
safe_mv_via_exec(const char *src, const char *dst, int *out_errno)
504+
{
505+
pid_t pid;
506+
int status;
507+
int saved_errno = 0;
508+
509+
pid = fork();
510+
if (pid < 0)
511+
{
512+
saved_errno = errno;
513+
if (out_errno)
514+
*out_errno = saved_errno;
515+
return -1;
516+
}
517+
518+
if (pid == 0)
519+
{
520+
/* child: exec /bin/mv directly (argv[0] is "mv") */
521+
char *const argv_mv[] = { "mv", (char *) src, (char *) dst, NULL };
522+
execv("/bin/mv", argv_mv);
523+
/* if execv fails, set errno and exit with a distinct code */
524+
_exit(127);
525+
}
526+
527+
/* parent: wait for child */
528+
if (waitpid(pid, &status, 0) < 0)
529+
{
530+
saved_errno = errno;
531+
if (out_errno)
532+
*out_errno = saved_errno;
533+
return -1;
534+
}
535+
536+
if (WIFEXITED(status))
537+
{
538+
int code = WEXITSTATUS(status);
539+
if (code == 0)
540+
{
541+
if (out_errno)
542+
*out_errno = 0;
543+
return 0;
544+
}
545+
/* child program returned nonzero. we cannot see its errno.
546+
map common mv exit codes to errno conservatively. */
547+
saved_errno = EIO;
548+
if (out_errno)
549+
*out_errno = saved_errno;
550+
return code;
551+
}
552+
553+
/* child was terminated by signal */
554+
saved_errno = EINTR;
555+
if (out_errno)
556+
*out_errno = saved_errno;
557+
return -1;
558+
}
559+
560+
498561
///////////////////////////////////////////////////////////////////////
499562
#ifdef TEST_LIB
500563

src/utils.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/*
22
This file is part of rmw<https://theimpossibleastronaut.github.io/rmw-website/>
33
4-
Copyright (C) 2012-2024 Andy Alt (arch_stanton5995@proton.me)
4+
Copyright (C) 2012-2025 Andy Alt (arch_stanton5995@proton.me)
55
Other authors: https://github.com/theimpossibleastronaut/rmw/blob/master/AUTHORS.md
66
77
This program is free software: you can redistribute it and/or modify
@@ -64,4 +64,6 @@ bool is_dir_f(const char *pathname);
6464

6565
int count_chars(const char c, const char *str);
6666

67+
int safe_mv_via_exec(const char *src, const char *dst, int *out_errno);
68+
6769
#endif

test/conf/btrfs_img.testrc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#waste = /tmp/rmw-loop/.Trash-$UID
2-
waste = /tmp/rmw-loop/three/Waste
32
waste = /tmp/rmw-loop/@two/Waste
43
waste = /tmp/rmw-loop/trashlink
54
WASTE = $HOME/.local/share/Waste

test/test_btrfs_clone.sh

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,22 @@ fi
2828
cd "$BTRFS_IMAGE_MOUNTPOINT"
2929
RMW_TEST_CMD_STRING="$BIN_DIR/rmw -c ${MESON_SOURCE_ROOT}/test/conf/btrfs_img.testrc"
3030

31-
SUBVOLUME_USED="/tmp/rmw-loop/three"
31+
SUBVOLUME_USED="/tmp/rmw-loop/@two"
3232

3333
WASTE_USED="$SUBVOLUME_USED/Waste"
3434
if [ -d "$WASTE_USED" ]; then
3535
rm -rf "$WASTE_USED"
3636
fi
3737

38-
TEST_DIR="$SUBVOLUME_USED/test_dir"
38+
TEST_DIR="$BTRFS_IMAGE_MOUNTPOINT/test_dir"
3939
if [ -d "$TEST_DIR" ]; then
4040
rm -rf "$TEST_DIR"
4141
fi
4242

4343
mkdir "$TEST_DIR"
44-
$RMW_TEST_CMD_STRING -v "$TEST_DIR"
44+
$RMW_TEST_CMD_STRING "$TEST_DIR"
45+
46+
$RMW_TEST_CMD_STRING -u
4547

4648
touch foo
4749
$RMW_TEST_CMD_STRING foo

0 commit comments

Comments
 (0)