Skip to content

Commit 2c10b72

Browse files
authored
bugfix: fix cross-subvolume rename failure on bcachefs (#527)
* bugfix: fix cross-subvolume rename failure on bcachefs On bcachefs, subvolumes share the same st_dev (unlike btrfs), so rmw correctly matched the waste folder by device number but rename() still returned EXDEV when moving across subvolumes. Add a fallback to safe_mv_via_exec() when rename() fails with EXDEV on a same-device move. Also switch from BTRFS_IOC_CLONE (linux/btrfs.h) to the generic FICLONE ioctl (linux/fs.h), which is supported by any reflink-capable filesystem (btrfs, bcachefs, xfs, etc.), and update the meson header check accordingly. Add test/test_bcachefs.sh which creates an 8M bcachefs image on-the-fly, exercises cross-subvolume file and directory moves, and cleans up after itself. Skips gracefully when bcachefs kernel support is unavailable. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Fixes #526 * refactor/test: bcachefs test improvements and ficlone rename - test/COMMON: add SKIP=77 for Meson skip exit code - test/test_bcachefs.sh: use pre-existing image from test dir instead of creating one; skip if kernel bcachefs support or image is absent; guard against stale mounts/loop devices and leftover files from previous runs - .github/workflows/c-cpp.yml: add bcachefs image cache/download step - test/rmw-bcachefs-test.img.sha256sum: add checksum file for CI caching - man/rmw.1: add BTRFS AND BCACHEFS subsection under NOTES - Rename want_btrfs_clone option to want_ficlone - Rename HAVE_LINUX_BTRFS macro to HAVE_FICLONE - Rename has_btrfs_header variable to has_linux_fs_header - Rename src/btrfs.c and src/btrfs.h to src/ficlone.c and src/ficlone.h - Rename is_btrfs() to is_ficlone_fs() and do_btrfs_clone() to do_ficlone() - Rename is_btrfs struct field in st_waste to is_ficlone_fs Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs: update ChangeLog; note breaking meson option rename Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Fixes #526
1 parent 71c15db commit 2c10b72

19 files changed

Lines changed: 212 additions & 45 deletions

.github/workflows/c-cpp.yml

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ on:
2929

3030
env:
3131
TERM: xterm
32+
BTRFS_IMG: test/rmw-btrfs-test.img
33+
BCACHEFS_IMG: test/rmw-bcachefs-test.img
3234

3335
jobs:
3436
build:
@@ -180,14 +182,27 @@ jobs:
180182
uses: actions/cache@v5
181183
id: btrfs-img-cache
182184
with:
183-
path: test/rmw-btrfs-test.img
185+
path: ${{ env.BTRFS_IMG }}
184186
key: ${{ hashFiles('test/rmw-btrfs-test.img.sha256sum') }}
185187

186188
- if: ${{ steps.btrfs-img-cache.outputs.cache-hit != 'true' }}
187-
run: curl -L -o test/rmw-btrfs-test.img 'https://www.dropbox.com/scl/fi/57g3ixd3w3tuz4qoc2zp1/rmw-btrfs-test.img?rlkey=yc7krtntswsa1bwz0sbugy4gi&st=hkgrht05&dl=0'
189+
run: curl -L -o "$BTRFS_IMG" 'https://www.dropbox.com/scl/fi/57g3ixd3w3tuz4qoc2zp1/rmw-btrfs-test.img?rlkey=yc7krtntswsa1bwz0sbugy4gi&st=hkgrht05&dl=0'
188190

189191
- name: Test btrfs image existence
190-
run: test -f test/rmw-btrfs-test.img
192+
run: test -f "$BTRFS_IMG"
193+
194+
- name: Cache bcachefs Image
195+
uses: actions/cache@v5
196+
id: bcachefs-img-cache
197+
with:
198+
path: ${{ env.BCACHEFS_IMG }}
199+
key: ${{ hashFiles('test/rmw-bcachefs-test.img.sha256sum') }}
200+
201+
- if: ${{ steps.bcachefs-img-cache.outputs.cache-hit != 'true' }}
202+
run: curl -L -o "$BCACHEFS_IMG" 'https://www.dropbox.com/scl/fi/key20cj08ca9engqt5kzi/rmw-bcachefs-test.img?rlkey=th6zw1ar7t0dy9m7qzah5wvq9&st=nrwczcqn&dl=0'
203+
204+
- name: Test bcachefs image existence
205+
run: test -f "$BCACHEFS_IMG"
191206

192207
- name: Install Dependencies
193208
run: |

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,4 @@ subprojects/**
1717
!subprojects/**/*.wrap
1818
/packaging/appimage/.env
1919
/test/rmw-btrfs-test.img
20+
/test/rmw-bcachefs-test.img

ChangeLog

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
11

2+
rmw (in-progress):
3+
* BREAKING: The meson build option `want_btrfs_clone` has been renamed to
4+
`want_ficlone`. Update any build scripts that pass `-Dwant_btrfs_clone=`
5+
to use `-Dwant_ficlone=` instead.
6+
* refactor: Rename btrfs.c/btrfs.h to ficlone.c/ficlone.h; rename
7+
do_btrfs_clone() to do_ficlone() and is_btrfs() to is_ficlone_fs();
8+
rename HAVE_LINUX_BTRFS macro to HAVE_FICLONE
9+
* Add bcachefs test (test_bcachefs.sh); uses a pre-existing image and
10+
skips if kernel bcachefs support or the image is absent
11+
* docs: Add BTRFS AND BCACHEFS section to man page
12+
* bugfix: Fix "Invalid cross-device link" error on bcachefs when source and
13+
waste folder are on separate subvolumes (#526); use generic FICLONE ioctl
14+
(linux/fs.h) instead of BTRFS_IOC_CLONE, and fall back to 'mv' when
15+
rename() returns EXDEV on a same-device move
16+
217
2026-04-07
318

419
- rmw 0.9.5:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# rmw-0.9.5
1+
# rmw-0.10.0-dev
22
## Description
33

44
rmw (ReMove to Waste) is a trashcan/recycle bin utility for the command line.

man/rmw.1

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,17 @@ directory will not be used for the current run of rmw.
192192
With the media mounted, once you manually create the waste directory
193193
for that device (e.g. "/mnt/flash/.Trash-$UID") and run rmw, it will
194194
automatically create the two required child directories "files" and "info".
195+
.SS BTRFS AND BCACHEFS
196+
rmw supports moving files across subvolumes on btrfs and bcachefs
197+
filesystems. Because the kernel treats subvolumes as separate
198+
filesystems, a cross-subvolume move would normally fail; rmw detects
199+
this case and performs a copy-then-delete instead, preserving the
200+
expected trash semantics.
201+
202+
To use this feature, define a WASTE directory on the destination
203+
subvolume in your configuration file. For example, if your waste
204+
directory is on a different subvolume than the files you want to
205+
remove, rmw will handle the move transparently.
195206
.SH EXAMPLES
196207
.SS RESTORING
197208
rmw -z ~/.local/share/Waste/files/foo

meson.build

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
project(
22
'rmw',
33
'c',
4-
version: '0.9.5',
4+
version: '0.10.0-dev',
55
meson_version: '>= 0.59.0',
66
default_options: [
77
'c_std=gnu99',

meson_options.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
option('build_tests', type : 'boolean', value : true,
22
description : 'build tests')
33
option(
4-
'want_btrfs_clone',
4+
'want_ficlone',
55
type : 'boolean',
66
value: 'true',
7-
description: 'Include support for cloning files between btrfs root volumes and subvolumes')
7+
description: 'Include support for cloning files between subvolumes using FICLONE (btrfs, bcachefs, xfs, etc.)')
88
option('nls', type : 'boolean', value : true,
99
description : 'include native language support (install translations)')
1010

src/config_rmw.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
2020

2121
#include <unistd.h>
2222

23-
#include "btrfs.h"
23+
#include "ficlone.h"
2424
#include "config_rmw.h"
2525
#include "utils.h"
2626
#include "main.h"
@@ -275,7 +275,7 @@ parse_line_waste(st_waste *waste_curr, struct Canfigger *node,
275275
else if (p_state == -1)
276276
exit(p_state);
277277

278-
waste_curr->is_btrfs = is_btrfs(waste_curr->parent);
278+
waste_curr->is_ficlone_fs = is_ficlone_fs(waste_curr->parent);
279279

280280
// get device number to use later for rename
281281
struct stat st, mp_st;

src/btrfs.c renamed to src/ficlone.c

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
2323
#include "globals.h"
2424
#endif
2525

26-
#ifdef HAVE_LINUX_BTRFS
26+
#ifdef HAVE_FICLONE
2727
#include <fcntl.h>
28-
#include <linux/btrfs.h>
28+
#include <linux/fs.h>
2929
#include <sys/ioctl.h>
3030
#include <sys/statfs.h>
3131
#include <sys/stat.h>
3232
#include <unistd.h>
3333
#endif
3434

35-
#include "btrfs.h"
35+
#include "ficlone.h"
3636
#include "messages.h"
3737

3838
bool
39-
is_btrfs(const char *path)
39+
is_ficlone_fs(const char *path)
4040
{
41-
#ifdef HAVE_LINUX_BTRFS
41+
#ifdef HAVE_FICLONE
4242
struct statfs buf;
4343

4444
if (statfs(path, &buf) == -1)
@@ -57,9 +57,9 @@ is_btrfs(const char *path)
5757

5858

5959
int
60-
do_btrfs_clone(const char *source, const char *dest, int *save_errno)
60+
do_ficlone(const char *source, const char *dest, int *save_errno)
6161
{
62-
#ifdef HAVE_LINUX_BTRFS
62+
#ifdef HAVE_FICLONE
6363
int src_fd, dest_fd;
6464
struct stat src_stat;
6565

@@ -91,7 +91,7 @@ do_btrfs_clone(const char *source, const char *dest, int *save_errno)
9191
return dest_fd;
9292
}
9393

94-
int res = ioctl(dest_fd, BTRFS_IOC_CLONE, src_fd);
94+
int res = ioctl(dest_fd, FICLONE, src_fd);
9595
*save_errno = errno;
9696

9797
close(src_fd);

src/btrfs.h renamed to src/ficlone.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ You should have received a copy of the GNU General Public License
1818
along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
*/
2020

21-
#ifndef _INC_BTRFS_H
22-
#define _INC_BTRFS_H
21+
#ifndef _INC_FICLONE_H
22+
#define _INC_FICLONE_H
2323

2424
#include <stdbool.h>
2525

2626
#define BTRFS_SUPER_MAGIC 0x9123683E
2727

28-
bool is_btrfs(const char *path);
28+
bool is_ficlone_fs(const char *path);
2929

30-
int do_btrfs_clone(const char *source, const char *dest, int *save_errno);
30+
int do_ficlone(const char *source, const char *dest, int *save_errno);
3131

3232
#endif

0 commit comments

Comments
 (0)