Add Bcachefs support

This commit is contained in:
Andrea Mazzoleni 2026-05-03 21:39:35 +02:00
parent 4ed6f21823
commit aa55b96aa8
15 changed files with 387 additions and 129 deletions

View file

@ -23,7 +23,7 @@ jobs:
- uses: actions/checkout@v5
- run: autoreconf -i
- run: ./configure --enable-warning-as-error
- run: make all btrfscheck
- run: make all snapcheck
- run: make distcheck
build-win:
runs-on: windows-latest

View file

@ -7,7 +7,7 @@ SnapRAID HISTORY
consistency and improve recovery reliability. When the 'snapshot'
option is enabled, SnapRAID manages a 'pending' snapshot at the start
of a sync and promotes it to a 'stable' state upon successful
completion (currently supported on Btrfs).
completion (currently supported on Btrfs and Bcachefs).
This ensures that 'scrub', 'check', and 'fix' operations utilize a
frozen point-in-time reference for data disks, eliminating
mismatches caused by concurrent live filesystem modifications.

View file

@ -154,7 +154,7 @@ EXTRA_DIST = \
$(LIST_D) \
$(LIST_MAN) \
$(LIST_TXT) \
test/test-btrfs.conf \
test/test-snap.conf \
test/test-par1.conf \
test/test-par2.conf \
test/test-par3.conf \
@ -268,8 +268,8 @@ CHECKFLAGS_VERBOSE = $(CHECKFLAGS_ALPHA) -v --gui
# Quiet options
CHECKFLAGS = $(CHECKFLAGS_ALPHA) -q -q -q
# Btrfs with no parity limit
CHECKFLAGS_BTRFS = --test-skip-device --test-skip-self --test-io-advise-none --test-force-order-alpha -q -q -q
# Snapshots with no parity limit
CHECKFLAGS_SNAP = --test-skip-device --test-skip-self --test-io-advise-none --test-force-order-alpha -q -q -q
# Number of files to create
if !HAVE_MEMORY_CHECKER
@ -301,7 +301,7 @@ PARZ = $(srcdir)/test/test-parz.conf
PAR4 = $(srcdir)/test/test-par4.conf
PAR5 = $(srcdir)/test/test-par5.conf
PAR6 = $(srcdir)/test/test-par6.conf
BTRFS = $(srcdir)/test/test-btrfs.conf
SNAP = $(srcdir)/test/test-snap.conf
MSG = @echo =====
SNAPRAID = ./checker$(EXEEXT)
@ -311,89 +311,89 @@ setupcheck:
mkdir bench
# Not included in main check because it requires root permissions
btrfscheck: mktest checker setupcheck
sudo $(MAKE) btrfscheck_run; \
snapcheck: mktest checker setupcheck
sudo $(MAKE) snapcheck_run; \
status=$$?; \
sudo $(MAKE) btrfscheck_cleanup; \
sudo $(MAKE) snapcheck_cleanup; \
exit $$status
btrfscheck_run:
mkdir bench/btrfs1
mkdir bench/btrfs2
mkdir bench/btrfs3
snapcheck_run:
mkdir bench/snap1
mkdir bench/snap2
mkdir bench/snap3
$(MSG) Create storages
dd bs=1048576 count=32 if=/dev/zero of=bench/btrfs1.img
dd bs=1048576 count=32 if=/dev/zero of=bench/btrfs2.img
dd bs=1048576 count=32 if=/dev/zero of=bench/btrfs3.img
/sbin/mkfs.btrfs --mixed bench/btrfs1.img
/sbin/mkfs.btrfs --mixed bench/btrfs2.img
/sbin/mkfs.btrfs --mixed bench/btrfs3.img
mount -o loop bench/btrfs1.img bench/btrfs1
mount -o loop bench/btrfs2.img bench/btrfs2
mount -o loop bench/btrfs3.img bench/btrfs3
dd bs=1048576 count=32 if=/dev/zero of=bench/snap1.img
dd bs=1048576 count=32 if=/dev/zero of=bench/snap2.img
dd bs=1048576 count=32 if=/dev/zero of=bench/snap3.img
/sbin/mkfs.btrfs --mixed bench/snap1.img
/sbin/mkfs.btrfs --mixed bench/snap2.img
/sbin/mkfs.btrfs --mixed bench/snap3.img
mount -o loop bench/snap1.img bench/snap1
mount -o loop bench/snap2.img bench/snap2
mount -o loop bench/snap3.img bench/snap3
$(MSG) Create some files
$(TESTENV) ./mktest$(EXEEXT) generate 1 btrfs 3 $(CHECKCOUNT) $(CHECKSIZE)
$(TESTENV) ./mktest$(EXEEXT) generate 1 snap 3 $(CHECKCOUNT) $(CHECKSIZE)
$(MSG) Basic
# Setup
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) scrub -p full
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) check -d btrfs1
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) sync
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) scrub -p full
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) check -d snap1
# Recover test
$(MSG) Recover with file deletions from two disk with ONLY one parity OUTSIDE SYNC
rm -rf bench/btrfs1/a
rm -rf bench/btrfs2/a
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) fix -d btrfs1 -l test.log
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) fix -d btrfs2 -l test.log
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) check
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync --force-full
rm -rf bench/snap1/a
rm -rf bench/snap2/a
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) fix -d snap1 -l test.log
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) fix -d snap2 -l test.log
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) check
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) sync --force-full
# Recover test
$(MSG) Recover with file deletions from two disk with ONLY one parity aborted BEFORE SYNC
# Changes (including deletion) on disk1
$(TESTENV) ./mktest$(EXEEXT) change 1 500 bench/btrfs1/a/* bench/btrfs1/b/*
$(TESTENV) ./mktest$(EXEEXT) change 1 500 bench/snap1/a/* bench/snap1/b/*
# Sync aborted BEFORE
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync --test-kill-before-sync
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) status
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) sync --test-kill-before-sync
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) status
# Destroy disk2
rm -rf bench/btrfs2/a bench/btrfs2/b
rm -rf bench/snap2/a bench/snap2/b
# Recover disk2 (disk1 is intentionally deleted, so nothing to recover)
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) fix -d btrfs2 -l test.log
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) check
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync --force-full
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) fix -d snap2 -l test.log
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) check
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) sync --force-full
# Recover test
$(MSG) Recover with file deletions from two disk with ONLY one parity aborted AFTER SYNC
# Changes (including deletion) on disk1
$(TESTENV) ./mktest$(EXEEXT) change 2 500 bench/btrfs1/a/* bench/btrfs1/b/*
$(TESTENV) ./mktest$(EXEEXT) change 2 500 bench/snap1/a/* bench/snap1/b/*
# Sync aborted AFTER
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync --test-kill-after-sync
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) status
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) sync --test-kill-after-sync
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) status
# Destroy disk2
rm -rf bench/btrfs2/a bench/btrfs2/b
rm -rf bench/snap2/a bench/snap2/b
# Recover disk2 (disk1 is intentionally deleted, so nothing to recover)
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) fix -d btrfs2 -l test.log
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) check
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync --force-full
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) fix -d snap2 -l test.log
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) check
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) sync --force-full
# Sync recover
$(MSG) Sync recover aborted AFTER
# Change files in disk3
$(TESTENV) ./mktest$(EXEEXT) change 1 500 bench/btrfs3/a/*
$(TESTENV) ./mktest$(EXEEXT) change 1 500 bench/snap3/a/*
# Sync abort and sync retry
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync --test-kill-after-sync
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) check
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) sync --test-kill-after-sync
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) sync
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) check
# Sync recover
$(MSG) Sync recover aborted BEFORE
# Change files in disk3
$(TESTENV) ./mktest$(EXEEXT) change 1 500 bench/btrfs3/a/*
$(TESTENV) ./mktest$(EXEEXT) change 1 500 bench/snap3/a/*
# Sync abort and sync retry
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync --test-kill-before-sync
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) sync
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_BTRFS) -c $(BTRFS) check
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) sync --test-kill-before-sync
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) sync
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) check
btrfscheck_cleanup:
snapcheck_cleanup:
$(MSG) Unmount storages
umount bench/btrfs1
umount bench/btrfs2
umount bench/btrfs3
umount bench/snap1
umount bench/snap2
umount bench/snap3
check-local: setupcheck
mkdir bench/disk1

View file

@ -812,6 +812,7 @@ struct snapraid_disk* disk_alloc(const char* name, const char* dir, uint64_t dev
/* no snapshot by default */
disk->snapshot_root[0] = 0;
disk->snapshot_magic = 0;
pathcpy(disk->dir, sizeof(disk->dir), disk->mount_point);
#if HAVE_THREAD

View file

@ -383,6 +383,7 @@ struct snapraid_disk {
* It always terminates with /
*/
char snapshot_root[PATH_MAX];
uint32_t snapshot_magic; /**< Magic number of the snapshot filesystem */
char smartctl[PATH_MAX]; /**< Custom command for smartctl. Empty means auto. */
int smartignore[SMART_IGNORE_MAX]; /**< Smart attributes to ignore for this device. */

View file

@ -2037,31 +2037,35 @@ int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_har
return 0;
}
int fssnapshot(const char* path, char* root, size_t root_size)
int fssnapshot(const char* path, char* root, size_t root_size, uint32_t* magic)
{
(void)path;
(void)root;
(void)root_size;
(void)magic;
return -1;
}
int fssnapshot_create(const char* source, const char* parent_dir, const char* name)
int fssnapshot_create(uint32_t magic, const char* source, const char* parent_dir, const char* name)
{
(void)magic;
(void)source;
(void)parent_dir;
(void)name;
return -1;
}
int fssnapshot_delete(const char* parent_dir, const char* name)
int fssnapshot_delete(uint32_t magic, const char* parent_dir, const char* name)
{
(void)magic;
(void)parent_dir;
(void)name;
return -1;
}
int fssnapshot_rename(const char* parent_dir, const char* old_name, const char* new_name)
int fssnapshot_rename(uint32_t magic, const char* parent_dir, const char* old_name, const char* new_name)
{
(void)magic;
(void)parent_dir;
(void)old_name;
(void)new_name;

View file

@ -366,32 +366,36 @@ int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_har
/**
* Get the subvolume directory if the underline filesystem support it
* @param path Full path of the mount point to check. It must end with /
* @param root The full path of the subvolume root that contains the specified path. It will end with /.
* @param root Set the full path of the subvolume root that contains the specified path. It will end with /.
* @param magic Set the magic number of the filesystem
*/
int fssnapshot(const char* path, char* root, size_t root_size);
int fssnapshot(const char* path, char* root, size_t root_size, uint32_t* magic);
/**
* Creates a snapshot of a subvolume.
* Creates a snapshot of a subvolume
* @param magic The magic number of the filesystem.
* @param source Path to the source subvolume.
* @param parent_dir Directory where the snapshot should be created.
* @param name Name of snapshot.
* @return 0 on success, -1 on failure.
*/
int fssnapshot_create(const char* source, const char* parent_dir, const char* name);
int fssnapshot_create(uint32_t magic, const char* source, const char* parent_dir, const char* name);
/**
* Deletes a subvolume/snapshot.
* @param magic The magic number of the filesystem.
* @param parent_dir Directory where the snapshot is.
* @param name Name of snapshot.
* @return 0 on success, -1 on failure.
*/
int fssnapshot_delete(const char* parent_dir, const char* name);
int fssnapshot_delete(uint32_t magic, const char* parent_dir, const char* name);
/**
* Renames a subvolume.
* @param magic The magic number of the filesystem.
* @return 0 on success, -1 on failure.
*/
int fssnapshot_rename(const char* parent_dir, const char* old_name, const char* new_name);
int fssnapshot_rename(uint32_t magic, const char* parent_dir, const char* old_name, const char* new_name);
/*
* Log file.

View file

@ -5838,7 +5838,7 @@ int state_snapshot_new(struct snapraid_state* state)
int ret;
/* check if it supports snapshot */
if (fssnapshot(disk->mount_point, root, sizeof(root)) != 0)
if (fssnapshot(disk->mount_point, root, sizeof(root), &disk->snapshot_magic) != 0)
continue;
size_t root_len = strlen(root);
@ -5854,13 +5854,13 @@ int state_snapshot_new(struct snapraid_state* state)
}
/* delete a potential previous pending snapshot */
if (fssnapshot_delete(container, SNAPSHOT_PENDING) != 0) {
if (fssnapshot_delete(disk->snapshot_magic, container, SNAPSHOT_PENDING) != 0) {
log_fatal(errno, "Failed to delete pending snapshot in '%s'. %s.\n", container, strerror(errno));
return -1;
}
/* create a new snapshot */
if (fssnapshot_create(root, container, SNAPSHOT_PENDING) != 0) {
if (fssnapshot_create(disk->snapshot_magic, root, container, SNAPSHOT_PENDING) != 0) {
log_fatal(errno, "Failed to create pending snapshot '%s'. %s.\n", container, strerror(errno));
return -1;
}
@ -5901,13 +5901,13 @@ int state_snapshot_commit(struct snapraid_state* state)
pathcat(container, sizeof(container), SNAPSHOT_CONTAINER);
/* delete a potential previous stable snapshot */
if (fssnapshot_delete(container, SNAPSHOT_STABLE) != 0) {
if (fssnapshot_delete(disk->snapshot_magic, container, SNAPSHOT_STABLE) != 0) {
log_fatal(errno, "Failed to delete stable snapshot in '%s'. %s.\n", container, strerror(errno));
return -1;
}
/* rename pending to stable */
if (fssnapshot_rename(container, SNAPSHOT_PENDING, SNAPSHOT_STABLE) != 0) {
if (fssnapshot_rename(disk->snapshot_magic, container, SNAPSHOT_PENDING, SNAPSHOT_STABLE) != 0) {
log_fatal(errno, "Failed to rename snapshot in '%s'. %s.\n", container, strerror(errno));
return -1;
}
@ -5930,7 +5930,7 @@ void state_snapshot_read(struct snapraid_state* state)
char vol[PATH_MAX];
/* check if it supports snapshot */
if (fssnapshot(disk->mount_point, root, sizeof(root)) != 0)
if (fssnapshot(disk->mount_point, root, sizeof(root), &disk->snapshot_magic) != 0)
continue;
size_t root_len = strlen(root);
@ -5975,7 +5975,7 @@ void state_snapshot_write(struct snapraid_state* state, tommy_list* filterlist_d
char vol[PATH_MAX];
/* check if it supports snapshot */
if (fssnapshot(disk->mount_point, root, sizeof(root)) != 0)
if (fssnapshot(disk->mount_point, root, sizeof(root), &disk->snapshot_magic) != 0)
continue;
size_t root_len = strlen(root);

View file

@ -29,6 +29,7 @@ int exit_failure = 1;
int exit_sync_needed = 2;
#define BTRFS_SUPER_MAGIC 0x9123683E
#define BCACHEFS_SUPER_MAGIC 0xCA451A4E
#if HAVE_POSIX_FADVISE
/**
@ -125,6 +126,7 @@ const char* stat_desc(struct stat* st)
return "unknown";
}
#if HAVE_LINUX_DEVICE
static const char* smartctl_paths[] = {
/* Linux & BSD */
"/usr/sbin/smartctl",
@ -132,22 +134,24 @@ static const char* smartctl_paths[] = {
"/usr/local/sbin/smartctl",
"/usr/bin/smartctl",
"/usr/local/bin/smartctl",
#ifdef __APPLE__
/* macOS (Intel & Apple Silicon) */
"/opt/homebrew/sbin/smartctl",
#endif
0
};
const char* find_smartctl(void)
static const char* find_smartctl(void)
{
int i;
for (i = 0; smartctl_paths[i]; ++i) {
if (eaccess(smartctl_paths[i], X_OK) == 0)
for (int i = 0; smartctl_paths[i]; ++i) {
if (eaccess(smartctl_paths[i], X_OK) == 0) {
return smartctl_paths[i];
}
}
return 0;
}
#endif
/**
* Read a file from sys
@ -986,6 +990,66 @@ static int devuuid_btrfs(uint64_t device, const char* dir, char* uuid, size_t uu
}
#endif
#if HAVE_LINUX_DEVICE
struct bch_ioctl_query_uuid {
__u8 uuid[16];
};
#define BCH_IOCTL_QUERY_UUID _IOR(0xbc, 1, struct bch_ioctl_query_uuid)
static int devuuid_bcachefs(uint64_t device, const char* dir, char* uuid, size_t uuid_size)
{
struct statfs sfs;
int fd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (fd < 0) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP*/
}
if (fstatfs(fd, &sfs) != 0) {
/* LCOV_EXCL_START */
close(fd);
return -1;
/* LCOV_EXCL_STOP */
}
if (sfs.f_type != BCACHEFS_SUPER_MAGIC) {
close(fd);
return -1;
}
struct bch_ioctl_query_uuid info;
memset(&info, 0, sizeof(info));
if (ioctl(fd, BCH_IOCTL_QUERY_UUID, &info) < 0) {
/* LCOV_EXCL_START */
close(fd);
return -1;
/* LCOV_EXCL_STOP */
}
close(fd);
snprintf(uuid, uuid_size,
"%02x%02x%02x%02x-"
"%02x%02x-"
"%02x%02x-"
"%02x%02x-"
"%02x%02x%02x%02x%02x%02x",
info.uuid[0], info.uuid[1], info.uuid[2], info.uuid[3],
info.uuid[4], info.uuid[5],
info.uuid[6], info.uuid[7],
info.uuid[8], info.uuid[9],
info.uuid[10], info.uuid[11], info.uuid[12], info.uuid[13], info.uuid[14], info.uuid[15]
);
log_tag("uuid:by-bcachefs:%u:%u:%s: found %s\n", major(device), minor(device), uuid, dir);
return 0;
}
#endif
int devuuid(uint64_t device_id, const char* device_path, char* uuid, size_t uuid_size)
{
(void)device_path;
@ -1001,6 +1065,11 @@ int devuuid(uint64_t device_id, const char* device_path, char* uuid, size_t uuid
return 0;
#endif
#if HAVE_LINUX_DEVICE
if (devuuid_bcachefs(device_id, device_path, uuid, uuid_size) == 0)
return 0;
#endif
#if HAVE_LINUX_DEVICE
if (devuuid_dev(device_id, uuid, uuid_size) == 0)
return 0;
@ -1142,7 +1211,6 @@ int filephy(const char* path, uint64_t size, uint64_t* physical)
#define BEFS_SUPER_MAGIC 0x42465331
#define BFS_MAGIC 0x1BADFACE
#define BINFMTFS_MAGIC 0x42494e4d
#define BTRFS_SUPER_MAGIC 0x9123683E
#define CGROUP_SUPER_MAGIC 0x27e0eb
#define CIFS_MAGIC_NUMBER 0xFF534D42
#define CODA_SUPER_MAGIC 0x73757245
@ -1246,6 +1314,7 @@ struct filesystem_entry {
{ BFS_MAGIC, "bfs", 0 },
{ BINFMTFS_MAGIC, "binfmt_misc", 0 },
{ BTRFS_SUPER_MAGIC, "btrfs", 0 },
{ BCACHEFS_SUPER_MAGIC, "bcachefs", 0 },
{ CEPH_SUPER_MAGIC, "ceph", 1 },
{ CGROUP_SUPER_MAGIC, "cgroupfs", 0 },
{ CIFS_MAGIC_NUMBER, "cifs", 1 },
@ -1465,12 +1534,40 @@ int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_har
return 0;
}
int fssnapshot(const char* path, char* root, size_t root_size)
#if HAVE_LINUX_DEVICE
static const char* const bcachefs_paths[] = {
"/usr/sbin/bcachefs",
"/sbin/bcachefs",
"/usr/local/sbin/bcachefs",
"/usr/bin/bcachefs",
"/bin/bcachefs",
"/usr/local/bin/bcachefs",
#ifdef __APPLE__
/* macOS (Intel & Apple Silicon) */
"/opt/homebrew/sbin/bcachefs",
#endif
0
};
static const char* find_bcachefs(void)
{
for (int i = 0; bcachefs_paths[i]; ++i) {
if (access(bcachefs_paths[i], X_OK) == 0) {
return bcachefs_paths[i];
}
}
return 0;
}
#endif
int fssnapshot(const char* path, char* root, size_t root_size, uint32_t* magic)
{
#if HAVE_LINUX_DEVICE
struct statfs sfs;
struct stat st;
char current_path[PATH_MAX];
uint64_t root_inode;
pathcpy(current_path, sizeof(current_path), path);
@ -1481,9 +1578,15 @@ int fssnapshot(const char* path, char* root, size_t root_size)
/* LCOV_EXCL_STOP */
}
/* only btrfs */
if (sfs.f_type != BTRFS_SUPER_MAGIC)
if (sfs.f_type == BTRFS_SUPER_MAGIC) {
/* btrfs reserved inode 256 for subvolume roots */
root_inode = 256;
} else if (sfs.f_type == BCACHEFS_SUPER_MAGIC) {
/* Bcachefs reserves inode 4096 for each subvolume's root directory */
root_inode = 4096;
} else {
return -1;
}
/* walk up the directory tree to find the subvolume root (Inode 256) */
while (1) {
@ -1494,10 +1597,10 @@ int fssnapshot(const char* path, char* root, size_t root_size)
/* LCOV_EXCL_STOP */
}
/* btrfs reserved inode 256 for subvolume roots */
if (st.st_ino == 256) {
if (st.st_ino == root_inode) {
/* copy the subvol root */
pathcpy(root, root_size, current_path);
*magic = sfs.f_type;
return 0;
}
@ -1515,13 +1618,14 @@ int fssnapshot(const char* path, char* root, size_t root_size)
(void)path;
(void)root;
(void)root_size;
(void)magic;
return -1;
#endif
}
int fssnapshot_create(const char* source, const char* parent_dir, const char* name)
{
#if HAVE_LINUX_DEVICE
int fssnapshot_btrfs_create(const char* source, const char* parent_dir, const char* name)
{
struct btrfs_ioctl_vol_args_v2 args;
int fd_source;
int fd_dest_parent;
@ -1562,7 +1666,53 @@ int fssnapshot_create(const char* source, const char* parent_dir, const char* na
close(fd_dest_parent);
return 0;
}
#endif
#if HAVE_LINUX_DEVICE
static int fssnapshot_bcachefs_create(const char* source, const char* parent_dir, const char* name)
{
char dst_path[PATH_MAX];
char cmd[PATH_MAX * 3 + 64];
const char* bcachefs;
int ret;
bcachefs = find_bcachefs();
if (!bcachefs) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
pathcpy(dst_path, sizeof(dst_path), parent_dir);
pathcatc(dst_path, sizeof(dst_path), '/');
pathcat(dst_path, sizeof(dst_path), name);
snprintf(cmd, sizeof(cmd), "%s subvolume snapshot -r %s %s", bcachefs, source, dst_path);
ret = system(cmd);
if (ret != 0) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
return 0;
}
#endif
int fssnapshot_create(uint32_t magic, const char* source, const char* parent_dir, const char* name)
{
#if HAVE_LINUX_DEVICE
if (magic == BTRFS_SUPER_MAGIC) {
return fssnapshot_btrfs_create(source, parent_dir, name);
} else if (magic == BCACHEFS_SUPER_MAGIC) {
return fssnapshot_bcachefs_create(source, parent_dir, name);
} else {
return -1;
}
#else
(void)magic;
(void)source;
(void)parent_dir;
(void)name;
@ -1570,9 +1720,9 @@ int fssnapshot_create(const char* source, const char* parent_dir, const char* na
#endif
}
int fssnapshot_delete(const char* parent_dir, const char* name)
{
#if HAVE_LINUX_DEVICE
int fssnapshot_btrfs_delete(const char* parent_dir, const char* name)
{
struct btrfs_ioctl_vol_args args;
int fd_parent;
int ret;
@ -1588,7 +1738,6 @@ int fssnapshot_delete(const char* parent_dir, const char* name)
pathcpy(args.name, BTRFS_PATH_NAME_MAX, name);
ret = ioctl(fd_parent, BTRFS_IOC_SNAP_DESTROY, &args);
if (ret < 0 && errno != ENOENT) {
/* LCOV_EXCL_START */
close(fd_parent);
@ -1598,16 +1747,70 @@ int fssnapshot_delete(const char* parent_dir, const char* name)
close(fd_parent);
return 0;
}
#endif
#if HAVE_LINUX_DEVICE
static int fssnapshot_bcachefs_delete(const char* parent_dir, const char* name)
{
char target_path[PATH_MAX];
char cmd[PATH_MAX + 64];
const char* bcachefs;
int ret;
bcachefs = find_bcachefs();
if (!bcachefs) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
pathcpy(target_path, sizeof(target_path), parent_dir);
pathcatc(target_path, sizeof(target_path), '/');
pathcat(target_path, sizeof(target_path), name);
if (access(target_path, F_OK) < 0) {
if (errno == ENOENT)
return 0;
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
snprintf(cmd, sizeof(cmd), "%s subvolume delete %s", bcachefs, target_path);
ret = system(cmd);
if (ret != 0) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
return 0;
}
#endif
int fssnapshot_delete(uint32_t magic, const char* parent_dir, const char* name)
{
#if HAVE_LINUX_DEVICE
if (magic == BTRFS_SUPER_MAGIC) {
return fssnapshot_btrfs_delete(parent_dir, name);
} else if (magic == BCACHEFS_SUPER_MAGIC) {
return fssnapshot_bcachefs_delete(parent_dir, name);
} else {
return -1;
}
#else
(void)magic;
(void)parent_dir;
(void)name;
return -1;
#endif
}
int fssnapshot_rename(const char* parent_dir, const char* old_name, const char* new_name)
{
#if HAVE_LINUX_DEVICE
static int fssnapshot_btrfs_rename(const char* parent_dir, const char* old_name, const char* new_name)
{
char old_path[PATH_MAX];
char new_path[PATH_MAX];
@ -1625,7 +1828,46 @@ int fssnapshot_rename(const char* parent_dir, const char* old_name, const char*
}
return 0;
}
#endif
#if HAVE_LINUX_DEVICE
static int fssnapshot_bcachefs_rename(const char* parent_dir, const char* old_name, const char* new_name)
{
char old_path[PATH_MAX];
pathcpy(old_path, sizeof(old_path), parent_dir);
pathcatc(old_path, sizeof(old_path), '/');
pathcat(old_path, sizeof(old_path), old_name);
if (fssnapshot_bcachefs_create(old_path, parent_dir, new_name) != 0) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
if (fssnapshot_bcachefs_delete(parent_dir, old_name) != 0) {
/* LCOV_EXCL_START */
return -1;
/* LCOV_EXCL_STOP */
}
return 0;
}
#endif
int fssnapshot_rename(uint32_t magic, const char* parent_dir, const char* old_name, const char* new_name)
{
#if HAVE_LINUX_DEVICE
if (magic == BTRFS_SUPER_MAGIC) {
return fssnapshot_btrfs_rename(parent_dir, old_name, new_name);
} else if (magic == BCACHEFS_SUPER_MAGIC) {
return fssnapshot_bcachefs_rename(parent_dir, old_name, new_name);
} else {
return -1;
}
#else
(void)magic;
(void)parent_dir;
(void)old_name;
(void)new_name;

View file

@ -1434,18 +1434,18 @@ When enabled, SnapRAID creates a read\-only snapshot of your data at the
start of a \'sync\'. This ensures a consistent, point\-in\-time view of your
files, preventing errors caused by concurrent file modifications.
.PP
This option applies exclusively to data disks formatted with the Btrfs
filesystem. Parity disks, or data disks using other filesystems, will
always use the live version of the filesystem.
This option applies exclusively to data disks formatted with the
Btrfs or Bcachefs filesystems. Parity disks, or data disks using
other filesystems, will always use the live version of the filesystem.
.PP
This significantly improves recovery: if a file is deleted from the live
filesystem, it remains preserved in the snapshot. This prevents the parity
from becoming \[dq]broken\[dq] for that block, ensuring you can still successfully
recover data if another disk fails.
.PP
Only Btrfs is supported. Snapshots are stored in a hidden \'.snapraid\'
directory at the root of the Btrfs subvolume containing your data.
SnapRAID will automatically detect the subvolume root for each data path.
Snapshots are stored in a hidden \'.snapraid\' directory at the root
of the subvolume containing your data. SnapRAID will automatically
detect the subvolume root for each data path.
.PP
Snapshot creation and deletion require administrative privileges.
Ensure SnapRAID is run with the necessary permissions (e.g., sudo) when
@ -1833,7 +1833,9 @@ fix other failed disks.
.PP
Snapshots are created only for data disks and only if the underlying
filesystem supports this functionality. Parity disks always use
the live filesystem. At present, this is supported exclusively on Btrfs.
the live filesystem.
.PP
At present, this is supported exclusively on Btrfs and Bcachefs.
.PP
You can mix data disks with different filesystems. Only those that
support snapshots will utilize them, while other disks will continue

View file

@ -1154,18 +1154,18 @@ Configuration
start of a 'sync'. This ensures a consistent, point-in-time view of your
files, preventing errors caused by concurrent file modifications.
This option applies exclusively to data disks formatted with the Btrfs
filesystem. Parity disks, or data disks using other filesystems, will
always use the live version of the filesystem.
This option applies exclusively to data disks formatted with the
Btrfs or Bcachefs filesystems. Parity disks, or data disks using
other filesystems, will always use the live version of the filesystem.
This significantly improves recovery: if a file is deleted from the live
filesystem, it remains preserved in the snapshot. This prevents the parity
from becoming "broken" for that block, ensuring you can still successfully
recover data if another disk fails.
Only Btrfs is supported. Snapshots are stored in a hidden '.snapraid'
directory at the root of the Btrfs subvolume containing your data.
SnapRAID will automatically detect the subvolume root for each data path.
Snapshots are stored in a hidden '.snapraid' directory at the root
of the subvolume containing your data. SnapRAID will automatically
detect the subvolume root for each data path.
Snapshot creation and deletion require administrative privileges.
Ensure SnapRAID is run with the necessary permissions (e.g., sudo) when
@ -1445,7 +1445,9 @@ Snapshots
Snapshots are created only for data disks and only if the underlying
filesystem supports this functionality. Parity disks always use
the live filesystem. At present, this is supported exclusively on Btrfs.
the live filesystem.
At present, this is supported exclusively on Btrfs and Bcachefs.
You can mix data disks with different filesystems. Only those that
support snapshots will utilize them, while other disks will continue

View file

@ -1232,18 +1232,18 @@ When enabled, SnapRAID creates a read-only snapshot of your data at the
start of a 'sync'. This ensures a consistent, point-in-time view of your
files, preventing errors caused by concurrent file modifications.
This option applies exclusively to data disks formatted with the Btrfs
filesystem. Parity disks, or data disks using other filesystems, will
always use the live version of the filesystem.
This option applies exclusively to data disks formatted with the
Btrfs or Bcachefs filesystems. Parity disks, or data disks using
other filesystems, will always use the live version of the filesystem.
This significantly improves recovery: if a file is deleted from the live
filesystem, it remains preserved in the snapshot. This prevents the parity
from becoming "broken" for that block, ensuring you can still successfully
recover data if another disk fails.
Only Btrfs is supported. Snapshots are stored in a hidden '.snapraid'
directory at the root of the Btrfs subvolume containing your data.
SnapRAID will automatically detect the subvolume root for each data path.
Snapshots are stored in a hidden '.snapraid' directory at the root
of the subvolume containing your data. SnapRAID will automatically
detect the subvolume root for each data path.
Snapshot creation and deletion require administrative privileges.
Ensure SnapRAID is run with the necessary permissions (e.g., sudo) when
@ -1550,7 +1550,9 @@ Recovery - If a file is updated or deleted from the live filesystem,
Snapshots are created only for data disks and only if the underlying
filesystem supports this functionality. Parity disks always use
the live filesystem. At present, this is supported exclusively on Btrfs.
the live filesystem.
At present, this is supported exclusively on Btrfs and Bcachefs.
You can mix data disks with different filesystems. Only those that
support snapshots will utilize them, while other disks will continue

View file

@ -50,18 +50,18 @@ extra boot /
# start of a 'sync'. This ensures a consistent, point-in-time view of your
# files, preventing errors caused by concurrent file modifications.
#
# This option applies exclusively to data disks formatted with the Btrfs
# filesystem. Parity disks, or data disks using other filesystems, will
# always use the live version of the filesystem.
# This option applies exclusively to data disks formatted with the
# Btrfs or Bcachefs filesystems. Parity disks, or data disks using
# other filesystems, will always use the live version of the filesystem.
#
# This significantly improves recovery: if a file is deleted from the live
# filesystem, it remains preserved in the snapshot. This prevents the parity
# from becoming "broken" for that block, ensuring you can still successfully
# recover data if another disk fails.
#
# Only Btrfs is supported. Snapshots are stored in a hidden '.snapraid'
# directory at the root of the Btrfs subvolume containing your data.
# SnapRAID will automatically detect the subvolume root for each data path.
# Snapshots are stored in a hidden '.snapraid' directory at the root
# of the subvolume containing your data. SnapRAID will automatically
# detect the subvolume root for each data path.
#
# Snapshot creation and deletion require administrative privileges.
# Ensure SnapRAID is run with the necessary permissions (e.g., sudo) when

View file

@ -1,9 +0,0 @@
blocksize 64
parity bench/btrfs-parity
content bench/btrfs-content
content bench/btrfs-1-content
disk btrfs1 bench/btrfs1/
disk btrfs2 bench/btrfs2/
disk btrfs3 bench/btrfs3/
snapshot
exclude .snapraid/

9
test/test-snap.conf Normal file
View file

@ -0,0 +1,9 @@
blocksize 64
parity bench/snap-parity
content bench/snap-content
content bench/snap-1-content
disk snap1 bench/snap1/
disk snap2 bench/snap2/
disk snap3 bench/snap3/
snapshot
exclude .snapraid/