In Windows ensures to use synced information for new files.

Keeps track of the sync state of info with a new st_sync field
in the stat struct.
This commit is contained in:
Andrea Mazzoleni 2014-10-04 10:27:23 +02:00
parent a0c6965959
commit ca5e635aa1
6 changed files with 108 additions and 29 deletions

2
TODO
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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