From f11707400456fb60f40e8084d9196927eb9675a8 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Tue, 30 Sep 2014 21:54:49 +0200 Subject: [PATCH 1/6] Uses Windows dir stream to make dir listing faster. --- HISTORY | 1 + cmdline/mingw.c | 467 +++++++++++++++++++++++++++++++++--------------- cmdline/mingw.h | 32 ++-- cmdline/scan.c | 44 +++-- configure.ac | 2 +- 5 files changed, 371 insertions(+), 175 deletions(-) diff --git a/HISTORY b/HISTORY index dea9138b..84a06933 100644 --- a/HISTORY +++ b/HISTORY @@ -4,6 +4,7 @@ SnapRAID HISTORY 7.0 2014/9 ========== * Adds AVX2 support to improve parity generation speed. + * Faster directory scanning for Windows. * Prints the time spent waiting for each disk also in 'scrub'. * The CPU usage, speed and ETA estimations are now based on the last 100 seconds rather than from the start. diff --git a/cmdline/mingw.c b/cmdline/mingw.c index 52b3891f..fe1c4826 100644 --- a/cmdline/mingw.c +++ b/cmdline/mingw.c @@ -32,7 +32,27 @@ typedef struct _FILE_ATTRIBUTE_TAG_INFO { DWORD FileAttributes; DWORD ReparseTag; } FILE_ATTRIBUTE_TAG_INFO; + +typedef struct _FILE_ID_BOTH_DIR_INFO { + DWORD NextEntryOffset; + DWORD FileIndex; + LARGE_INTEGER CreationTime; + LARGE_INTEGER LastAccessTime; + LARGE_INTEGER LastWriteTime; + LARGE_INTEGER ChangeTime; + LARGE_INTEGER EndOfFile; + LARGE_INTEGER AllocationSize; + DWORD FileAttributes; + DWORD FileNameLength; + DWORD EaSize; + CCHAR ShortNameLength; + WCHAR ShortName[12]; + LARGE_INTEGER FileId; + WCHAR FileName[1]; +} FILE_ID_BOTH_DIR_INFO; + #define FileAttributeTagInfo 9 +#define FileIdBothDirectoryInfo 10 #ifndef IO_REPARSE_TAG_DEDUP #define IO_REPARSE_TAG_DEDUP (0x80000013) @@ -184,26 +204,7 @@ static wchar_t* u8tou16(const char* src) /** * Converts a generic string from UTF16 to UTF8. */ -static char* u16tou8(const wchar_t* src) -{ - int ret; - - if (++conv_utf8 == CONV_ROLL) - conv_utf8 = 0; - - ret = WideCharToMultiByte(CP_UTF8, 0, src, -1, conv_utf8_buffer[conv_utf8], sizeof(conv_utf8_buffer[0]), 0, 0); - if (ret <= 0) { - fwprintf(stderr, L"Error converting name %s from UTF-16 to UTF-8\n", src); - exit(EXIT_FAILURE); - } - - return conv_utf8_buffer[conv_utf8]; -} - -/** - * Converts a generic string from UTF16 to UTF8. - */ -static char* u16tou8n(const wchar_t* src, size_t number_of_wchar, size_t* result_length_without_terminator) +static char* u16tou8(const wchar_t* src, size_t number_of_wchar, size_t* result_length_without_terminator) { int ret; @@ -212,7 +213,7 @@ static char* u16tou8n(const wchar_t* src, size_t number_of_wchar, size_t* result ret = WideCharToMultiByte(CP_UTF8, 0, src, number_of_wchar, conv_utf8_buffer[conv_utf8], sizeof(conv_utf8_buffer[0]), 0, 0); if (ret <= 0) { - fwprintf(stderr, L"Error converting from UTF-16 to UTF-8\n"); + fprintf(stderr, "Error converting from UTF-16 to UTF-8\n"); exit(EXIT_FAILURE); } @@ -340,27 +341,31 @@ static BOOL GetReparseTagInfoByHandle(HANDLE hFile, FILE_ATTRIBUTE_TAG_INFO* lpF } /** - * Converts Windows info to the Unix stat format. + * Converts Windows attr to the Unix stat format. */ -static void windows_info2stat(const BY_HANDLE_FILE_INFORMATION* info, const FILE_ATTRIBUTE_TAG_INFO* tag, struct windows_stat* st) +static void windows_attr2stat(DWORD FileAttributes, DWORD ReparseTag, struct windows_stat* st) { - uint64_t mtime; - /* Convert special attributes */ - if ((info->dwFileAttributes & FILE_ATTRIBUTE_DEVICE) != 0) { + if ((FileAttributes & FILE_ATTRIBUTE_DEVICE) != 0) { st->st_mode = S_IFBLK; st->st_desc = "device"; - } else if ((info->dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) != 0) { /* Offline */ + } else if ((FileAttributes & FILE_ATTRIBUTE_OFFLINE) != 0) { /* Offline */ st->st_mode = S_IFCHR; st->st_desc = "offline"; - } else if ((info->dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) != 0) { /* Temporary */ + } else if ((FileAttributes & FILE_ATTRIBUTE_TEMPORARY) != 0) { /* Temporary */ st->st_mode = S_IFCHR; st->st_desc = "temporary"; - } else if ((info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { /* Reparse point */ - switch (tag->ReparseTag) { - /* For deduplicated files, assume that they are regular ones */ + } else if ((FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { /* Reparse point */ + 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() */ + st->st_mode = 0; + st->st_desc = "unknown"; + break; + /* for deduplicated files, assume that they are regular ones */ case IO_REPARSE_TAG_DEDUP : - if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { + if ((FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { st->st_mode = S_IFDIR; st->st_desc = "directory-dedup"; } else { @@ -369,7 +374,7 @@ static void windows_info2stat(const BY_HANDLE_FILE_INFORMATION* info, const FILE } break; case IO_REPARSE_TAG_SYMLINK : - if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { + if ((FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { st->st_mode = S_IFLNKDIR; st->st_desc = "reparse-point-symlink-dir"; } else { @@ -377,11 +382,11 @@ static void windows_info2stat(const BY_HANDLE_FILE_INFORMATION* info, const FILE st->st_desc = "reparse-point-symlink-file"; } break; - /* All the other are skipped as reparse-point */ + /* all the other are skipped as reparse-point */ case IO_REPARSE_TAG_MOUNT_POINT : st->st_mode = S_IFCHR; st->st_desc = "reparse-point-mount"; - break; + break; case IO_REPARSE_TAG_NFS : st->st_mode = S_IFCHR; st->st_desc = "reparse-point-nfs"; @@ -391,8 +396,8 @@ static void windows_info2stat(const BY_HANDLE_FILE_INFORMATION* info, const FILE st->st_desc = "reparse-point"; break; } - } else if ((info->dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) != 0) { /* System */ - if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { + } else if ((FileAttributes & FILE_ATTRIBUTE_SYSTEM) != 0) { /* System */ + if ((FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { st->st_mode = S_IFCHR; st->st_desc = "system-directory"; } else { @@ -400,7 +405,7 @@ static void windows_info2stat(const BY_HANDLE_FILE_INFORMATION* info, const FILE st->st_desc = "system-file"; } } else { - if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { + if ((FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { st->st_mode = S_IFDIR; st->st_desc = "directory"; } else { @@ -409,8 +414,18 @@ static void windows_info2stat(const BY_HANDLE_FILE_INFORMATION* info, const FILE } } - /* Store the HIDDEN attribute in a separate field */ - st->st_hidden = (info->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0; + /* store the HIDDEN attribute in a separate field */ + st->st_hidden = (FileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0; +} + +/** + * Converts Windows info to the Unix stat format. + */ +static void windows_info2stat(const BY_HANDLE_FILE_INFORMATION* info, const FILE_ATTRIBUTE_TAG_INFO* tag, struct windows_stat* st) +{ + uint64_t mtime; + + windows_attr2stat(info->dwFileAttributes, tag->ReparseTag, st); st->st_size = info->nFileSizeHigh; st->st_size <<= 32; @@ -434,7 +449,36 @@ static void windows_info2stat(const BY_HANDLE_FILE_INFORMATION* info, const FILE st->st_ino <<= 32; st->st_ino |= info->nFileIndexLow; - st->st_nlink = info->nNumberOfLinks; + st->st_dev = info->dwVolumeSerialNumber; +} + +/** + * Converts Windows info to the Unix stat format. + */ +static void windows_stream2stat(const BY_HANDLE_FILE_INFORMATION* info, const FILE_ID_BOTH_DIR_INFO* stream, struct windows_stat* st) +{ + uint64_t mtime; + + /* The FILE_ID_BOTH_DIR_INFO doesn't have the ReparseTag information */ + /* we could use instead FILE_ID_EXTD_DIR_INFO, but it's available only */ + /* from Windows Server 2012 */ + windows_attr2stat(stream->FileAttributes, 0, st); + + st->st_size = stream->EndOfFile.QuadPart; + + mtime = stream->LastWriteTime.QuadPart; + + /* + * Convert to unix time + * + * How To Convert a UNIX time_t to a Win32 FILETIME or SYSTEMTIME + * http://support.microsoft.com/kb/167296 + */ + mtime -= 116444736000000000LL; + st->st_mtime = mtime / 10000000; + st->st_mtimensec = (mtime % 10000000) * 100; + + st->st_ino = stream->FileId.QuadPart; st->st_dev = info->dwVolumeSerialNumber; } @@ -446,71 +490,7 @@ static void windows_finddata2stat(const WIN32_FIND_DATAW* info, struct windows_s { uint64_t mtime; - /* Convert special attributes */ - if ((info->dwFileAttributes & FILE_ATTRIBUTE_DEVICE) != 0) { - st->st_mode = S_IFBLK; - st->st_desc = "device"; - } else if ((info->dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) != 0) { /* Offline */ - st->st_mode = S_IFCHR; - st->st_desc = "offline"; - } else if ((info->dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY) != 0) { /* Temporary */ - st->st_mode = S_IFCHR; - st->st_desc = "temporary"; - } else if ((info->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { /* Reparse point */ - switch (info->dwReserved0) { - /* For deduplicated files, assume that they are regular ones */ - case IO_REPARSE_TAG_DEDUP : - if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { - st->st_mode = S_IFDIR; - st->st_desc = "directory-dedup"; - } else { - st->st_mode = S_IFREG; - st->st_desc = "regular-dedup"; - } - break; - case IO_REPARSE_TAG_SYMLINK : - if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { - st->st_mode = S_IFLNKDIR; - st->st_desc = "reparse-point-symlink-dir"; - } else { - st->st_mode = S_IFLNK; - st->st_desc = "reparse-point-symlink-file"; - } - break; - /* All the other are skipped as reparse-point */ - case IO_REPARSE_TAG_MOUNT_POINT : - st->st_mode = S_IFCHR; - st->st_desc = "reparse-point-mount"; - break; - case IO_REPARSE_TAG_NFS : - st->st_mode = S_IFCHR; - st->st_desc = "reparse-point-nfs"; - break; - default : - st->st_mode = S_IFCHR; - st->st_desc = "reparse-point"; - break; - } - } else if ((info->dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) != 0) { /* System */ - if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { - st->st_mode = S_IFCHR; - st->st_desc = "system-directory"; - } else { - st->st_mode = S_IFREG; - st->st_desc = "system-file"; - } - } else { - if ((info->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { - st->st_mode = S_IFDIR; - st->st_desc = "directory"; - } else { - st->st_mode = S_IFREG; - st->st_desc = "regular"; - } - } - - /* Store the HIDDEN attribute in a separate field */ - st->st_hidden = (info->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0; + windows_attr2stat(info->dwFileAttributes, info->dwReserved0, st); st->st_size = info->nFileSizeHigh; st->st_size <<= 32; @@ -533,9 +513,6 @@ static void windows_finddata2stat(const WIN32_FIND_DATAW* info, struct windows_s /* No inode information available */ st->st_ino = 0; - /* No link information available */ - st->st_nlink = 0; - /* No device information available */ st->st_dev = 0; } @@ -545,19 +522,35 @@ static void windows_finddata2dirent(const WIN32_FIND_DATAW* info, struct windows const char* name; size_t len; - name = u16tou8(info->cFileName); - - len = strlen(name); + name = u16tou8(info->cFileName, wcslen(info->cFileName), &len); if (len + 1 >= sizeof(dirent->d_name)) { fprintf(stderr, "Name too long\n"); exit(EXIT_FAILURE); } - memcpy(dirent->d_name, name, len + 1); + memcpy(dirent->d_name, name, len); + dirent->d_name[len] = 0; - /* Store the HIDDEN attribute in a separate field */ - dirent->d_hidden = (info->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0; + windows_finddata2stat(info, &dirent->d_stat); +} + +static void windows_stream2dirent(const BY_HANDLE_FILE_INFORMATION* info, const FILE_ID_BOTH_DIR_INFO* stream, struct windows_dirent* dirent) +{ + const char* name; + size_t len; + + name = u16tou8(stream->FileName, stream->FileNameLength / 2, &len); + + if (len + 1 >= sizeof(dirent->d_name)) { + fprintf(stderr, "Name too long\n"); + exit(EXIT_FAILURE); + } + + memcpy(dirent->d_name, name, len); + dirent->d_name[len] = 0; + + windows_stream2stat(info, stream, &dirent->d_stat); } /** @@ -658,7 +651,7 @@ int windows_lstat(const char* file, struct windows_stat* st) void windows_dirent_lstat(const struct windows_dirent* dd, struct windows_stat* st) { - windows_finddata2stat(&dd->d_data, st); + memcpy(st, &dd->d_stat, sizeof(struct windows_stat)); } int windows_mkdir(const char* file) @@ -931,7 +924,22 @@ int windows_open(const char* file, int flags, ...) return ret; } -windows_dir* windows_opendir(const char* dir) +struct windows_dir_struct { + BY_HANDLE_FILE_INFORMATION info; + WIN32_FIND_DATAW find; + HANDLE h; + struct windows_dirent entry; + unsigned char* buffer; + unsigned buffer_size; + unsigned buffer_pos; + int state; +}; + +#define DIR_STATE_EOF -1 /**< End of the dir stream */ +#define DIR_STATE_EMPTY 0 /**< The entry is empty. */ +#define DIR_STATE_FILLED 1 /**< The entry is valid. */ + +static windows_dir* windows_opendir_find(const char* dir) { wchar_t* wdir; windows_dir* dirstream; @@ -952,12 +960,12 @@ windows_dir* windows_opendir(const char* dir) wdir[len++] = L'*'; wdir[len++] = 0; - dirstream->h = FindFirstFileW(wdir, &dirstream->buffer.d_data); + dirstream->h = FindFirstFileW(wdir, &dirstream->find); if (dirstream->h == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); if (error == ERROR_FILE_NOT_FOUND) { - dirstream->flags = -1; /* empty dir */ + dirstream->state = DIR_STATE_EOF; return dirstream; } @@ -966,47 +974,48 @@ windows_dir* windows_opendir(const char* dir) return 0; } - dirstream->flags = 1; - - windows_finddata2dirent(&dirstream->buffer.d_data, &dirstream->buffer); + windows_finddata2dirent(&dirstream->find, &dirstream->entry); + dirstream->state = DIR_STATE_FILLED; return dirstream; } -struct windows_dirent* windows_readdir(windows_dir* dirstream) +static struct windows_dirent* windows_readdir_find(windows_dir* dirstream) { - if (dirstream->flags == -1) { - errno = 0; /* end of stream */ - return 0; - } + if (dirstream->state == DIR_STATE_EMPTY) { + if (!FindNextFileW(dirstream->h, &dirstream->find)) { + DWORD error = GetLastError(); - if (dirstream->flags == 1) { - dirstream->flags = 0; - return &dirstream->buffer; - } + if (error != ERROR_NO_MORE_FILES) { + windows_errno(error); + return 0; + } - if (!FindNextFileW(dirstream->h, &dirstream->buffer.d_data)) { - DWORD error = GetLastError(); - - if (error == ERROR_NO_MORE_FILES) { - errno = 0; /* end of stream */ - return 0; + dirstream->state = DIR_STATE_EOF; + } else { + windows_finddata2dirent(&dirstream->find, &dirstream->entry); + dirstream->state = DIR_STATE_FILLED; } - - windows_errno(error); - return 0; } - windows_finddata2dirent(&dirstream->buffer.d_data, &dirstream->buffer); + if (dirstream->state == DIR_STATE_FILLED) { + dirstream->state = DIR_STATE_EMPTY; + return &dirstream->entry; + } - return &dirstream->buffer; + /* otherwise it's the end of stream */ + assert(dirstream->state == DIR_STATE_EOF); + errno = 0; + + return 0; } -int windows_closedir(windows_dir* dirstream) +static int windows_closedir_find(windows_dir* dirstream) { if (dirstream->h != INVALID_HANDLE_VALUE) { if (!FindClose(dirstream->h)) { DWORD error = GetLastError(); + free(dirstream); windows_errno(error); @@ -1019,9 +1028,177 @@ int windows_closedir(windows_dir* dirstream) return 0; } +static int windows_first_stream(windows_dir* dirstream) +{ + FILE_ID_BOTH_DIR_INFO* fd; + + if (!ptr_GetFileInformationByHandleEx(dirstream->h, FileIdBothDirectoryInfo, dirstream->buffer, dirstream->buffer_size)) { + DWORD error = GetLastError(); + + if (error == ERROR_NO_MORE_FILES) { + dirstream->state = DIR_STATE_EOF; + return 0; + } + + windows_errno(error); + return -1; + } + + /* get the first entry */ + dirstream->buffer_pos = 0; + fd = (FILE_ID_BOTH_DIR_INFO*)dirstream->buffer; + windows_stream2dirent(&dirstream->info, fd, &dirstream->entry); + dirstream->state = DIR_STATE_FILLED; + + return 0; +} + +static int windows_next_stream(windows_dir* dirstream) +{ + FILE_ID_BOTH_DIR_INFO* fd; + + /* last entry read */ + fd = (FILE_ID_BOTH_DIR_INFO*)(dirstream->buffer + dirstream->buffer_pos); + + /* check if there is a next one */ + if (fd->NextEntryOffset == 0) { + /* if not, fill it up again */ + if (windows_first_stream(dirstream) != 0) + return -1; + return 0; + } + + /* go to the next one */ + dirstream->buffer_pos += fd->NextEntryOffset; + fd = (FILE_ID_BOTH_DIR_INFO*)(dirstream->buffer + dirstream->buffer_pos); + windows_stream2dirent(&dirstream->info, fd, &dirstream->entry); + dirstream->state = DIR_STATE_FILLED; + + return 0; +} + +static windows_dir* windows_opendir_stream(const char* dir) +{ + windows_dir* dirstream; + WCHAR* wdir; + + dirstream = malloc(sizeof(windows_dir)); + if (!dirstream) { + fprintf(stderr, "Low memory\n"); + exit(EXIT_FAILURE); + } + + wdir = convert(dir); + + /* uses a 64 kB buffer for reading directory */ + dirstream->buffer_size = 64 * 1024; + dirstream->buffer = malloc(dirstream->buffer_size); + if (!dirstream->buffer) { + fprintf(stderr, "Low memory\n"); + exit(EXIT_FAILURE); + } + + dirstream->h = CreateFileW(wdir, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0); + if (dirstream->h == INVALID_HANDLE_VALUE) { + DWORD error = GetLastError(); + free(dirstream->buffer); + free(dirstream); + windows_errno(error); + return 0; + } + + /* get dir information for the VolumeSerialNumber */ + /* this value is used for all the files in the dir */ + if (!GetFileInformationByHandle(dirstream->h, &dirstream->info)) { + DWORD error = GetLastError(); + CloseHandle(dirstream->h); + free(dirstream->buffer); + free(dirstream); + windows_errno(error); + return 0; + } + + if (windows_first_stream(dirstream) != 0) { + CloseHandle(dirstream->h); + free(dirstream->buffer); + free(dirstream); + return 0; + } + + return dirstream; +} + +static struct windows_dirent* windows_readdir_stream(windows_dir* dirstream) +{ + if (dirstream->state == DIR_STATE_EMPTY) { + if (windows_next_stream(dirstream) != 0) { + free(dirstream->buffer); + free(dirstream); + return 0; + } + } + + if (dirstream->state == DIR_STATE_FILLED) { + dirstream->state = DIR_STATE_EMPTY; + return &dirstream->entry; + } + + /* otherwise it's the end of stream */ + assert(dirstream->state == DIR_STATE_EOF); + errno = 0; + + return 0; +} + +static int windows_closedir_stream(windows_dir* dirstream) +{ + if (dirstream->h != INVALID_HANDLE_VALUE) { + if (!CloseHandle(dirstream->h)) { + DWORD error = GetLastError(); + + free(dirstream->buffer); + free(dirstream); + + windows_errno(error); + return -1; + } + } + + free(dirstream->buffer); + free(dirstream); + + return 0; +} + +windows_dir* windows_opendir(const char* dir) +{ + /* if we have GetFileInformationByHandleEx() we can read */ + /* the directory using a stream */ + if (ptr_GetFileInformationByHandleEx != 0) + return windows_opendir_stream(dir); + else + return windows_opendir_find(dir); +} + +struct windows_dirent* windows_readdir(windows_dir* dirstream) +{ + if (ptr_GetFileInformationByHandleEx != 0) + return windows_readdir_stream(dirstream); + else + return windows_readdir_find(dirstream); +} + +int windows_closedir(windows_dir* dirstream) +{ + if (ptr_GetFileInformationByHandleEx != 0) + return windows_closedir_stream(dirstream); + else + return windows_closedir_find(dirstream); +} + int windows_dirent_hidden(struct dirent* dd) { - return dd->d_hidden; + return dd->d_stat.st_hidden; } const char* windows_stat_desc(struct stat* st) @@ -1134,7 +1311,7 @@ int windows_readlink(const char* file, char* buffer, size_t size) } /* convert the name to UTF-8 */ - name = u16tou8n(rdb->SymbolicLinkReparseBuffer.PathBuffer + rdb->SymbolicLinkReparseBuffer.PrintNameOffset, + name = u16tou8(rdb->SymbolicLinkReparseBuffer.PathBuffer + rdb->SymbolicLinkReparseBuffer.PrintNameOffset, rdb->SymbolicLinkReparseBuffer.PrintNameLength / 2, &len); /* check for overflow */ diff --git a/cmdline/mingw.h b/cmdline/mingw.h index 0b2a5dc2..9d660c85 100644 --- a/cmdline/mingw.h +++ b/cmdline/mingw.h @@ -80,6 +80,7 @@ #define dirent_hidden windows_dirent_hidden #define HAVE_STRUCT_DIRENT_D_STAT 1 #undef HAVE_STRUCT_DIRENT_D_INO +#undef HAVE_STRUCT_STAT_ST_NLINK #define dirent_lstat windows_dirent_lstat #define stat_desc windows_stat_desc #undef sleep @@ -130,21 +131,22 @@ struct windows_stat { int64_t st_mtime; int32_t st_mtimensec; uint32_t st_mode; - uint32_t st_nlink; uint32_t st_dev; int st_hidden; const char* st_desc; }; /** - * Like the C fstat() including the inode/device information. + * Like the C fstat(). */ int windows_fstat(int fd, struct windows_stat* st); /** - * Like the C lstat() but without the inode information. - * In Windows the inode information is not reported. - * In Windows and in case of hardlinks, the size and the attributes of the file can + * Like the C lstat() but without some information. + * + * The st_ino field may be 0 if it's not possible to read it in a fast way. + * + * In case of hardlinks, the size and the attributes of the file can * be completely bogus, because changes made by other hardlinks are reported in the * directory entry only when the file is opened. * @@ -164,7 +166,7 @@ int windows_fstat(int fd, struct windows_stat* st); int windows_lstat(const char* file, struct windows_stat* st); /** - * Like the C stat() including the inode/device information. + * Like the C stat(). */ int windows_stat(const char* file, struct windows_stat* st); @@ -179,7 +181,8 @@ int windows_mkdir(const char* file); int windows_rmdir(const char* file); /** - * Like the C lstat() including the inode/device information. + * Like the C lstat(). + * It doesn't work for all kind of files and directories. For example "\System Volume Information" cannot be opened. * Note that instead lstat() works for all the files. */ @@ -238,18 +241,13 @@ int windows_open(const char* file, int flags, ...); */ struct windows_dirent { char d_name[PATH_MAX]; - int d_hidden; - WIN32_FIND_DATAW d_data; + struct windows_stat d_stat; }; /** * Like the C DIR. */ -struct windows_dir_struct { - HANDLE h; - struct windows_dirent buffer; - int flags; -}; +struct windows_dir_struct; typedef struct windows_dir_struct windows_dir; /** @@ -269,7 +267,11 @@ int windows_closedir(windows_dir* dirstream); /** * Convert a dirent record to a lstat record. - * Just like the one obtained calling lstat(). + * + * The st_mode field may be 0 if the file is a reparse point. + * The st_ino field may be 0 if it's not possible to read it in a fast way. + * + * In such cases, call lstat_ex() 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 e00c17a6..0526e2a6 100644 --- a/cmdline/scan.c +++ b/cmdline/scan.c @@ -499,16 +499,17 @@ static void scan_file(struct snapraid_scan* scan, int output, const char* sub, c ) { /* check if multiple files have the same inode */ if (file_flag_has(file, FILE_IS_PRESENT)) { - if (st->st_nlink > 1) { - /* it's a hardlink */ - scan_link(scan, output, sub, file->sub, FILE_IS_HARDLINK); - return; - } else { +#if HAVE_STRUCT_STAT_ST_NLINK + if (st->st_nlink <= 1) { /* LCOV_EXCL_START */ fprintf(stderr, "Internal inode '%" PRIu64 "' inconsistency for file '%s%s' already present\n", (uint64_t)st->st_ino, disk->dir, sub); exit(EXIT_FAILURE); /* LCOV_EXCL_STOP */ } +#endif + /* it's a hardlink */ + scan_link(scan, output, sub, file->sub, FILE_IS_HARDLINK); + return; } /* mark as present */ @@ -1083,6 +1084,18 @@ static int scan_dir(struct snapraid_scan* scan, int output, const char* dir, con /* get the type from stat */ st = DSTAT(path_next, dd, &st_buf); +#if HAVE_LSTAT_EX + /* if the stat entry is incomplete, take care to fill it using the extended lstat() */ + if (st->st_mode == 0) { + if (lstat_ex(path_next, st) != 0) { + /* LCOV_EXCL_START */ + fprintf(stderr, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno)); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + } +#endif + if (S_ISREG(st->st_mode)) type = 0; else if (S_ISLNK(st->st_mode)) @@ -1097,19 +1110,22 @@ static int scan_dir(struct snapraid_scan* scan, int output, const char* dir, con if (filter_path(&state->filterlist, disk->name, sub_next) == 0) { uint64_t physical; - /* late stat */ - if (!st) st = DSTAT(path_next, dd, &st_buf); + /* late stat, if not yet called */ + if (!st) + st = DSTAT(path_next, dd, &st_buf); #if HAVE_LSTAT_EX - /* get inode info about the file, Windows needs an additional step */ - /* also for hardlink, the real size of the file is read here */ - if (lstat_ex(path_next, st) != 0) { - /* LCOV_EXCL_START */ - fprintf(stderr, "Error in stat_inode file '%s'. %s.\n", path_next, strerror(errno)); - exit(EXIT_FAILURE); - /* LCOV_EXCL_STOP */ + /* if the stat entry is incomplete, take care to fill it using the extended lstat() */ + if (st->st_ino == 0) { + if (lstat_ex(path_next, st) != 0) { + /* LCOV_EXCL_START */ + fprintf(stderr, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno)); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } } #endif + if (state->opt.force_order == SORT_PHYSICAL) { if (filephy(path_next, st, &physical) != 0) { /* LCOV_EXCL_START */ diff --git a/configure.ac b/configure.ac index cf4b2e39..3224d3bc 100644 --- a/configure.ac +++ b/configure.ac @@ -42,7 +42,7 @@ AC_TYPE_UINT8_T AC_TYPE_INT8_T AC_STRUCT_DIRENT_D_INO AC_STRUCT_DIRENT_D_TYPE -AC_CHECK_MEMBERS([struct stat.st_mtim.tv_nsec, struct stat.st_mtimensec, struct stat.st_mtimespec.tv_nsec], [], [], [[ +AC_CHECK_MEMBERS([struct stat.st_nlink, struct stat.st_mtim.tv_nsec, struct stat.st_mtimensec, struct stat.st_mtimespec.tv_nsec], [], [], [[ #if HAVE_SYS_TYPES_H #include #endif From c513af08ff048e7b80d6ab7c5ebf67ee367227cb Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Wed, 1 Oct 2014 21:44:35 +0200 Subject: [PATCH 2/6] Ensures to call lstat_ex() only on regular files. --- cmdline/import.c | 11 +++++++++++ cmdline/mingw.h | 25 ++++++++++++++++++------- cmdline/pool.c | 11 +++++++++++ cmdline/scan.c | 25 ++++++++++++++++--------- cmdline/state.c | 2 +- 5 files changed, 57 insertions(+), 17 deletions(-) diff --git a/cmdline/import.c b/cmdline/import.c index 328a1f22..adfda914 100644 --- a/cmdline/import.c +++ b/cmdline/import.c @@ -265,6 +265,17 @@ static void import_dir(struct snapraid_state* state, const char* dir) #if HAVE_STRUCT_DIRENT_D_STAT /* convert dirent to lstat result */ dirent_lstat(dd, &st); + + /* if the st_mode field is missing, takes care to fill it using normal lstat() */ + /* at now this can happen only in Windows */ + if (st.st_mode == 0) { + if (lstat(path, &st) != 0) { + /* LCOV_EXCL_START */ + fprintf(stderr, "Error in stat file/directory '%s'. %s.\n", path, strerror(errno)); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + } #else /* get lstat info about the file */ if (lstat(path, &st) != 0) { diff --git a/cmdline/mingw.h b/cmdline/mingw.h index 9d660c85..f6f232c5 100644 --- a/cmdline/mingw.h +++ b/cmdline/mingw.h @@ -142,13 +142,14 @@ struct windows_stat { int windows_fstat(int fd, struct windows_stat* st); /** - * Like the C lstat() but without some information. + * Like the C lstat(), but with some limitations. * * The st_ino field may be 0 if it's not possible to read it in a fast way. + * Specifically this always happens. * * In case of hardlinks, the size and the attributes of the file can - * be completely bogus, because changes made by other hardlinks are reported in the - * directory entry only when the file is opened. + * be completely bogus, because changes made by other hardlinks are reported + * in the directory entry only when the file is opened. * * MSDN CreateHardLinks * http://msdn.microsoft.com/en-us/library/windows/desktop/aa363860%28v=vs.85%29.aspx @@ -181,9 +182,14 @@ int windows_mkdir(const char* file); int windows_rmdir(const char* file); /** - * Like the C lstat(). - - * It doesn't work for all kind of files and directories. For example "\System Volume Information" cannot be opened. + * Like the C lstat(), but with some limitations. + * + * This call fills all the st_* fields of the stat struct. + * + * It doesn't work for all kinds of files and directories. + * You must call it only for regular files. + * For example, "\System Volume Information" cannot be accessed. + * * Note that instead lstat() works for all the files. */ #define HAVE_LSTAT_EX 1 @@ -266,10 +272,15 @@ struct windows_dirent* windows_readdir(windows_dir* dirstream); int windows_closedir(windows_dir* dirstream); /** - * Convert a dirent record to a lstat record. + * Convert a dirent record to a lstat record, but with some limitations. * * The st_mode field may be 0 if the file is a reparse point. + * Specifically this happens if we are using GetFileInformationByHandleEx() + * to read the directory stream. + * * The st_ino field may be 0 if it's not possible to read it in a fast way. + * Specifically this happens if we are using FindFirst/FindNext to enumerate + * the directory. * * In such cases, call lstat_ex() to fill the missing fields. */ diff --git a/cmdline/pool.c b/cmdline/pool.c index b327d5d8..481381ff 100644 --- a/cmdline/pool.c +++ b/cmdline/pool.c @@ -67,6 +67,17 @@ static int clean_dir(struct snapraid_state* state, const char* dir) #if HAVE_STRUCT_DIRENT_D_STAT /* convert dirent to lstat result */ dirent_lstat(dd, &st); + + /* if the st_mode field is missing, takes care to fill it using normal lstat() */ + /* at now this can happen only in Windows */ + if (st.st_mode == 0) { + if (lstat(path_next, &st) != 0) { + /* LCOV_EXCL_START */ + fprintf(stderr, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno)); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + } #else /* get lstat info about the file */ if (lstat(path_next, &st) != 0) { diff --git a/cmdline/scan.c b/cmdline/scan.c index 0526e2a6..09b88a88 100644 --- a/cmdline/scan.c +++ b/cmdline/scan.c @@ -1019,6 +1019,8 @@ static int scan_dir(struct snapraid_scan* scan, int output, const char* dir, con #if HAVE_STRUCT_DIRENT_D_STAT /* convert dirent to lstat result */ dirent_lstat(dd, &entry->d_stat); + + /* note that at this point the st_mode may be 0 */ #endif memcpy(entry->d_name, dd->d_name, name_len + 1); @@ -1084,10 +1086,12 @@ static int scan_dir(struct snapraid_scan* scan, int output, const char* dir, con /* get the type from stat */ st = DSTAT(path_next, dd, &st_buf); -#if HAVE_LSTAT_EX - /* if the stat entry is incomplete, take care to fill it using the extended lstat() */ +#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 */ if (st->st_mode == 0) { - if (lstat_ex(path_next, st) != 0) { + if (lstat(path_next, st) != 0) { /* LCOV_EXCL_START */ fprintf(stderr, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno)); exit(EXIT_FAILURE); @@ -1115,8 +1119,9 @@ static int scan_dir(struct snapraid_scan* scan, int output, const char* dir, con st = DSTAT(path_next, dd, &st_buf); #if HAVE_LSTAT_EX - /* if the stat entry is incomplete, take care to fill it using the extended lstat() */ - if (st->st_ino == 0) { + /* 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) { /* LCOV_EXCL_START */ fprintf(stderr, "Error in stat file/directory '%s'. %s.\n", path_next, strerror(errno)); @@ -1177,8 +1182,9 @@ static int scan_dir(struct snapraid_scan* scan, int output, const char* dir, con } else if (type == 2) { /* DIR */ if (filter_dir(&state->filterlist, disk->name, sub_next) == 0) { #ifndef _WIN32 - /* late stat */ - if (!st) st = DSTAT(path_next, dd, &st_buf); + /* late stat, if not yet called */ + if (!st) + st = DSTAT(path_next, dd, &st_buf); /* in Unix don't follow mount points in different devices */ /* in Windows we are already skipping them reporting them as special files */ @@ -1207,8 +1213,9 @@ static int scan_dir(struct snapraid_scan* scan, int output, const char* dir, con } } else { if (filter_path(&state->filterlist, disk->name, sub_next) == 0) { - /* late stat */ - if (!st) st = DSTAT(path_next, dd, &st_buf); + /* late stat, if not yet called */ + if (!st) + st = DSTAT(path_next, dd, &st_buf); fprintf(stderr, "WARNING! Ignoring special '%s' file '%s'\n", stat_desc(st), path_next); } else { diff --git a/cmdline/state.c b/cmdline/state.c index 65505e7c..8f5f8590 100644 --- a/cmdline/state.c +++ b/cmdline/state.c @@ -367,7 +367,7 @@ void state_config(struct snapraid_state* state, const char* path, const char* co /* copy the options */ state->opt = *opt; - /* if unsed, sort by physical order */ + /* if unset, sort by physical order */ if (!state->opt.force_order) state->opt.force_order = SORT_PHYSICAL; From be24f82cfdf49fa1b26b6dd78d33cb88002ffb20 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Fri, 3 Oct 2014 21:21:20 +0200 Subject: [PATCH 3/6] Fixes the use of star in the regression test. --- Makefile.am | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am b/Makefile.am index 3dcbea74..4847ea69 100644 --- a/Makefile.am +++ b/Makefile.am @@ -225,7 +225,7 @@ endif $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS) --gen-conf bench/content echo --- Filter $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS_VERBOSE) -c $(CONF) -d disk1 check - $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS_VERBOSE) -c $(CONF) -f "*a" -f "a/" -f "/b/a*" check + $(TESTENV) ./snapraid$(EXEEXT) $(CHECKFLAGS_VERBOSE) -c $(CONF) -f \\*a -f a/ -f /b/a\\* check echo --- Delete some files and sync rm bench/disk4/a/9* rm bench/disk5/a/9* From 0f05d40f25e6b391f08a8e70e2cb110b5d32ad3d Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Fri, 3 Oct 2014 21:24:19 +0200 Subject: [PATCH 4/6] Adds the --test-force-scan-winfind option to use the legacy dir listing. --- cmdline/mingw.c | 15 +++++++++++---- cmdline/snapraid.c | 27 +++++++++++++++++++++------ cmdline/state.h | 1 + 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/cmdline/mingw.c b/cmdline/mingw.c index fe1c4826..c741a5d9 100644 --- a/cmdline/mingw.c +++ b/cmdline/mingw.c @@ -119,10 +119,17 @@ static char* last_error; */ static int is_wine; -void os_init(void) +/** + * If we should use the legacy FindFirst/Next way to list directories. + */ +static int is_scan_winfind; + +void os_init(int opt) { HMODULE kernel32, ntdll; + is_scan_winfind = opt != 0; + kernel32 = GetModuleHandle("KERNEL32.DLL"); if (!kernel32) { fprintf(stderr, "Error loading the KERNEL32 module.\n"); @@ -1174,7 +1181,7 @@ windows_dir* windows_opendir(const char* dir) { /* if we have GetFileInformationByHandleEx() we can read */ /* the directory using a stream */ - if (ptr_GetFileInformationByHandleEx != 0) + if (!is_scan_winfind && ptr_GetFileInformationByHandleEx != 0) return windows_opendir_stream(dir); else return windows_opendir_find(dir); @@ -1182,7 +1189,7 @@ windows_dir* windows_opendir(const char* dir) struct windows_dirent* windows_readdir(windows_dir* dirstream) { - if (ptr_GetFileInformationByHandleEx != 0) + if (!is_scan_winfind && ptr_GetFileInformationByHandleEx != 0) return windows_readdir_stream(dirstream); else return windows_readdir_find(dirstream); @@ -1190,7 +1197,7 @@ struct windows_dirent* windows_readdir(windows_dir* dirstream) int windows_closedir(windows_dir* dirstream) { - if (ptr_GetFileInformationByHandleEx != 0) + if (!is_scan_winfind && ptr_GetFileInformationByHandleEx != 0) return windows_closedir_stream(dirstream); else return windows_closedir_find(dirstream); diff --git a/cmdline/snapraid.c b/cmdline/snapraid.c index 3b2c46df..468ee2ee 100644 --- a/cmdline/snapraid.c +++ b/cmdline/snapraid.c @@ -233,6 +233,7 @@ void config(char* conf, size_t conf_size, const char* argv0) #define OPT_TEST_SKIP_PARITY_ACCESS 276 #define OPT_TEST_EXPECT_FAILURE 277 #define OPT_TEST_RUN 278 +#define OPT_TEST_FORCE_SCAN_WINFIND 279 #if HAVE_GETOPT_LONG struct option long_options[] = { @@ -328,6 +329,9 @@ struct option long_options[] = { /* Run some command after loading the state and before the command */ { "test-run", 1, 0, OPT_TEST_RUN }, + /* Use the FindFirst/Next approach in Windows to list files */ + { "test-force-scan-winfind", 0, 0, OPT_TEST_FORCE_SCAN_WINFIND }, + { 0, 0, 0, 0 } }; #endif @@ -384,15 +388,12 @@ int main(int argc, char* argv[]) int lock; const char* gen_conf; const char* run; + int speedtest; int period; time_t t; struct tm* tm; int i; - os_init(); - raid_init(); - crc32c_init(); - /* always different random numbers */ srand(time(0)); @@ -412,6 +413,7 @@ int main(int argc, char* argv[]) log = 0; lock = 0; gen_conf = 0; + speedtest = 0; run = 0; opterr = 0; @@ -546,8 +548,8 @@ int main(int argc, char* argv[]) version(); exit(EXIT_SUCCESS); case 'T' : - speed(period); - exit(EXIT_SUCCESS); + speedtest = 1; + break; case 'C' : gen_conf = optarg; break; @@ -623,6 +625,9 @@ int main(int argc, char* argv[]) case OPT_TEST_RUN : run = optarg; break; + case OPT_TEST_FORCE_SCAN_WINFIND : + opt.force_scan_winfind = 1; + break; default : /* LCOV_EXCL_START */ fprintf(stderr, "Unknown option '%c'\n", (char)c); @@ -631,6 +636,16 @@ int main(int argc, char* argv[]) } } + os_init(opt.force_scan_winfind); + raid_init(); + crc32c_init(); + + if (speedtest != 0) { + speed(period); + os_done(); + exit(EXIT_SUCCESS); + } + if (gen_conf != 0) { generate_configuration(gen_conf); os_done(); diff --git a/cmdline/state.h b/cmdline/state.h index 71921428..3816866f 100644 --- a/cmdline/state.h +++ b/cmdline/state.h @@ -90,6 +90,7 @@ struct snapraid_option { int force_scrub_even; /**< Force scrub of all the even blocks. */ int force_content_write; /**< Force the update of the content file. */ int force_content_text; /**< Force the use of text version of content file. */ + int force_scan_winfind; /**< Force the use of FindFirst/Next in Windows to list directories. */ }; /** From a0c696595920eb30310035cd83f0ff08253b2fe7 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Fri, 3 Oct 2014 21:28:56 +0200 Subject: [PATCH 5/6] Reads the physical offset only for new files. --- cmdline/mingw.c | 8 ++++--- cmdline/portable.h | 17 +++++++++----- cmdline/scan.c | 57 ++++++++++++++++++++++++++++++---------------- cmdline/unix.c | 9 ++++---- 4 files changed, 58 insertions(+), 33 deletions(-) diff --git a/cmdline/mingw.c b/cmdline/mingw.c index c741a5d9..c136a324 100644 --- a/cmdline/mingw.c +++ b/cmdline/mingw.c @@ -1339,7 +1339,7 @@ int devuuid(uint64_t device, char* uuid, size_t uuid_size) return 0; } -int filephy(const char* file, struct stat* st, uint64_t* physical) +int filephy(const char* file, uint64_t size, uint64_t* physical) { HANDLE h; STARTING_VCN_INPUT_BUFFER svib; @@ -1348,9 +1348,11 @@ int filephy(const char* file, struct stat* st, uint64_t* physical) BOOL ret; DWORD n; - /* in Wine just uses the inode number because FSCTL_GET_RETRIVIAL_POINTERS is not supported */ + (void)size; + + /* in Wine FSCTL_GET_RETRIVIAL_POINTERS is not supported */ if (is_wine) { - *physical = st->st_ino; + *physical = FILEPHY_UNREPORTED_OFFSET; return 0; } diff --git a/cmdline/portable.h b/cmdline/portable.h index 4af2e761..bb17ccee 100644 --- a/cmdline/portable.h +++ b/cmdline/portable.h @@ -212,27 +212,32 @@ static inline int hardlink(const char* a, const char* b) int devuuid(uint64_t device, char* uuid, size_t size); /** - * Special value returned when the file doesn't have a real offset. - * For example, because it's stored in the NTFS MFT. + * Physical offset not yet read. */ -#define FILEPHY_WITHOUT_OFFSET 0 +#define FILEPHY_UNREAD_OFFSET 0 /** * Special value returned when the filesystem doesn't report any offset for unknown reason. */ #define FILEPHY_UNREPORTED_OFFSET 1 +/** + * Special value returned when the file doesn't have a real offset. + * For example, because it's stored in the NTFS MFT. + */ +#define FILEPHY_WITHOUT_OFFSET 2 + /** * Value indicating real offsets. All offsets greater or equal at this one are real. */ -#define FILEPHY_REAL_OFFSET 2 +#define FILEPHY_REAL_OFFSET 3 /** * Get the physcal address of the specified file. * This is expected to be just a hint and not necessarely correct or unique. * Returns 0 on success. */ -int filephy(const char* path, struct stat* st, uint64_t* physical); +int filephy(const char* path, uint64_t size, uint64_t* physical); /** * Checks if the underline filesystem support persistent inodes. @@ -251,7 +256,7 @@ uint64_t tick(void); /** * Initializes the system. */ -void os_init(void); +void os_init(int opt); /** * Deintialize the system. diff --git a/cmdline/scan.c b/cmdline/scan.c index 09b88a88..4fb876d2 100644 --- a/cmdline/scan.c +++ b/cmdline/scan.c @@ -290,6 +290,28 @@ static int file_is_full_hashed_and_stable(struct snapraid_state* state, struct s return 1; } +static void scan_file_delayed_insert(struct snapraid_scan* scan, struct snapraid_file* file) +{ + struct snapraid_state* state = scan->state; + struct snapraid_disk* disk = scan->disk; + + /* if we sort for physical offsets we have to read them for the new files */ + if (state->opt.force_order == SORT_PHYSICAL) { + char path_next[PATH_MAX]; + + pathprint(path_next, sizeof(path_next), "%s%s", disk->dir, file->sub); + + if (filephy(path_next, file->size, &file->physical) != 0) { + /* LCOV_EXCL_START */ + fprintf(stderr, "Error in getting the physical offset of file '%s'. %s.\n", path_next, strerror(errno)); + exit(EXIT_FAILURE); + /* LCOV_EXCL_STOP */ + } + } + + tommy_list_insert_tail(&scan->file_insert_list, &file->nodelist, file); +} + /** * Keeps the file as it's (or with only a name/inode modification). * @@ -314,7 +336,7 @@ static void scan_file_keep(struct snapraid_scan* scan, struct snapraid_file* fil tommy_list_remove_existing(&disk->filelist, &file->nodelist); /* insert the file in the delayed block allocation */ - tommy_list_insert_tail(&scan->file_insert_list, &file->nodelist, file); + scan_file_delayed_insert(scan, file); } } @@ -789,7 +811,7 @@ static void scan_file(struct snapraid_scan* scan, int output, const char* sub, c tommy_hashdyn_insert(&disk->stampset, &file->stampset, file, file_stamp_hash(file->size, file->mtime_sec, file->mtime_nsec)); /* insert the file in the delayed block allocation */ - tommy_list_insert_tail(&scan->file_insert_list, &file->nodelist, file); + scan_file_delayed_insert(scan, file); } /** @@ -1112,7 +1134,6 @@ static int scan_dir(struct snapraid_scan* scan, int output, const char* dir, con if (type == 0) { /* REG */ if (filter_path(&state->filterlist, disk->name, sub_next) == 0) { - uint64_t physical; /* late stat, if not yet called */ if (!st) @@ -1131,18 +1152,7 @@ static int scan_dir(struct snapraid_scan* scan, int output, const char* dir, con } #endif - if (state->opt.force_order == SORT_PHYSICAL) { - if (filephy(path_next, st, &physical) != 0) { - /* LCOV_EXCL_START */ - fprintf(stderr, "Error in getting the physical offset of file '%s'. %s.\n", path_next, strerror(errno)); - exit(EXIT_FAILURE); - /* LCOV_EXCL_STOP */ - } - } else { - physical = 0; - } - - scan_file(scan, output, sub_next, st, physical); + scan_file(scan, output, sub_next, st, FILEPHY_UNREAD_OFFSET); processed = 1; } else { if (state->opt.verbose) { @@ -1326,6 +1336,9 @@ void state_scan(struct snapraid_state* state, int output) uint64_t phy_last; struct snapraid_file* phy_file_last; + if (!output) + printf("Mapping disk %s...\n", disk->name); + /* check for removed files */ node = disk->filelist; while (node) { @@ -1402,20 +1415,24 @@ void state_scan(struct snapraid_state* state, int output) /* insert all the new files, we insert them only after the deletion */ /* to reuse the just freed space */ + /* also check if the physical offset reported are fakes or not */ node = scan->file_insert_list; phy_count = 0; phy_dup = 0; - phy_last = -1; + phy_last = FILEPHY_UNREAD_OFFSET; phy_file_last = 0; while (node) { struct snapraid_file* file = node->data; /* if the file is not empty, count duplicate physical offsets */ if (state->opt.force_order == SORT_PHYSICAL && file->size != 0) { - if (phy_file_last != 0 && file->physical == phy_last && phy_last != FILEPHY_WITHOUT_OFFSET) { - /* if verbose, prints the list of duplicates */ - /* if they are supposed real offsets */ - if (state->opt.verbose && phy_last != FILEPHY_UNREPORTED_OFFSET) { + if (phy_file_last != 0 && file->physical == phy_last + /* files without offset are expected to have duplicates */ + && phy_last != FILEPHY_WITHOUT_OFFSET + ) { + /* if verbose, prints the list of duplicates real offsets */ + /* other cases are for offsets not supported, so we don't need to report them file by file */ + if (state->opt.verbose && phy_last >= FILEPHY_REAL_OFFSET) { fprintf(stderr, "WARNING! Files '%s%s' and '%s%s' have the same physical offset %" PRId64 ".\n", disk->dir, phy_file_last->sub, disk->dir, file->sub, phy_last); } ++phy_dup; diff --git a/cmdline/unix.c b/cmdline/unix.c index 77fee7e3..7ac6b628 100644 --- a/cmdline/unix.c +++ b/cmdline/unix.c @@ -129,7 +129,7 @@ int devuuid(uint64_t device, char* uuid, size_t uuid_size) } #endif -int filephy(const char* path, struct stat* st, uint64_t* physical) +int filephy(const char* path, uint64_t size, uint64_t* physical) { #if HAVE_LINUX_FIEMAP_H /* In Linux gets the real physical address of the file */ @@ -162,7 +162,7 @@ int filephy(const char* path, struct stat* st, uint64_t* physical) } /* if the file is empty, FIBMAP doesn't work, and we don't even try to use it */ - if (st->st_size == 0) { + if (size == 0) { *physical = FILEPHY_WITHOUT_OFFSET; if (close(f) == -1) return -1; @@ -199,7 +199,7 @@ int filephy(const char* path, struct stat* st, uint64_t* physical) *physical = FILEPHY_UNREPORTED_OFFSET; (void)path; /* not used here */ - (void)st; + (void)size; #endif return 0; @@ -267,8 +267,9 @@ uint64_t tick(void) #endif } -void os_init(void) +void os_init(int opt) { + (void)opt; } void os_done(void) From ca5e635aa19455b470f26df27fd3be0e81b5f623 Mon Sep 17 00:00:00 2001 From: Andrea Mazzoleni Date: Sat, 4 Oct 2014 10:27:23 +0200 Subject: [PATCH 6/6] 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. --- TODO | 2 + cmdline/elem.c | 2 +- cmdline/elem.h | 2 +- cmdline/mingw.c | 13 +++++- cmdline/mingw.h | 9 ++-- cmdline/scan.c | 109 ++++++++++++++++++++++++++++++++++++++---------- 6 files changed, 108 insertions(+), 29 deletions(-) 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 */ }