mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-25 18:40:19 -04:00
Add ZFS support
This commit is contained in:
parent
ec9ddb474a
commit
033ce93d5b
12 changed files with 767 additions and 242 deletions
2
HISTORY
2
HISTORY
|
|
@ -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 and Bcachefs).
|
||||
completion (currently supported on Btrfs, Bcachefs, and ZFS).
|
||||
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.
|
||||
|
|
|
|||
12
Makefile.am
12
Makefile.am
|
|
@ -356,7 +356,7 @@ snapcheck_run:
|
|||
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) status
|
||||
# Destroy disk2
|
||||
rm -rf bench/snap2/a bench/snap2/b
|
||||
# Recover disk2 (disk1 is intentionally deleted, so nothing to recover)
|
||||
# Recover disk2 (disk1 is intentionally modified, so nothing to recover)
|
||||
$(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
|
||||
|
|
@ -369,22 +369,22 @@ snapcheck_run:
|
|||
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) status
|
||||
# Destroy disk2
|
||||
rm -rf bench/snap2/a bench/snap2/b
|
||||
# Recover disk2 (disk1 is intentionally deleted, so nothing to recover)
|
||||
# Recover disk2 (disk1 is intentionally modified, so nothing to recover)
|
||||
$(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/snap3/a/*
|
||||
# Change files in disk1
|
||||
$(TESTENV) ./mktest$(EXEEXT) change 1 500 bench/snap1/a/*
|
||||
# Sync abort and sync retry
|
||||
$(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/snap3/a/*
|
||||
# Change files in disk1
|
||||
$(TESTENV) ./mktest$(EXEEXT) change 1 500 bench/snap1/b/*
|
||||
# Sync abort and sync retry
|
||||
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) sync --test-kill-before-sync
|
||||
$(TESTENV) $(SNAPRAID) $(CHECKFLAGS_SNAP) -c $(SNAP) sync
|
||||
|
|
|
|||
21
TODO
21
TODO
|
|
@ -13,12 +13,18 @@ This is the list of TODO items for SnapRAID.
|
|||
https://github.com/linuxhw/SMART
|
||||
https://github.com/linuxhw/EnterpriseDrive
|
||||
|
||||
* Implement the use Shadow Copy in Windows for creating snapshots.
|
||||
See Jens's Windows script at:
|
||||
http://sourceforge.net/p/snapraid/discussion/1677233/thread/a1707211/
|
||||
https://sourceforge.net/p/snapraid/discussion/1677233/thread/5c9e670c/
|
||||
Note that a difference between Windows and Unix is that in Windows old snapshots
|
||||
are automatically deleted.
|
||||
|
||||
* A "dependents" output option to [--test-fmt file|disk|path|dependents] for LIST.
|
||||
Example, snapraid LIST --test-fmt dependents >FileList_with_dependents.txt
|
||||
The goal is a filelist that, for every file, includes full paths to the dependent files
|
||||
on other data disks needed to repair that file (in addition to the parity data).
|
||||
|
||||
I created a thread about it here: https://sourceforge.net/p/snapraid/discussion/1677233/thread/6e872fdae0/
|
||||
See: https://sourceforge.net/p/snapraid/discussion/1677233/thread/6e872fdae0/
|
||||
|
||||
- Minor
|
||||
|
||||
|
|
@ -212,17 +218,6 @@ splitting.
|
|||
the number of hash for each file to three.
|
||||
- No way to handle block import.
|
||||
|
||||
* Create a filesystem snapshot at every sync, and use it in all the other commands
|
||||
automatically.
|
||||
At the next sync, drop the old snapshot and create a new one.
|
||||
This should help recovering, because we'll have the exact copy used by sync.
|
||||
This feature can be enabled with a specific option, and available
|
||||
in Windows using Shadow Copy, and in Linux using Btrfs, and in a generic
|
||||
Unix using ZFS.
|
||||
See Jens's Windows script at: http://sourceforge.net/p/snapraid/discussion/1677233/thread/a1707211/
|
||||
Note that a difference between Windows and Unix is that in Windows old snapshots
|
||||
are automatically deleted.
|
||||
|
||||
* Checks if splitting hash/parity computation in 4K pages
|
||||
can improve speed in sync. That should increase cache locality,
|
||||
because we read the data two times for hash and parity,
|
||||
|
|
|
|||
|
|
@ -2069,6 +2069,14 @@ int fssnapshot(const char* path, struct fssnapshot_struct* fss)
|
|||
return -1;
|
||||
}
|
||||
|
||||
int fssnapshot_stat(struct fssnapshot_struct* fss, const char* name, struct stat* st)
|
||||
{
|
||||
(void)fss;
|
||||
(void)name;
|
||||
(void)st;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fssnapshot_create(const struct fssnapshot_struct* fss, const char* name)
|
||||
{
|
||||
(void)fss;
|
||||
|
|
|
|||
|
|
@ -399,16 +399,26 @@ struct fssnapshot_struct {
|
|||
/**
|
||||
* Initialize snapshot context from an arbitrary path.
|
||||
*
|
||||
* @param path Full path inside the filesystem (must end with '/')
|
||||
* @param dir Full path inside the filesystem (must end with '/')
|
||||
* @param fss Output snapshot context
|
||||
* @return 0 on success, -1 on failure
|
||||
*/
|
||||
int fssnapshot(const char* path, struct fssnapshot_struct* fss);
|
||||
int fssnapshot(const char* dir, struct fssnapshot_struct* fss);
|
||||
|
||||
/**
|
||||
* Stat the snapshot directory.
|
||||
*
|
||||
* @param fss Snapshot context
|
||||
* @param name Snapshot name
|
||||
* @param st Set the stat info
|
||||
* @return 0 on success, -1 on failure
|
||||
*/
|
||||
int fssnapshot_stat(struct fssnapshot_struct* fss, const char* name, struct stat* st);
|
||||
|
||||
/**
|
||||
* Create a snapshot of the filesystem root/dataset.
|
||||
*
|
||||
* @param fss Snapshot context
|
||||
* @param fss Snapshot context
|
||||
* @param name Snapshot name
|
||||
* @return 0 on success, -1 on failure
|
||||
*/
|
||||
|
|
@ -426,9 +436,9 @@ int fssnapshot_delete(const struct fssnapshot_struct* fss, const char* name);
|
|||
/**
|
||||
* Rename a snapshot.
|
||||
*
|
||||
* @param fss Snapshot context
|
||||
* @param old_name Existing snapshot name
|
||||
* @param new_name New snapshot name
|
||||
* @param fss Snapshot context
|
||||
* @param old_name Existing snapshot name
|
||||
* @param new_name New snapshot name
|
||||
* @return 0 on success, -1 on failure
|
||||
*/
|
||||
int fssnapshot_rename(const struct fssnapshot_struct* fss, const char* old_name, const char* new_name);
|
||||
|
|
|
|||
|
|
@ -5813,18 +5813,22 @@ void state_load_ignore_file(tommy_list* filter_list, const char* path, const cha
|
|||
#define SNAPSHOT_PENDING "pending"
|
||||
#define SNAPSHOT_STABLE "stable"
|
||||
|
||||
static int state_snapshot_dir(const char* dir, struct snapraid_disk* disk)
|
||||
static int state_snapshot_dir(struct fssnapshot_struct* fss, const char* name, const char* dir, struct snapraid_disk* disk)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if (lstat(dir, &st) != 0)
|
||||
return -1;
|
||||
|
||||
if (!S_ISDIR(st.st_mode))
|
||||
/* fail if it doesn't exists at high level */
|
||||
if (fssnapshot_stat(fss, name, &st) != 0)
|
||||
return -1;
|
||||
|
||||
if (disk) {
|
||||
pathcpy(disk->dir, sizeof(disk->dir), dir);
|
||||
char vol[PATH_MAX];
|
||||
pathcpy(vol, sizeof(vol), fss->snapshot_dir);
|
||||
pathcat(vol, sizeof(vol), name);
|
||||
pathcatc(vol, sizeof(vol), '/');
|
||||
pathcat(vol, sizeof(vol), dir);
|
||||
|
||||
pathcpy(disk->dir, sizeof(disk->dir), vol);
|
||||
disk->dir_device = st.st_dev;
|
||||
}
|
||||
|
||||
|
|
@ -5846,9 +5850,11 @@ int state_snapshot_new(struct snapraid_state* state)
|
|||
size_t root_len = strlen(disk->fss.root_dir);
|
||||
|
||||
/* delete a potential previous pending snapshot */
|
||||
if (fssnapshot_delete(&disk->fss, SNAPSHOT_PENDING) != 0) {
|
||||
log_fatal(errno, "Failed to delete pending snapshot in '%s'. %s.\n", disk->fss.snapshot_dir, strerror(errno));
|
||||
return -1;
|
||||
if (state_snapshot_dir(&disk->fss, SNAPSHOT_PENDING, 0, 0) == 0) {
|
||||
if (fssnapshot_delete(&disk->fss, SNAPSHOT_PENDING) != 0) {
|
||||
log_fatal(errno, "Failed to delete pending snapshot in '%s'. %s.\n", disk->fss.snapshot_dir, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* create a new snapshot */
|
||||
|
|
@ -5860,13 +5866,8 @@ int state_snapshot_new(struct snapraid_state* state)
|
|||
msg_progress("Created disk %s pending snapshot...\n", disk->name);
|
||||
|
||||
/* setup the snapshot in use */
|
||||
char vol[PATH_MAX];
|
||||
pathcpy(vol, sizeof(vol), disk->fss.snapshot_dir);
|
||||
pathcat(vol, sizeof(vol), SNAPSHOT_PENDING "/");
|
||||
pathcat(vol, sizeof(vol), disk->mount_point + root_len);
|
||||
|
||||
if (state_snapshot_dir(vol, disk) != 0) {
|
||||
log_error(errno, "Error stating snapshot dir '%s'. %s.\n", vol, strerror(errno));
|
||||
if (state_snapshot_dir(&disk->fss, SNAPSHOT_PENDING, disk->mount_point + root_len, disk) != 0) {
|
||||
log_error(errno, "Error stating pending snapshot '%s'. %s.\n", disk->fss.snapshot_dir, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
@ -5887,9 +5888,11 @@ int state_snapshot_commit(struct snapraid_state* state)
|
|||
continue;
|
||||
|
||||
/* delete a potential previous stable snapshot */
|
||||
if (fssnapshot_delete(&disk->fss, SNAPSHOT_STABLE) != 0) {
|
||||
log_fatal(errno, "Failed to delete stable snapshot in '%s'. %s.\n", disk->fss.snapshot_dir, strerror(errno));
|
||||
return -1;
|
||||
if (state_snapshot_dir(&disk->fss, SNAPSHOT_STABLE, 0, 0) == 0) {
|
||||
if (fssnapshot_delete(&disk->fss, SNAPSHOT_STABLE) != 0) {
|
||||
log_fatal(errno, "Failed to delete stable snapshot in '%s'. %s.\n", disk->fss.snapshot_dir, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* rename pending to stable */
|
||||
|
|
@ -5911,7 +5914,6 @@ void state_snapshot_read(struct snapraid_state* state)
|
|||
|
||||
for (tommy_node* i = state->disklist; i != 0; i = i->next) {
|
||||
struct snapraid_disk* disk = i->data;
|
||||
char vol[PATH_MAX];
|
||||
|
||||
/* check if it supports snapshot */
|
||||
if (fssnapshot(disk->mount_point, &disk->fss) != 0)
|
||||
|
|
@ -5919,16 +5921,10 @@ void state_snapshot_read(struct snapraid_state* state)
|
|||
|
||||
size_t root_len = strlen(disk->fss.root_dir);
|
||||
|
||||
pathcpy(vol, sizeof(vol), disk->fss.snapshot_dir);
|
||||
pathcat(vol, sizeof(vol), SNAPSHOT_PENDING "/");
|
||||
pathcat(vol, sizeof(vol), disk->mount_point + root_len);
|
||||
if (state_snapshot_dir(vol, disk) == 0) {
|
||||
if (state_snapshot_dir(&disk->fss, SNAPSHOT_PENDING, disk->mount_point + root_len, disk) == 0) {
|
||||
msg_progress("Using disk %s pending snapshot...\n", disk->name);
|
||||
} else {
|
||||
pathcpy(vol, sizeof(vol), disk->fss.snapshot_dir);
|
||||
pathcat(vol, sizeof(vol), SNAPSHOT_STABLE "/");
|
||||
pathcat(vol, sizeof(vol), disk->mount_point + root_len);
|
||||
if (state_snapshot_dir(vol, disk) == 0) {
|
||||
if (state_snapshot_dir(&disk->fss, SNAPSHOT_STABLE, disk->mount_point + root_len, disk) == 0) {
|
||||
msg_progress("Using disk %s stable snapshot...\n", disk->name);
|
||||
} else {
|
||||
/* fallback to standard mount point */
|
||||
|
|
@ -5951,7 +5947,6 @@ void state_snapshot_write(struct snapraid_state* state, tommy_list* filterlist_d
|
|||
|
||||
for (tommy_node* i = state->disklist; i != 0; i = i->next) {
|
||||
struct snapraid_disk* disk = i->data;
|
||||
char vol[PATH_MAX];
|
||||
|
||||
/* check if it supports snapshot */
|
||||
if (fssnapshot(disk->mount_point, &disk->fss) != 0)
|
||||
|
|
@ -5965,30 +5960,26 @@ void state_snapshot_write(struct snapraid_state* state, tommy_list* filterlist_d
|
|||
continue;
|
||||
}
|
||||
|
||||
pathcpy(vol, sizeof(vol), disk->fss.snapshot_dir);
|
||||
pathcat(vol, sizeof(vol), SNAPSHOT_PENDING "/");
|
||||
pathcat(vol, sizeof(vol), disk->mount_point + root_len);
|
||||
if (state_snapshot_dir(vol, disk) == 0) {
|
||||
if (state_snapshot_dir(&disk->fss, SNAPSHOT_PENDING, disk->mount_point + root_len, disk) == 0) {
|
||||
msg_progress("Using disk %s pending snapshot...\n", disk->name);
|
||||
|
||||
/* if there is a dealloc list */
|
||||
if (!tommy_list_empty(&disk->dealloclist)) {
|
||||
pathcpy(vol, sizeof(vol), disk->fss.snapshot_dir);
|
||||
pathcat(vol, sizeof(vol), SNAPSHOT_STABLE "/");
|
||||
pathcat(vol, sizeof(vol), disk->mount_point + root_len);
|
||||
|
||||
/* if there is a previous snapshot */
|
||||
if (state_snapshot_dir(vol, 0) == 0) {
|
||||
msg_progress("Importing disk %s stable snapshot %" PRIu64 " deallocated files...\n", disk->name, tommy_list_count(&disk->dealloclist));
|
||||
if (state_snapshot_dir(&disk->fss, SNAPSHOT_STABLE, disk->mount_point + root_len, 0) == 0) {
|
||||
msg_progress("Importing disk %s stable snapshot %" PRIu64 " deallocated files...\n", disk->name, (uint64_t)tommy_list_count(&disk->dealloclist));
|
||||
|
||||
/* set where to find deallocated files */
|
||||
char vol[PATH_MAX];
|
||||
pathcpy(vol, sizeof(vol), disk->fss.snapshot_dir);
|
||||
pathcat(vol, sizeof(vol), SNAPSHOT_STABLE "/");
|
||||
pathcat(vol, sizeof(vol), disk->mount_point + root_len);
|
||||
state_dealloc(state, vol, &disk->dealloclist);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pathcpy(vol, sizeof(vol), disk->fss.snapshot_dir);
|
||||
pathcat(vol, sizeof(vol), SNAPSHOT_STABLE "/");
|
||||
pathcat(vol, sizeof(vol), disk->mount_point + root_len);
|
||||
if (state_snapshot_dir(vol, disk) == 0) {
|
||||
if (state_snapshot_dir(&disk->fss, SNAPSHOT_STABLE, disk->mount_point + root_len, disk) == 0) {
|
||||
msg_progress("Using disk %s stable snapshot...\n", disk->name);
|
||||
} else {
|
||||
/* fallback to standard mount point */
|
||||
|
|
|
|||
812
cmdline/unix.c
812
cmdline/unix.c
|
|
@ -30,6 +30,84 @@ int exit_sync_needed = 2;
|
|||
|
||||
#define BTRFS_SUPER_MAGIC 0x9123683E
|
||||
#define BCACHEFS_SUPER_MAGIC 0xCA451A4E
|
||||
#define ZFS_SUPER_MAGIC 0x2FC12FC1
|
||||
|
||||
#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
|
||||
|
||||
#if HAVE_LINUX_DEVICE
|
||||
static const char* const zfs_paths[] = {
|
||||
"/usr/sbin/zfs",
|
||||
"/sbin/zfs",
|
||||
"/usr/local/sbin/zfs",
|
||||
"/usr/bin/zfs",
|
||||
"/bin/zfs",
|
||||
"/usr/local/bin/zfs",
|
||||
#ifdef __APPLE__
|
||||
/* macOS (Intel & Apple Silicon) */
|
||||
"/opt/homebrew/sbin/zfs",
|
||||
#endif
|
||||
0
|
||||
};
|
||||
|
||||
static const char* find_zfs(void)
|
||||
{
|
||||
for (int i = 0; zfs_paths[i]; ++i) {
|
||||
if (access(zfs_paths[i], X_OK) == 0) {
|
||||
return zfs_paths[i];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const char* const zpool_paths[] = {
|
||||
"/usr/sbin/zpool",
|
||||
"/sbin/zpool",
|
||||
"/usr/local/sbin/zpool",
|
||||
"/usr/bin/zpool",
|
||||
"/bin/zpool",
|
||||
"/usr/local/bin/zpool",
|
||||
#ifdef __APPLE__
|
||||
/* macOS (Intel & Apple Silicon) */
|
||||
"/opt/homebrew/sbin/zpool",
|
||||
#endif
|
||||
0
|
||||
};
|
||||
|
||||
static const char* find_zpool(void)
|
||||
{
|
||||
for (int i = 0; zpool_paths[i]; ++i) {
|
||||
if (access(zpool_paths[i], X_OK) == 0) {
|
||||
return zpool_paths[i];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAVE_POSIX_FADVISE
|
||||
/**
|
||||
|
|
@ -448,10 +526,313 @@ struct dev_struct {
|
|||
tommy_node node;
|
||||
};
|
||||
|
||||
#if HAVE_LINUX_DEVICE
|
||||
static int devdereference_btrfs(uint64_t device, const char* dir, int fd, tommy_list* devlist)
|
||||
{
|
||||
struct btrfs_ioctl_fs_info_args fs_info;
|
||||
memset(&fs_info, 0, sizeof(fs_info));
|
||||
if (ioctl(fd, BTRFS_IOC_FS_INFO, &fs_info) < 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
if (fs_info.max_id == 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
for (__u64 i = 1; i <= fs_info.max_id; ++i) {
|
||||
struct btrfs_ioctl_dev_info_args dev_info;
|
||||
memset(&dev_info, 0, sizeof(dev_info));
|
||||
dev_info.devid = i;
|
||||
|
||||
if (ioctl(fd, BTRFS_IOC_DEV_INFO, &dev_info) != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* get major:minor, use stat on the path returned */
|
||||
struct stat st;
|
||||
if (stat((char*)dev_info.path, &st) != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
struct dev_struct* dev = malloc_nofail(sizeof(struct dev_struct));
|
||||
dev->device = st.st_rdev;
|
||||
tommy_list_insert_tail(devlist, &dev->node, dev);
|
||||
|
||||
log_tag("dereference:btrfs:%s:%u:%u:%u:%u\n", dir, major(device), minor(device), major(dev->device), minor(dev->device));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int devdereference_bcachefs(uint64_t device, const char* dir, tommy_list* devlist)
|
||||
{
|
||||
char resolved[PATH_MAX];
|
||||
char device_list[PATH_MAX * 8];
|
||||
|
||||
if (realpath(dir, resolved) == 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
FILE* fd = fopen("/proc/self/mountinfo", "r");
|
||||
if (!fd) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/*
|
||||
* mountinfo format
|
||||
* 0 - mount ID
|
||||
* 1 - parent ID
|
||||
* 2 - major:minor
|
||||
* 3 - root
|
||||
* 4 - mount point
|
||||
* 5 - options
|
||||
* 6 - "-" (separator)
|
||||
* 7 - fs
|
||||
* 8 - mount source - /dev/device
|
||||
*/
|
||||
size_t best_len = 0;
|
||||
while (1) {
|
||||
char buf[PATH_MAX * 2 + 64];
|
||||
char* id_map[8];
|
||||
unsigned id_mac;
|
||||
char* fs_map[8];
|
||||
unsigned fs_mac;
|
||||
|
||||
char* s = fgets(buf, sizeof(buf), fd);
|
||||
if (s == 0)
|
||||
break;
|
||||
|
||||
/* find the separator position */
|
||||
char* separator = strstr(s, " - ");
|
||||
if (!separator)
|
||||
continue;
|
||||
|
||||
/* skip the separator */
|
||||
*separator = 0;
|
||||
separator += 3;
|
||||
|
||||
/* split the line */
|
||||
id_mac = strsplit(id_map, 8, s, " \t\n");
|
||||
fs_mac = strsplit(fs_map, 8, separator, " \t\n");
|
||||
|
||||
/* if too short, it's the wrong line */
|
||||
if (id_mac < 5)
|
||||
continue;
|
||||
if (fs_mac < 2)
|
||||
continue;
|
||||
|
||||
/* mount point must contain the directory */
|
||||
const char* mp = id_map[4];
|
||||
size_t mp_len = strlen(mp);
|
||||
if (strncmp(resolved, mp, mp_len) != 0)
|
||||
continue;
|
||||
if (mp_len > 1 && resolved[mp_len] != '/' && resolved[mp_len] != 0)
|
||||
continue;
|
||||
|
||||
/* it should be bcachefs */
|
||||
const char* fs = fs_map[0];
|
||||
if (strcmp(fs, "bcachefs") != 0)
|
||||
continue;
|
||||
|
||||
/* keep the longest (innermost) match */
|
||||
if (mp_len > best_len) {
|
||||
best_len = mp_len;
|
||||
pathcpy(device_list, sizeof(device_list), fs_map[1]);
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fd);
|
||||
|
||||
if (best_len == 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* split the device list */
|
||||
char* dev_map[64];
|
||||
unsigned dev_mac = strsplit(dev_map, 64, device_list, ":");
|
||||
for (unsigned i = 0; i < dev_mac; ++i) {
|
||||
/* get major:minor, use stat on the path returned */
|
||||
struct stat st;
|
||||
if (stat(dev_map[i], &st) != 0)
|
||||
continue;
|
||||
|
||||
struct dev_struct* dev = malloc_nofail(sizeof(struct dev_struct));
|
||||
dev->device = st.st_rdev;
|
||||
tommy_list_insert_tail(devlist, &dev->node, dev);
|
||||
|
||||
log_tag("dereference:bcachefs:%s:%u:%u:%u:%u\n", dir, major(device), minor(device), major(dev->device), minor(dev->device));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int extract_zfs(const char* dir, char* dataset, size_t dataset_size, char* uuid, size_t uuid_size, char* mount_point, size_t mount_point_size)
|
||||
{
|
||||
char resolved[PATH_MAX];
|
||||
char cmd[PATH_MAX * 2 + 64];
|
||||
|
||||
const char* zfs = find_zfs();
|
||||
if (!zfs) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
if (realpath(dir, resolved) == 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* list all ZFS filesystems in one shot */
|
||||
snprintf(cmd, sizeof(cmd), "%s list -H -o name,guid,mountpoint -t filesystem 2>/dev/null", zfs);
|
||||
|
||||
FILE* fp = popen(cmd, "r");
|
||||
if (!fp) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
size_t best_len = 0;
|
||||
while (1) {
|
||||
char buf[PATH_MAX * 2 + 64];
|
||||
char* map[3];
|
||||
unsigned mac;
|
||||
|
||||
char* s = fgets(buf, sizeof(buf), fp);
|
||||
if (s == 0)
|
||||
break;
|
||||
|
||||
/* split the line */
|
||||
mac = strsplit(map, 3, s, " \t\n");
|
||||
|
||||
if (mac < 3)
|
||||
continue;
|
||||
|
||||
const char* mp = map[2];
|
||||
size_t mp_len = strlen(mp);
|
||||
if (strncmp(resolved, mp, mp_len) != 0)
|
||||
continue;
|
||||
if (mp_len > 1 && resolved[mp_len] != '/' && resolved[mp_len] != 0)
|
||||
continue;
|
||||
|
||||
/* keep the longest (innermost) match */
|
||||
if (mp_len > best_len) {
|
||||
best_len = mp_len;
|
||||
if (dataset)
|
||||
pathcpy(dataset, dataset_size, map[0]);
|
||||
if (uuid)
|
||||
pathcpy(uuid, uuid_size, map[1]);
|
||||
if (mount_point)
|
||||
pathcpy(mount_point, mount_point_size, map[2]);
|
||||
}
|
||||
}
|
||||
|
||||
pclose(fp);
|
||||
|
||||
if (best_len == 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int devdereference_zfs(uint64_t device, const char* dir, tommy_list* devlist)
|
||||
{
|
||||
char pool[PATH_MAX];
|
||||
char cmd[PATH_MAX * 2 + 64];
|
||||
|
||||
const char* zpool = find_zpool();
|
||||
if (!zpool) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
if (extract_zfs(dir, pool, sizeof(pool), 0, 0, 0, 0) != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* extract pool name */
|
||||
char* slash = strchr(pool, '/');
|
||||
if (slash)
|
||||
*slash = 0;
|
||||
|
||||
snprintf(cmd, sizeof(cmd), "%s status -P %s 2>/dev/null", zpool, pool);
|
||||
|
||||
FILE* fp = popen(cmd, "r");
|
||||
if (!fp) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
size_t count = 0;
|
||||
while (1) {
|
||||
char buf[PATH_MAX * 2 + 64];
|
||||
|
||||
char* s = fgets(buf, sizeof(buf), fp);
|
||||
if (s == 0)
|
||||
break;
|
||||
|
||||
char* file = strstr(s, "/dev/");
|
||||
if (!file)
|
||||
continue;
|
||||
|
||||
size_t i = 0;
|
||||
while (file[i] != 0 && file[i] != ' ' && file[i] != '\t' && file[i] != '\n')
|
||||
++i;
|
||||
file[i] = 0;
|
||||
|
||||
struct stat st;
|
||||
if (stat(file, &st) != 0)
|
||||
continue;
|
||||
|
||||
if (!S_ISBLK(st.st_mode))
|
||||
continue;
|
||||
|
||||
struct dev_struct* dev = malloc_nofail(sizeof(struct dev_struct));
|
||||
dev->device = st.st_rdev;
|
||||
tommy_list_insert_tail(devlist, &dev->node, dev);
|
||||
|
||||
log_tag("dereference:zfs:%s:%u:%u:%u:%u\n", dir, major(device), minor(device), major(dev->device), minor(dev->device));
|
||||
++count;
|
||||
}
|
||||
|
||||
pclose(fp);
|
||||
|
||||
if (count == 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the devices of a virtual device.
|
||||
*
|
||||
* This is intended to resolve the case of Btrfs filesystems that
|
||||
* This is intended to resolve the case of Btrfs/ZFS filesystems that
|
||||
* create a virtual superblock (major==0) not backed by any low
|
||||
* level device.
|
||||
*
|
||||
|
|
@ -459,85 +840,40 @@ struct dev_struct {
|
|||
* Bug 711881 - too funny btrfs st_dev numbers
|
||||
* https://bugzilla.redhat.com/show_bug.cgi?id=711881
|
||||
*/
|
||||
#if HAVE_LINUX_DEVICE
|
||||
static int devdereference(uint64_t device, const char* dir, tommy_list* devlist)
|
||||
{
|
||||
if (major(device) == 0) {
|
||||
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 != BTRFS_SUPER_MAGIC) {
|
||||
/* LCOV_EXCL_START */
|
||||
close(fd);
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
struct btrfs_ioctl_fs_info_args fs_info;
|
||||
memset(&fs_info, 0, sizeof(fs_info));
|
||||
if (ioctl(fd, BTRFS_IOC_FS_INFO, &fs_info) < 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
close(fd);
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
if (fs_info.max_id == 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
close(fd);
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
for (__u64 i = 1; i <= fs_info.max_id; ++i) {
|
||||
struct btrfs_ioctl_dev_info_args dev_info;
|
||||
memset(&dev_info, 0, sizeof(dev_info));
|
||||
dev_info.devid = i;
|
||||
|
||||
if (ioctl(fd, BTRFS_IOC_DEV_INFO, &dev_info) != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
close(fd);
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* get major:minor, use stat on the path returned */
|
||||
struct stat st;
|
||||
if (stat((char*)dev_info.path, &st) != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
close(fd);
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
struct dev_struct* dev = malloc_nofail(sizeof(struct dev_struct));
|
||||
dev->device = st.st_rdev;
|
||||
tommy_list_insert_tail(devlist, &dev->node, dev);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return 0;
|
||||
struct statfs sfs;
|
||||
int fd = open(dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
|
||||
if (fd < 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
/* insert the device itself in the list */
|
||||
struct dev_struct* dev = malloc_nofail(sizeof(struct dev_struct));
|
||||
dev->device = device;
|
||||
tommy_list_insert_tail(devlist, &dev->node, dev);
|
||||
if (fstatfs(fd, &sfs) != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
close(fd);
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
return 0;
|
||||
int ret;
|
||||
if (sfs.f_type == BTRFS_SUPER_MAGIC) {
|
||||
ret = devdereference_btrfs(device, dir, fd, devlist);
|
||||
} else if (sfs.f_type == BCACHEFS_SUPER_MAGIC) {
|
||||
ret = devdereference_bcachefs(device, dir, devlist);
|
||||
} else if (sfs.f_type == ZFS_SUPER_MAGIC) {
|
||||
ret = devdereference_zfs(device, dir, devlist);
|
||||
} else {
|
||||
/* insert the device itself in the list */
|
||||
struct dev_struct* dev = malloc_nofail(sizeof(struct dev_struct));
|
||||
dev->device = device;
|
||||
tommy_list_insert_tail(devlist, &dev->node, dev);
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
close(fd);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -1050,6 +1386,18 @@ static int devuuid_bcachefs(uint64_t device, const char* dir, char* uuid, size_t
|
|||
}
|
||||
#endif
|
||||
|
||||
#if HAVE_LINUX_DEVICE
|
||||
int devuuid_zfs(uint64_t device, const char* dir, char* uuid, size_t uuid_size)
|
||||
{
|
||||
if (extract_zfs(dir, 0, 0, uuid, uuid_size, 0, 0) != 0)
|
||||
return -1;
|
||||
|
||||
log_tag("uuid:by-zfs:%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;
|
||||
|
|
@ -1070,6 +1418,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_zfs(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;
|
||||
|
|
@ -1298,7 +1651,6 @@ int filephy(const char* path, uint64_t size, uint64_t* physical)
|
|||
#define UFS_BYTESWAPPED_SUPER_MAGIC 0x54190100
|
||||
#define VMHGFS_SUPER_MAGIC 0xBACBACBC
|
||||
#define VZFS_SUPER_MAGIC 0x565A4653
|
||||
#define ZFS_SUPER_MAGIC 0x2FC12FC1
|
||||
|
||||
struct filesystem_entry {
|
||||
unsigned id;
|
||||
|
|
@ -1534,64 +1886,18 @@ int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_har
|
|||
return 0;
|
||||
}
|
||||
|
||||
#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
|
||||
|
||||
#define SNAPSHOT_CONTAINER ".snapraid"
|
||||
|
||||
int fssnapshot(const char* path, struct fssnapshot_struct* fss)
|
||||
{
|
||||
#if HAVE_LINUX_DEVICE
|
||||
struct statfs sfs;
|
||||
struct stat st;
|
||||
static int fssnapshot_inode(const char* path, uint32_t magic, uint64_t root_inode, struct fssnapshot_struct* fss)
|
||||
{
|
||||
char current_path[PATH_MAX];
|
||||
uint64_t root_inode;
|
||||
|
||||
pathcpy(current_path, sizeof(current_path), path);
|
||||
|
||||
if (statfs(current_path, &sfs) != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_error(errno, "Error stating filesystem '%s'. %s.\n", current_path, strerror(errno));
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
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) {
|
||||
struct stat st;
|
||||
if (stat(current_path, &st) != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_error(errno, "Error stating '%s'. %s.\n", current_path, strerror(errno));
|
||||
|
|
@ -1601,6 +1907,7 @@ int fssnapshot(const char* path, struct fssnapshot_struct* fss)
|
|||
|
||||
if (st.st_ino == root_inode) {
|
||||
/* copy the subvol root */
|
||||
pathcpy(fss->root_dir, sizeof(fss->root_dir), current_path);
|
||||
pathcpy(fss->snapshot_dir, sizeof(fss->snapshot_dir), current_path);
|
||||
pathcat(fss->snapshot_dir, sizeof(fss->snapshot_dir), SNAPSHOT_CONTAINER "/");
|
||||
|
||||
|
|
@ -1611,8 +1918,7 @@ int fssnapshot(const char* path, struct fssnapshot_struct* fss)
|
|||
return -1;
|
||||
}
|
||||
|
||||
pathcpy(fss->root_dir, sizeof(fss->root_dir), current_path);
|
||||
fss->magic = sfs.f_type;
|
||||
fss->magic = magic;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -1626,6 +1932,53 @@ int fssnapshot(const char* path, struct fssnapshot_struct* fss)
|
|||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAVE_LINUX_DEVICE
|
||||
static int fssnapshot_zfs(const char* dir, uint32_t magic, struct fssnapshot_struct* fss)
|
||||
{
|
||||
if (extract_zfs(dir, fss->dataset, sizeof(fss->dataset), 0, 0, fss->root_dir, sizeof(fss->root_dir)) != 0)
|
||||
return -1;
|
||||
|
||||
pathslash(fss->root_dir, sizeof(fss->root_dir));
|
||||
pathcpy(fss->snapshot_dir, sizeof(fss->snapshot_dir), fss->root_dir);
|
||||
pathcat(fss->snapshot_dir, sizeof(fss->snapshot_dir), ".zfs/snapshot/");
|
||||
|
||||
/* ensure ZFS snapshot directory is visible */
|
||||
if (access(fss->snapshot_dir, R_OK | X_OK) != 0) {
|
||||
log_error(errno, "ZFS snapshot directory not accessible: '%s'. %s.\n", fss->snapshot_dir, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
fss->magic = magic;
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int fssnapshot(const char* path, struct fssnapshot_struct* fss)
|
||||
{
|
||||
#if HAVE_LINUX_DEVICE
|
||||
struct statfs sfs;
|
||||
|
||||
if (statfs(path, &sfs) != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
log_error(errno, "Error stating filesystem '%s'. %s.\n", path, strerror(errno));
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
if (sfs.f_type == BTRFS_SUPER_MAGIC) {
|
||||
/* btrfs reserved inode 256 for subvolume roots */
|
||||
return fssnapshot_inode(path, sfs.f_type, 256, fss);
|
||||
} else if (sfs.f_type == BCACHEFS_SUPER_MAGIC) {
|
||||
/* Bcachefs reserves inode 4096 for each subvolume's root directory */
|
||||
return fssnapshot_inode(path, sfs.f_type, 4096, fss);
|
||||
} else if (sfs.f_type == ZFS_SUPER_MAGIC) {
|
||||
return fssnapshot_zfs(path, sfs.f_type, fss);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
(void)path;
|
||||
(void)fss;
|
||||
|
|
@ -1633,6 +1986,109 @@ int fssnapshot(const char* path, struct fssnapshot_struct* fss)
|
|||
#endif
|
||||
}
|
||||
|
||||
#if HAVE_LINUX_DEVICE
|
||||
static int fssnapshot_stat_fs(const struct fssnapshot_struct* fss, const char* name, struct stat* st)
|
||||
{
|
||||
char target_path[PATH_MAX];
|
||||
|
||||
pathcpy(target_path, sizeof(target_path), fss->snapshot_dir);
|
||||
pathcat(target_path, sizeof(target_path), name);
|
||||
|
||||
if (stat(target_path, st) != 0)
|
||||
return -1;
|
||||
|
||||
/* it must be a directory */
|
||||
if (!S_ISDIR(st->st_mode))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAVE_LINUX_DEVICE
|
||||
static int fssnapshot_stat_zfs(const struct fssnapshot_struct* fss, const char* name, struct stat* st)
|
||||
{
|
||||
char target_path[PATH_MAX];
|
||||
|
||||
pathcpy(target_path, sizeof(target_path), fss->snapshot_dir);
|
||||
pathcat(target_path, sizeof(target_path), name);
|
||||
|
||||
/*
|
||||
* Ensure to get into into the snapshot
|
||||
*
|
||||
* This is required by ZFS, otherwise we get a different st_dev
|
||||
*/
|
||||
pathcat(target_path, sizeof(target_path), "/.");
|
||||
|
||||
if (stat(target_path, st) != 0)
|
||||
return -1;
|
||||
|
||||
/* it must be a directory */
|
||||
if (!S_ISDIR(st->st_mode))
|
||||
return -1;
|
||||
|
||||
/**
|
||||
* When verifying if a ZFS snapshot has been successfully deleted, a direct
|
||||
* path lookup (stat, access, open) is unreliable. A directory listing
|
||||
* (readdir) of the .zfs/snapshot directory must be used instead.
|
||||
*
|
||||
* On Linux, the Virtual File System (VFS) maintains a 'dentry cache'
|
||||
* (dcache). If a snapshot path was accessed recently, the kernel caches
|
||||
* the directory entry in RAM. When ZFS destroys the snapshot, the kernel
|
||||
* dcache is not always immediately synchronized. Consequently, stat()
|
||||
* may hit the stale cache and report the snapshot still exists.
|
||||
*
|
||||
* Unlike stat(), a directory listing (ls/readdir) forces the kernel to
|
||||
* query the ZFS module for the current set of valid entries, bypassing
|
||||
* the stale dcache for a "ground truth" check.
|
||||
*
|
||||
* This behavior can be confirmed by forcing the kernel to purge its
|
||||
* dcache manually using the following command:
|
||||
* sync; echo 2 > /proc/sys/vm/drop_caches
|
||||
*
|
||||
* After dropping caches, stat() will correctly return ENOENT. To avoid
|
||||
* requiring root privileges or impacting system performance with
|
||||
* drop_caches, always use readdir() for existence checks in this context.
|
||||
*/
|
||||
DIR* d = opendir(fss->snapshot_dir);
|
||||
if (!d) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
while (1) {
|
||||
struct dirent* entry = readdir(d);
|
||||
if (!entry)
|
||||
break;
|
||||
|
||||
if (strcmp(entry->d_name, name) == 0) {
|
||||
closedir(d);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(d);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
int fssnapshot_stat(struct fssnapshot_struct* fss, const char* name, struct stat* st)
|
||||
{
|
||||
#if HAVE_LINUX_DEVICE
|
||||
if (fss->magic == ZFS_SUPER_MAGIC) {
|
||||
return fssnapshot_stat_zfs(fss, name, st);
|
||||
} else {
|
||||
return fssnapshot_stat_fs(fss, name, st);
|
||||
}
|
||||
#else
|
||||
(void)fss;
|
||||
(void)name;
|
||||
(void)st;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if HAVE_LINUX_DEVICE
|
||||
int fssnapshot_btrfs_create(const struct fssnapshot_struct* fss, const char* name)
|
||||
{
|
||||
|
|
@ -1682,22 +2138,47 @@ int fssnapshot_btrfs_create(const struct fssnapshot_struct* fss, const char* nam
|
|||
#if HAVE_LINUX_DEVICE
|
||||
static int fssnapshot_bcachefs_create(const struct fssnapshot_struct* fss, const char* name)
|
||||
{
|
||||
char dst_path[PATH_MAX];
|
||||
char target_path[PATH_MAX];
|
||||
char cmd[PATH_MAX * 3 + 64];
|
||||
const char* bcachefs;
|
||||
int ret;
|
||||
|
||||
bcachefs = find_bcachefs();
|
||||
const char* bcachefs = find_bcachefs();
|
||||
if (!bcachefs) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
pathcpy(dst_path, sizeof(dst_path), fss->snapshot_dir);
|
||||
pathcat(dst_path, sizeof(dst_path), name);
|
||||
pathcpy(target_path, sizeof(target_path), fss->snapshot_dir);
|
||||
pathcat(target_path, sizeof(target_path), name);
|
||||
|
||||
snprintf(cmd, sizeof(cmd), "%s subvolume snapshot -r %s %s", bcachefs, fss->root_dir, dst_path);
|
||||
snprintf(cmd, sizeof(cmd), "%s subvolume snapshot -r %s %s", bcachefs, fss->root_dir, target_path);
|
||||
|
||||
ret = system(cmd);
|
||||
if (ret != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAVE_LINUX_DEVICE
|
||||
static int fssnapshot_zfs_create(const struct fssnapshot_struct* fss, const char* name)
|
||||
{
|
||||
char cmd[PATH_MAX * 2 + 64];
|
||||
int ret;
|
||||
|
||||
const char* zfs = find_zfs();
|
||||
if (!zfs) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof(cmd), "%s snapshot %s@%s", zfs, fss->dataset, name);
|
||||
|
||||
ret = system(cmd);
|
||||
if (ret != 0) {
|
||||
|
|
@ -1717,6 +2198,8 @@ int fssnapshot_create(const struct fssnapshot_struct* fss, const char* name)
|
|||
return fssnapshot_btrfs_create(fss, name);
|
||||
} else if (fss->magic == BCACHEFS_SUPER_MAGIC) {
|
||||
return fssnapshot_bcachefs_create(fss, name);
|
||||
} else if (fss->magic == ZFS_SUPER_MAGIC) {
|
||||
return fssnapshot_zfs_create(fss, name);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -1745,7 +2228,7 @@ int fssnapshot_btrfs_delete(const struct fssnapshot_struct* fss, const char* nam
|
|||
pathcpy(args.name, BTRFS_PATH_NAME_MAX, name);
|
||||
|
||||
ret = ioctl(fd_parent, BTRFS_IOC_SNAP_DESTROY, &args);
|
||||
if (ret < 0 && errno != ENOENT) {
|
||||
if (ret < 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
close(fd_parent);
|
||||
return -1;
|
||||
|
|
@ -1761,11 +2244,10 @@ int fssnapshot_btrfs_delete(const struct fssnapshot_struct* fss, const char* nam
|
|||
static int fssnapshot_bcachefs_delete(const struct fssnapshot_struct* fss, const char* name)
|
||||
{
|
||||
char target_path[PATH_MAX];
|
||||
char cmd[PATH_MAX + 64];
|
||||
const char* bcachefs;
|
||||
char cmd[PATH_MAX * 2 + 64];
|
||||
int ret;
|
||||
|
||||
bcachefs = find_bcachefs();
|
||||
const char* bcachefs = find_bcachefs();
|
||||
if (!bcachefs) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
|
|
@ -1775,15 +2257,33 @@ static int fssnapshot_bcachefs_delete(const struct fssnapshot_struct* fss, const
|
|||
pathcpy(target_path, sizeof(target_path), fss->snapshot_dir);
|
||||
pathcat(target_path, sizeof(target_path), name);
|
||||
|
||||
if (access(target_path, F_OK) < 0) {
|
||||
if (errno == ENOENT)
|
||||
return 0;
|
||||
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 */
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof(cmd), "%s subvolume delete %s", bcachefs, target_path);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAVE_LINUX_DEVICE
|
||||
static int fssnapshot_zfs_delete(const struct fssnapshot_struct* fss, const char* name)
|
||||
{
|
||||
char cmd[PATH_MAX * 2 + 64];
|
||||
int ret;
|
||||
|
||||
const char* zfs = find_zfs();
|
||||
if (!zfs) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof(cmd), "%s destroy %s@%s", zfs, fss->dataset, name);
|
||||
|
||||
ret = system(cmd);
|
||||
if (ret != 0) {
|
||||
|
|
@ -1803,6 +2303,8 @@ int fssnapshot_delete(const struct fssnapshot_struct* fss, const char* name)
|
|||
return fssnapshot_btrfs_delete(fss, name);
|
||||
} else if (fss->magic == BCACHEFS_SUPER_MAGIC) {
|
||||
return fssnapshot_bcachefs_delete(fss, name);
|
||||
} else if (fss->magic == ZFS_SUPER_MAGIC) {
|
||||
return fssnapshot_zfs_delete(fss, name);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -1853,6 +2355,32 @@ static int fssnapshot_bcachefs_rename(const struct fssnapshot_struct* fss, const
|
|||
}
|
||||
#endif
|
||||
|
||||
#if HAVE_LINUX_DEVICE
|
||||
static int fssnapshot_zfs_rename(const struct fssnapshot_struct* fss, const char* old_name, const char* new_name)
|
||||
{
|
||||
char cmd[PATH_MAX * 2 + 64];
|
||||
int ret;
|
||||
|
||||
const char* zfs = find_zfs();
|
||||
if (!zfs) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof(cmd), "%s rename %s@%s %s@%s", zfs, fss->dataset, old_name, fss->dataset, new_name);
|
||||
|
||||
ret = system(cmd);
|
||||
if (ret != 0) {
|
||||
/* LCOV_EXCL_START */
|
||||
return -1;
|
||||
/* LCOV_EXCL_STOP */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int fssnapshot_rename(const struct fssnapshot_struct* fss, const char* old_name, const char* new_name)
|
||||
{
|
||||
#if HAVE_LINUX_DEVICE
|
||||
|
|
@ -1860,6 +2388,8 @@ int fssnapshot_rename(const struct fssnapshot_struct* fss, const char* old_name,
|
|||
return fssnapshot_btrfs_rename(fss, old_name, new_name);
|
||||
} else if (fss->magic == BCACHEFS_SUPER_MAGIC) {
|
||||
return fssnapshot_bcachefs_rename(fss, old_name, new_name);
|
||||
} else if (fss->magic == ZFS_SUPER_MAGIC) {
|
||||
return fssnapshot_zfs_rename(fss, old_name, new_name);
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1435,7 +1435,7 @@ 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 or Bcachefs filesystems. Parity disks, or data disks using
|
||||
Btrfs, Bcachefs or ZFS 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
|
||||
|
|
@ -1443,15 +1443,11 @@ 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
|
||||
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
|
||||
this option is enabled.
|
||||
.PP
|
||||
See the SNAPSHOTSsection for a detailed explanation of the snapshot
|
||||
See the SNAPSHOTS section for a detailed explanation of the snapshot
|
||||
lifecycle.
|
||||
.SS nohidden
|
||||
Excludes all hidden files and directories.
|
||||
|
|
@ -1835,7 +1831,7 @@ Snapshots are created only for data disks and only if the underlying
|
|||
filesystem supports this functionality. Parity disks always use
|
||||
the live filesystem.
|
||||
.PP
|
||||
At present, this is supported exclusively on Btrfs and Bcachefs.
|
||||
At present, this is supported exclusively on Btrfs, Bcachefs, and ZFS.
|
||||
.PP
|
||||
You can mix data disks with different filesystems. Only those that
|
||||
support snapshots will utilize them, while other disks will continue
|
||||
|
|
@ -1877,8 +1873,9 @@ live filesystems exclusively.
|
|||
All other commands operate exclusively on the live filesystem.
|
||||
.SH SNAPSHOTS LIFECYCLE
|
||||
SnapRAID manages two specific snapshots, \`stable\` and \`pending\`,
|
||||
within a hidden \`.snapraid\` directory at the root of each data
|
||||
subvolume.
|
||||
within a hidden directory at the root of each data subvolume.
|
||||
In Btrfs an Bcachefs it\'s used the \`.snapraid/\` directory, in ZFS
|
||||
the standard \`.zfs/snapshot/\`.
|
||||
.PP
|
||||
The \`stable\` snapshot represents the state of the last
|
||||
successfully completed \`sync\`, containing the exact data used to
|
||||
|
|
|
|||
|
|
@ -1155,7 +1155,7 @@ Configuration
|
|||
files, preventing errors caused by concurrent file modifications.
|
||||
|
||||
This option applies exclusively to data disks formatted with the
|
||||
Btrfs or Bcachefs filesystems. Parity disks, or data disks using
|
||||
Btrfs, Bcachefs or ZFS 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
|
||||
|
|
@ -1163,15 +1163,11 @@ Configuration
|
|||
from becoming "broken" for that block, ensuring you can still successfully
|
||||
recover data if another disk fails.
|
||||
|
||||
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
|
||||
this option is enabled.
|
||||
|
||||
See the SNAPSHOTSsection for a detailed explanation of the snapshot
|
||||
See the SNAPSHOTS section for a detailed explanation of the snapshot
|
||||
lifecycle.
|
||||
|
||||
nohidden
|
||||
|
|
@ -1447,7 +1443,7 @@ Snapshots
|
|||
filesystem supports this functionality. Parity disks always use
|
||||
the live filesystem.
|
||||
|
||||
At present, this is supported exclusively on Btrfs and Bcachefs.
|
||||
At present, this is supported exclusively on Btrfs, Bcachefs, and ZFS.
|
||||
|
||||
You can mix data disks with different filesystems. Only those that
|
||||
support snapshots will utilize them, while other disks will continue
|
||||
|
|
@ -1491,8 +1487,9 @@ Snapshots
|
|||
|
||||
Snapshots Lifecycle
|
||||
SnapRAID manages two specific snapshots, `stable` and `pending`,
|
||||
within a hidden `.snapraid` directory at the root of each data
|
||||
subvolume.
|
||||
within a hidden directory at the root of each data subvolume.
|
||||
In Btrfs an Bcachefs it's used the `.snapraid/` directory, in ZFS
|
||||
the standard `.zfs/snapshot/`.
|
||||
|
||||
The `stable` snapshot represents the state of the last
|
||||
successfully completed `sync`, containing the exact data used to
|
||||
|
|
|
|||
|
|
@ -1233,7 +1233,7 @@ 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 or Bcachefs filesystems. Parity disks, or data disks using
|
||||
Btrfs, Bcachefs or ZFS 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
|
||||
|
|
@ -1241,15 +1241,11 @@ 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.
|
||||
|
||||
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
|
||||
this option is enabled.
|
||||
|
||||
See the SNAPSHOTSsection for a detailed explanation of the snapshot
|
||||
See the SNAPSHOTS section for a detailed explanation of the snapshot
|
||||
lifecycle.
|
||||
|
||||
7.8 nohidden
|
||||
|
|
@ -1552,7 +1548,7 @@ 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 and Bcachefs.
|
||||
At present, this is supported exclusively on Btrfs, Bcachefs, and ZFS.
|
||||
|
||||
You can mix data disks with different filesystems. Only those that
|
||||
support snapshots will utilize them, while other disks will continue
|
||||
|
|
@ -1601,8 +1597,9 @@ All other commands operate exclusively on the live filesystem.
|
|||
=====================
|
||||
|
||||
SnapRAID manages two specific snapshots, `stable` and `pending`,
|
||||
within a hidden `.snapraid` directory at the root of each data
|
||||
subvolume.
|
||||
within a hidden directory at the root of each data subvolume.
|
||||
In Btrfs an Bcachefs it's used the `.snapraid/` directory, in ZFS
|
||||
the standard `.zfs/snapshot/`.
|
||||
|
||||
The `stable` snapshot represents the state of the last
|
||||
successfully completed `sync`, containing the exact data used to
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ make distclean
|
|||
# Reconfigure (with force) to get the latest revision from git
|
||||
autoreconf -f
|
||||
|
||||
if ! ./configure.windows-x86; then
|
||||
if ! ./configure.windows-x86 --enable-warning-as-error; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ if ! make distwindows-x86 distclean; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
if ! ./configure.windows-x64; then
|
||||
if ! ./configure.windows-x64 --enable-warning-as-error; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ if ! make distwindows-x64 distclean; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
if ! ./configure ; then
|
||||
if ! ./configure --enable-warning-as-error; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -44,14 +44,15 @@ data d3 /mnt/disk3/
|
|||
# Format: "other DISK_NAME DISK_MOUNT_POINT"
|
||||
extra boot /
|
||||
|
||||
# Enable the use of Btrfs snapshots for sync, scrub, check, and fix commands.
|
||||
# Enable the use of filesystem snapshots for sync, scrub, check, and fix
|
||||
# commands.
|
||||
#
|
||||
# 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 or Bcachefs filesystems. Parity disks, or data disks using
|
||||
# Btrfs, Bcachefs or ZFS 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
|
||||
|
|
@ -137,4 +138,3 @@ exclude .snapraid/
|
|||
#smartctl d2 -d usbjmicron %s
|
||||
#smartctl parity -d areca,1/1 /dev/sg0
|
||||
#smartctl 2-parity -d areca,2/1 /dev/sg0
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue