mirror of
https://github.com/amadvance/snapraid.git
synced 2026-10-26 10:51:54 -04:00
Introduce "relocated" state to unify and replace "copied/removed" logic
This commit is contained in:
parent
189029749e
commit
c449864fc7
9 changed files with 167 additions and 64 deletions
3
HISTORY
3
HISTORY
|
|
@ -5,6 +5,9 @@ SnapRAID HISTORY
|
|||
==================
|
||||
* New 'extra' config option to define additional disks to monitor with the
|
||||
'probe' and 'smart' commands. This replaces the previous autodetection.
|
||||
* Introduced the 'relocated' file state to unify the previous 'copied/removed'
|
||||
logic. This new status specifically identifies files moved to a different
|
||||
path or disk with a new inode where the original has disappeared.
|
||||
* Removed the UAC request for Windows executable.
|
||||
|
||||
14.0rc2 08/03/2026
|
||||
|
|
|
|||
|
|
@ -265,11 +265,21 @@ struct snapraid_block {
|
|||
*/
|
||||
#define FILE_IS_MISSING 0x800
|
||||
|
||||
#define FILE_IS_HARDLINK 0x1000 /**< If it's an hardlink. */
|
||||
#define FILE_IS_SYMLINK 0x2000 /**< If it's a file symlink. */
|
||||
#define FILE_IS_SYMDIR 0x4000 /**< If it's a dir symlink for Windows. Not yet supported. */
|
||||
#define FILE_IS_JUNCTION 0x8000 /**< If it's a junction for Windows. Not yet supported. */
|
||||
#define FILE_IS_LINK_MASK 0xF000 /**< Mask for link type. */
|
||||
/**
|
||||
* During scan if the file is found missing and relocated to another place
|
||||
*
|
||||
* This is used to avoid to report it "removed".
|
||||
*
|
||||
* This specific bit is written using protection from the stamp_mutex during
|
||||
* the multithread scan.
|
||||
*/
|
||||
#define FILE_IS_RELOCATED 0x1000
|
||||
|
||||
#define FILE_IS_HARDLINK 0x10000 /**< If it's an hardlink. */
|
||||
#define FILE_IS_SYMLINK 0x20000 /**< If it's a file symlink. */
|
||||
#define FILE_IS_SYMDIR 0x40000 /**< If it's a dir symlink for Windows. Not yet supported. */
|
||||
#define FILE_IS_JUNCTION 0x80000 /**< If it's a junction for Windows. Not yet supported. */
|
||||
#define FILE_IS_LINK_MASK 0xF0000 /**< Mask for link type. */
|
||||
|
||||
/**
|
||||
* File.
|
||||
|
|
@ -405,6 +415,10 @@ struct snapraid_disk {
|
|||
* Mutex for protecting the scan process.
|
||||
*
|
||||
* It's used during the scan process to protect the stampset to identify copy of files
|
||||
* and the FILE_IS_RELOCATED flag of files.
|
||||
*
|
||||
* Note that only the FILE_IS_RELOCATED single bit is protected.
|
||||
* Not the other bits.
|
||||
*/
|
||||
thread_mutex_t stamp_mutex;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -30,13 +30,6 @@ struct snapraid_scan {
|
|||
int is_diff; /**< If it's a diff command or a scanning */
|
||||
int need_write; /**< If a state write is required */
|
||||
|
||||
#if HAVE_THREAD
|
||||
/**
|
||||
* Mutex for protecting the disk stampset table
|
||||
*/
|
||||
thread_mutex_t mutex;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Counters of changes.
|
||||
*/
|
||||
|
|
@ -45,6 +38,7 @@ struct snapraid_scan {
|
|||
unsigned count_restore; /**< Files with equal name, size and timestamp, but different inode. */
|
||||
unsigned count_change; /**< Files with same name, but different size and/or timestamp. */
|
||||
unsigned count_copy; /**< Files new, with same name size and timestamp of a file in a different disk. */
|
||||
unsigned count_relocate; /**< Like copy, but with the original disappeared. */
|
||||
unsigned count_insert; /**< Files new. */
|
||||
unsigned count_remove; /**< Files removed. */
|
||||
|
||||
|
|
@ -67,6 +61,7 @@ static struct snapraid_scan* scan_alloc(struct snapraid_state* state, struct sna
|
|||
scan->count_equal = 0;
|
||||
scan->count_move = 0;
|
||||
scan->count_copy = 0;
|
||||
scan->count_relocate = 0;
|
||||
scan->count_restore = 0;
|
||||
scan->count_change = 0;
|
||||
scan->count_remove = 0;
|
||||
|
|
@ -1058,15 +1053,38 @@ static void scan_file(struct snapraid_scan* scan, int is_diff, const char* sub,
|
|||
|
||||
/* if found, and it's a fully hashed file */
|
||||
if (other_file && file_is_full_hashed_and_stable(scan->state, other_disk, other_file)) {
|
||||
char path_other[PATH_MAX];
|
||||
struct stat other_st;
|
||||
|
||||
/*
|
||||
* Protect the write as multiple threads may write the same FILE_IS_RELOCATED bit.
|
||||
*
|
||||
* The bit is always written as 1 and never read, so protection is likely unnecessary
|
||||
* but still valuable to avoid data race reports from checker tools
|
||||
*/
|
||||
stamp_lock(other_disk);
|
||||
file_flag_set(other_file, FILE_IS_RELOCATED);
|
||||
stamp_unlock(other_disk);
|
||||
|
||||
/* assume that the file is a copy, and reuse the hash */
|
||||
file_copy(other_file, file);
|
||||
|
||||
/* revert old counter and use the copy one */
|
||||
++scan->count_copy;
|
||||
/* check if other file still exists */
|
||||
pathprint(path_other, sizeof(path_other), "%s%s", other_disk->dir, other_file->sub);
|
||||
if (lstat(path_other, &other_st) == 0) {
|
||||
++scan->count_copy;
|
||||
|
||||
log_tag("scan:copy:%s:%s:%s:%s\n", other_disk->name, esc_tag(other_file->sub, esc_buffer), disk->name, esc_tag(file->sub, esc_buffer_alt));
|
||||
if (is_diff) {
|
||||
msg_info("copy %s -> %s\n", fmt_term(other_disk, other_file->sub, esc_buffer), fmt_term(disk, file->sub, esc_buffer_alt));
|
||||
log_tag("scan:copy:%s:%s:%s:%s\n", other_disk->name, esc_tag(other_file->sub, esc_buffer), disk->name, esc_tag(file->sub, esc_buffer_alt));
|
||||
if (is_diff) {
|
||||
msg_info("copy %s -> %s\n", fmt_term(other_disk, other_file->sub, esc_buffer), fmt_term(disk, file->sub, esc_buffer_alt));
|
||||
}
|
||||
} else {
|
||||
++scan->count_relocate;
|
||||
|
||||
log_tag("scan:relocate:%s:%s:%s:%s\n", other_disk->name, esc_tag(other_file->sub, esc_buffer), disk->name, esc_tag(file->sub, esc_buffer_alt));
|
||||
if (is_diff) {
|
||||
msg_info("relocate %s -> %s\n", fmt_term(other_disk, other_file->sub, esc_buffer), fmt_term(disk, file->sub, esc_buffer_alt));
|
||||
}
|
||||
}
|
||||
|
||||
/* mark it as reported */
|
||||
|
|
@ -1700,11 +1718,13 @@ static int state_diffscan(struct snapraid_state* state, int is_diff)
|
|||
|
||||
/* remove if not present */
|
||||
if (!file_flag_has(file, FILE_IS_PRESENT)) {
|
||||
++scan->count_remove;
|
||||
if (!file_flag_has(file, FILE_IS_RELOCATED)) {
|
||||
++scan->count_remove;
|
||||
|
||||
log_tag("scan:remove:%s:%s\n", disk->name, esc_tag(file->sub, esc_buffer));
|
||||
if (is_diff) {
|
||||
msg_info("remove %s\n", fmt_term(disk, file->sub, esc_buffer));
|
||||
log_tag("scan:remove:%s:%s\n", disk->name, esc_tag(file->sub, esc_buffer));
|
||||
if (is_diff) {
|
||||
msg_info("remove %s\n", fmt_term(disk, file->sub, esc_buffer));
|
||||
}
|
||||
}
|
||||
|
||||
scan_file_remove(scan, file);
|
||||
|
|
@ -1969,6 +1989,7 @@ static int state_diffscan(struct snapraid_state* state, int is_diff)
|
|||
total.count_equal = 0;
|
||||
total.count_move = 0;
|
||||
total.count_copy = 0;
|
||||
total.count_relocate = 0;
|
||||
total.count_restore = 0;
|
||||
total.count_change = 0;
|
||||
total.count_remove = 0;
|
||||
|
|
@ -1979,6 +2000,7 @@ static int state_diffscan(struct snapraid_state* state, int is_diff)
|
|||
total.count_equal += scan->count_equal;
|
||||
total.count_move += scan->count_move;
|
||||
total.count_copy += scan->count_copy;
|
||||
total.count_relocate += scan->count_relocate;
|
||||
total.count_restore += scan->count_restore;
|
||||
total.count_change += scan->count_change;
|
||||
total.count_remove += scan->count_remove;
|
||||
|
|
@ -1998,6 +2020,7 @@ static int state_diffscan(struct snapraid_state* state, int is_diff)
|
|||
msg("%8u updated\n", total.count_change);
|
||||
msg("%8u moved\n", total.count_move);
|
||||
msg("%8u copied\n", total.count_copy);
|
||||
msg("%8u relocated\n", total.count_relocate);
|
||||
msg("%8u restored\n", total.count_restore);
|
||||
|
||||
log_tag("summary:equal:%u\n", total.count_equal);
|
||||
|
|
@ -2006,6 +2029,7 @@ static int state_diffscan(struct snapraid_state* state, int is_diff)
|
|||
log_tag("summary:updated:%u\n", total.count_change);
|
||||
log_tag("summary:moved:%u\n", total.count_move);
|
||||
log_tag("summary:copied:%u\n", total.count_copy);
|
||||
log_tag("summary:relocated:%u\n", total.count_relocate);
|
||||
log_tag("summary:restored:%u\n", total.count_restore);
|
||||
log_tag("list:scan_end\n");
|
||||
|
||||
|
|
@ -2013,7 +2037,7 @@ static int state_diffscan(struct snapraid_state* state, int is_diff)
|
|||
state->removed_files = total.count_remove;
|
||||
state->updated_files = total.count_change;
|
||||
|
||||
no_difference = !total.count_move && !total.count_copy && !total.count_restore
|
||||
no_difference = !total.count_move && !total.count_copy && !total.count_relocate && !total.count_restore
|
||||
&& !total.count_change && !total.count_remove && !total.count_insert;
|
||||
|
||||
if (is_diff) {
|
||||
|
|
|
|||
|
|
@ -793,15 +793,23 @@ They are identified by having the same name, size, timestamp,
|
|||
and inode, but a different directory.
|
||||
.HP 4
|
||||
.I copied
|
||||
Files copied on the same or a different disk. Note that if
|
||||
they are truly moved to a different disk, they will also be
|
||||
counted in \`removed\`.
|
||||
They are identified by having the same name, size, and
|
||||
timestamp. If the sub\-second timestamp is zero,
|
||||
the full path must match, not just the name.
|
||||
Files copied on the same or a different disk where the
|
||||
original file still exists. They are identified by having
|
||||
the same name, size, and timestamp. If the sub\-second
|
||||
timestamp is zero, the full path must match to be
|
||||
identified, not just the name.
|
||||
.HP 4
|
||||
.I relocated
|
||||
Files moved on the same or a different disk where
|
||||
the original has disappeared. They are identified by
|
||||
having the same name, size, and timestamp. If the
|
||||
sub\-second timestamp is zero, the full path must match
|
||||
to be identified. Unlike \'moved\' files on the same disk,
|
||||
relocated files have a different inode.
|
||||
.HP 4
|
||||
.I restored
|
||||
Files with a different inode but matching name, size, and timestamp.
|
||||
Files with a different inode but matching directory,
|
||||
name, size, and timestamp.
|
||||
These are usually files restored after being deleted.
|
||||
.PD
|
||||
.RE
|
||||
|
|
|
|||
|
|
@ -505,13 +505,19 @@ Commands
|
|||
moved - Files moved to a different directory on the same disk.
|
||||
They are identified by having the same name, size, timestamp,
|
||||
and inode, but a different directory.
|
||||
copied - Files copied on the same or a different disk. Note that if
|
||||
they are truly moved to a different disk, they will also be
|
||||
counted in `removed`.
|
||||
They are identified by having the same name, size, and
|
||||
timestamp. If the sub-second timestamp is zero,
|
||||
the full path must match, not just the name.
|
||||
restored - Files with a different inode but matching name, size, and timestamp.
|
||||
copied - Files copied on the same or a different disk where the
|
||||
original file still exists. They are identified by having
|
||||
the same name, size, and timestamp. If the sub-second
|
||||
timestamp is zero, the full path must match to be
|
||||
identified, not just the name.
|
||||
relocated - Files moved on the same or a different disk where
|
||||
the original has disappeared. They are identified by
|
||||
having the same name, size, and timestamp. If the
|
||||
sub-second timestamp is zero, the full path must match
|
||||
to be identified. Unlike 'moved' files on the same disk,
|
||||
relocated files have a different inode.
|
||||
restored - Files with a different inode but matching directory,
|
||||
name, size, and timestamp.
|
||||
These are usually files restored after being deleted.
|
||||
|
||||
If a `sync` is required, the process return code is 2, instead of the
|
||||
|
|
|
|||
|
|
@ -541,13 +541,19 @@ presented, grouped by:
|
|||
moved - Files moved to a different directory on the same disk.
|
||||
They are identified by having the same name, size, timestamp,
|
||||
and inode, but a different directory.
|
||||
copied - Files copied on the same or a different disk. Note that if
|
||||
they are truly moved to a different disk, they will also be
|
||||
counted in `removed`.
|
||||
They are identified by having the same name, size, and
|
||||
timestamp. If the sub-second timestamp is zero,
|
||||
the full path must match, not just the name.
|
||||
restored - Files with a different inode but matching name, size, and timestamp.
|
||||
copied - Files copied on the same or a different disk where the
|
||||
original file still exists. They are identified by having
|
||||
the same name, size, and timestamp. If the sub-second
|
||||
timestamp is zero, the full path must match to be
|
||||
identified, not just the name.
|
||||
relocated - Files moved on the same or a different disk where
|
||||
the original has disappeared. They are identified by
|
||||
having the same name, size, and timestamp. If the
|
||||
sub-second timestamp is zero, the full path must match
|
||||
to be identified. Unlike 'moved' files on the same disk,
|
||||
relocated files have a different inode.
|
||||
restored - Files with a different inode but matching directory,
|
||||
name, size, and timestamp.
|
||||
These are usually files restored after being deleted.
|
||||
|
||||
If a `sync` is required, the process return code is 2, instead of the
|
||||
|
|
|
|||
|
|
@ -431,8 +431,9 @@ A file or link has been updated (size, timestamp, or link target changed).
|
|||
This item will have its content data blocks re\-hashed in a \`sync\`.
|
||||
.TP
|
||||
.B scan:move:<disk_name>:<old_path>:<new_path>
|
||||
A file was moved on the same disk. This is an efficient operation
|
||||
that only updates the path metadata without re\-hashing blocks.
|
||||
A file was moved to a different directory on the same disk.
|
||||
This is an efficient operation that only updates the path
|
||||
metadata without rehashing blocks.
|
||||
.RS 4
|
||||
.PD 0
|
||||
.HP 4
|
||||
|
|
@ -445,9 +446,11 @@ The new path to the file (escaped).
|
|||
.RE
|
||||
.TP
|
||||
.B scan:copy:<disk_name>:<path>:<source_disk>:<source_path>
|
||||
A new file was found to be a copy of a file from another disk.
|
||||
This is detected by matching file size and timestamp, and is an
|
||||
efficient operation that avoids re\-hashing all blocks.
|
||||
A new file was found to be a copy of a file on the same or
|
||||
any other disk.
|
||||
This is detected by matching the file name,
|
||||
size, and timestamp, and is an efficient operation that avoids
|
||||
rehashing all blocks.
|
||||
.RS 4
|
||||
.PD 0
|
||||
.HP 4
|
||||
|
|
@ -459,9 +462,16 @@ The original file\'s location (escaped).
|
|||
.PD
|
||||
.RE
|
||||
.TP
|
||||
.B scan:relocate:<disk_name>:<path>:<source_disk>:<source_path>
|
||||
Like \`copy\`, but the original file has disappeared.
|
||||
The difference between \`move\` and \`relocate\` on the same
|
||||
disk is that \`move\` is only used if the inode remains
|
||||
the same.
|
||||
.TP
|
||||
.B scan:restore:<disk_name>:<path>
|
||||
A file\'s inode has changed but not its date\-time and size,
|
||||
which suggests the file may be restored from backup.
|
||||
A file\'s inode has changed but not its directory, name,
|
||||
timestamp and size, which suggests the file may be restored
|
||||
from backup.
|
||||
The file won\'t be re\-hashed in \`sync\` as its precomputed hash
|
||||
is assumed to be correct.
|
||||
.PP
|
||||
|
|
@ -491,6 +501,10 @@ retaining the same inode (uint).
|
|||
Total number of new files created by copying an existing
|
||||
protected file to a new path (uint).
|
||||
.TP
|
||||
.B summary:relocated:<count>
|
||||
Total number of new files created by relocating an existing
|
||||
protected file to a new path (uint).
|
||||
.TP
|
||||
.B summary:restored:<count>
|
||||
Total number of new files matching a parity entry by
|
||||
name/time/size but with a new inode (uint).
|
||||
|
|
|
|||
|
|
@ -287,23 +287,33 @@ Scan Tags
|
|||
This item will have its content data blocks re-hashed in a `sync`.
|
||||
|
||||
=scan:move:<disk_name>:<old_path>:<new_path>
|
||||
A file was moved on the same disk. This is an efficient operation
|
||||
that only updates the path metadata without re-hashing blocks.
|
||||
A file was moved to a different directory on the same disk.
|
||||
This is an efficient operation that only updates the path
|
||||
metadata without rehashing blocks.
|
||||
|
||||
<old_path> - The old path to the file (escaped).
|
||||
<new_path> - The new path to the file (escaped).
|
||||
|
||||
=scan:copy:<disk_name>:<path>:<source_disk>:<source_path>
|
||||
A new file was found to be a copy of a file from another disk.
|
||||
This is detected by matching file size and timestamp, and is an
|
||||
efficient operation that avoids re-hashing all blocks.
|
||||
A new file was found to be a copy of a file on the same or
|
||||
any other disk.
|
||||
This is detected by matching the file name,
|
||||
size, and timestamp, and is an efficient operation that avoids
|
||||
rehashing all blocks.
|
||||
|
||||
<source_disk> - The original file's disk.
|
||||
<source_path> - The original file's location (escaped).
|
||||
|
||||
=scan:relocate:<disk_name>:<path>:<source_disk>:<source_path>
|
||||
Like `copy`, but the original file has disappeared.
|
||||
The difference between `move` and `relocate` on the same
|
||||
disk is that `move` is only used if the inode remains
|
||||
the same.
|
||||
|
||||
=scan:restore:<disk_name>:<path>
|
||||
A file's inode has changed but not its date-time and size,
|
||||
which suggests the file may be restored from backup.
|
||||
A file's inode has changed but not its directory, name,
|
||||
timestamp and size, which suggests the file may be restored
|
||||
from backup.
|
||||
The file won't be re-hashed in `sync` as its precomputed hash
|
||||
is assumed to be correct.
|
||||
|
||||
|
|
@ -333,6 +343,10 @@ Scan Tags
|
|||
Total number of new files created by copying an existing
|
||||
protected file to a new path (uint).
|
||||
|
||||
=summary:relocated:<count>
|
||||
Total number of new files created by relocating an existing
|
||||
protected file to a new path (uint).
|
||||
|
||||
=summary:restored:<count>
|
||||
Total number of new files matching a parity entry by
|
||||
name/time/size but with a new inode (uint).
|
||||
|
|
|
|||
|
|
@ -303,23 +303,33 @@ content file. They are output for every discovered change.
|
|||
This item will have its content data blocks re-hashed in a `sync`.
|
||||
|
||||
scan:move:<disk_name>:<old_path>:<new_path>
|
||||
A file was moved on the same disk. This is an efficient operation
|
||||
that only updates the path metadata without re-hashing blocks.
|
||||
A file was moved to a different directory on the same disk.
|
||||
This is an efficient operation that only updates the path
|
||||
metadata without rehashing blocks.
|
||||
|
||||
<old_path> - The old path to the file (escaped).
|
||||
<new_path> - The new path to the file (escaped).
|
||||
|
||||
scan:copy:<disk_name>:<path>:<source_disk>:<source_path>
|
||||
A new file was found to be a copy of a file from another disk.
|
||||
This is detected by matching file size and timestamp, and is an
|
||||
efficient operation that avoids re-hashing all blocks.
|
||||
A new file was found to be a copy of a file on the same or
|
||||
any other disk.
|
||||
This is detected by matching the file name,
|
||||
size, and timestamp, and is an efficient operation that avoids
|
||||
rehashing all blocks.
|
||||
|
||||
<source_disk> - The original file's disk.
|
||||
<source_path> - The original file's location (escaped).
|
||||
|
||||
scan:relocate:<disk_name>:<path>:<source_disk>:<source_path>
|
||||
Like `copy`, but the original file has disappeared.
|
||||
The difference between `move` and `relocate` on the same
|
||||
disk is that `move` is only used if the inode remains
|
||||
the same.
|
||||
|
||||
scan:restore:<disk_name>:<path>
|
||||
A file's inode has changed but not its date-time and size,
|
||||
which suggests the file may be restored from backup.
|
||||
A file's inode has changed but not its directory, name,
|
||||
timestamp and size, which suggests the file may be restored
|
||||
from backup.
|
||||
The file won't be re-hashed in `sync` as its precomputed hash
|
||||
is assumed to be correct.
|
||||
|
||||
|
|
@ -349,6 +359,10 @@ These tags provide a final count of all detected changes.
|
|||
Total number of new files created by copying an existing
|
||||
protected file to a new path (uint).
|
||||
|
||||
summary:relocated:<count>
|
||||
Total number of new files created by relocating an existing
|
||||
protected file to a new path (uint).
|
||||
|
||||
summary:restored:<count>
|
||||
Total number of new files matching a parity entry by
|
||||
name/time/size but with a new inode (uint).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue