Remove the requirement to snapshot content files

The new content file format now includes metadata for previous versions
of files. This allows the fix command to access historical file data from
snapshots as needed, ensuring recovery even if the live filesystem has
changed or the file was deleted during a sync.
This commit is contained in:
Andrea Mazzoleni 2026-04-28 00:35:15 +02:00
parent 69d467e9ef
commit 20cdfa7535
13 changed files with 209 additions and 368 deletions

20
HISTORY
View file

@ -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.

View file

@ -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);

View file

@ -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;

View file

@ -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. */

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)