From 237aedc590b58c0e69d7dfcac92b5f767b7c004a Mon Sep 17 00:00:00 2001 From: Daniel Wilhelm Date: Fri, 18 Apr 2014 17:17:51 +0200 Subject: 4.6 --- zen/FindFilePlus/find_file_plus.cpp | 127 ++++++++++++-------------------- zen/FindFilePlus/find_file_plus.h | 15 +++- zen/basic_math.h | 2 +- zen/debug_log.h | 2 +- zen/deprecate.h | 1 + zen/dst_hack.cpp | 2 +- zen/file_handling.cpp | 50 +++++++------ zen/file_handling.h | 16 ++-- zen/file_id.cpp | 2 +- zen/file_traverser.cpp | 143 ++++++++++++++++++++++++++---------- zen/file_traverser.h | 4 - zen/fixed_list.h | 4 +- zen/i18n.h | 20 ++++- zen/int64.h | 4 +- zen/privilege.cpp | 2 +- zen/string_base.h | 11 +-- zen/symlink_target.h | 2 +- zen/zstring.cpp | 68 ++++++++--------- 18 files changed, 276 insertions(+), 199 deletions(-) (limited to 'zen') diff --git a/zen/FindFilePlus/find_file_plus.cpp b/zen/FindFilePlus/find_file_plus.cpp index e613177b..876d0c0f 100644 --- a/zen/FindFilePlus/find_file_plus.cpp +++ b/zen/FindFilePlus/find_file_plus.cpp @@ -110,16 +110,12 @@ public: FileSearcher(const wchar_t* dirname); //throw FileError ~FileSearcher(); - void readDir(FileInformation& output) { (this->*readDirFun)(output); } //throw FileError - - bool tryFallbackToDefaultQuery(); //call if readDir() throws STATUS_NOT_SUPPORTED or similar + void readDir(FileInformation& output); //throw FileError private: template void readDirImpl(FileInformation& output); //throw FileError - //handle fallback, if retrieving file id is not supported by file system layer - void (FileSearcher::*readDirFun)(FileInformation& output); - UNICODE_STRING ntPathName; //it seems hDir implicitly keeps a reference to this, at least ::FindFirstFile() does no cleanup before ::FindClose()! + UNICODE_STRING dirnameNt; //it seems hDir implicitly keeps a reference to this, at least ::FindFirstFile() does no cleanup before ::FindClose()! HANDLE hDir; ULONG nextEntryOffset; //!= 0 if entry is waiting in buffer @@ -132,47 +128,13 @@ private: }; -namespace -{ -/* -Common C-style policy handling directory traversal: - -struct QueryPolicy -{ - typedef ... RawFileInfo; - static const FILE_INFORMATION_CLASS fileInformationClass = ...; - static void extractFileId(const RawFileInfo& rawInfo, FileInformation& fileInfo); -}; -*/ - -struct DirQueryDefault -{ - typedef FILE_BOTH_DIR_INFORMATION RawFileInfo; - static const FILE_INFORMATION_CLASS fileInformationClass = FileBothDirectoryInformation; - static void extractFileId(const RawFileInfo& rawInfo, FileInformation& fileInfo) { fileInfo.fileId.QuadPart = 0; } -}; - -struct DirQueryFileId -{ - typedef FILE_ID_BOTH_DIR_INFORMATION RawFileInfo; - static const FILE_INFORMATION_CLASS fileInformationClass = FileIdBothDirectoryInformation; - static void extractFileId(const RawFileInfo& rawInfo, FileInformation& fileInfo) - { - fileInfo.fileId.QuadPart = rawInfo.FileId.QuadPart; //may be 0 even in this context, e.g. for mapped FTP drive! - static_assert(sizeof(fileInfo.fileId) == sizeof(rawInfo.FileId), "dang!"); - } -}; -} - - FileSearcher::FileSearcher(const wchar_t* dirname) : hDir(NULL), - nextEntryOffset(0), - readDirFun(&FileSearcher::readDirImpl) //start optimistically + nextEntryOffset(0) { - ntPathName.Buffer = NULL; - ntPathName.Length = 0; - ntPathName.MaximumLength = 0; + dirnameNt.Buffer = NULL; + dirnameNt.Length = 0; + dirnameNt.MaximumLength = 0; zen::ScopeGuard guardConstructor = zen::makeGuard([&]() { this->~FileSearcher(); }); //-------------------------------------------------------------------------------------------------------------- @@ -184,19 +146,19 @@ FileSearcher::FileSearcher(const wchar_t* dirname) : //NOTE: RtlDosPathNameToNtPathName_U may be used on all XP/Win7/Win8 for compatibility // RtlDosPathNameToNtPathName_U: used by Windows XP available with OS version 3.51 (Windows NT) and higher // RtlDosPathNameToRelativeNtPathName_U: used by Win7/Win8 available with OS version 5.2 (Windows Server 2003) and higher - if (!rtlDosPathNameToNtPathName_U(dirname, //__in dosFileName, - &ntPathName, //__out ntFileName, - NULL, //__out_optFilePart, - NULL)) //__out_opt relativeName - empty if dosFileName is absolute + if (!rtlDosPathNameToNtPathName_U(dirname, //__in dosFileName, + &dirnameNt, //__out ntFileName, + NULL, //__out_optFilePart, + NULL)) //__out_opt relativeName - empty if dosFileName is absolute throw NtFileError(STATUS_OBJECT_PATH_NOT_FOUND); //translates to ERROR_PATH_NOT_FOUND, same behavior like ::FindFirstFileEx() + OBJECT_ATTRIBUTES objAttr = {}; InitializeObjectAttributes(&objAttr, //[out] POBJECT_ATTRIBUTES initializedAttributes, - &ntPathName, //[in] PUNICODE_STRING objectName, + &dirnameNt, //[in] PUNICODE_STRING objectName, OBJ_CASE_INSENSITIVE, //[in] ULONG attributes, NULL, //[in] HANDLE rootDirectory, NULL); //[in, optional] PSECURITY_DESCRIPTOR securityDescriptor - { IO_STATUS_BLOCK status = {}; NTSTATUS rv = ntOpenFile(&hDir, //__out PHANDLE FileHandle, @@ -220,28 +182,49 @@ FileSearcher::~FileSearcher() if (hDir) ntClose(hDir); - if (ntPathName.Buffer) - rtlFreeUnicodeString(&ntPathName); //cleanup identical to ::FindFirstFile() + if (dirnameNt.Buffer) + rtlFreeUnicodeString(&dirnameNt); //cleanup identical to ::FindFirstFile() //note that most of this function seems inlined by the linker, so that its assembly looks equivalent to "RtlFreeHeap(GetProcessHeap(), 0, ntPathName.Buffer)" } -inline -bool FileSearcher::tryFallbackToDefaultQuery() +namespace { - if (readDirFun == &FileSearcher::readDirImpl) - return false; //already default +/* +Common C-style policy handling directory traversal: - //Note: NtQueryDirectoryFile() may not respect "restartScan" for some weird Win2000 file system drivers, so we won't bother - //Samba before v3.0.22 (Mar 30, 2006) seems to have a bug which sucessfully returns 128 elements via NtQueryDirectoryFile() - //and FileIdBothDirectoryInformation, then fails with STATUS_INVALID_LEVEL. - //Although traversal is NOT finished yet, it will further return STATUS_NO_MORE_FILES, even if falling back to FileBothDirectoryInformation!!! +struct QueryPolicy +{ + typedef ... RawFileInfo; + static const FILE_INFORMATION_CLASS fileInformationClass = ...; + static void extractFileId(const RawFileInfo& rawInfo, FileInformation& fileInfo); +}; +*/ - readDirFun = &FileSearcher::readDirImpl; - return true; +struct DirQueryDefault //as implemented in Win32 FindFirstFile()/FindNextFile() +{ + typedef FILE_BOTH_DIR_INFORMATION RawFileInfo; + static const FILE_INFORMATION_CLASS fileInformationClass = FileBothDirectoryInformation; + static void extractFileId(const RawFileInfo& rawInfo, FileInformation& fileInfo) { fileInfo.fileId.QuadPart = 0; } +}; + +struct DirQueryFileId +{ + typedef FILE_ID_BOTH_DIR_INFORMATION RawFileInfo; + static const FILE_INFORMATION_CLASS fileInformationClass = FileIdBothDirectoryInformation; + static void extractFileId(const RawFileInfo& rawInfo, FileInformation& fileInfo) + { + fileInfo.fileId.QuadPart = rawInfo.FileId.QuadPart; //may be 0 even in this context, e.g. for mapped FTP drive! + static_assert(sizeof(fileInfo.fileId) == sizeof(rawInfo.FileId), "dang!"); + } +}; } +inline +void FileSearcher::readDir(FileInformation& output) { readDirImpl(output); } //throw FileError + + template void FileSearcher::readDirImpl(FileInformation& output) //throw FileError { @@ -283,9 +266,9 @@ void FileSearcher::readDirImpl(FileInformation& output) //throw FileError if (!NT_SUCCESS(rv)) { if (rv == STATUS_NO_SUCH_FILE) //harmonize ntQueryDirectoryFile() error handling! failure to find a file on first call returns STATUS_NO_SUCH_FILE, - rv = STATUS_NO_MORE_FILES; //while on subsequent accesses return STATUS_NO_MORE_FILES + rv = STATUS_NO_MORE_FILES; //while on subsequent accesses returns STATUS_NO_MORE_FILES //note: not all directories contain "., .." entries! E.g. a drive's root directory or NetDrive + ftp.gnu.org\CRYPTO.README" - //-> addon: this is NOT a directory, it looks like on in NetDrive, but it's a file in Opera + //-> addon: this is NOT a directory, it looks like one in NetDrive, but it's a file in Opera throw NtFileError(rv); //throws STATUS_NO_MORE_FILES when finished } @@ -353,22 +336,6 @@ bool findplus::readDir(FindHandle hnd, FileInformation& output) } catch (const NtFileError& e) { - /* - fallback to default directory query method, if FileIdBothDirectoryInformation is not properly implemented - this is required for NetDrive mounted Webdav, e.g. www.box.net and NT4, 2000 remote drives, et al. - */ - if (e.ntError != STATUS_NO_MORE_FILES) - if (e.ntError == STATUS_INVALID_LEVEL || - e.ntError == STATUS_NOT_SUPPORTED || - e.ntError == STATUS_INVALID_PARAMETER || - e.ntError == STATUS_INVALID_NETWORK_RESPONSE || - e.ntError == STATUS_INVALID_INFO_CLASS || - e.ntError == STATUS_ACCESS_VIOLATION) //FileIdBothDirectoryInformation on XP accessing UDF - { - if (hnd->tryFallbackToDefaultQuery()) - return readDir(hnd, output); //implementation of tryFallbackToDefaultQuery() promises, that we don't land in an endless recursion here! - } - setWin32Error(rtlNtStatusToDosError(e.ntError)); return false; } diff --git a/zen/FindFilePlus/find_file_plus.h b/zen/FindFilePlus/find_file_plus.h index 33e9a178..2ef5affe 100644 --- a/zen/FindFilePlus/find_file_plus.h +++ b/zen/FindFilePlus/find_file_plus.h @@ -38,7 +38,7 @@ struct FileInformation DWORD fileAttributes; DWORD shortNameLength; WCHAR shortName[MAX_PATH + 1]; //shortName is 0-terminated -}; //no need for #pragma pack -> all members already starting at 4 byte boundary! +}; //no need for #pragma pack -> all members are perfectly 4, 8 byte aligned! class FileSearcher; typedef FileSearcher* FindHandle; @@ -49,6 +49,19 @@ FindHandle openDir(const wchar_t* dirname); //returns NULL on error, call ::GetL DLL_FUNCTION_DECLARATION bool readDir(FindHandle hnd, FileInformation& output); //returns false on error or if there are no more files; ::GetLastError() returns ERROR_NO_MORE_FILES in this case +/* +warning:; this may also return file system implementation dependent error codes like ERROR_NOT_SUPPORTED, ERROR_INVALID_LEVEL, +ect. if "FileIdBothDirectoryInformation" is not supported! We need a fallback: + + sometimes it's *not* sufficient to use fallback for NtQueryDirectoryFile() alone, we need to reset "hDir", since it may be f$ck$d $p by some poor file system layer implementation: + - Samba before v3.0.22 (Mar 30, 2006) seems to have a bug which sucessfully returns 128 elements via NtQueryDirectoryFile() and FileIdBothDirectoryInformation, + then fails with STATUS_INVALID_LEVEL. Fallback to FileBothDirectoryInformation will return STATUS_NO_MORE_FILES, even if there *are* more files + - NtQueryDirectoryFile() may *not* respect "restartScan" for some weird Win2000 file system drivers, so we cannot rely on this as a replacement for a "hDir" reset + - Windows 7 Remote Desktop sharing does not work unless "hDir" is reset! + => let's assume worst case in general and do a reset! + perf note: implementing this reset at a folder level is possible, but a huge perf-killer (additional open/close handle), therefore fallback must apply to a complete folder (sub-)tree! + => caller needs to handle this and implement FindFirstFile()/FindNextFile() fallback! +*/ DLL_FUNCTION_DECLARATION void closeDir(FindHandle hnd); diff --git a/zen/basic_math.h b/zen/basic_math.h index 606d90ad..f4f46ce0 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -304,7 +304,7 @@ template inline double stdDeviation(InputIterator first, InputIterator last, double* arithMean) { //implementation minimizing rounding errors, see: http://en.wikipedia.org/wiki/Standard_deviation - //combined with techinque avoiding overflow, see: http://www.netlib.org/blas/dnrm2.f -> only 10% performance degregation + //combined with techinque avoiding overflow, see: http://www.netlib.org/blas/dnrm2.f -> only 10% performance degradation size_t n = 0; double mean = 0; diff --git a/zen/debug_log.h b/zen/debug_log.h index bd9af25f..e7116156c 100644 --- a/zen/debug_log.h +++ b/zen/debug_log.h @@ -55,7 +55,7 @@ public: int sourceRow, const std::string& message) { - const std::string logEntry = "[" + formatTime(FORMAT_TIME()) + "] " + afterLast(sourceFile, ZEN_FILE_NAME_SEPARATOR) + + const std::string logEntry = "[" + formatTime(FORMAT_TIME) + "] " + afterLast(sourceFile, ZEN_FILE_NAME_SEPARATOR) + ", line " + toString(sourceRow) + ": " + message + "\n"; const size_t bytesWritten = ::fwrite(logEntry.c_str(), 1, logEntry.size(), handle); diff --git a/zen/deprecate.h b/zen/deprecate.h index 3481a062..b045d3c0 100644 --- a/zen/deprecate.h +++ b/zen/deprecate.h @@ -7,6 +7,7 @@ #ifndef DEPRECATE_HEADER_2348970348 #define DEPRECATE_HEADER_2348970348 +//compiler macros: http://predef.sourceforge.net/precomp.html #ifdef __GNUC__ #define ZEN_DEPRECATE __attribute__ ((deprecated)) diff --git a/zen/dst_hack.cpp b/zen/dst_hack.cpp index e4f48c2f..ac5774d0 100644 --- a/zen/dst_hack.cpp +++ b/zen/dst_hack.cpp @@ -94,7 +94,7 @@ bool dst::isFatDrive(HANDLE hFile) //throw() LPWSTR lpFileSystemNameBuffer, DWORD nFileSystemNameSize); - const DllFun getVolumeInformationByHandle(L"kernel32.dll", "GetVolumeInformationByHandleW"); + const SysDllFun getVolumeInformationByHandle(L"kernel32.dll", "GetVolumeInformationByHandleW"); if (!getVolumeInformationByHandle) { assert(false); diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp index d6804e34..334128d9 100644 --- a/zen/file_handling.cpp +++ b/zen/file_handling.cpp @@ -505,13 +505,19 @@ void zen::renameFile(const Zstring& oldName, const Zstring& newName) //throw Fil class CopyCallbackImpl : public zen::CallbackCopyFile //callback functionality { public: - CopyCallbackImpl(const Zstring& sourceFile, CallbackMoveFile& callback) : sourceFile_(sourceFile), moveCallback(callback) {} + CopyCallbackImpl(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackMoveFile& callback) : sourceFile_(sourceFile), + targetFile_(targetFile), + moveCallback(callback) {} virtual void deleteTargetFile(const Zstring& targetFile) { assert(!fileExists(targetFile)); } virtual void updateCopyStatus(UInt64 totalBytesTransferred) { - moveCallback.requestUiRefresh(sourceFile_); + const Int64 delta = to(totalBytesTransferred) - bytesReported; + moveCallback.updateStatus(delta); + bytesReported += delta; } private: @@ -519,15 +525,15 @@ private: CopyCallbackImpl& operator=(const CopyCallbackImpl&); const Zstring sourceFile_; + const Zstring targetFile_; CallbackMoveFile& moveCallback; + Int64 bytesReported; }; void zen::moveFile(const Zstring& sourceFile, const Zstring& targetFile, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError; { - //call back once per file (moveFile() is called by moveDirectory()) - if (callback) - callback->requestUiRefresh(sourceFile); + if (callback) callback->onBeforeFileMove(sourceFile, targetFile); //call back once *after* work was done const bool targetExisting = fileExists(targetFile); @@ -541,24 +547,28 @@ void zen::moveFile(const Zstring& sourceFile, const Zstring& targetFile, bool ig try { renameFile(sourceFile, targetFile); //throw FileError, ErrorDifferentVolume - return; //great, we get away cheaply! + //great, we get away cheaply! + if (callback) callback->objectProcessed(); + return; } //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the file) catch (const ErrorDifferentVolume&) {} //file is on a different volume: let's copy it - std::unique_ptr copyCallback(callback != NULL ? new CopyCallbackImpl(sourceFile, *callback) : NULL); - if (symlinkExists(sourceFile)) copySymlink(sourceFile, targetFile, false); //throw FileError; don't copy filesystem permissions else + { + std::unique_ptr copyCallback(callback != NULL ? new CopyCallbackImpl(sourceFile, targetFile, *callback) : NULL); copyFile(sourceFile, targetFile, false, true, copyCallback.get()); //throw FileError; + } //attention: if copy-operation was cancelled an exception is thrown => sourcefile is not deleted, as we wish! } removeFile(sourceFile); //throw FileError //note: copying file is NOT undone in case of exception: currently this function is called in context of user-defined deletion dir, where this behavior is fine + if (callback) callback->objectProcessed(); } namespace @@ -605,19 +615,15 @@ private: struct RemoveCallbackImpl : public CallbackRemoveDir { - RemoveCallbackImpl(const Zstring& sourceDir, - CallbackMoveFile& moveCallback) : - sourceDir_(sourceDir), - moveCallback_(moveCallback) {} + RemoveCallbackImpl(CallbackMoveFile& moveCallback) : moveCallback_(moveCallback) {} - virtual void notifyFileDeletion(const Zstring& filename) { moveCallback_.requestUiRefresh(sourceDir_); } - virtual void notifyDirDeletion (const Zstring& dirname ) { moveCallback_.requestUiRefresh(sourceDir_); } + virtual void notifyFileDeletion(const Zstring& filename) { moveCallback_.updateStatus(0); } + virtual void notifyDirDeletion (const Zstring& dirname ) { moveCallback_.updateStatus(0); } private: RemoveCallbackImpl(const RemoveCallbackImpl&); RemoveCallbackImpl& operator=(const RemoveCallbackImpl&); - const Zstring sourceDir_; CallbackMoveFile& moveCallback_; }; } @@ -625,9 +631,7 @@ private: void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError { - //call back once per folder - if (callback) - callback->requestUiRefresh(sourceDir); + if (callback) callback->onBeforeDirMove(sourceDir, targetDir); //call back once *after* work was done const bool targetExisting = dirExists(targetDir); @@ -643,7 +647,9 @@ void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool try { renameFile(sourceDir, targetDir); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting - return; //great, we get away cheaply! + //great, we get away cheaply! + if (callback) callback->objectProcessed(); + return; } //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the directory) catch (const ErrorDifferentVolume&) {} @@ -681,8 +687,10 @@ void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool } //delete source - std::unique_ptr removeCallback(callback != NULL ? new RemoveCallbackImpl(sourceDir, *callback) : NULL); + std::unique_ptr removeCallback(callback != NULL ? new RemoveCallbackImpl(*callback) : NULL); removeDirectory(sourceDir, removeCallback.get()); //throw FileError; + + if (callback) callback->objectProcessed(); } @@ -1458,7 +1466,7 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, if source is a symlink and COPY_FILE_COPY_SYMLINK is NOT specified, this callback is called and hSourceFile is a handle to the *target* of the link! file time handling: - ::CopyFileEx() will copy file modification time (only) over from source file AFTER the last inocation of this callback + ::CopyFileEx() will copy file modification time (only) over from source file AFTER the last invokation of this callback => it is possible to adapt file creation time of target in here, but NOT file modification time! alternate data stream handling: diff --git a/zen/file_handling.h b/zen/file_handling.h index 8ffe38d0..e6819322 100644 --- a/zen/file_handling.h +++ b/zen/file_handling.h @@ -47,7 +47,7 @@ void setFileTime(const Zstring& filename, const Int64& modificationTime, ProcSym UInt64 getFilesize(const Zstring& filename); //throw FileError //file handling -bool removeFile(const Zstring& filename); //return "true" if file was actually deleted; throw (FileError) +bool removeFile(const Zstring& filename); //return "true" if file was actually deleted; throw FileError void removeDirectory(const Zstring& directory, CallbackRemoveDir* callback = NULL); //throw FileError @@ -93,8 +93,8 @@ void copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copy struct CallbackRemoveDir { virtual ~CallbackRemoveDir() {} - virtual void notifyFileDeletion(const Zstring& filename) = 0; - virtual void notifyDirDeletion(const Zstring& dirname) = 0; + virtual void notifyFileDeletion(const Zstring& filename) = 0; //one call for each (existing) object! + virtual void notifyDirDeletion (const Zstring& dirname ) = 0; // }; @@ -115,8 +115,14 @@ struct CallbackCopyFile //callback functionality struct CallbackMoveFile //callback functionality { - virtual ~CallbackMoveFile() {} - virtual void requestUiRefresh(const Zstring& currentObject) = 0; //see CallbackCopyFile! + virtual ~CallbackMoveFile() {} //see CallbackCopyFile for limitations when trowing exceptions! + + virtual void onBeforeFileMove(const Zstring& fileFrom, const Zstring& fileTo) = 0; //one call before each (planned) move + virtual void onBeforeDirMove (const Zstring& dirFrom, const Zstring& dirTo ) = 0; // + virtual void objectProcessed() = 0; //one call after each completed move (count objects total) + + //called frequently if move has to revert to copy + delete: + virtual void updateStatus(Int64 bytesDelta) = 0; }; } diff --git a/zen/file_id.cpp b/zen/file_id.cpp index 7527062b..fa05a142 100644 --- a/zen/file_id.cpp +++ b/zen/file_id.cpp @@ -27,7 +27,7 @@ zen::FileId zen::getFileID(const Zstring& filename) const HANDLE hFile = ::CreateFile(zen::applyLongPathPrefix(filename).c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - 0, + NULL, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, //FILE_FLAG_BACKUP_SEMANTICS needed to open a directory NULL); diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index 236581a3..0fb8a332 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -16,6 +16,7 @@ #include "file_update_handle.h" #include "dll.h" #include "FindFilePlus/find_file_plus.h" +#include #elif defined FFS_LINUX #include @@ -36,7 +37,7 @@ bool tryReportingError(Command cmd, zen::TraverseCallback& callback) //return "t for (;;) try { - cmd(); + cmd(); //throw FileError return true; } catch (const FileError& e) @@ -47,9 +48,9 @@ bool tryReportingError(Command cmd, zen::TraverseCallback& callback) //return "t break; case TraverseCallback::TRAV_ERROR_IGNORE: return false; - default: - assert(false); - break; + //default: + // assert(false); + //break; } } } @@ -78,14 +79,54 @@ bool extractFileInfoFromSymlink(const Zstring& linkName, zen::TraverseCallback:: //write output output.fileSize = zen::UInt64(fileInfoByHandle.nFileSizeLow, fileInfoByHandle.nFileSizeHigh); output.lastWriteTimeRaw = toTimeT(fileInfoByHandle.ftLastWriteTime); - //output.id = extractFileID(fileInfoByHandle); -> id from dereferenced symlink is problematic, since renaming will consider the link, not the target! + output.id = FileId(); //= extractFileID(fileInfoByHandle); -> id from dereferenced symlink is problematic, since renaming will consider the link, not the target! return true; } -DWORD retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! +DWORD retrieveVolumeSerial(const Zstring& pathName) //returns 0 on error or if serial is not supported! +{ + //this works for: + //- root paths "C:\", "D:\" + //- network shares: \\share\dirname + //- indirection: subst S: %USERPROFILE% + // -> GetVolumePathName() on the other hand resolves "S:\Desktop\somedir" to "S:\Desktop\" - nice try... + + //dynamically load windows API function (existing since Windows XP) + typedef BOOL (WINAPI *GetFileInformationByHandleFunc)(HANDLE hFile, + LPBY_HANDLE_FILE_INFORMATION lpFileInformation); + + const SysDllFun getFileInformationByHandle(L"kernel32.dll", "GetFileInformationByHandle"); + if (!getFileInformationByHandle) + { + assert(false); + return 0; + } + + const HANDLE hDir = ::CreateFile(zen::applyLongPathPrefix(pathName).c_str(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS /*needed to open a directory*/ /*| FILE_FLAG_OPEN_REPARSE_POINT -> no, we follow symlinks!*/ , + NULL); + if (hDir == INVALID_HANDLE_VALUE) + return 0; + ZEN_ON_BLOCK_EXIT(::CloseHandle(hDir)); + + BY_HANDLE_FILE_INFORMATION fileInfo = {}; + if (!getFileInformationByHandle(hDir, //__in HANDLE hFile, + &fileInfo)) //__out LPBY_HANDLE_FILE_INFORMATION lpFileInformation + return 0; + + return fileInfo.dwVolumeSerialNumber; +} + + +/* +DWORD retrieveVolumeSerial(const Zstring& pathName) //returns 0 on error! { - //note: this even works for network shares: \\share\dirname + //note: this works for network shares: \\share\dirname, but not "subst"! const DWORD BUFFER_SIZE = 10000; std::vector buffer(BUFFER_SIZE); @@ -113,11 +154,14 @@ DWORD retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! return volumeSerial; } +*/ + +const bool isXpOrLater = winXpOrLater(); //VS2010 compiled DLLs are not supported on Win 2000: Popup dialog "DecodePointer not found" -const DllFun openDir (findplus::getDllName(), findplus::openDirFuncName ); // -const DllFun readDir (findplus::getDllName(), findplus::readDirFuncName ); //load at startup: avoid pre C++11 static initialization MT issues -const DllFun closeDir(findplus::getDllName(), findplus::closeDirFuncName); // +const DllFun openDir = isXpOrLater ? DllFun(findplus::getDllName(), findplus::openDirFuncName ) : DllFun(); // +const DllFun readDir = isXpOrLater ? DllFun(findplus::getDllName(), findplus::readDirFuncName ) : DllFun(); //load at startup: avoid pre C++11 static initialization MT issues +const DllFun closeDir= isXpOrLater ? DllFun(findplus::getDllName(), findplus::closeDirFuncName) : DllFun(); // /* Common C-style interface for Win32 FindFirstFile(), FindNextFile() and FileFilePlus openDir(), closeDir(): @@ -125,12 +169,15 @@ struct TraverserPolicy //see "policy based design" { typedef ... DirHandle; typedef ... FindData; + static void create(const Zstring& directory, DirHandle& hnd); //throw FileError - *no* concession to FindFirstFile(): open handle only, *no* return of data! static void destroy(DirHandle hnd); //throw() -static bool getEntry(DirHandle hnd, const Zstring& directory, FindData& fileInfo) //throw FileError + +template +static bool getEntry(DirHandle hnd, const Zstring& directory, FindData& fileInfo, FallbackFun fb) //throw FileError -> fb: fallback to FindFirstFile()/FindNextFile() //FindData "member" functions -static void extractFileInfo (const FindData& fileInfo, const DWORD* volumeSerial, TraverseCallback::FileInfo& output); +static void extractFileInfo (const FindData& fileInfo, DWORD volumeSerial, TraverseCallback::FileInfo& output); //volumeSerial may be 0 if not available! static Int64 getModTime (const FindData& fileInfo); static const FILETIME& getModTimeRaw (const FindData& fileInfo); //yet another concession to DST hack static const FILETIME& getCreateTimeRaw(const FindData& fileInfo); // @@ -177,7 +224,8 @@ struct Win32Traverser static void destroy(const DirHandle& hnd) { ::FindClose(hnd.searchHandle); } //throw() - static bool getEntry(DirHandle& hnd, const Zstring& directory, FindData& fileInfo) //throw FileError + template + static bool getEntry(DirHandle& hnd, const Zstring& directory, FindData& fileInfo, FallbackFun) //throw FileError { if (hnd.firstRead) { @@ -197,10 +245,11 @@ struct Win32Traverser } template - static void extractFileInfo(const FindData& fileInfo, const DWORD* volumeSerial, TraverseCallback::FileInfo& output) + static void extractFileInfo(const FindData& fileInfo, DWORD volumeSerial, TraverseCallback::FileInfo& output) { - output.lastWriteTimeRaw = getModTime(fileInfo); output.fileSize = UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); + output.lastWriteTimeRaw = getModTime(fileInfo); + output.id = FileId(); } template @@ -243,12 +292,40 @@ struct FilePlusTraverser static void destroy(DirHandle hnd) { ::closeDir(hnd.searchHandle); } //throw() - static bool getEntry(DirHandle hnd, const Zstring& directory, FindData& fileInfo) //throw FileError + template + static bool getEntry(DirHandle hnd, const Zstring& directory, FindData& fileInfo, FallbackFun fb) //throw FileError { if (!::readDir(hnd.searchHandle, fileInfo)) { - if (::GetLastError() == ERROR_NO_MORE_FILES) //not an error situation + const DWORD lastError = ::GetLastError(); + if (lastError == ERROR_NO_MORE_FILES) //not an error situation return false; + + /* + fallback to default directory query method, if FileIdBothDirectoryInformation is not properly implemented + this is required for NetDrive mounted Webdav, e.g. www.box.net and NT4, 2000 remote drives, et al. + + NT status code | Win32 error code + ----------------------------------------------------------- + STATUS_INVALID_LEVEL | ERROR_INVALID_LEVEL + STATUS_NOT_SUPPORTED | ERROR_NOT_SUPPORTED + STATUS_INVALID_PARAMETER | ERROR_INVALID_PARAMETER + STATUS_INVALID_NETWORK_RESPONSE | ERROR_BAD_NET_RESP + STATUS_INVALID_INFO_CLASS | ERROR_INVALID_PARAMETER + STATUS_UNSUCCESSFUL | ERROR_GEN_FAILURE + STATUS_ACCESS_VIOLATION | ERROR_NOACCESS ->FileIdBothDirectoryInformation on XP accessing UDF + */ + if (lastError == ERROR_INVALID_LEVEL || + lastError == ERROR_NOT_SUPPORTED || + lastError == ERROR_INVALID_PARAMETER || + lastError == ERROR_BAD_NET_RESP || + lastError == ERROR_GEN_FAILURE || + lastError == ERROR_NOACCESS) + { + fb(); //fallback should apply to whole directory sub-tree! + return false; + } + //else we have a problem... report it: throw FileError(_("Error traversing directory:") + L"\n\"" + directory + L"\"" + L"\n\n" + zen::getLastErrorFormatted()); } @@ -256,12 +333,11 @@ struct FilePlusTraverser } template - static void extractFileInfo(const FindData& fileInfo, const DWORD* volumeSerial, TraverseCallback::FileInfo& output) + static void extractFileInfo(const FindData& fileInfo, DWORD volumeSerial, TraverseCallback::FileInfo& output) { output.fileSize = UInt64(fileInfo.fileSize.QuadPart); output.lastWriteTimeRaw = getModTime(fileInfo); - if (volumeSerial) - output.id = extractFileID(*volumeSerial, fileInfo.fileId); + output.id = extractFileID(volumeSerial, fileInfo.fileId); } template @@ -296,7 +372,7 @@ public: { activatePrivilege(SE_BACKUP_NAME); //throw FileError } - catch (...) {} //don't cause issues in user mode + catch (FileError&) {} //don't cause issues in user mode if (::openDir && ::readDir && ::closeDir) traverse(baseDirectory, sink, 0); @@ -335,24 +411,26 @@ private: typename Trav::FindData fileInfo = {}; + auto fallback = [&] { this->traverse(directory, sink, level); }; //help VS2010 a little by avoiding too deeply nested lambdas + while ([&]() -> bool { - bool moreData = false; + bool gotEntry = false; typedef Trav Trav1; //f u VS! tryReportingError([&] { typedef Trav1 Trav; //f u VS! - moreData = Trav::getEntry(searchHandle, directory, fileInfo); //throw FileError + gotEntry = Trav::getEntry(searchHandle, directory, fileInfo, fallback); //throw FileError }, sink); - return moreData; + return gotEntry; }()) { //skip "." and ".." const Zchar* const shortName = Trav::getShortName(fileInfo); if (shortName[0] == L'.' && - (shortName[1] == L'\0' || (shortName[1] == L'.' && shortName[2] == L'\0'))) + (shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0))) continue; const Zstring& fullName = endsWith(directory, FILE_NAME_SEPARATOR) ? @@ -393,7 +471,7 @@ private: } else { - Trav::extractFileInfo(fileInfo, volumeSerial != 0 ? &volumeSerial : nullptr, details); //make optional character of volumeSerial explicit in the interface + Trav::extractFileInfo(fileInfo, volumeSerial, details); //make optional character of volumeSerial explicit in the interface //####################################### DST hack ########################################### if (isFatFileSystem) @@ -553,7 +631,7 @@ private: //don't return "." and ".." const char* const shortName = dirEntry->d_name; //evaluate dirEntry *before* going into recursion => we use a single "buffer"! if (shortName[0] == '.' && - (shortName[1] == '\0' || (shortName[1] == '.' && shortName[2] == '\0'))) + (shortName[1] == 0 || (shortName[1] == '.' && shortName[2] == 0))) continue; const Zstring& fullName = endsWith(directory, FILE_NAME_SEPARATOR) ? //e.g. "/" @@ -637,14 +715,3 @@ void zen::traverseFolder(const Zstring& directory, bool followSymlinks, Traverse { DirTraverser(directory, followSymlinks, sink, dstCallback); } - - -bool zen::supportForFileId() //Linux: always; Windows: if FindFilePlus_Win32.dll was loaded correctly -{ -#ifdef FFS_WIN - return ::openDir && ::readDir && ::closeDir; - -#elif defined FFS_LINUX - return true; -#endif -} diff --git a/zen/file_traverser.h b/zen/file_traverser.h index 075c32e5..b277b6ab 100644 --- a/zen/file_traverser.h +++ b/zen/file_traverser.h @@ -70,10 +70,6 @@ void traverseFolder(const Zstring& directory, //throw(); //followSymlinks: //"true": Symlinks dereferenced and reported via onFile() and onDir() => onSymlink not used! //"false": Symlinks directly reported via onSymlink(), directory symlinks are not followed - - -//determine whether FileId can be expected to be retrieved -bool supportForFileId(); //Linux: always; Windows: if FindFilePlus_Win32.dll was loaded correctly } #endif // FILETRAVERSER_H_INCLUDED diff --git a/zen/fixed_list.h b/zen/fixed_list.h index e80adb99..f08a4815 100644 --- a/zen/fixed_list.h +++ b/zen/fixed_list.h @@ -82,7 +82,9 @@ public: void remove_if(Predicate pred) { Node* prev = NULL; - for (auto ptr = first; ptr;) + Node* ptr = first; + + while (ptr) if (pred(ptr->val)) { Node* tmp = ptr->next; diff --git a/zen/i18n.h b/zen/i18n.h index de615cdb..08ebd05c 100644 --- a/zen/i18n.h +++ b/zen/i18n.h @@ -9,6 +9,8 @@ #include #include +#include //thousands separator +#include "utf8.h" // //thin layer to enable localization - without platform/library dependencies! #ifndef WXINTL_NO_GETTEXT_MACRO @@ -27,7 +29,6 @@ struct TranslationHandler { virtual ~TranslationHandler() {} - virtual std::wstring thousandsSeparator() = 0; virtual std::wstring translate(const std::wstring& text) = 0; //simple translation virtual std::wstring translate(const std::wstring& singular, const std::wstring& plural, int n) = 0; }; @@ -35,8 +36,9 @@ struct TranslationHandler void setTranslator(TranslationHandler* newHandler = NULL); //takes ownership TranslationHandler* getTranslator(); -inline -std::wstring getThousandsSeparator() { return getTranslator() ? getTranslator()->thousandsSeparator() : L","; }; +std::wstring getThousandsSeparator(); + + @@ -91,6 +93,18 @@ void setTranslator(TranslationHandler* newHandler) { implementation::globalHandl inline TranslationHandler* getTranslator() { return implementation::globalHandler().get(); } + + +inline +std::wstring getThousandsSeparator() //consistency with sprintf(): just use the same values the C-runtime uses!!! +{ + //::setlocale (LC_ALL, ""); -> implicitly called by wxLocale + const lconv* localInfo = ::localeconv(); //always bound according to doc + return utf8CvrtTo(localInfo->thousands_sep); + // why not working? + // THOUSANDS_SEPARATOR = std::use_facet >(std::locale("")).thousands_sep(); + // DECIMAL_POINT = std::use_facet >(std::locale("")).decimal_point(); +} } #endif //I18_N_HEADER_3843489325045 diff --git a/zen/int64.h b/zen/int64.h index 91e24437..31c278ca 100644 --- a/zen/int64.h +++ b/zen/int64.h @@ -33,7 +33,8 @@ zen::Int64/zen::UInt64: wrapper classes around std::int64_t/std::uint64_t namespace zen { -template inline void checkRange(U value) +template inline +void checkRange(U value) { //caveat: std::numeric_limits::min returns minimum positive(!) number for T = double, while behaving correctly for integer types... sigh assert(double(std::numeric_limits::lowest()) <= double(value) && //new with C++11! @@ -93,6 +94,7 @@ public: Int64& operator|=(const Int64& rhs) { value |= rhs.value; return *this;} Int64& operator<<=(int rhs) { assert(rhs < 0 || (value << rhs) >> rhs == value); value <<= rhs; return *this; } Int64& operator>>=(int rhs) { assert(rhs > 0 || (value >> rhs) << rhs == value); value >>= rhs; return *this; } + Int64 operator-() const { return -value; } inline friend bool operator==(const Int64& lhs, const Int64& rhs) { return lhs.value == rhs.value; } inline friend bool operator!=(const Int64& lhs, const Int64& rhs) { return lhs.value != rhs.value; } diff --git a/zen/privilege.cpp b/zen/privilege.cpp index 6dd0b2d7..3b7e9cc5 100644 --- a/zen/privilege.cpp +++ b/zen/privilege.cpp @@ -107,7 +107,7 @@ private: if (iter->second) try { - setPrivilege(iter->first.c_str(), false); + setPrivilege(iter->first.c_str(), false); //throw FileError } catch (...) {} } diff --git a/zen/string_base.h b/zen/string_base.h index ffc2f839..88da13bf 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -9,6 +9,7 @@ #include #include +#include #include "string_tools.h" #include @@ -76,7 +77,7 @@ protected: newDescr->length = size; newDescr->capacity = newCapacity; - return reinterpret_cast(newDescr + 1); + return reinterpret_cast(newDescr + 1); //alignment note: "newDescr + 1" is Descriptor-aligned, which is larger than alignment for Char-array! => no problem! } static Char* clone(Char* ptr) @@ -101,8 +102,8 @@ protected: private: struct Descriptor { - size_t length; - size_t capacity; //allocated size without null-termination + std::uint32_t length; + std::uint32_t capacity; //allocated size without null-termination }; static Descriptor* descr( Char* ptr) { return reinterpret_cast< Descriptor*>(ptr) - 1; } @@ -173,8 +174,8 @@ private: Descriptor(long rc, size_t len, size_t cap) : refCount(rc), length(len), capacity(cap) {} boost::detail::atomic_count refCount; //practically no perf loss: ~0.2%! (FFS comparison) - size_t length; - size_t capacity; //allocated size without null-termination + std::uint32_t length; + std::uint32_t capacity; //allocated size without null-termination }; static Descriptor* descr( Char* ptr) { return reinterpret_cast< Descriptor*>(ptr) - 1; } diff --git a/zen/symlink_target.h b/zen/symlink_target.h index dfbbba6d..b66d5c0e 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -68,7 +68,7 @@ Zstring getSymlinkRawTargetString(const Zstring& linkPath) //throw FileError { activatePrivilege(SE_BACKUP_NAME); //throw FileError } - catch (...) {} + catch (FileError&) {} const HANDLE hLink = ::CreateFile(applyLongPathPrefix(linkPath).c_str(), GENERIC_READ, diff --git a/zen/zstring.cpp b/zen/zstring.cpp index a559f9de..38d9b4c4 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -128,41 +128,41 @@ int z_impl::compareFilenamesWin(const wchar_t* a, const wchar_t* b, size_t sizeA const int minSize = std::min(sizeA, sizeB); - if (minSize == 0) //LCMapString does not allow input sizes of 0! - return static_cast(sizeA) - static_cast(sizeB); - - int rv = 0; //always initialize... - if (minSize <= 5000) //performance optimization: stack - { - wchar_t bufferA[5000]; - wchar_t bufferB[5000]; - - //faster than CharUpperBuff + wmemcpy or CharUpper + wmemcpy and same speed like ::CompareString() - if (::LCMapString(ZSTRING_INVARIANT_LOCALE, //__in LCID Locale, - LCMAP_UPPERCASE, //__in DWORD dwMapFlags, - a, //__in LPCTSTR lpSrcStr, - minSize, //__in int cchSrc, - bufferA, //__out LPTSTR lpDestStr, - 5000) == 0) //__in int cchDest - throw std::runtime_error("Error comparing strings! (LCMapString)"); - - if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, b, minSize, bufferB, 5000) == 0) - throw std::runtime_error("Error comparing strings! (LCMapString)"); - - rv = ::wmemcmp(bufferA, bufferB, minSize); - } - else //use freestore + int rv = 0; + if (minSize != 0) //LCMapString does not allow input sizes of 0! { - std::vector bufferA(minSize); - std::vector bufferB(minSize); - - if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, a, minSize, &bufferA[0], minSize) == 0) - throw std::runtime_error("Error comparing strings! (LCMapString: FS)"); - - if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, b, minSize, &bufferB[0], minSize) == 0) - throw std::runtime_error("Error comparing strings! (LCMapString: FS)"); - - rv = ::wmemcmp(&bufferA[0], &bufferB[0], minSize); + if (minSize <= 5000) //performance optimization: stack + { + wchar_t bufferA[5000]; + wchar_t bufferB[5000]; + + //faster than CharUpperBuff + wmemcpy or CharUpper + wmemcpy and same speed like ::CompareString() + if (::LCMapString(ZSTRING_INVARIANT_LOCALE, //__in LCID Locale, + LCMAP_UPPERCASE, //__in DWORD dwMapFlags, + a, //__in LPCTSTR lpSrcStr, + minSize, //__in int cchSrc, + bufferA, //__out LPTSTR lpDestStr, + 5000) == 0) //__in int cchDest + throw std::runtime_error("Error comparing strings! (LCMapString)"); + + if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, b, minSize, bufferB, 5000) == 0) + throw std::runtime_error("Error comparing strings! (LCMapString)"); + + rv = ::wmemcmp(bufferA, bufferB, minSize); + } + else //use freestore + { + std::vector bufferA(minSize); + std::vector bufferB(minSize); + + if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, a, minSize, &bufferA[0], minSize) == 0) + throw std::runtime_error("Error comparing strings! (LCMapString: FS)"); + + if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, b, minSize, &bufferB[0], minSize) == 0) + throw std::runtime_error("Error comparing strings! (LCMapString: FS)"); + + rv = ::wmemcmp(&bufferA[0], &bufferB[0], minSize); + } } return rv == 0 ? -- cgit