mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-25 18:40:19 -04:00
Add Windows VirtualShadowCopy support for snapshots
This commit is contained in:
parent
38f78154f9
commit
ab72f1cfc8
8 changed files with 468 additions and 54 deletions
10
HISTORY
10
HISTORY
|
|
@ -4,10 +4,12 @@ SnapRAID HISTORY
|
|||
15.0 WIP
|
||||
========
|
||||
* 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 'stable' state upon successful
|
||||
completion (currently supported on Btrfs, Bcachefs, and ZFS).
|
||||
consistency and improve recovery reliability.
|
||||
When the 'snapshot' option is enabled, SnapRAID transparently manages
|
||||
filesystem snapshots during operations, requiring no changes to the
|
||||
existing user workflow or file management.
|
||||
Supported on Btrfs, Bcachefs, and ZFS in Linux, and on NTFS in
|
||||
Windows.
|
||||
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.
|
||||
|
|
|
|||
443
cmdline/mingw.c
443
cmdline/mingw.c
|
|
@ -2078,43 +2078,6 @@ int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_har
|
|||
return 0;
|
||||
}
|
||||
|
||||
int fssnapshot(const char* path, struct fssnapshot_struct* fss)
|
||||
{
|
||||
(void)path;
|
||||
(void)fss;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fssnapshot_stat(struct fssnapshot_struct* fss, const char* name, struct stat* st)
|
||||
{
|
||||
(void)fss;
|
||||
(void)name;
|
||||
(void)st;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fssnapshot_create(const struct fssnapshot_struct* fss, const char* name)
|
||||
{
|
||||
(void)fss;
|
||||
(void)name;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fssnapshot_delete(const struct fssnapshot_struct* fss, const char* name)
|
||||
{
|
||||
(void)fss;
|
||||
(void)name;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fssnapshot_rename(const struct fssnapshot_struct* fss, const char* old_name, const char* new_name)
|
||||
{
|
||||
(void)fss;
|
||||
(void)old_name;
|
||||
(void)new_name;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* ensure to call the real C strerror() */
|
||||
#undef strerror
|
||||
|
||||
|
|
@ -2338,6 +2301,412 @@ size_t windows_direct_size(void)
|
|||
return si.dwPageSize;
|
||||
}
|
||||
|
||||
#define WINDOWS_NTFS_MAGIC 0x5346544E /* 'N','T','F','S' */
|
||||
|
||||
#define PS_CMD_MAX 4096
|
||||
|
||||
#define SNAPSHOT_GUID ".guid"
|
||||
|
||||
/*
|
||||
* PowerShell helper
|
||||
*
|
||||
* Runs a PowerShell one-liner via _popen() and captures the first line
|
||||
* of stdout into `out` as UTF-8.
|
||||
* `out` may be NULL when output is not needed.
|
||||
*
|
||||
* The caller builds `command` as a string using snprintf().
|
||||
* Any embedded path literals must use single quotes so they survive
|
||||
* the outer double-quote wrapping passed to cmd.exe.
|
||||
*
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
static int windows_ps(const char* ps_command, char* out, size_t out_size)
|
||||
{
|
||||
char cmd[PS_CMD_MAX];
|
||||
FILE* fp;
|
||||
int ret;
|
||||
|
||||
ret = snprintf(cmd, sizeof(cmd), "powershell.exe -NoProfile -NonInteractive -Command \"%s\" 2>nul", ps_command);
|
||||
if (ret < 0 || ret >= (int)sizeof(cmd)) {
|
||||
errno = EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
fp = _popen(cmd, "r");
|
||||
if (!fp) {
|
||||
windows_errno(GetLastError());
|
||||
log_error(errno, "Failed to run PowerShell command '%s' (from popen).\n", cmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (out && out_size > 0) {
|
||||
/* get the first line */
|
||||
if (!fgets(out, (int)out_size, fp)) {
|
||||
out[0] = 0;
|
||||
} else {
|
||||
/* trim spaces and newlines */
|
||||
strtrim(out);
|
||||
}
|
||||
}
|
||||
|
||||
ret = _pclose(fp);
|
||||
if (ret == -1) {
|
||||
errno = EINVAL;
|
||||
log_error(errno, "Failed to run PowerShell command '%s' (from pclose).\n", cmd);
|
||||
return -1;
|
||||
}
|
||||
if (ret != 0) {
|
||||
errno = EINVAL;
|
||||
log_error(errno, "PowerShell command '%s' failed with '%d' (from pclose).\n", cmd, ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int windows_read_guid(const char* guid_link, char* guid, size_t guid_size)
|
||||
{
|
||||
int len = windows_readlink(guid_link, guid, guid_size);
|
||||
if (len < 0 && errno == ENOENT) {
|
||||
guid[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
if (len < 0) {
|
||||
log_error(errno, "Error readlink '%s'. %s.\n", guid_link, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((size_t)len >= guid_size) {
|
||||
errno = ENAMETOOLONG;
|
||||
log_error(errno, "GUID too long in '%s'.\n", guid_link);
|
||||
return -1;
|
||||
}
|
||||
|
||||
guid[len] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int windows_rebuild_link(const struct fssnapshot_struct* fss, const char* name)
|
||||
{
|
||||
char cmd[PS_CMD_MAX];
|
||||
char device_path[PATH_MAX];
|
||||
char target_path[PATH_MAX];
|
||||
char link_path[PATH_MAX];
|
||||
char guid_link[PATH_MAX];
|
||||
char guid[PATH_MAX];
|
||||
|
||||
pathcpy(guid_link, sizeof(guid_link), fss->snapshot_dir);
|
||||
pathcat(guid_link, sizeof(guid_link), name);
|
||||
pathcat(guid_link, sizeof(guid_link), SNAPSHOT_GUID);
|
||||
|
||||
pathcpy(link_path, sizeof(link_path), fss->snapshot_dir);
|
||||
pathcat(link_path, sizeof(link_path), name);
|
||||
|
||||
int ret = windows_read_guid(guid_link, guid, sizeof(guid));
|
||||
if (ret != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* if no GUID, assume it absent */
|
||||
if (guid[0] == 0) {
|
||||
/* remove stale links */
|
||||
windows_rmdir(link_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"(Get-WmiObject Win32_ShadowCopy | Where-Object {$_.ID -eq '%s'}).DeviceObject",
|
||||
guid);
|
||||
|
||||
if (windows_ps(cmd, device_path, sizeof(device_path)) != 0) {
|
||||
log_error(errno, "Error getting DeviceObject from GUID '%s'. %s.\n", guid, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* if no device path, assume snapshot absent */
|
||||
if (device_path[0] == 0) {
|
||||
/* remove stale links */
|
||||
windows_rmdir(guid_link);
|
||||
windows_rmdir(link_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
pathimport(target_path, sizeof(target_path), device_path);
|
||||
pathslash(target_path, sizeof(target_path));
|
||||
|
||||
/* remove stale link if present */
|
||||
windows_rmdir(link_path);
|
||||
|
||||
if (windows_symlink_directory(target_path, link_path) != 0) {
|
||||
log_error(errno, "Error creating symlink '%s'. %s.\n", link_path, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fssnapshot(const char* dir, struct fssnapshot_struct* fss)
|
||||
{
|
||||
wchar_t conv_buf_vol[CONV_MAX];
|
||||
char conv_buf_root[CONV_MAX];
|
||||
wchar_t volume_root[PATH_MAX];
|
||||
wchar_t volume_name[PATH_MAX];
|
||||
wchar_t fs_name[32];
|
||||
uint32_t magic;
|
||||
|
||||
/*
|
||||
* GetVolumePathNameW() accepts any path: file, directory, or deep
|
||||
* subdirectory, and resolves it to the volume root (e.g. L"C:\").
|
||||
* The result always carries a trailing backslash.
|
||||
*
|
||||
* Use convert_if_required() to avoid the automatic addition of \\?\
|
||||
* made by convert() that is propagated in the resulting volume_root
|
||||
*/
|
||||
if (!GetVolumePathNameW(convert_if_required(conv_buf_vol, dir), volume_root, PATH_MAX)) {
|
||||
windows_errno(GetLastError());
|
||||
log_error(errno, "Error getting VolumeRoot from '%s'. %s.\n", dir, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!GetVolumeInformationW(volume_root, 0, 0, 0, 0, 0, fs_name, 32)) {
|
||||
windows_errno(GetLastError());
|
||||
log_error(errno, "Error getting information of VolumeRoot '%s'. %s.\n", u16tou8(conv_buf_root, volume_root), strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (wcscmp(fs_name, L"NTFS") == 0)
|
||||
magic = WINDOWS_NTFS_MAGIC;
|
||||
else
|
||||
return -1; /* support only NTFS */
|
||||
|
||||
/*
|
||||
* Obtain the canonical volume name: \\?\Volume{GUID}\
|
||||
*
|
||||
* GetVolumePathNameW() may return a drive letter path ("C:\") or a
|
||||
* directory mount point ("D:\Mount\"). Neither is guaranteed to be
|
||||
* accepted by Win32_ShadowCopy.Create() on volumes without a drive
|
||||
* letter. GetVolumeNameForVolumeMountPointW() always returns the
|
||||
* stable kernel-form GUID path that VSS requires, regardless of how
|
||||
* many (or how few) mount points the volume has.
|
||||
*
|
||||
* We store it in fss->dataset.
|
||||
*/
|
||||
if (!GetVolumeNameForVolumeMountPointW(volume_root, volume_name, PATH_MAX)) {
|
||||
windows_errno(GetLastError());
|
||||
log_error(errno, "Error getting VolumeName of VolumeRoot '%s'. %s.\n", u16tou8(conv_buf_root, volume_root), strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* don't use pathimport for dataset to keep backslashes */
|
||||
pathcpy(fss->dataset, sizeof(fss->dataset), u16tou8(conv_buf_root, volume_name));
|
||||
|
||||
/* use pathimport to convert backslashes to slashes */
|
||||
pathimport(fss->root_dir, sizeof(fss->root_dir), u16tou8(conv_buf_root, volume_root));
|
||||
|
||||
/* the returned root_dir should match the start of the passed dir */
|
||||
if (pathncmp(fss->root_dir, dir, strlen(fss->root_dir)) != 0) {
|
||||
errno = EINVAL;
|
||||
log_error(errno, "Not matching VolumeRoot '%s' for '%s'.\n", fss->root_dir, dir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pathcpy(fss->snapshot_dir, sizeof(fss->snapshot_dir), fss->root_dir);
|
||||
pathcat(fss->snapshot_dir, sizeof(fss->snapshot_dir), SNAPSHOT_CONTAINER "/");
|
||||
|
||||
if (windows_mkdir(fss->snapshot_dir) != 0 && errno != EEXIST) {
|
||||
log_error(errno, "Error creating directory '%s'. %s.\n", fss->snapshot_dir, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* refresh the links, after reboots they gets invalidated */
|
||||
if (windows_rebuild_link(fss, SNAPSHOT_PENDING) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (windows_rebuild_link(fss, SNAPSHOT_STABLE) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
fss->magic = magic;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fssnapshot_stat(struct fssnapshot_struct* fss, const char* name, struct stat* st)
|
||||
{
|
||||
char link_path[PATH_MAX];
|
||||
|
||||
pathcpy(link_path, sizeof(link_path), fss->snapshot_dir);
|
||||
pathcat(link_path, sizeof(link_path), name);
|
||||
|
||||
/* use lstat because we want to check the link */
|
||||
if (windows_lstat(link_path, st) != 0)
|
||||
return -1;
|
||||
|
||||
/* it must be a link to a directory */
|
||||
if (!S_ISLNKDIR(st->st_mode))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fssnapshot_create(const struct fssnapshot_struct* fss, const char* name)
|
||||
{
|
||||
char cmd[PS_CMD_MAX];
|
||||
char device_path[PATH_MAX];
|
||||
char target_path[PATH_MAX];
|
||||
char link_path[PATH_MAX];
|
||||
char guid_link[PATH_MAX];
|
||||
char out[PATH_MAX];
|
||||
|
||||
/* create VSS shadow copy and capture the system-assigned GUID */
|
||||
snprintf(cmd, sizeof(cmd),
|
||||
"$r = (Get-WmiObject -List Win32_ShadowCopy).Create('%s', 'ClientAccessible'); "
|
||||
"if ($r.ReturnValue -eq 0) { "
|
||||
"Write-Output ('ID_' + $r.ShadowID); "
|
||||
"} else { "
|
||||
"Write-Output ('0x{0:X8}' -f $r.ReturnValue); "
|
||||
"}",
|
||||
fss->dataset);
|
||||
if (windows_ps(cmd, out, sizeof(out)) != 0) {
|
||||
log_error(errno, "Error creating snapshot of VolumeName '%s'. %s.\n", fss->dataset, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
if (strncmp(out, "ID_", 3) != 0) {
|
||||
errno = ENODATA;
|
||||
log_error(errno, "VSS snapshot creation of '%s' failed with error %s.\n", fss->dataset, out);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* guid = out + 3;
|
||||
|
||||
/* resolve the shadow copy's device object path */
|
||||
snprintf(cmd, sizeof(cmd), "(Get-WmiObject Win32_ShadowCopy | Where-Object {$_.ID -eq '%s'}).DeviceObject", guid);
|
||||
if (windows_ps(cmd, device_path, sizeof(device_path)) != 0) {
|
||||
log_error(errno, "Error getting the snapshot DeviceObject from GUID '%s'. %s.\n", guid, strerror(errno));
|
||||
goto bail_and_delete;
|
||||
}
|
||||
if (device_path[0] == 0) {
|
||||
errno = ENODATA;
|
||||
log_error(errno, "Empty snapshot DeviceObject of GUID '%s'. %s.\n", guid, strerror(errno));
|
||||
goto bail_and_delete;
|
||||
}
|
||||
|
||||
/* use pathimport to convert backslashes to slashes */
|
||||
pathimport(target_path, sizeof(target_path), device_path);
|
||||
|
||||
/* the ending slash is required, otherwise the link won't work */
|
||||
pathslash(target_path, sizeof(target_path));
|
||||
|
||||
pathcpy(link_path, sizeof(link_path), fss->snapshot_dir);
|
||||
pathcat(link_path, sizeof(link_path), name);
|
||||
|
||||
int ret = windows_symlink_directory(target_path, link_path);
|
||||
if (ret != 0) {
|
||||
log_error(errno, "Error creating symlink '%s'. %s.\n", link_path, strerror(errno));
|
||||
goto bail_and_delete;
|
||||
}
|
||||
|
||||
pathcpy(guid_link, sizeof(guid_link), fss->snapshot_dir);
|
||||
pathcat(guid_link, sizeof(guid_link), name);
|
||||
pathcat(guid_link, sizeof(guid_link), SNAPSHOT_GUID);
|
||||
|
||||
/* store GUID persistently as symlink target */
|
||||
ret = windows_symlink_directory(guid, guid_link);
|
||||
if (ret != 0) {
|
||||
log_error(errno, "Error creating GUID symlink '%s'. %s.\n", guid_link, strerror(errno));
|
||||
windows_rmdir(link_path);
|
||||
goto bail_and_delete;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
bail_and_delete:
|
||||
/* destroy the shadow copy we just created */
|
||||
snprintf(cmd, sizeof(cmd), "Get-WmiObject Win32_ShadowCopy | Where-Object {$_.ID -eq '%s'} | Remove-WmiObject", guid);
|
||||
windows_ps(cmd, 0, 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int fssnapshot_delete(const struct fssnapshot_struct* fss, const char* name)
|
||||
{
|
||||
char link_path[PATH_MAX];
|
||||
char guid[PATH_MAX];
|
||||
char cmd[PS_CMD_MAX];
|
||||
char guid_link[PATH_MAX];
|
||||
|
||||
pathcpy(guid_link, sizeof(guid_link), fss->snapshot_dir);
|
||||
pathcat(guid_link, sizeof(guid_link), name);
|
||||
pathcat(guid_link, sizeof(guid_link), SNAPSHOT_GUID);
|
||||
|
||||
pathcpy(link_path, sizeof(link_path), fss->snapshot_dir);
|
||||
pathcat(link_path, sizeof(link_path), name);
|
||||
|
||||
if (windows_read_guid(guid_link, guid, sizeof(guid)) != 0)
|
||||
return -1;
|
||||
|
||||
/* if the GUID link is gone, assume everything is gone */
|
||||
if (guid[0] == 0) {
|
||||
/* remove as not valid anymore */
|
||||
windows_rmdir(link_path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* destroy the VSS shadow copy */
|
||||
snprintf(cmd, sizeof(cmd), "Get-WmiObject Win32_ShadowCopy | Where-Object {$_.ID -eq '%s'} | Remove-WmiObject", guid);
|
||||
int ret = windows_ps(cmd, 0, 0);
|
||||
if (ret != 0) {
|
||||
log_error(errno, "Error destroy the snapshot from GUID '%s'. %s.\n", guid, strerror(errno));
|
||||
/* remove as not valid anymore */
|
||||
windows_rmdir(link_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* remove live symlink if present */
|
||||
windows_rmdir(link_path);
|
||||
|
||||
/* remove persistent GUID symlink */
|
||||
if (windows_rmdir(guid_link) != 0) {
|
||||
log_error(errno, "Error rmdir '%s'. %s.\n", guid_link, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fssnapshot_rename(const struct fssnapshot_struct* fss, const char* old_name, const char* new_name)
|
||||
{
|
||||
char old_link[PATH_MAX];
|
||||
char new_link[PATH_MAX];
|
||||
char old_guid[PATH_MAX];
|
||||
char new_guid[PATH_MAX];
|
||||
|
||||
pathcpy(old_link, sizeof(old_link), fss->snapshot_dir);
|
||||
pathcat(old_link, sizeof(old_link), old_name);
|
||||
pathcpy(new_link, sizeof(new_link), fss->snapshot_dir);
|
||||
pathcat(new_link, sizeof(new_link), new_name);
|
||||
|
||||
pathcpy(old_guid, sizeof(old_guid), fss->snapshot_dir);
|
||||
pathcat(old_guid, sizeof(old_guid), old_name);
|
||||
pathcat(old_guid, sizeof(old_guid), SNAPSHOT_GUID);
|
||||
pathcpy(new_guid, sizeof(new_guid), fss->snapshot_dir);
|
||||
pathcat(new_guid, sizeof(new_guid), new_name);
|
||||
pathcat(new_guid, sizeof(new_guid), SNAPSHOT_GUID);
|
||||
|
||||
if (windows_rename(old_link, new_link) != 0) {
|
||||
log_error(errno, "Error renaming '%s' to '%s'. %s.\n", old_link, new_link, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (windows_rename(old_guid, new_guid) != 0) {
|
||||
log_error(errno, "Error renaming '%s' to '%s'. %s.\n", old_guid, new_guid, strerror(errno));
|
||||
/* try to restore */
|
||||
windows_rename(new_link, old_link);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t os_tick(void)
|
||||
{
|
||||
LARGE_INTEGER t;
|
||||
|
|
|
|||
|
|
@ -363,6 +363,17 @@ int filephy(const char* path, uint64_t size, uint64_t* physical);
|
|||
*/
|
||||
int fsinfo(const char* path, int* has_persistent_inode, int* has_syncronized_hardlinks, uint64_t* total_space, uint64_t* free_space, char* fstype, size_t fstype_size, char* fslabel, size_t fslabel_size);
|
||||
|
||||
/**
|
||||
* Where snapshot are accessible
|
||||
*/
|
||||
#define SNAPSHOT_CONTAINER ".snapraid"
|
||||
|
||||
/**
|
||||
* Snapshots names
|
||||
*/
|
||||
#define SNAPSHOT_PENDING "pending"
|
||||
#define SNAPSHOT_STABLE "stable"
|
||||
|
||||
/**
|
||||
* Snapshots context
|
||||
*/
|
||||
|
|
@ -390,7 +401,8 @@ struct fssnapshot_struct {
|
|||
char snapshot_dir[PATH_MAX];
|
||||
|
||||
/*
|
||||
* Dataset name (ZFS only, e.g. "pool/data").
|
||||
* Dataset name for ZFS (e.g. "pool/data")
|
||||
* or VolumeName in Windows.
|
||||
* Empty string for filesystems where not applicable.
|
||||
*/
|
||||
char dataset[PATH_MAX];
|
||||
|
|
|
|||
|
|
@ -5810,9 +5810,6 @@ void state_load_ignore_file(tommy_list* filter_list, const char* path, const cha
|
|||
sclose(f);
|
||||
}
|
||||
|
||||
#define SNAPSHOT_PENDING "pending"
|
||||
#define SNAPSHOT_STABLE "stable"
|
||||
|
||||
static int state_snapshot_dir(struct fssnapshot_struct* fss, const char* name, const char* dir, struct snapraid_disk* disk)
|
||||
{
|
||||
struct stat st;
|
||||
|
|
|
|||
|
|
@ -1435,8 +1435,9 @@ start of a \'sync\'. This ensures a consistent, point\-in\-time view of your
|
|||
files, preventing errors caused by concurrent file modifications.
|
||||
.PP
|
||||
This option applies exclusively to data disks formatted with the
|
||||
Btrfs, Bcachefs or ZFS filesystems. Parity disks, or data disks using
|
||||
other filesystems, will always use the live version of the filesystem.
|
||||
Btrfs, Bcachefs or ZFS filesystems in Linux and NTFS in Windows.
|
||||
Parity disks, or data disks using other filesystems, will always use
|
||||
the live version of the filesystem.
|
||||
.PP
|
||||
This significantly improves recovery: if a file is deleted from the live
|
||||
filesystem, it remains preserved in the snapshot. This prevents the parity
|
||||
|
|
@ -1831,7 +1832,8 @@ Snapshots are created only for data disks and only if the underlying
|
|||
filesystem supports this functionality. Parity disks always use
|
||||
the live filesystem.
|
||||
.PP
|
||||
At present, this is supported exclusively on Btrfs, Bcachefs, and ZFS.
|
||||
At present, this is supported on Btrfs, Bcachefs, and ZFS in Linux
|
||||
and on NTFS in Windows.
|
||||
.PP
|
||||
You can mix data disks with different filesystems. Only those that
|
||||
support snapshots will utilize them, while other disks will continue
|
||||
|
|
|
|||
|
|
@ -1155,8 +1155,9 @@ Configuration
|
|||
files, preventing errors caused by concurrent file modifications.
|
||||
|
||||
This option applies exclusively to data disks formatted with the
|
||||
Btrfs, Bcachefs or ZFS filesystems. Parity disks, or data disks using
|
||||
other filesystems, will always use the live version of the filesystem.
|
||||
Btrfs, Bcachefs or ZFS filesystems in Linux and NTFS in Windows.
|
||||
Parity disks, or data disks using other filesystems, will always use
|
||||
the live version of the filesystem.
|
||||
|
||||
This significantly improves recovery: if a file is deleted from the live
|
||||
filesystem, it remains preserved in the snapshot. This prevents the parity
|
||||
|
|
@ -1443,7 +1444,8 @@ Snapshots
|
|||
filesystem supports this functionality. Parity disks always use
|
||||
the live filesystem.
|
||||
|
||||
At present, this is supported exclusively on Btrfs, Bcachefs, and ZFS.
|
||||
At present, this is supported on Btrfs, Bcachefs, and ZFS in Linux
|
||||
and on NTFS in Windows.
|
||||
|
||||
You can mix data disks with different filesystems. Only those that
|
||||
support snapshots will utilize them, while other disks will continue
|
||||
|
|
|
|||
|
|
@ -1233,8 +1233,9 @@ start of a 'sync'. This ensures a consistent, point-in-time view of your
|
|||
files, preventing errors caused by concurrent file modifications.
|
||||
|
||||
This option applies exclusively to data disks formatted with the
|
||||
Btrfs, Bcachefs or ZFS filesystems. Parity disks, or data disks using
|
||||
other filesystems, will always use the live version of the filesystem.
|
||||
Btrfs, Bcachefs or ZFS filesystems in Linux and NTFS in Windows.
|
||||
Parity disks, or data disks using other filesystems, will always use
|
||||
the live version of the filesystem.
|
||||
|
||||
This significantly improves recovery: if a file is deleted from the live
|
||||
filesystem, it remains preserved in the snapshot. This prevents the parity
|
||||
|
|
@ -1548,7 +1549,8 @@ Snapshots are created only for data disks and only if the underlying
|
|||
filesystem supports this functionality. Parity disks always use
|
||||
the live filesystem.
|
||||
|
||||
At present, this is supported exclusively on Btrfs, Bcachefs, and ZFS.
|
||||
At present, this is supported on Btrfs, Bcachefs, and ZFS in Linux
|
||||
and on NTFS in Windows.
|
||||
|
||||
You can mix data disks with different filesystems. Only those that
|
||||
support snapshots will utilize them, while other disks will continue
|
||||
|
|
|
|||
|
|
@ -44,6 +44,34 @@ data d3 M:\array\
|
|||
# Format: "other DISK_NAME DISK_MOUNT_POINT"
|
||||
extra boot C:\
|
||||
|
||||
# Enable the use of filesystem snapshots for sync, scrub, check, and fix
|
||||
# commands.
|
||||
#
|
||||
# When enabled, SnapRAID creates a read-only snapshot of your data at the
|
||||
# start of a 'sync'. This ensures a consistent, point-in-time view of your
|
||||
# files, preventing errors caused by concurrent file modifications.
|
||||
#
|
||||
# This option applies exclusively to data disks formatted with the
|
||||
# NTFS filesystem. Parity disks, or data disks using other filesystems,
|
||||
# will always use the live version of the filesystem.
|
||||
#
|
||||
# This significantly improves recovery: if a file is deleted from the live
|
||||
# filesystem, it remains preserved in the snapshot. This prevents the parity
|
||||
# from becoming "broken" for that block, ensuring you can still successfully
|
||||
# recover data if another disk fails.
|
||||
#
|
||||
# Snapshots are stored in a hidden '.snapraid' directory at the root
|
||||
# of the subvolume containing your data. SnapRAID will automatically
|
||||
# detect the subvolume root for each data path.
|
||||
#
|
||||
# Snapshot creation and deletion require administrative privileges.
|
||||
# Ensure SnapRAID is run with the necessary permissions when this option
|
||||
# is enabled.
|
||||
#
|
||||
# See the 'Snapshots' section in the snapraid.txt documentation for a
|
||||
# detailed explanation of the snapshot lifecycle.
|
||||
#snapshot
|
||||
|
||||
# Excludes hidden files and directories (uncomment to enable).
|
||||
#nohidden
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue