Fix snapshot path resolution and aliased paths by storing sub_dir in fssnapshot_struct

This commit is contained in:
Andrea Mazzoleni 2026-08-25 23:09:39 +02:00
parent abcdd688dd
commit ebb199de3d
4 changed files with 68 additions and 26 deletions

View file

@ -76,8 +76,9 @@ struct fssnapshot_struct {
/**
* Root directory of the volume/dataset mountpoint.
* It ALWAYS terminates with /
* - Btrfs/Bcachefs: subvolume root
* - ZFS: dataset mountpoint
* - Btrfs/Bcachefs: subvolume root (canonical path via realpath)
* - ZFS: dataset mountpoint (canonical path via realpath)
* - Windows: volume mountpoint (via GetVolumePathName)
*/
char root_dir[PATH_MAX];
@ -86,12 +87,24 @@ struct fssnapshot_struct {
* It ALWAYS terminates with /
* - Btrfs/Bcachefs: real directory (e.g. <root>/.snapraid/)
* - ZFS: virtual directory (e.g. <root>/.zfs/snapshot/)
* - Windows: symlink container directory (e.g. <root>/.snapraid/)
*/
char snapshot_dir[PATH_MAX];
/**
* Data directory relative to root_dir in snapshots.
*
* It NEVER starts with '/'.
* It is empty if the configured data directory is exactly root_dir.
* If non-empty, it ALWAYS terminates with '/'.
* - Unix: relative suffix computed from the realpath() canonical representation
* - Windows: relative suffix computed from the configured path
*/
char sub_dir[PATH_MAX];
/*
* Dataset name for ZFS (e.g. "pool/data")
* or VolumeName in Windows.
* or VolumeName in Windows (e.g. "\\?\Volume{guid}\").
* Empty string for filesystems where not applicable.
*/
char dataset[PATH_MAX];

View file

@ -532,6 +532,9 @@ int fssnapshot_mount(const char* dir, struct fssnapshot_struct* fss)
return -1;
}
size_t root_len = strlen(fss->root_dir);
pathcpy(fss->sub_dir, sizeof(fss->sub_dir), dir + root_len);
pathcpy(fss->snapshot_dir, sizeof(fss->snapshot_dir), fss->root_dir);
pathcat(fss->snapshot_dir, sizeof(fss->snapshot_dir), SNAPSHOT_CONTAINER "/");

View file

@ -6026,7 +6026,7 @@ void state_load_ignore_file(tommy_list* filter_list, const char* path, const cha
sclose(f);
}
static int state_snapshot_dir(struct fssnapshot_struct* fss, const char* name, const char* dir, struct snapraid_disk* disk)
static int state_snapshot_dir(struct fssnapshot_struct* fss, const char* name, struct snapraid_disk* disk)
{
struct stat st;
@ -6039,7 +6039,7 @@ static int state_snapshot_dir(struct fssnapshot_struct* fss, const char* name, c
pathcpy(vol, sizeof(vol), fss->snapshot_dir);
pathcat(vol, sizeof(vol), name);
pathcatc(vol, sizeof(vol), '/');
pathcat(vol, sizeof(vol), dir);
pathcat(vol, sizeof(vol), fss->sub_dir);
pathcpy(disk->dir, sizeof(disk->dir), vol);
disk->dir_device = st.st_dev;
@ -6065,10 +6065,8 @@ int state_snapshot_scan(struct snapraid_state* state)
return -1;
}
size_t root_len = strlen(disk->fss.root_dir);
/* delete a potential previous scan snapshot */
if (state_snapshot_dir(&disk->fss, SNAPSHOT_SCAN, 0, 0) == 0) {
if (state_snapshot_dir(&disk->fss, SNAPSHOT_SCAN, 0) == 0) {
if (fssnapshot_delete(&disk->fss, SNAPSHOT_SCAN) != 0) {
log_fatal(errno, "Failed to delete scan snapshot in '%s'. %s.\n", disk->fss.snapshot_dir, strerror(errno));
return -1;
@ -6084,7 +6082,7 @@ int state_snapshot_scan(struct snapraid_state* state)
msg_progress("Created disk %s scan snapshot...\n", disk->name);
/* setup the snapshot in use */
if (state_snapshot_dir(&disk->fss, SNAPSHOT_SCAN, disk->mount_point + root_len, disk) != 0) {
if (state_snapshot_dir(&disk->fss, SNAPSHOT_SCAN, disk) != 0) {
log_error(errno, "Error stating scan snapshot '%s'. %s.\n", disk->fss.snapshot_dir, strerror(errno));
return -1;
}
@ -6107,10 +6105,8 @@ int state_snapshot_pending(struct snapraid_state* state)
if (disk->fss.magic == 0)
continue;
size_t root_len = strlen(disk->fss.root_dir);
/* delete the pending snapshot only after the content state was saved */
if (state_snapshot_dir(&disk->fss, SNAPSHOT_PENDING, 0, 0) == 0) {
if (state_snapshot_dir(&disk->fss, SNAPSHOT_PENDING, 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));
error = -1;
@ -6128,7 +6124,7 @@ int state_snapshot_pending(struct snapraid_state* state)
msg_progress("Created disk %s pending snapshot...\n", disk->name);
/* setup the snapshot in use */
if (state_snapshot_dir(&disk->fss, SNAPSHOT_PENDING, disk->mount_point + root_len, disk) != 0) {
if (state_snapshot_dir(&disk->fss, SNAPSHOT_PENDING, disk) != 0) {
log_error(errno, "Error stating pending snapshot '%s'. %s.\n", disk->fss.snapshot_dir, strerror(errno));
error = -1;
continue;
@ -6153,7 +6149,7 @@ int state_snapshot_commit(struct snapraid_state* state)
continue;
/* delete a potential previous stable snapshot */
if (state_snapshot_dir(&disk->fss, SNAPSHOT_STABLE, 0, 0) == 0) {
if (state_snapshot_dir(&disk->fss, SNAPSHOT_STABLE, 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));
error = -1;
@ -6192,12 +6188,10 @@ void state_snapshot_read(struct snapraid_state* state)
continue;
}
size_t root_len = strlen(disk->fss.root_dir);
if (state_snapshot_dir(&disk->fss, SNAPSHOT_PENDING, disk->mount_point + root_len, disk) == 0) {
if (state_snapshot_dir(&disk->fss, SNAPSHOT_PENDING, disk) == 0) {
msg_progress("Using disk %s pending snapshot...\n", disk->name);
} else {
if (state_snapshot_dir(&disk->fss, SNAPSHOT_STABLE, disk->mount_point + root_len, disk) == 0) {
if (state_snapshot_dir(&disk->fss, SNAPSHOT_STABLE, disk) == 0) {
msg_progress("Using disk %s stable snapshot...\n", disk->name);
} else {
/* fallback to standard mount point */
@ -6233,34 +6227,32 @@ void state_snapshot_write(struct snapraid_state* state, tommy_list* filterlist_d
continue;
}
size_t root_len = strlen(disk->fss.root_dir);
/* if the disk is filtered in, it could be written, then we cannot use the snapshot */
if (filter_path(filterlist_disk, 0, disk->name, 0) == 0) {
msg_progress("Using disk %s live filesystem...\n", disk->name);
continue;
}
if (state_snapshot_dir(&disk->fss, SNAPSHOT_PENDING, disk->mount_point + root_len, disk) == 0) {
if (state_snapshot_dir(&disk->fss, SNAPSHOT_PENDING, disk) == 0) {
msg_progress("Using disk %s pending snapshot...\n", disk->name);
/* if there is a dealloc list */
if (!tommy_list_empty(&disk->dealloclist)) {
/* if there is a previous snapshot */
if (state_snapshot_dir(&disk->fss, SNAPSHOT_STABLE, disk->mount_point + root_len, 0) == 0) {
if (state_snapshot_dir(&disk->fss, SNAPSHOT_STABLE, 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);
pathcat(vol, sizeof(vol), disk->fss.sub_dir);
state_dealloc(state, vol, &disk->dealloclist);
}
}
} else {
if (state_snapshot_dir(&disk->fss, SNAPSHOT_STABLE, disk->mount_point + root_len, disk) == 0) {
if (state_snapshot_dir(&disk->fss, SNAPSHOT_STABLE, disk) == 0) {
msg_progress("Using disk %s stable snapshot...\n", disk->name);
} else {
/* fallback to standard mount point */

View file

@ -2144,9 +2144,14 @@ int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_har
#if HAVE_LINUX_DEVICE
static int fssnapshot_inode(const char* path, uint32_t magic, uint64_t root_inode, struct fssnapshot_struct* fss)
{
char resolved[PATH_MAX];
char current_path[PATH_MAX];
pathcpy(current_path, sizeof(current_path), path);
if (realpath(path, resolved) == 0)
return -1;
pathslash(resolved, sizeof(resolved));
pathcpy(current_path, sizeof(current_path), resolved);
/* walk up the directory tree to find the subvolume root (Inode 256) */
while (1) {
@ -2171,6 +2176,15 @@ static int fssnapshot_inode(const char* path, uint32_t magic, uint64_t root_inod
return -1;
}
size_t root_len = strlen(fss->root_dir);
if (pathncmp(fss->root_dir, resolved, root_len) != 0) {
errno = EINVAL;
return -1;
}
pathcpy(fss->sub_dir, sizeof(fss->sub_dir), resolved + root_len);
fss->magic = magic;
return 0;
}
@ -2191,10 +2205,30 @@ static int fssnapshot_inode(const char* path, uint32_t magic, uint64_t root_inod
#if HAVE_LINUX_DEVICE
static int fssnapshot_zfs(const char* dir, uint32_t magic, struct fssnapshot_struct* fss)
{
char resolved[PATH_MAX];
char root[PATH_MAX];
if (realpath(dir, resolved) == 0)
return -1;
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));
if (realpath(fss->root_dir, root) == 0)
return -1;
pathslash(resolved, sizeof(resolved));
pathslash(root, sizeof(root));
size_t root_len = strlen(root);
if (pathncmp(root, resolved, root_len) != 0) {
errno = EINVAL;
return -1;
}
pathcpy(fss->sub_dir, sizeof(fss->sub_dir), resolved + root_len);
pathcpy(fss->root_dir, sizeof(fss->root_dir), root);
pathcpy(fss->snapshot_dir, sizeof(fss->snapshot_dir), fss->root_dir);
pathcat(fss->snapshot_dir, sizeof(fss->snapshot_dir), ".zfs/snapshot/");