Synchronize file metadata for new and modified files during scan on Windows

This commit is contained in:
Andrea Mazzoleni 2026-10-11 23:28:55 +02:00
parent 3b8c8c10e7
commit f775075bc5

View file

@ -20,8 +20,8 @@ static const char* es(int err)
* If the file metadata (size and timestamp) is known to be synchronized with disk. * If the file metadata (size and timestamp) is known to be synchronized with disk.
* *
* Always set on POSIX where stat()/lstat() returns coherent metadata. On Windows NTFS, * Always set on POSIX where stat()/lstat() returns coherent metadata. On Windows NTFS,
* directory enumeration may return stale size/mtime for hardlinks until synchronized * directory enumeration may return stale size/mtime for open/modified files and hardlinks
* via lstat_sync() (GetFileInformationByHandle). * until synchronized via lstat_sync() (GetFileInformationByHandle).
*/ */
#define DISCOVERY_IS_SCAN_SYNCED 0x1 #define DISCOVERY_IS_SCAN_SYNCED 0x1
@ -1655,13 +1655,24 @@ static int scan_sub(struct snapraid_scan* scan, int level, int is_diff, char* pa
if (!st) if (!st)
st = DSTAT(path_next, dd, &st_buf); st = DSTAT(path_next, dd, &st_buf);
existing = tommy_hashdyn_search(&disk->pathset, file_path_compare_to_arg, sub_next, file_path_hash(sub_next));
if (existing)
file_flag_set(existing, FILE_IS_DISCOVERED);
#if HAVE_LSTAT_SYNC #if HAVE_LSTAT_SYNC
/* /*
* In Windows fast directory enumeration, st_ino may be missing (0). * In Windows fast directory enumeration, st_ino may be missing (0).
* Only st_ino is required for file identity and hardlink tracking; * In addition, directory enumeration may report stale size and mtime
* we call lstat_sync() only when st_ino is missing to avoid expensive file opens. * for files currently open or being written to.
* Refresh via lstat_sync() if st_ino is missing, or if the metadata
* is not synchronized and the file is new or modified.
*/ */
if (st->st_ino == INODE_INVALID) { if (st->st_ino == INODE_INVALID
|| (st->st_sync == 0
&& (!existing
|| existing->size != st->st_size
|| existing->mtime_sec != st->st_mtime
|| existing->mtime_nsec != STAT_NSEC(st)))) {
if (lstat_sync(path_next, st, 0) != 0) { if (lstat_sync(path_next, st, 0) != 0) {
/* LCOV_EXCL_START */ /* LCOV_EXCL_START */
log_tag("%s:%u:%s:%s: Stat error. %s.\n", es(errno), 0, disk->name, esc_tag(path_next), strerror(errno)); log_tag("%s:%u:%s:%s: Stat error. %s.\n", es(errno), 0, disk->name, esc_tag(path_next), strerror(errno));
@ -1672,10 +1683,6 @@ static int scan_sub(struct snapraid_scan* scan, int level, int is_diff, char* pa
} }
#endif #endif
existing = tommy_hashdyn_search(&disk->pathset, file_path_compare_to_arg, sub_next, file_path_hash(sub_next));
if (existing)
file_flag_set(existing, FILE_IS_DISCOVERED);
scan_file_discover(scan, sub_next, st); scan_file_discover(scan, sub_next, st);
processed = 1; processed = 1;
} else { } else {
@ -1937,8 +1944,8 @@ static int state_diffscan(struct snapraid_state* state, int is_diff)
* - Each disk is read only by its scan thread, enumerating files and populating * - Each disk is read only by its scan thread, enumerating files and populating
* file_discovery_list without observable SnapRAID classification side effects. * file_discovery_list without observable SnapRAID classification side effects.
* - No old file, link, directory, or parity allocation is removed. * - No old file, link, directory, or parity allocation is removed.
* - Inode collisions on volatile-hardlink filesystems trigger selective lstat_sync() * - New/modified files and inode collisions on volatile filesystems trigger selective
* to normalize authoritative metadata across hardlink groups before Apply. * lstat_sync() to obtain authoritative metadata before Apply.
*/ */
for (i = scanlist; i != 0; i = i->next) { for (i = scanlist; i != 0; i = i->next) {
struct snapraid_scan* scan = i->data; struct snapraid_scan* scan = i->data;