diff --git a/cmdline/app.h b/cmdline/app.h index fbe8c897..751c7d6c 100644 --- a/cmdline/app.h +++ b/cmdline/app.h @@ -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. /.snapraid/) * - ZFS: virtual directory (e.g. /.zfs/snapshot/) + * - Windows: symlink container directory (e.g. /.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]; diff --git a/cmdline/mingwapp.c b/cmdline/mingwapp.c index 77451a2c..9c35b37c 100644 --- a/cmdline/mingwapp.c +++ b/cmdline/mingwapp.c @@ -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 "/"); diff --git a/cmdline/state.c b/cmdline/state.c index 8b6261b2..23bc8928 100644 --- a/cmdline/state.c +++ b/cmdline/state.c @@ -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 */ diff --git a/cmdline/unixapp.c b/cmdline/unixapp.c index 8228da56..3044a51b 100644 --- a/cmdline/unixapp.c +++ b/cmdline/unixapp.c @@ -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/");