diff --git a/HISTORY b/HISTORY index 26f4a7a5..5f17b62a 100644 --- a/HISTORY +++ b/HISTORY @@ -6,18 +6,14 @@ SnapRAID HISTORY * Added support for automated filesystem snapshots to ensure data 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 read-only 'stable' state upon - successful completion (currently supported on Btrfs). - This ensures that 'scrub', 'check', and 'fix' operations can use a - frozen point-in-time reference for data disks, eliminating mismatches - caused by concurrent live filesystem changes. - The implementation includes a "time-capsule" feature that reflinks - the .content file into the snapshot area, preserving the - metadata-to-parity correlation even if files are later deleted from - the live disk. During targeted 'check' or 'fix' operations, SnapRAID - intelligently mixes live access for the disks being repaired with - snapshot access for reference disks to maintain mathematical - integrity across the array. + of a sync and promotes it to a 'stable' state upon successful + completion (currently supported on Btrfs). + 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. + The use of snapshots significantly improves recovery success by + preserving access to files that were updated or deleted after the + last parity computation. * Implemented AVX-512BW (Byte and Word) support for parity generation and data recovery, optimizing performance on modern architectures like Intel Sapphire Rapids and AMD Zen 4/5. diff --git a/cmdline/device.c b/cmdline/device.c index 0ca1e87f..dc888bfa 100644 --- a/cmdline/device.c +++ b/cmdline/device.c @@ -1047,7 +1047,7 @@ int state_device(struct snapraid_state* state, int operation, tommy_list* filter entry = calloc_nofail(1, sizeof(devinfo_t)); - entry->device = disk->device; + entry->device = disk->mount_device; device_name_set(entry, disk->name, 0); pathcpy(entry->mount, sizeof(entry->mount), disk->dir); pathcpy(entry->smartctl, sizeof(entry->smartctl), disk->smartctl); diff --git a/cmdline/elem.c b/cmdline/elem.c index 28c0defe..df88b106 100644 --- a/cmdline/elem.c +++ b/cmdline/elem.c @@ -824,7 +824,8 @@ struct snapraid_disk* disk_alloc(const char* name, const char* dir, uint64_t dev disk->smartignore[i] = 0; disk->fstype[0] = 0; disk->fslabel[0] = 0; - disk->device = dev; + disk->mount_device = dev; + disk->dir_device = dev; disk->tick = 0; disk->cached_blocks = 0; disk->progress_file = 0; diff --git a/cmdline/elem.h b/cmdline/elem.h index e83f5d8c..5fe432d0 100644 --- a/cmdline/elem.h +++ b/cmdline/elem.h @@ -390,7 +390,8 @@ struct snapraid_disk { char fstype[FSINFO_MAX]; /**< Filesystem type */ char fslabel[FSINFO_MAX]; /**< Filesystem label */ - uint64_t device; /**< Device identifier. */ + uint64_t mount_device; /**< Device identifier of the mount_point. */ + uint64_t dir_device; /**< Device identifier of the effective dir. */ block_off_t total_blocks; /**< Number of total blocks. */ block_off_t free_blocks; /**< Number of free blocks at the last sync. */ diff --git a/cmdline/mingw.c b/cmdline/mingw.c index a3f73928..4e81fae8 100644 --- a/cmdline/mingw.c +++ b/cmdline/mingw.c @@ -2078,13 +2078,6 @@ int fssnapshot_create(const char* source, const char* parent_dir, const char* na return -1; } -int fssnapshot_readonly(const char* parent_dir, const char* name) -{ - (void)parent_dir; - (void)name; - return -1; -} - int fssnapshot_delete(const char* parent_dir, const char* name) { (void)parent_dir; @@ -2100,14 +2093,6 @@ int fssnapshot_rename(const char* parent_dir, const char* old_name, const char* return -1; } -int fssnapshot_clone(const char* source, const char* dest) -{ - (void)source; - (void)dest; - return -1; -} - - /* ensure to call the real C strerror() */ #undef strerror diff --git a/cmdline/portable.h b/cmdline/portable.h index 2733e058..d2109999 100644 --- a/cmdline/portable.h +++ b/cmdline/portable.h @@ -378,15 +378,6 @@ int fssnapshot(const char* path, char* root, size_t root_size); */ int fssnapshot_create(const char* source, const char* parent_dir, const char* name); - -/** - * Make a snapshot read-only - * @param parent_dir Directory where the snapshot is. - * @param name Name of snapshot. - * @return 0 on success, -1 on failure. - */ -int fssnapshot_readonly(const char* parent_dir, const char* name); - /** * Deletes a subvolume/snapshot. * @param parent_dir Directory where the snapshot is. @@ -401,13 +392,7 @@ int fssnapshot_delete(const char* parent_dir, const char* name); */ int fssnapshot_rename(const char* parent_dir, const char* old_name, const char* new_name); -/** - * Clone a file between snapshots - * @return 0 on success, -1 on failure. - */ -int fssnapshot_clone(const char* source, const char* dest); - -/** +/* * Log file. * * This stream if fully buffered. diff --git a/cmdline/scan.c b/cmdline/scan.c index fe1f2414..78e96001 100644 --- a/cmdline/scan.c +++ b/cmdline/scan.c @@ -1601,7 +1601,7 @@ static int scan_sub(struct snapraid_scan* scan, int level, int is_diff, char* pa * In Unix don't follow mount points in different devices * in Windows we are already skipping them reporting them as special files */ - if ((uint64_t)st->st_dev != disk->device) { + if ((uint64_t)st->st_dev != disk->dir_device) { log_tag("%s:%u:%s:%s: Ignoring mount point.\n", es(ESOFT), 0, disk->name, esc_tag(path_next)); log_error(ESOFT, "WARNING! Ignoring mount point '%s' because it appears to be in a different device\n", path_next); } else diff --git a/cmdline/state.c b/cmdline/state.c index 627d70b6..109173fa 100644 --- a/cmdline/state.c +++ b/cmdline/state.c @@ -272,7 +272,7 @@ static void state_config_check(struct snapraid_state* state, const char* path, t continue; #ifdef _WIN32 - if (disk->device == 0) { + if (disk->dir_device == 0) { /* LCOV_EXCL_START */ log_fatal(ESOFT, "Disk '%s' has a zero serial number.\n", disk->dir); log_fatal(ESOFT, "This is not necessarily wrong, but for using SnapRAID\n"); @@ -285,7 +285,7 @@ static void state_config_check(struct snapraid_state* state, const char* path, t for (j = i->next; j != 0; j = j->next) { struct snapraid_disk* other = j->data; - if (disk->device == other->device) { + if (disk->mount_device == other->mount_device) { if (state->opt.force_device) { /* * Note that we just ignore the issue @@ -298,7 +298,7 @@ static void state_config_check(struct snapraid_state* state, const char* path, t /* LCOV_EXCL_START */ log_fatal(ESOFT, "Disks '%s' and '%s' are on the same device.\n", disk->mount_point, other->mount_point); #ifdef _WIN32 - log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", disk->device); + log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", disk->dir_device); log_fatal(ESOFT, "Try using the 'VolumeID' tool by 'Mark Russinovich'\n"); log_fatal(ESOFT, "to change one of the disk serial.\n"); #endif @@ -317,7 +317,7 @@ static void state_config_check(struct snapraid_state* state, const char* path, t if (!state->opt.skip_parity_access) { for (l = 0; l < state->level; ++l) { for (s = 0; s < state->parity[l].split_mac; ++s) { - if (disk->device == state->parity[l].split_map[s].device) { + if (disk->mount_device == state->parity[l].split_map[s].device) { if (state->opt.force_device) { /* * Note that we just ignore the issue @@ -330,7 +330,7 @@ static void state_config_check(struct snapraid_state* state, const char* path, t /* LCOV_EXCL_START */ log_fatal(ESOFT, "Disk '%s' and %s '%s' are on the same device.\n", disk->mount_point, lev_name(l), state->parity[l].split_map[s].path); #ifdef _WIN32 - log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", disk->device); + log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", disk->dir_device); log_fatal(ESOFT, "Try using the 'VolumeID' tool by 'Mark Russinovich'\n"); log_fatal(ESOFT, "to change one of the disk serial.\n"); #endif @@ -342,11 +342,11 @@ static void state_config_check(struct snapraid_state* state, const char* path, t } } - if (state->pool[0] != 0 && disk->device == state->pool_device) { + if (state->pool[0] != 0 && disk->mount_device == state->pool_device) { /* LCOV_EXCL_START */ log_fatal(ESOFT, "Disk '%s' and pool '%s' are on the same device.\n", disk->mount_point, state->pool); #ifdef _WIN32 - log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", disk->device); + log_fatal(ESOFT, "Both have the serial number '%" PRIx64 "'.\n", disk->dir_device); log_fatal(ESOFT, "Try using the 'VolumeID' tool by 'Mark Russinovich'\n"); log_fatal(ESOFT, "to change one of the disk serial.\n"); #endif @@ -4559,7 +4559,7 @@ void state_read(struct snapraid_state* state) } if (state->unsynced_blocks) - msg_progress("The latest sync was interrupted!\n"); + msg_progress("WARNING! The latest sync was interrupted!\n"); /* update the mapping */ state_map(state); @@ -5814,6 +5814,24 @@ 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) +{ + struct stat st; + + if (lstat(dir, &st) != 0) + return -1; + + if (!S_ISDIR(st.st_mode)) + return -1; + + if (disk) { + pathcpy(disk->dir, sizeof(disk->dir), dir); + disk->dir_device = st.st_dev; + } + + return 0; +} + int state_snapshot_new(struct snapraid_state* state) { if (!state->snapshot) @@ -5823,6 +5841,7 @@ int state_snapshot_new(struct snapraid_state* state) struct snapraid_disk* disk = i->data; char root[PATH_MAX]; char container[PATH_MAX]; + char vol[PATH_MAX]; int ret; /* check if it supports snapshot */ @@ -5843,22 +5862,27 @@ int state_snapshot_new(struct snapraid_state* state) /* delete a potential previous pending snapshot */ if (fssnapshot_delete(container, SNAPSHOT_PENDING) != 0) { - log_fatal(errno, "Failed to delete pending snapshot in '%s' \n", container); + 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) { - log_fatal(errno, "Failed to create pending snapshot '%s'\n", container); + log_fatal(errno, "Failed to create pending snapshot '%s'. %s.\n", container, strerror(errno)); return -1; } msg_progress("Created disk %s pending snapshot...\n", disk->name); /* setup the snapshot in use */ - pathcpy(disk->dir, sizeof(disk->dir), container); - pathcat(disk->dir, sizeof(disk->dir), "/" SNAPSHOT_PENDING "/"); - pathcat(disk->dir, sizeof(disk->dir), disk->mount_point + root_len); + pathcpy(vol, sizeof(vol), container); + 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)); + return -1; + } /* store the snapshot root */ pathcpy(disk->snapshot_root, sizeof(disk->snapshot_root), root); @@ -5880,45 +5904,18 @@ int state_snapshot_commit(struct snapraid_state* state) if (disk->snapshot_root[0] == 0) continue; - size_t root_len = strlen(disk->snapshot_root); - pathcpy(container, sizeof(container), disk->snapshot_root); pathcat(container, sizeof(container), SNAPSHOT_CONTAINER); - /* for each content file */ - for (tommy_node* j = state->contentlist; j != 0; j = j->next) { - struct snapraid_content* content = j->data; - - /* check if the content file is in the subvolume */ - if (strncmp(content->content, disk->snapshot_root, root_len) == 0) { - /* clone the just written content file into the snapshot */ - char clone[PATH_MAX]; - - pathcpy(clone, sizeof(clone), container); - pathcat(clone, sizeof(clone), "/" SNAPSHOT_PENDING "/"); - pathcat(clone, sizeof(clone), content->content + root_len); - - if (fssnapshot_clone(content->content, clone) != 0) { - log_fatal(errno, "Failed to clone the content file '%s'\n", content->content); - return -1; - } - } - } - - if (fssnapshot_readonly(container, SNAPSHOT_PENDING) != 0) { - log_fatal(errno, "Failed to make pending snapshot read-only in '%s'\n", container); - return -1; - } - /* delete a potential previous stable snapshot */ if (fssnapshot_delete(container, SNAPSHOT_STABLE) != 0) { - log_fatal(errno, "Failed to delete stable snapshot in '%s'\n", container); + 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) { - log_fatal(errno, "Failed to rename snapshot in '%s'\n", container); + log_fatal(errno, "Failed to rename snapshot in '%s'. %s.\n", container, strerror(errno)); return -1; } @@ -5928,16 +5925,6 @@ int state_snapshot_commit(struct snapraid_state* state) return 0; } -static int is_dir(const char* path) -{ - struct stat st; - - if (lstat(path, &st) != 0) - return 0; - - return S_ISDIR(st.st_mode); -} - void state_snapshot_read(struct snapraid_state* state) { if (!state->snapshot) @@ -5959,21 +5946,18 @@ void state_snapshot_read(struct snapraid_state* state) pathcat(container, sizeof(container), SNAPSHOT_CONTAINER); pathcpy(vol, sizeof(vol), container); - pathcat(vol, sizeof(vol), "/" SNAPSHOT_PENDING); - if (is_dir(vol)) { + pathcat(vol, sizeof(vol), "/" SNAPSHOT_PENDING "/"); + pathcat(vol, sizeof(vol), disk->mount_point + root_len); + if (state_snapshot_dir(vol, disk) == 0) { msg_progress("Using disk %s pending snapshot...\n", disk->name); - pathcpy(disk->dir, sizeof(disk->dir), vol); - pathcat(disk->dir, sizeof(disk->dir), "/"); - pathcat(disk->dir, sizeof(disk->dir), disk->mount_point + root_len); } else { pathcpy(vol, sizeof(vol), container); - pathcat(vol, sizeof(vol), "/" SNAPSHOT_STABLE); - if (is_dir(vol)) { + pathcat(vol, sizeof(vol), "/" SNAPSHOT_STABLE "/"); + pathcat(vol, sizeof(vol), disk->mount_point + root_len); + if (state_snapshot_dir(vol, disk) == 0) { msg_progress("Using disk %s stable snapshot...\n", disk->name); - pathcpy(disk->dir, sizeof(disk->dir), vol); - pathcat(disk->dir, sizeof(disk->dir), "/"); - pathcat(disk->dir, sizeof(disk->dir), disk->mount_point + root_len); } else { + /* fallback to standard mount point */ msg_progress("Using disk %s live filesystem...\n", disk->name); } } @@ -6013,37 +5997,32 @@ void state_snapshot_write(struct snapraid_state* state, tommy_list* filterlist_d pathcat(container, sizeof(container), SNAPSHOT_CONTAINER); pathcpy(vol, sizeof(vol), container); - pathcat(vol, sizeof(vol), "/" SNAPSHOT_PENDING); - if (is_dir(vol)) { + pathcat(vol, sizeof(vol), "/" SNAPSHOT_PENDING "/"); + pathcat(vol, sizeof(vol), disk->mount_point + root_len); + if (state_snapshot_dir(vol, disk) == 0) { msg_progress("Using disk %s pending snapshot...\n", disk->name); - pathcpy(disk->dir, sizeof(disk->dir), vol); - pathcat(disk->dir, sizeof(disk->dir), "/"); - pathcat(disk->dir, sizeof(disk->dir), disk->mount_point + root_len); /* if there is a dealloc list */ if (!tommy_list_empty(&disk->dealloclist)) { pathcpy(vol, sizeof(vol), container); - pathcat(vol, sizeof(vol), "/" SNAPSHOT_STABLE); + pathcat(vol, sizeof(vol), "/" SNAPSHOT_STABLE "/"); + pathcat(vol, sizeof(vol), disk->mount_point + root_len); /* if there is a previous snapshot */ - if (is_dir(vol)) { - pathcat(vol, sizeof(vol), "/"); - pathcat(vol, sizeof(vol), disk->mount_point + root_len); - - msg_progress("Importing disk %s stable snapshot deallocated files...\n", disk->name); + 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)); state_dealloc(state, vol, &disk->dealloclist); } } } else { pathcpy(vol, sizeof(vol), container); - pathcat(vol, sizeof(vol), "/" SNAPSHOT_STABLE); - if (is_dir(vol)) { + pathcat(vol, sizeof(vol), "/" SNAPSHOT_STABLE "/"); + pathcat(vol, sizeof(vol), disk->mount_point + root_len); + if (state_snapshot_dir(vol, disk) == 0) { msg_progress("Using disk %s stable snapshot...\n", disk->name); - pathcpy(disk->dir, sizeof(disk->dir), vol); - pathcat(disk->dir, sizeof(disk->dir), "/"); - pathcat(disk->dir, sizeof(disk->dir), disk->mount_point + root_len); } else { + /* fallback to standard mount point */ msg_progress("Using disk %s live filesystem...\n", disk->name); } } diff --git a/cmdline/thermal.c b/cmdline/thermal.c index c86e5d3e..9cace80b 100644 --- a/cmdline/thermal.c +++ b/cmdline/thermal.c @@ -129,7 +129,7 @@ void state_thermal(struct snapraid_state* state, time_t now) entry = calloc_nofail(1, sizeof(devinfo_t)); - entry->device = disk->device; + entry->device = disk->mount_device; device_name_set(entry, disk->name, 0); pathcpy(entry->mount, sizeof(entry->mount), disk->mount_point); pathcpy(entry->smartctl, sizeof(entry->smartctl), disk->smartctl); diff --git a/cmdline/unix.c b/cmdline/unix.c index 2c4530a8..cd8cc3cb 100644 --- a/cmdline/unix.c +++ b/cmdline/unix.c @@ -1461,7 +1461,7 @@ int fssnapshot_create(const char* source, const char* parent_dir, const char* na memset(&args, 0, sizeof(args)); args.fd = fd_source; - args.flags = 0; + args.flags = BTRFS_SUBVOL_RDONLY; pathcpy(args.name, BTRFS_SUBVOL_NAME_MAX, name); /* issue the snapshot command to the PARENT directory of the destination */ @@ -1485,42 +1485,6 @@ int fssnapshot_create(const char* source, const char* parent_dir, const char* na #endif } -int fssnapshot_readonly(const char* parent_dir, const char* name) -{ -#if HAVE_LINUX_DEVICE - char path[PATH_MAX]; - - pathcpy(path, sizeof(path), parent_dir); - pathcatc(path, sizeof(path), '/'); - pathcat(path, sizeof(path), name); - - int fd = open(path, O_RDONLY | O_DIRECTORY); - if (fd < 0) - return -1; - - /* get existing flags to avoid overwriting other potential flags */ - __u64 flags; - if (ioctl(fd, BTRFS_IOC_SUBVOL_GETFLAGS, &flags) < 0) { - close(fd); - return -1; - } - - flags |= BTRFS_SUBVOL_RDONLY; - - if (ioctl(fd, BTRFS_IOC_SUBVOL_SETFLAGS, &flags) < 0) { - close(fd); - return -1; - } - - close(fd); - return 0; -#else - (void)parent_dir; - (void)name; - return -1; -#endif -} - int fssnapshot_delete(const char* parent_dir, const char* name) { #if HAVE_LINUX_DEVICE diff --git a/doc/snapraid.1 b/doc/snapraid.1 index b6fa214b..4711e889 100644 --- a/doc/snapraid.1 +++ b/doc/snapraid.1 @@ -1436,9 +1436,10 @@ filesystem. Parity disks, or data disks using other filesystems, will always use the live version of the filesystem. .PP When enabled, SnapRAID ensures atomic and consistent operations by -reading from a frozen point\-in\-time image. This prevents errors caused -by file modifications during execution and ensures that deleted files -remain available for parity\-based recovery of other disks. +reading from a frozen point\-in\-time image. This prevents errors +caused by file modifications during execution and ensures that +updated and deleted files remain available for parity\-based +recovery of other disks. .PP See the SNAPSHOTS section for more details. .SS nohidden @@ -1794,12 +1795,13 @@ sync or scrub will not cause parity mismatches or aborted operations, as SnapRAID sees a frozen point\-in\-time image. .HP 4 .I Recovery -If a file is deleted from the live filesystem, it remains -preserved in the snapshot. If a disk failure occurs before the -next sync is run, SnapRAID uses the data preserved in the -snapshot to reconstruct the failed disk. Without snapshots, -a deleted file on a healthy disk results in missing data -blocks that may be required to fix other failed disks. +If a file is updated or deleted from the live filesystem, +it remains preserved in the snapshot. If a disk failure occurs +before the next sync is run, SnapRAID uses the data preserved +in the snapshot to reconstruct the failed disk. +Without snapshots, an updated or deleted file on a healthy +disk results in missing data blocks that may be required to +fix other failed disks. .PD .RE .PP @@ -1813,9 +1815,9 @@ to operate directly on the live filesystem. .SS Command Behavior with Snapshots The \`sync\` and \`scrub\` commands both operate exclusively on snapshots to ensure that all data operations are performed against a consistent, -frozen state. By utilizing these snapshots, SnapRAID prevents false -positives and parity mismatches that would otherwise be triggered -by concurrent file modifications on the live filesystem. +frozen state. By utilizing these snapshots, SnapRAID prevents parity +mismatches that would otherwise be triggered by concurrent file +modifications on the live filesystem. During a \`sync\`, the command uses a new snapshot created at the start of the process to capture the current state for parity computation. In contrast, the \`scrub\` command utilizes the last snapshot, the one @@ -1837,67 +1839,38 @@ provided, SnapRAID assumes the operation applies to the entire array, in this case, \`check\` and \`fix\` will use the live filesystems exclusively. .PP All other commands operate exclusively on the live filesystem. -.SS Sync Safety -To ensure maximum data safety during a sync operation, it is -recommended to place the .content files inside the same subvolume -used for the data, ensuring they are also part of the snapshots. +.SH SNAPSHOTS LIFECYCLE +SnapRAID manages two specific snapshots, \`stable\` and \`pending\`, +within a hidden \`.snapraid\` directory at the root of each data +subvolume. .PP -When a sync starts, SnapRAID immediately updates the .content file to -reflect the current state of the filesystem. If you have deleted files -on a disk, the new .content file loses the metadata for those -deleted items and their relation to the parity. +The \`stable\` snapshot represents the state of the last +successfully completed \`sync\`, containing the exact data used to +compute the current parity. It serves as the primary data source +for \`scrub\`, \`check\`, and \`fix\` commands. .PP -If a disk fails while a \`sync\` is running, the parity for that -failed disk may still depend on the deleted files. -Even if your snapshot physically contains the deleted files, -the new .content file no longer knows how they relate to the -parity blocks. This missing correlation can prevent a full recovery. -.PP -Note that this specific risk only exists if a disk fails during -an active sync. If a disk fails between syncs, the snapshot -already contains data that is perfectly matched with the -last successfully saved content file on the live filesystem, -ensuring a successful recovery. -.PP -By storing the .content file inside the snapshotted subvolume, the -snapshot preserves an older version of the content file, and -the metadata within it, alongside the corresponding version of -the data. -.SS Snapshots Lifecycle -SnapRAID manages two specific snapshots within a hidden \`.snapraid\` -directory at the root of each data subvolume: -.RS 0 -.PD 0 -.HP 4 -.I stable -The reference snapshot representing the state of the last -successfully completed \`sync\`. This snapshot contains the exact -data used to compute the parity and serves as the primary data -source for all \`scrub\`, \`check\`, and \`fix\` commands, provided -that a pending snapshot is not present. -.HP 4 -.I pending -A temporary snapshot created at the start of a sync operation. -Upon the successful completion of a sync, the previous stable +The \`pending\` snapshot is a temporary image created at the start +of a \`sync\` to provide a frozen state for parity computation. +Upon successful completion of the operation, the previous stable snapshot is deleted, and the pending snapshot is promoted to -stable. -During this promotion, if a content file is stored on the -same subvolume of the data, the final version of the content -file is cloned (via reflink) into the snapshot. -This ensures that the snapshot contains a metadata map that -is perfectly in sync with the data blocks it preserves. -Once promoted, the stable snapshot is set to a read\-only -state to ensure its integrity as a recovery reference. -If the sync fails or is interrupted, the pending snapshot is -preserved in its current state. This allows subsequent \`scrub\`, -\`check\`, and \`fix\` commands to function correctly by using the -exact filesystem state recorded in the live content file, -even if the parity is only partially synchronized. -When sync is restarted after the interruption, the existing -pending snapshot is deleted and a new one is created, -capturing the new state of the live filesystem. -.PD -.RE +take its place as the new stable reference. +.PP +If a \`sync\` is interrupted, the \`pending\` snapshot is preserved. +Subsequent \`scrub\`, \`check\`, and \`fix\` commands will use this +pending snapshot as it matches the filesystem state recorded in +the .content file, even if the parity is only partially synchronized. +.PP +In the event of a \`sync\` interruption, \`check\` and \`fix\` commands +can also read from the \`stable\` snapshot to retrieve data blocks +from files that were updated or deleted during the sync. +This is possible because the .content file retains metadata for +all modified or deleted files throughout the sync process. +This historical information is only cleared once a sync finishes +successfully and the final version of the .content file is saved. +.PP +If a \`sync\` is restarted after an interruption, the existing +pending snapshot is deleted and a new one is created to +capture the current state of the live filesystem. .SH PATTERN Patterns are used to select a subset of files to exclude or include in the process. Globbing characters can be used to match files and paths diff --git a/doc/snapraid.d b/doc/snapraid.d index dc72f640..b2638d5b 100644 --- a/doc/snapraid.d +++ b/doc/snapraid.d @@ -1156,9 +1156,10 @@ Configuration always use the live version of the filesystem. When enabled, SnapRAID ensures atomic and consistent operations by - reading from a frozen point-in-time image. This prevents errors caused - by file modifications during execution and ensures that deleted files - remain available for parity-based recovery of other disks. + reading from a frozen point-in-time image. This prevents errors + caused by file modifications during execution and ensures that + updated and deleted files remain available for parity-based + recovery of other disks. See the SNAPSHOTS section for more details. @@ -1414,12 +1415,13 @@ Snapshots Consistency - Files modified on the live filesystem during a long-running sync or scrub will not cause parity mismatches or aborted operations, as SnapRAID sees a frozen point-in-time image. - Recovery - If a file is deleted from the live filesystem, it remains - preserved in the snapshot. If a disk failure occurs before the - next sync is run, SnapRAID uses the data preserved in the - snapshot to reconstruct the failed disk. Without snapshots, - a deleted file on a healthy disk results in missing data - blocks that may be required to fix other failed disks. + Recovery - If a file is updated or deleted from the live filesystem, + it remains preserved in the snapshot. If a disk failure occurs + before the next sync is run, SnapRAID uses the data preserved + in the snapshot to reconstruct the failed disk. + Without snapshots, an updated or deleted file on a healthy + disk results in missing data blocks that may be required to + fix other failed disks. Snapshots are created only for data disks and only if the underlying filesystem supports this functionality. Parity disks always use @@ -1432,9 +1434,9 @@ Snapshots Command Behavior with Snapshots The `sync` and `scrub` commands both operate exclusively on snapshots to ensure that all data operations are performed against a consistent, - frozen state. By utilizing these snapshots, SnapRAID prevents false - positives and parity mismatches that would otherwise be triggered - by concurrent file modifications on the live filesystem. + frozen state. By utilizing these snapshots, SnapRAID prevents parity + mismatches that would otherwise be triggered by concurrent file + modifications on the live filesystem. During a `sync`, the command uses a new snapshot created at the start of the process to capture the current state for parity computation. In contrast, the `scrub` command utilizes the last snapshot, the one @@ -1457,61 +1459,38 @@ Snapshots All other commands operate exclusively on the live filesystem. - Sync Safety - To ensure maximum data safety during a sync operation, it is - recommended to place the .content files inside the same subvolume - used for the data, ensuring they are also part of the snapshots. +Snapshots Lifecycle + SnapRAID manages two specific snapshots, `stable` and `pending`, + within a hidden `.snapraid` directory at the root of each data + subvolume. - When a sync starts, SnapRAID immediately updates the .content file to - reflect the current state of the filesystem. If you have deleted files - on a disk, the new .content file loses the metadata for those - deleted items and their relation to the parity. + The `stable` snapshot represents the state of the last + successfully completed `sync`, containing the exact data used to + compute the current parity. It serves as the primary data source + for `scrub`, `check`, and `fix` commands. - If a disk fails while a `sync` is running, the parity for that - failed disk may still depend on the deleted files. - Even if your snapshot physically contains the deleted files, - the new .content file no longer knows how they relate to the - parity blocks. This missing correlation can prevent a full recovery. + The `pending` snapshot is a temporary image created at the start + of a `sync` to provide a frozen state for parity computation. + Upon successful completion of the operation, the previous stable + snapshot is deleted, and the pending snapshot is promoted to + take its place as the new stable reference. - Note that this specific risk only exists if a disk fails during - an active sync. If a disk fails between syncs, the snapshot - already contains data that is perfectly matched with the - last successfully saved content file on the live filesystem, - ensuring a successful recovery. + If a `sync` is interrupted, the `pending` snapshot is preserved. + Subsequent `scrub`, `check`, and `fix` commands will use this + pending snapshot as it matches the filesystem state recorded in + the .content file, even if the parity is only partially synchronized. - By storing the .content file inside the snapshotted subvolume, the - snapshot preserves an older version of the content file, and - the metadata within it, alongside the corresponding version of - the data. + In the event of a `sync` interruption, `check` and `fix` commands + can also read from the `stable` snapshot to retrieve data blocks + from files that were updated or deleted during the sync. + This is possible because the .content file retains metadata for + all modified or deleted files throughout the sync process. + This historical information is only cleared once a sync finishes + successfully and the final version of the .content file is saved. - Snapshots Lifecycle - SnapRAID manages two specific snapshots within a hidden `.snapraid` - directory at the root of each data subvolume: - - stable - The reference snapshot representing the state of the last - successfully completed `sync`. This snapshot contains the exact - data used to compute the parity and serves as the primary data - source for all `scrub`, `check`, and `fix` commands, provided - that a pending snapshot is not present. - pending - A temporary snapshot created at the start of a sync operation. - Upon the successful completion of a sync, the previous stable - snapshot is deleted, and the pending snapshot is promoted to - stable. - During this promotion, if a content file is stored on the - same subvolume of the data, the final version of the content - file is cloned (via reflink) into the snapshot. - This ensures that the snapshot contains a metadata map that - is perfectly in sync with the data blocks it preserves. - Once promoted, the stable snapshot is set to a read-only - state to ensure its integrity as a recovery reference. - If the sync fails or is interrupted, the pending snapshot is - preserved in its current state. This allows subsequent `scrub`, - `check`, and `fix` commands to function correctly by using the - exact filesystem state recorded in the live content file, - even if the parity is only partially synchronized. - When sync is restarted after the interruption, the existing - pending snapshot is deleted and a new one is created, - capturing the new state of the live filesystem. + If a `sync` is restarted after an interruption, the existing + pending snapshot is deleted and a new one is created to + capture the current state of the live filesystem. Pattern Patterns are used to select a subset of files to exclude or include in diff --git a/doc/snapraid.txt b/doc/snapraid.txt index 395ccfad..0bb74177 100644 --- a/doc/snapraid.txt +++ b/doc/snapraid.txt @@ -1234,9 +1234,10 @@ filesystem. Parity disks, or data disks using other filesystems, will always use the live version of the filesystem. When enabled, SnapRAID ensures atomic and consistent operations by -reading from a frozen point-in-time image. This prevents errors caused -by file modifications during execution and ensures that deleted files -remain available for parity-based recovery of other disks. +reading from a frozen point-in-time image. This prevents errors +caused by file modifications during execution and ensures that +updated and deleted files remain available for parity-based +recovery of other disks. See the SNAPSHOTS section for more details. @@ -1519,12 +1520,13 @@ This provides two primary benefits: Consistency - Files modified on the live filesystem during a long-running sync or scrub will not cause parity mismatches or aborted operations, as SnapRAID sees a frozen point-in-time image. -Recovery - If a file is deleted from the live filesystem, it remains - preserved in the snapshot. If a disk failure occurs before the - next sync is run, SnapRAID uses the data preserved in the - snapshot to reconstruct the failed disk. Without snapshots, - a deleted file on a healthy disk results in missing data - blocks that may be required to fix other failed disks. +Recovery - If a file is updated or deleted from the live filesystem, + it remains preserved in the snapshot. If a disk failure occurs + before the next sync is run, SnapRAID uses the data preserved + in the snapshot to reconstruct the failed disk. + Without snapshots, an updated or deleted file on a healthy + disk results in missing data blocks that may be required to + fix other failed disks. Snapshots are created only for data disks and only if the underlying filesystem supports this functionality. Parity disks always use @@ -1539,9 +1541,9 @@ to operate directly on the live filesystem. The `sync` and `scrub` commands both operate exclusively on snapshots to ensure that all data operations are performed against a consistent, -frozen state. By utilizing these snapshots, SnapRAID prevents false -positives and parity mismatches that would otherwise be triggered -by concurrent file modifications on the live filesystem. +frozen state. By utilizing these snapshots, SnapRAID prevents parity +mismatches that would otherwise be triggered by concurrent file +modifications on the live filesystem. During a `sync`, the command uses a new snapshot created at the start of the process to capture the current state for parity computation. In contrast, the `scrub` command utilizes the last snapshot, the one @@ -1564,69 +1566,45 @@ in this case, `check` and `fix` will use the live filesystems exclusively. All other commands operate exclusively on the live filesystem. -8.2 Sync Safety ---------------- -To ensure maximum data safety during a sync operation, it is -recommended to place the .content files inside the same subvolume -used for the data, ensuring they are also part of the snapshots. +9 SNAPSHOTS LIFECYCLE +===================== -When a sync starts, SnapRAID immediately updates the .content file to -reflect the current state of the filesystem. If you have deleted files -on a disk, the new .content file loses the metadata for those -deleted items and their relation to the parity. +SnapRAID manages two specific snapshots, `stable` and `pending`, +within a hidden `.snapraid` directory at the root of each data +subvolume. -If a disk fails while a `sync` is running, the parity for that -failed disk may still depend on the deleted files. -Even if your snapshot physically contains the deleted files, -the new .content file no longer knows how they relate to the -parity blocks. This missing correlation can prevent a full recovery. +The `stable` snapshot represents the state of the last +successfully completed `sync`, containing the exact data used to +compute the current parity. It serves as the primary data source +for `scrub`, `check`, and `fix` commands. -Note that this specific risk only exists if a disk fails during -an active sync. If a disk fails between syncs, the snapshot -already contains data that is perfectly matched with the -last successfully saved content file on the live filesystem, -ensuring a successful recovery. +The `pending` snapshot is a temporary image created at the start +of a `sync` to provide a frozen state for parity computation. +Upon successful completion of the operation, the previous stable +snapshot is deleted, and the pending snapshot is promoted to +take its place as the new stable reference. -By storing the .content file inside the snapshotted subvolume, the -snapshot preserves an older version of the content file, and -the metadata within it, alongside the corresponding version of -the data. +If a `sync` is interrupted, the `pending` snapshot is preserved. +Subsequent `scrub`, `check`, and `fix` commands will use this +pending snapshot as it matches the filesystem state recorded in +the .content file, even if the parity is only partially synchronized. -8.3 Snapshots Lifecycle ------------------------ +In the event of a `sync` interruption, `check` and `fix` commands +can also read from the `stable` snapshot to retrieve data blocks +from files that were updated or deleted during the sync. +This is possible because the .content file retains metadata for +all modified or deleted files throughout the sync process. +This historical information is only cleared once a sync finishes +successfully and the final version of the .content file is saved. -SnapRAID manages two specific snapshots within a hidden `.snapraid` -directory at the root of each data subvolume: - -stable - The reference snapshot representing the state of the last - successfully completed `sync`. This snapshot contains the exact - data used to compute the parity and serves as the primary data - source for all `scrub`, `check`, and `fix` commands, provided - that a pending snapshot is not present. -pending - A temporary snapshot created at the start of a sync operation. - Upon the successful completion of a sync, the previous stable - snapshot is deleted, and the pending snapshot is promoted to - stable. - During this promotion, if a content file is stored on the - same subvolume of the data, the final version of the content - file is cloned (via reflink) into the snapshot. - This ensures that the snapshot contains a metadata map that - is perfectly in sync with the data blocks it preserves. - Once promoted, the stable snapshot is set to a read-only - state to ensure its integrity as a recovery reference. - If the sync fails or is interrupted, the pending snapshot is - preserved in its current state. This allows subsequent `scrub`, - `check`, and `fix` commands to function correctly by using the - exact filesystem state recorded in the live content file, - even if the parity is only partially synchronized. - When sync is restarted after the interruption, the existing - pending snapshot is deleted and a new one is created, - capturing the new state of the live filesystem. +If a `sync` is restarted after an interruption, the existing +pending snapshot is deleted and a new one is created to +capture the current state of the live filesystem. -9 PATTERN -========= +10 PATTERN +========== Patterns are used to select a subset of files to exclude or include in the process. Globbing characters can be used to match files and paths @@ -1743,7 +1721,7 @@ In Unix, when using globbing characters on the command line, you must quote them to prevent the shell from expanding them. -10 IGNORE FILE +11 IGNORE FILE ============== In addition to the global rules in the configuration file, you can @@ -1788,7 +1766,7 @@ For example, if you have a `.snapraidignore` in `/mnt/disk1/projects/`: *.tmp -11 CONTENT +12 CONTENT ========== SnapRAID stores the list and checksums of your files in the content file. @@ -1800,7 +1778,7 @@ This file is read and written by the `sync` and `scrub` commands and read by the `fix`, `check`, and `status` commands. -12 PARITY +13 PARITY ========= SnapRAID stores the parity information of your array in the parity @@ -1813,7 +1791,7 @@ These files are read and written by the `sync` and `fix` commands and only read by the `scrub` and `check` commands. -13 ENCODING +14 ENCODING =========== SnapRAID in Unix ignores any encoding. It reads and stores the @@ -1830,7 +1808,7 @@ redirect the console output to a file, the resulting file is always in UTF-8 format. -14 EXIT CODE +15 EXIT CODE ============ SnapRAID terminates with the following error codes: @@ -1841,13 +1819,13 @@ SnapRAID terminates with the following error codes: needed. -15 COPYRIGHT +16 COPYRIGHT ============ This file is Copyright (C) 2026 Andrea Mazzoleni -16 SEE ALSO +17 SEE ALSO =========== snapraid_log(1), snapraidd(1), rsync(1)