diff --git a/TODO b/TODO index 4867b1b3..e5b68364 100644 --- a/TODO +++ b/TODO @@ -15,6 +15,8 @@ same timestamp/size are present. * In the log print file name as escaped to avoid that internal ":" and "\n" get misinterpreted. +* Shall CreateFileW use FILE_SHARE_DELETE ? + - Minor * Provides a way to rename disks in content file. diff --git a/cmdline/elem.c b/cmdline/elem.c index 07f65ea0..d875c42a 100644 --- a/cmdline/elem.c +++ b/cmdline/elem.c @@ -377,7 +377,7 @@ void deleted_free(struct snapraid_deleted* deleted) free(deleted); } -struct snapraid_file* file_alloc(unsigned block_size, const char* sub, uint64_t size, uint64_t mtime_sec, int mtime_nsec, uint64_t inode, uint64_t physical) +struct snapraid_file* file_alloc(unsigned block_size, const char* sub, data_off_t size, uint64_t mtime_sec, int mtime_nsec, uint64_t inode, uint64_t physical) { struct snapraid_file* file; block_off_t i; diff --git a/cmdline/elem.h b/cmdline/elem.h index 5d3f1629..c921e8a7 100644 --- a/cmdline/elem.h +++ b/cmdline/elem.h @@ -643,7 +643,7 @@ static inline void file_flag_clear(struct snapraid_file* file, unsigned mask) /** * Allocates a file. */ -struct snapraid_file* file_alloc(unsigned block_size, const char* sub, uint64_t size, uint64_t mtime_sec, int mtime_nsec, uint64_t inode, uint64_t physical); +struct snapraid_file* file_alloc(unsigned block_size, const char* sub, data_off_t size, uint64_t mtime_sec, int mtime_nsec, uint64_t inode, uint64_t physical); /** * Deallocates a file. diff --git a/cmdline/mingw.c b/cmdline/mingw.c index c136a324..fc7014b4 100644 --- a/cmdline/mingw.c +++ b/cmdline/mingw.c @@ -366,7 +366,7 @@ static void windows_attr2stat(DWORD FileAttributes, DWORD ReparseTag, struct win switch (ReparseTag) { /* if we don't have the ReparseTag information */ case 0 : - /* don't set the st_mode, to set it later calling lstat_ex() */ + /* don't set the st_mode, to set it later calling lstat_sync() */ st->st_mode = 0; st->st_desc = "unknown"; break; @@ -457,6 +457,9 @@ static void windows_info2stat(const BY_HANDLE_FILE_INFORMATION* info, const FILE st->st_ino |= info->nFileIndexLow; st->st_dev = info->dwVolumeSerialNumber; + + /* GetFileInformationByHandle() ensures to return synced information */ + st->st_sync = 1; } /** @@ -488,6 +491,9 @@ static void windows_stream2stat(const BY_HANDLE_FILE_INFORMATION* info, const FI st->st_ino = stream->FileId.QuadPart; st->st_dev = info->dwVolumeSerialNumber; + + /* directory listing doesn't ensure to return synced information */ + st->st_sync = 0; } /** @@ -522,6 +528,9 @@ static void windows_finddata2stat(const WIN32_FIND_DATAW* info, struct windows_s /* No device information available */ st->st_dev = 0; + + /* directory listing doesn't ensure to return synced information */ + st->st_sync = 0; } static void windows_finddata2dirent(const WIN32_FIND_DATAW* info, struct windows_dirent* dirent) @@ -681,7 +690,7 @@ int windows_rmdir(const char* file) return 0; } -int lstat_ex(const char* file, struct windows_stat* st) +int lstat_sync(const char* file, struct windows_stat* st) { BY_HANDLE_FILE_INFORMATION info; FILE_ATTRIBUTE_TAG_INFO tag; diff --git a/cmdline/mingw.h b/cmdline/mingw.h index f6f232c5..88a92b42 100644 --- a/cmdline/mingw.h +++ b/cmdline/mingw.h @@ -134,6 +134,7 @@ struct windows_stat { uint32_t st_dev; int st_hidden; const char* st_desc; + int st_sync; /**< If the information are in sync with the filesystem. */ }; /** @@ -162,7 +163,7 @@ int windows_fstat(int fd, struct windows_stat* st); * 'However, the directory entry size and attribute information is updated only' * 'for the link through which the change was made.' * - * Use lstat_ex() to override these limitations. + * Use lstat_sync() to override these limitations. */ int windows_lstat(const char* file, struct windows_stat* st); @@ -192,8 +193,8 @@ int windows_rmdir(const char* file); * * Note that instead lstat() works for all the files. */ -#define HAVE_LSTAT_EX 1 -int lstat_ex(const char* file, struct windows_stat* st); +#define HAVE_LSTAT_SYNC 1 +int lstat_sync(const char* file, struct windows_stat* st); /** * Like the C ftruncate(). @@ -282,7 +283,7 @@ int windows_closedir(windows_dir* dirstream); * Specifically this happens if we are using FindFirst/FindNext to enumerate * the directory. * - * In such cases, call lstat_ex() to fill the missing fields. + * In such cases, call lstat_sync() to fill the missing fields. */ void windows_dirent_lstat(const struct windows_dirent* dd, struct windows_stat* st); diff --git a/cmdline/scan.c b/cmdline/scan.c index 4fb876d2..288abe08 100644 --- a/cmdline/scan.c +++ b/cmdline/scan.c @@ -312,6 +312,63 @@ static void scan_file_delayed_insert(struct snapraid_scan* scan, struct snapraid tommy_list_insert_tail(&scan->file_insert_list, &file->nodelist, file); } +/** + * Refresh the file info. + * + * This is needed by Windows as the normal way to list directories may report not + * updated info. Only the GetFileInformationByHandle() func, called file-by-file, + * relly ensures to return synced info. + */ +static void scan_file_refresh(struct snapraid_scan* scan, const char* sub, struct stat* st) +{ +#if HAVE_LSTAT_SYNC + struct snapraid_disk* disk = scan->disk; + + /* if the st_sync is not set, ensure to get synced info */ + if (st->st_sync == 0) { + char path_next[PATH_MAX]; + struct stat synced_st; + + pathprint(path_next, sizeof(path_next), "%s%s", disk->dir, sub); + + if (lstat_sync(path_next, &synced_st) != 0) { + /* LCOV_EXCL_START */ + fprintf(stderr, "Error in stat file '%s'. %s.\n", path_next, strerror(errno)); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + + if (st->st_mtime != synced_st.st_mtime + || st->st_mtimensec != synced_st.st_mtimensec + ) { + fprintf(stderr, "WARNING! Detected uncached time change for file '%s'\n", sub); + fprintf(stderr, "It's better if you run SnapRAID without other processes running.\n"); + st->st_mtime = synced_st.st_mtime; + st->st_mtimensec = synced_st.st_mtimensec; + } + + if (st->st_size != synced_st.st_size) { + fprintf(stderr, "WARNING! Detected uncached size change for file '%s'\n", sub); + fprintf(stderr, "It's better if you run SnapRAID without other processes running.\n"); + st->st_size = synced_st.st_size; + } + + if (st->st_ino != synced_st.st_ino) { + fprintf(stderr, "DANGER! Detected uncached inode change for file '%s'\n", sub); + fprintf(stderr, "Please ensure to run SnapRAID without other processes running.\n"); + /* at this point, it's too late to change inode */ + /* and having inconsistent inodes may result to internal failures */ + /* so, it's better to abort */ + exit(EXIT_FAILURE); + } + } +#else + (void)scan; + (void)sub; + (void)st; +#endif +} + /** * Keeps the file as it's (or with only a name/inode modification). * @@ -464,7 +521,7 @@ static void scan_file_insert(struct snapraid_scan* scan, struct snapraid_file* f /** * Processes a file. */ -static void scan_file(struct snapraid_scan* scan, int output, const char* sub, const struct stat* st, uint64_t physical) +static void scan_file(struct snapraid_scan* scan, int output, const char* sub, struct stat* st, uint64_t physical) { struct snapraid_state* state = scan->state; struct snapraid_disk* disk = scan->disk; @@ -472,6 +529,7 @@ static void scan_file(struct snapraid_scan* scan, int output, const char* sub, c int add_insert; int add_change; tommy_node* i; + int is_original_file_size_different_than_zero; /* * If the disk has persistent inodes and UUID, try a search on the past inodes, @@ -619,6 +677,7 @@ static void scan_file(struct snapraid_scan* scan, int output, const char* sub, c add_insert = 0; add_change = 0; + is_original_file_size_different_than_zero = 0; /* then try finding it by name */ file = tommy_hashdyn_search(&disk->pathset, file_path_compare_to_arg, sub, file_path_hash(sub)); @@ -720,19 +779,8 @@ static void scan_file(struct snapraid_scan* scan, int output, const char* sub, c /* here if the file is changed but with the correct name */ - /* do a safety check to ensure that the common ext4 case of zeroing */ - /* the size of a file after a crash doesn't propagate to the backup */ - if (file->size != 0 && st->st_size == 0) { - if (!state->opt.force_zero) { - /* LCOV_EXCL_START */ - fprintf(stderr, "The file '%s%s' has unexpected zero size! If this an expected state\n", disk->dir, sub); - fprintf(stderr, "you can '%s' anyway usinge 'snapraid --force-zero %s'\n", state->command, state->command); - fprintf(stderr, "Instead, it's possible that after a kernel crash this file was lost,\n"); - fprintf(stderr, "and you can use 'snapraid --filter %s fix' to recover it.\n", sub); - exit(EXIT_FAILURE); - /* LCOV_EXCL_STOP */ - } - } + /* keep track if the original file was not of zero size */ + is_original_file_size_different_than_zero = file->size != 0; /* it has the same name, so it's an update */ add_change = 1; @@ -742,10 +790,10 @@ static void scan_file(struct snapraid_scan* scan, int output, const char* sub, c printf("update %s%s\n", disk->dir, file->sub); } - /* remove it */ + /* remove it, and continue to insert it again */ scan_file_remove(scan, file); - /* and continue to reinsert it */ + /* and continue to reinsert it again */ } else { /* if the name doesn't exist, it's a new file */ add_insert = 1; @@ -758,6 +806,25 @@ static void scan_file(struct snapraid_scan* scan, int output, const char* sub, c /* and continue to insert it */ } + /* refresh the info, to ensure that they are synced, */ + /* not that we refresh only the info of the new or modified files */ + /* because this is slow operation */ + scan_file_refresh(scan, sub, st); + + /* do a safety check to ensure that the common ext4 case of zeroing */ + /* the size of a file after a crash doesn't propagate to the backup */ + if (is_original_file_size_different_than_zero && st->st_size == 0) { + if (!state->opt.force_zero) { + /* LCOV_EXCL_START */ + fprintf(stderr, "The file '%s%s' has unexpected zero size! If this an expected state\n", disk->dir, sub); + fprintf(stderr, "you can '%s' anyway usinge 'snapraid --force-zero %s'\n", state->command, state->command); + fprintf(stderr, "Instead, it's possible that after a kernel crash this file was lost,\n"); + fprintf(stderr, "and you can use 'snapraid --filter %s fix' to recover it.\n", sub); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + } + /* insert it */ file = file_alloc(state->block_size, sub, st->st_size, st->st_mtime, STAT_NSEC(st), st->st_ino, physical); @@ -1110,8 +1177,8 @@ static int scan_dir(struct snapraid_scan* scan, int output, const char* dir, con #if HAVE_STRUCT_DIRENT_D_STAT /* if the st_mode field is missing, takes care to fill it using normal lstat() */ - /* at now this can happen only in Windows, but we cannot call here lstat_ex(), */ - /* because we don't know what kind of file is it, and lstat_ex() doesn't always work */ + /* at now this can happen only in Windows, but we cannot call here lstat_sync(), */ + /* because we don't know what kind of file is it, and lstat_sync() doesn't always work */ if (st->st_mode == 0) { if (lstat(path_next, st) != 0) { /* LCOV_EXCL_START */ @@ -1139,13 +1206,13 @@ static int scan_dir(struct snapraid_scan* scan, int output, const char* dir, con if (!st) st = DSTAT(path_next, dd, &st_buf); -#if HAVE_LSTAT_EX +#if HAVE_LSTAT_SYNC /* if the st_ino field is missing, takes care to fill it using the extended lstat() */ /* this can happen only in Windows */ if (st->st_ino == 0) { - if (lstat_ex(path_next, st) != 0) { + if (lstat_sync(path_next, st) != 0) { /* LCOV_EXCL_START */ - fprintf(stderr, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno)); + fprintf(stderr, "Error in stat file '%s'. %s.\n", path_next, strerror(errno)); exit(EXIT_FAILURE); /* LCOV_EXCL_STOP */ }