diff options
Diffstat (limited to 'zen')
-rw-r--r-- | zen/basic_math.h | 93 | ||||
-rw-r--r-- | zen/crc.h | 2 | ||||
-rw-r--r-- | zen/dir_watcher.cpp | 16 | ||||
-rw-r--r-- | zen/dir_watcher.h | 4 | ||||
-rw-r--r-- | zen/file_access.cpp | 326 | ||||
-rw-r--r-- | zen/file_io.cpp | 58 | ||||
-rw-r--r-- | zen/file_io.h | 8 | ||||
-rw-r--r-- | zen/fixed_list.h | 185 | ||||
-rw-r--r-- | zen/format_unit.cpp | 4 | ||||
-rw-r--r-- | zen/globals.h | 28 | ||||
-rw-r--r-- | zen/i18n.h | 2 | ||||
-rw-r--r-- | zen/process_priority.cpp | 8 | ||||
-rw-r--r-- | zen/process_priority.h | 8 | ||||
-rw-r--r-- | zen/scope_guard.h | 14 | ||||
-rw-r--r-- | zen/shell_execute.h | 2 | ||||
-rw-r--r-- | zen/stl_tools.h | 25 | ||||
-rw-r--r-- | zen/string_base.h | 378 | ||||
-rw-r--r-- | zen/string_tools.h | 52 | ||||
-rw-r--r-- | zen/sys_error.h | 48 | ||||
-rw-r--r-- | zen/thread.h | 27 | ||||
-rw-r--r-- | zen/time.h | 12 | ||||
-rw-r--r-- | zen/utf.h | 16 | ||||
-rw-r--r-- | zen/warn_static.h | 33 | ||||
-rw-r--r-- | zen/zstring.h | 40 |
24 files changed, 744 insertions, 645 deletions
diff --git a/zen/basic_math.h b/zen/basic_math.h index 4dc9b43b..e9e17466 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -17,39 +17,28 @@ namespace numeric { -template <class T> -T abs(T value); - -template <class T> -T dist(T a, T b); - -template <class T> -int sign(T value); //returns -1/0/1 - -template <class T> -T min(T a, T b, T c); +template <class T> T abs(T value); +template <class T> T dist(T a, T b); +template <class T> int sign(T value); //returns one of {-1, 0, 1} +template <class T> T min(T a, T b, T c); +template <class T> T max(T a, T b, T c); +template <class T> bool isNull(T value); template <class T> -T max(T a, T b, T c); - -template <class T> -void clamp(T& val, const T& minVal, const T& maxVal); //make sure minVal <= val && val <= maxVal +void clamp(T& val, T minVal, T maxVal); //make sure minVal <= val && val <= maxVal template <class T> -T clampCpy(const T& val, const T& minVal, const T& maxVal); +T clampCpy(T val, T minVal, T maxVal); template <class T, class InputIterator> //precondition: range must be sorted! auto nearMatch(const T& val, InputIterator first, InputIterator last); -template <class T> -bool isNull(T value); - int round(double d); //"little rounding function" -template <class N> -N integerDivideRoundUp(N numerator, N denominator); +template <class N, class D> +auto integerDivideRoundUp(N numerator, D denominator); template <size_t N, class T> -T power(const T& value); +T power(T value); double radToDeg(double rad); //convert unit [rad] into [°] double degToRad(double degree); //convert unit [°] into [rad] @@ -108,8 +97,9 @@ T dist(T a, T b) template <class T> inline -int sign(T value) //returns -1/0/1 +int sign(T value) //returns one of {-1, 0, 1} { + static_assert(std::is_signed<T>::value, ""); return value < 0 ? -1 : (value > 0 ? 1 : 0); } @@ -117,19 +107,27 @@ int sign(T value) //returns -1/0/1 template <class T> inline T min(T a, T b, T c) //don't follow std::min's "const T&(const T&, const T&)" API { - return std::min(std::min(a, b), c); + if (a < b) + return a < c ? a : c; + else + return b < c ? b : c; + //return std::min(std::min(a, b), c); } template <class T> inline T max(T a, T b, T c) { - return std::max(std::max(a, b), c); + if (a > b) + return a > c ? a : c; + else + return b > c ? b : c; + //return std::max(std::max(a, b), c); } template <class T> inline -T clampCpy(const T& val, const T& minVal, const T& maxVal) +T clampCpy(T val, T minVal, T maxVal) { assert(minVal <= maxVal); if (val < minVal) @@ -140,7 +138,7 @@ T clampCpy(const T& val, const T& minVal, const T& maxVal) } template <class T> inline -void clamp(T& val, const T& minVal, const T& maxVal) +void clamp(T& val, T minVal, T maxVal) { assert(minVal <= maxVal); if (val < minVal) @@ -229,10 +227,11 @@ int round(double d) } -template <class N> inline -N integerDivideRoundUp(N numerator, N denominator) +template <class N, class D> inline +auto integerDivideRoundUp(N numerator, D denominator) { - static_assert(std::is_unsigned<N>::value, ""); + static_assert(std::is_integral<N>::value && std::is_unsigned<N>::value, ""); + static_assert(std::is_integral<D>::value && std::is_unsigned<D>::value, ""); assert(denominator > 0); return (numerator + denominator - 1) / denominator; } @@ -240,33 +239,17 @@ N integerDivideRoundUp(N numerator, N denominator) namespace { -template <size_t N, class T> -struct PowerImpl -{ - static T result(const T& value) - { - return PowerImpl<N - 1, T>::result(value) * value; - } -}; - -template <class T> -struct PowerImpl<2, T> -{ - static T result(const T& value) - { - return value * value; - } -}; - -template <class T> -struct PowerImpl<0, T>; //not defined: invalidates power<0> and power<1> - -template <class T> -struct PowerImpl<10, T>; //not defined: invalidates power<N> for N >= 10 +template <size_t N, class T> struct PowerImpl; +/* + template <size_t N, class T> -> let's use non-recursive specializations to help the compiler + struct PowerImpl { static T result(const T& value) { return PowerImpl<N - 1, T>::result(value) * value; } }; +*/ +template <class T> struct PowerImpl<2, T> { static T result(T value) { return value * value; } }; +template <class T> struct PowerImpl<3, T> { static T result(T value) { return value * value * value; } }; } template <size_t n, class T> inline -T power(const T& value) +T power(T value) { return PowerImpl<n, T>::result(value); } @@ -327,7 +310,7 @@ double mad(RandomAccessIterator first, RandomAccessIterator last) //note: invali { const double m = median(first, last); - //the second median needs to operate on absolute residuals => avoid transforming input range as it may decrease precision! + //the second median needs to operate on absolute residuals => avoid transforming input range which may have less than double precision! auto lessMedAbs = [m](double lhs, double rhs) { return abs(lhs - m) < abs(rhs - m); }; @@ -38,7 +38,7 @@ uint32_t getCrc32(ByteIterator first, ByteIterator last) static_assert(sizeof(typename std::iterator_traits<ByteIterator>::value_type) == 1, ""); boost::crc_32_type result; if (first != last) - result.process_bytes(&*first, last - first); + result.process_bytes(&*first, last - first); auto rv = result.checksum(); static_assert(sizeof(rv) == sizeof(uint32_t), ""); return rv; diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 769aa4f2..12a6a9f4 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -194,7 +194,7 @@ public: true, //__in BOOL bManualReset, false, //__in BOOL bInitialState, nullptr); //__in_opt LPCTSTR lpName - if (overlapped.hEvent == nullptr) + if (!overlapped.hEvent) { const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! return shared_->reportError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(dirPathPf)), formatSystemError(L"CreateEvent", ec)); @@ -319,7 +319,7 @@ private: } -struct DirWatcher::Pimpl +struct DirWatcher::Impl { InterruptibleThread worker; std::shared_ptr<SharedData> shared; @@ -329,7 +329,7 @@ struct DirWatcher::Pimpl DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError baseDirPath(dirPath), - pimpl_(std::make_unique<Pimpl>()) + pimpl_(std::make_unique<Impl>()) { pimpl_->shared = std::make_shared<SharedData>(); @@ -345,7 +345,7 @@ DirWatcher::~DirWatcher() { pimpl_->worker.interrupt(); pimpl_->worker.detach(); //we don't have time to wait... would take ~50ms - //Windows caveat: exitting the app will kill the thread and leak memory! + //Windows caveat: exitting the app will kill the thread and leak memory! } } @@ -375,7 +375,7 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() #elif defined ZEN_LINUX -struct DirWatcher::Pimpl +struct DirWatcher::Impl { int notifDescr = 0; std::map<int, Zstring> watchDescrs; //watch descriptor and (sub-)directory name (postfixed with separator) -> owned by "notifDescr" @@ -384,7 +384,7 @@ struct DirWatcher::Pimpl DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError baseDirPath(dirPath), - pimpl_(std::make_unique<Pimpl>()) + pimpl_(std::make_unique<Impl>()) { //get all subdirectories std::vector<Zstring> fullFolderList { baseDirPath }; @@ -553,7 +553,7 @@ void eventCallback(ConstFSEventStreamRef streamRef, } -struct DirWatcher::Pimpl +struct DirWatcher::Impl { FSEventStreamRef eventStream = nullptr; std::vector<DirWatcher::Entry> changedFiles; @@ -562,7 +562,7 @@ struct DirWatcher::Pimpl DirWatcher::DirWatcher(const Zstring& dirPath) : baseDirPath(dirPath), - pimpl_(std::make_unique<Pimpl>()) + pimpl_(std::make_unique<Impl>()) { CFStringRef dirpathCf = nullptr; try diff --git a/zen/dir_watcher.h b/zen/dir_watcher.h index 28396ae3..5676555f 100644 --- a/zen/dir_watcher.h +++ b/zen/dir_watcher.h @@ -66,8 +66,8 @@ private: const Zstring baseDirPath; - struct Pimpl; - std::unique_ptr<Pimpl> pimpl_; + struct Impl; + const std::unique_ptr<Impl> pimpl_; }; } diff --git a/zen/file_access.cpp b/zen/file_access.cpp index 2e9d93f8..bad1b60d 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -730,7 +730,7 @@ void setFileTimeByHandle(HANDLE hFile, //throw FileError if (creationTime) basicInfo.CreationTime = toLargeInteger(*creationTime); - //set file time + attributes + //set file time + attributes if (!::SetFileInformationByHandle(hFile, //__in HANDLE hFile, FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, &basicInfo, //__in LPVOID lpFileInformation, @@ -906,21 +906,21 @@ void setWriteTimeNative(const Zstring& itemPath, ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); #if 0 //waiting for user feedback... -#ifdef ZEN_WIN_VISTA_AND_LATER - //bugs, bugs, bugs.... on "SharePoint" SetFileAttributes() seems to affect file modification time: http://www.freefilesync.org/forum/viewtopic.php?t=3699 - //on Vista we can avoid reopening the file (and the SharePoint bug) -ZEN_ON_SCOPE_EXIT( - if (attribsToRestore != INVALID_FILE_ATTRIBUTES) - { - FILE_BASIC_INFO basicInfo = {}; - basicInfo.FileAttributes = attribsToRestore; - ::SetFileInformationByHandle(hFile, //__in HANDLE hFile, - FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, - &basicInfo, //__in LPVOID lpFileInformation, - sizeof(basicInfo)); //__in DWORD dwBufferSize - attribsToRestore = INVALID_FILE_ATTRIBUTES; - } - ); +#ifdef ZEN_WIN_VISTA_AND_LATER + //bugs, bugs, bugs.... on "SharePoint" SetFileAttributes() seems to affect file modification time: http://www.freefilesync.org/forum/viewtopic.php?t=3699 + //on Vista we can avoid reopening the file (and the SharePoint bug) + ZEN_ON_SCOPE_EXIT( + if (attribsToRestore != INVALID_FILE_ATTRIBUTES) + { + FILE_BASIC_INFO basicInfo = {}; + basicInfo.FileAttributes = attribsToRestore; + ::SetFileInformationByHandle(hFile, //__in HANDLE hFile, + FileBasicInfo, //__in FILE_INFO_BY_HANDLE_CLASS FileInformationClass, + &basicInfo, //__in LPVOID lpFileInformation, + sizeof(basicInfo)); //__in DWORD dwBufferSize + attribsToRestore = INVALID_FILE_ATTRIBUTES; + } + ); #endif #endif @@ -1407,26 +1407,26 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P } -void makeDirectoryRecursivelyImpl(const Zstring& directory) //FileError +void makeDirectoryRecursivelyImpl(const Zstring& dirPath) //FileError { - assert(!endsWith(directory, FILE_NAME_SEPARATOR)); //even "C:\" should be "C:" as input! + assert(!endsWith(dirPath, FILE_NAME_SEPARATOR)); //even "C:\" should be "C:" as input! try { - copyNewDirectory(Zstring(), directory, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing + copyNewDirectory(Zstring(), dirPath, false /*copyFilePermissions*/); //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing } catch (const ErrorTargetExisting&) {} //*something* existing: folder or FILE! catch (const ErrorTargetPathMissing&) { //we need to create parent directories first - const Zstring dirParent = beforeLast(directory, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); - if (!dirParent.empty()) + const Zstring parentPath = beforeLast(dirPath, FILE_NAME_SEPARATOR, IF_MISSING_RETURN_NONE); + if (!parentPath.empty()) { //recurse... - makeDirectoryRecursivelyImpl(dirParent); //throw FileError + makeDirectoryRecursivelyImpl(parentPath); //throw FileError //now try again... - copyNewDirectory(Zstring(), directory, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting), (ErrorTargetPathMissing) + copyNewDirectory(Zstring(), dirPath, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting), (ErrorTargetPathMissing) return; } throw; @@ -1586,7 +1586,7 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, nullptr); //_Inout_opt_ LPOVERLAPPED lpOverlapped } - //(try to) set creation and modification time + //(try to) set creation (and modification) time /*bool rv = */::SetFileTime(hDirTrg, //_In_ HANDLE hFile, &dirInfo.ftCreationTime, //_Out_opt_ LPFILETIME lpCreationTime, nullptr, //_Out_opt_ LPFILETIME lpLastAccessTime, @@ -1598,6 +1598,16 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, #elif defined ZEN_MAC if (hasNativeSupportForExtendedAtrributes(targetPath)) //throw FileError ::copyfile(sourcePath.c_str(), targetPath.c_str(), nullptr, COPYFILE_XATTR); //ignore errors, see related comments in copyFileOsSpecific() + + //(try to) set creation (and modification) time + if (dirInfo.st_birthtimespec.tv_sec != 0) + try + { + setWriteTimeNative(targetPath, dirInfo.st_mtimespec, &dirInfo.st_birthtimespec, ProcSymlink::FOLLOW); //throw FileError + //dirInfo.st_birthtime; -> only seconds-precision + //dirInfo.st_mtime; -> + } + catch (FileError&) {} #endif ZEN_ON_SCOPE_FAIL(try { removeDirectorySimple(targetPath); } @@ -1691,7 +1701,7 @@ namespace { #ifdef ZEN_WIN /* - CopyFileEx() BackupRead() FileRead() + CopyFileEx() BackupRead() ReadFile() -------------------------------------------- Attributes YES NO NO create time NO NO NO @@ -1702,7 +1712,7 @@ Sparse NO YES NO Nonstandard FS YES UNKNOWN -> error writing ADS to Samba, issues reading from NAS, error copying files having "blocked" state... ect. PERF - 6% faster -Mark stream as compressed: FSCTL_SET_COMPRESSION - compatible with both BackupRead() and FileRead() +Mark stream as compressed: FSCTL_SET_COMPRESSION - compatible with both BackupRead() and ReadFile() Current support for combinations of NTFS extended attributes: @@ -1710,9 +1720,9 @@ Current support for combinations of NTFS extended attributes: source attr | tf normal | tf compressed | tf encrypted | handled by ============|================================================================== --- | --- -C- E-- copyFileWindowsDefault - --S | --S -CS E-S copyFileWindowsBackupStream + --S | --S -CS E-S copyFileWindowsStream -C- | -C- -C- E-- copyFileWindowsDefault - -CS | -CS -CS E-S copyFileWindowsBackupStream + -CS | -CS -CS E-S copyFileWindowsStream E-- | E-- E-- E-- copyFileWindowsDefault E-S | E-- (NOK) E-- (NOK) E-- (NOK) copyFileWindowsDefault -> may fail with ERROR_DISK_FULL for large sparse files!! @@ -1810,18 +1820,26 @@ bool canCopyAsSparse(const Zstring& sourceFile, const Zstring& targetFile) //thr return false; ZEN_ON_SCOPE_EXIT(::CloseHandle(hSource)); - BY_HANDLE_FILE_INFORMATION fileInfoSource = {}; - if (!::GetFileInformationByHandle(hSource, &fileInfoSource)) + BY_HANDLE_FILE_INFORMATION sourceInfo = {}; + if (!::GetFileInformationByHandle(hSource, &sourceInfo)) return false; - return canCopyAsSparse(fileInfoSource.dwFileAttributes, targetFile); //throw () + return canCopyAsSparse(sourceInfo.dwFileAttributes, targetFile); //throw () } //============================================================================================= -InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked - const Zstring& targetFile, - const std::function<void(std::int64_t bytesDelta)>& notifyProgress) +enum class StreamCopyType +{ + READ_FILE, + BACKUP_READ, +}; + + +InSyncAttributes copyFileWindowsStream(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked + const Zstring& targetFile, + StreamCopyType scType, + const std::function<void(std::int64_t bytesDelta)>& notifyProgress) { //try to get backup read and write privileges: help solve most "access denied" errors with FILE_FLAG_BACKUP_SEMANTICS: //http://www.freefilesync.org/forum/viewtopic.php?t=1714 @@ -1830,7 +1848,6 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw try { activatePrivilege(PrivilegeName::RESTORE); } catch (const FileError&) {} - //open sourceFile for reading HANDLE hFileSource = ::CreateFile(applyLongPathPrefix(sourceFile).c_str(), //_In_ LPCTSTR lpFileName, GENERIC_READ, //_In_ DWORD dwDesiredAccess, FILE_SHARE_READ | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, @@ -1862,16 +1879,18 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw throw FileError(errorMsg, errorDescr); } - ZEN_ON_SCOPE_EXIT(::CloseHandle(hFileSource)); + FileInput fileIn(hFileSource, sourceFile); //pass ownership + + if (notifyProgress) notifyProgress(0); //throw X! //---------------------------------------------------------------------- - BY_HANDLE_FILE_INFORMATION fileInfoSource = {}; - if (!::GetFileInformationByHandle(hFileSource, &fileInfoSource)) + BY_HANDLE_FILE_INFORMATION sourceInfo = {}; + if (!::GetFileInformationByHandle(fileIn.getHandle(), &sourceInfo)) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(sourceFile)), L"GetFileInformationByHandle"); //encrypted files cannot be read with BackupRead which would fail silently! - const bool sourceIsEncrypted = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; - if (sourceIsEncrypted) + const bool sourceIsEncrypted = (sourceInfo.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; + if (sourceIsEncrypted && scType == StreamCopyType::BACKUP_READ) throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: Source file is encrypted."); //---------------------------------------------------------------------- @@ -1883,7 +1902,6 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw FILE_ATTRIBUTE_NOT_CONTENT_INDEXED; // //FILE_ATTRIBUTE_ENCRYPTED -> no! - //create targetFile and open it for writing HANDLE hFileTarget = ::CreateFile(applyLongPathPrefix(targetFile).c_str(), //_In_ LPCTSTR lpFileName, GENERIC_READ | GENERIC_WRITE | DELETE, //_In_ DWORD dwDesiredAccess, //GENERIC_READ required for FSCTL_SET_COMPRESSION, DELETE for ::SetFileInformationByHandle(),FileDispositionInfo @@ -1892,7 +1910,7 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, CREATE_NEW, //_In_ DWORD dwCreationDisposition, //FILE_FLAG_OVERLAPPED must not be used! FILE_FLAG_NO_BUFFERING should not be used! - (fileInfoSource.dwFileAttributes & validAttribs) | + (sourceInfo.dwFileAttributes & validAttribs) | FILE_FLAG_SEQUENTIAL_SCAN | //_In_ DWORD dwFlagsAndAttributes, FILE_FLAG_BACKUP_SEMANTICS, //-> also required by FSCTL_SET_SPARSE nullptr); //_In_opt_ HANDLE hTemplateFile @@ -1911,14 +1929,14 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw throw FileError(errorMsg, errorDescr); } #ifdef ZEN_WIN_VISTA_AND_LATER - ZEN_ON_SCOPE_EXIT(::CloseHandle(hFileTarget)); + FileOutput fileOut(hFileTarget, targetFile); //pass ownership //no need for ::DeleteFile(), we already have an open handle! Maybe this also prevents needless buffer-flushing in ::CloseHandle()??? Anyway, same behavior like ::CopyFileEx() ZEN_ON_SCOPE_FAIL ( FILE_DISPOSITION_INFO di = {}; di.DeleteFile = true; - if (!::SetFileInformationByHandle(hFileTarget, //_In_ HANDLE hFile, + if (!::SetFileInformationByHandle(fileOut.getHandle(), //_In_ HANDLE hFile, FileDispositionInfo, //_In_ FILE_INFO_BY_HANDLE_CLASS FileInformationClass, &di, //_In_ LPVOID lpFileInformation, sizeof(di))) //_In_ DWORD dwBufferSize @@ -1928,29 +1946,30 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw ZEN_ON_SCOPE_FAIL(try { removeFile(targetFile); } catch (FileError&) {} ); //transactional behavior: guard just after opening target and before managing hFileTarget - ZEN_ON_SCOPE_EXIT(::CloseHandle(hFileTarget)); + FileOutput fileOut(hFileTarget, targetFile); //pass ownership #endif + if (notifyProgress) notifyProgress(0); //throw X! //---------------------------------------------------------------------- - BY_HANDLE_FILE_INFORMATION fileInfoTarget = {}; - if (!::GetFileInformationByHandle(hFileTarget, &fileInfoTarget)) + BY_HANDLE_FILE_INFORMATION targetInfo = {}; + if (!::GetFileInformationByHandle(fileOut.getHandle(), &targetInfo)) THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(targetFile)), L"GetFileInformationByHandle"); //return up-to-date file attributes InSyncAttributes newAttrib; - newAttrib.fileSize = get64BitUInt(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh); - newAttrib.modificationTime = filetimeToTimeT(fileInfoSource.ftLastWriteTime); - newAttrib.sourceFileId = extractFileId(fileInfoSource); - newAttrib.targetFileId = extractFileId(fileInfoTarget); + newAttrib.fileSize = get64BitUInt(sourceInfo.nFileSizeLow, sourceInfo.nFileSizeHigh); + newAttrib.modificationTime = filetimeToTimeT(sourceInfo.ftLastWriteTime); + newAttrib.sourceFileId = extractFileId(sourceInfo); + newAttrib.targetFileId = extractFileId(targetInfo); //#################### copy NTFS compressed attribute ######################### - const bool sourceIsCompressed = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; - const bool targetIsCompressed = (fileInfoTarget.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; //already set by CreateFile if target parent folder is compressed! + const bool sourceIsCompressed = (sourceInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; + const bool targetIsCompressed = (targetInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; //already set by CreateFile if target parent folder is compressed! if (sourceIsCompressed && !targetIsCompressed) { USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; DWORD bytesReturned = 0; - if (!::DeviceIoControl(hFileTarget, //_In_ HANDLE hDevice, + if (!::DeviceIoControl(fileOut.getHandle(), //_In_ HANDLE hDevice, FSCTL_SET_COMPRESSION, //_In_ DWORD dwIoControlCode, &cmpState, //_In_opt_ LPVOID lpInBuffer, sizeof(cmpState), //_In_ DWORD nInBufferSize, @@ -1969,91 +1988,97 @@ InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw //The application should retrieve the attributes by using GetFileAttributes prior to creating a backup with BackupRead. //If a file originally had the sparse attribute (FILE_ATTRIBUTE_SPARSE_FILE), the backup utility must explicitly set the //attribute on the restored file. - + if (scType == StreamCopyType::BACKUP_READ) #ifdef ZEN_WIN_VISTA_AND_LATER - if (canCopyAsSparse(fileInfoSource.dwFileAttributes, hFileTarget)) //throw () + if (canCopyAsSparse(sourceInfo.dwFileAttributes, fileOut.getHandle())) //throw () #else - if (canCopyAsSparse(fileInfoSource.dwFileAttributes, targetFile)) //throw () + if (canCopyAsSparse(sourceInfo.dwFileAttributes, targetFile)) //throw () #endif - { - DWORD bytesReturned = 0; - if (!::DeviceIoControl(hFileTarget, //_In_ HANDLE hDevice, - FSCTL_SET_SPARSE, //_In_ DWORD dwIoControlCode, - nullptr, //_In_opt_ LPVOID lpInBuffer, - 0, //_In_ DWORD nInBufferSize, - nullptr, //_Out_opt_ LPVOID lpOutBuffer, - 0, //_In_ DWORD nOutBufferSize, - &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned, - nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(targetFile)), L"DeviceIoControl, FSCTL_SET_SPARSE"); - } + { + DWORD bytesReturned = 0; + if (!::DeviceIoControl(fileOut.getHandle(), //_In_ HANDLE hDevice, + FSCTL_SET_SPARSE, //_In_ DWORD dwIoControlCode, + nullptr, //_In_opt_ LPVOID lpInBuffer, + 0, //_In_ DWORD nInBufferSize, + nullptr, //_Out_opt_ LPVOID lpOutBuffer, + 0, //_In_ DWORD nOutBufferSize, + &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned, + nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file attributes of %x."), L"%x", fmtPath(targetFile)), L"DeviceIoControl, FSCTL_SET_SPARSE"); + } //---------------------------------------------------------------------- - const DWORD BUFFER_SIZE = std::max(128 * 1024, static_cast<int>(sizeof(WIN32_STREAM_ID))); //must be greater than sizeof(WIN32_STREAM_ID)! - std::vector<BYTE> buffer(BUFFER_SIZE); + if (scType == StreamCopyType::READ_FILE) + unbufferedStreamCopy(fileIn, fileOut, notifyProgress); //throw FileError, X + else + { + const DWORD BUFFER_SIZE = std::max(128 * 1024, static_cast<int>(sizeof(WIN32_STREAM_ID))); //must be greater than sizeof(WIN32_STREAM_ID)! + std::vector<BYTE> buffer(BUFFER_SIZE); - LPVOID contextRead = nullptr; //manage context for BackupRead()/BackupWrite() - LPVOID contextWrite = nullptr; // + LPVOID contextRead = nullptr; //manage context for BackupRead()/BackupWrite() + LPVOID contextWrite = nullptr; // - ZEN_ON_SCOPE_EXIT( - if (contextRead ) ::BackupRead (0, nullptr, 0, nullptr, true, false, &contextRead); //MSDN: "lpContext must be passed [...] all other parameters are ignored." - if (contextWrite) ::BackupWrite(0, nullptr, 0, nullptr, true, false, &contextWrite); ); // + ZEN_ON_SCOPE_EXIT( + if (contextRead ) ::BackupRead (0, nullptr, 0, nullptr, true, false, &contextRead); //MSDN: "lpContext must be passed [...] all other parameters are ignored." + if (contextWrite) ::BackupWrite(0, nullptr, 0, nullptr, true, false, &contextWrite); ); // - //stream-copy sourceFile to targetFile - bool eof = false; - bool someBytesRead = false; //try to detect failure reading encrypted files - do - { - DWORD bytesRead = 0; - if (!::BackupRead(hFileSource, //__in HANDLE hFile, - &buffer[0], //__out LPBYTE lpBuffer, - BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead, - &bytesRead, //__out LPDWORD lpNumberOfBytesRead, - false, //__in BOOL bAbort, - false, //__in BOOL bProcessSecurity, - &contextRead)) //__out LPVOID *lpContext - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead"); //better use fine-granular error messages "reading/writing"! - - if (bytesRead > BUFFER_SIZE) - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: buffer overflow."); //user should never see this - - if (bytesRead < BUFFER_SIZE) - eof = true; - - DWORD bytesWritten = 0; - if (!::BackupWrite(hFileTarget, //__in HANDLE hFile, - &buffer[0], //__in LPBYTE lpBuffer, - bytesRead, //__in DWORD nNumberOfBytesToWrite, - &bytesWritten, //__out LPDWORD lpNumberOfBytesWritten, - false, //__in BOOL bAbort, - false, //__in BOOL bProcessSecurity, - &contextWrite)) //__out LPVOID *lpContext - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)), L"BackupWrite"); - - if (bytesWritten != bytesRead) - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)), L"BackupWrite: incomplete write."); //user should never see this - - //total bytes transferred may be larger than file size! context information + ADS or smaller (sparse, compressed)! - if (notifyProgress) notifyProgress(bytesRead); //throw X! - - if (bytesRead > 0) - someBytesRead = true; - } - while (!eof); + //stream-copy sourceFile to targetFile + bool eof = false; + bool someBytesRead = false; //try to detect failure reading encrypted files + do + { + DWORD bytesRead = 0; + if (!::BackupRead(fileIn.getHandle(), //__in HANDLE hFile, + &buffer[0], //__out LPBYTE lpBuffer, + BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead, + &bytesRead, //__out LPDWORD lpNumberOfBytesRead, + false, //__in BOOL bAbort, + false, //__in BOOL bProcessSecurity, + &contextRead)) //__out LPVOID *lpContext + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead"); //better use fine-granular error messages "reading/writing"! + + if (bytesRead > BUFFER_SIZE) + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: buffer overflow."); //user should never see this + + if (bytesRead < BUFFER_SIZE) + eof = true; + + DWORD bytesWritten = 0; + if (!::BackupWrite(fileOut.getHandle(), //__in HANDLE hFile, + &buffer[0], //__in LPBYTE lpBuffer, + bytesRead, //__in DWORD nNumberOfBytesToWrite, + &bytesWritten, //__out LPDWORD lpNumberOfBytesWritten, + false, //__in BOOL bAbort, + false, //__in BOOL bProcessSecurity, + &contextWrite)) //__out LPVOID *lpContext + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)), L"BackupWrite"); + + if (bytesWritten != bytesRead) + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(targetFile)), L"BackupWrite: incomplete write."); //user should never see this + + //total bytes transferred may be larger than file size! context information + ADS or smaller (sparse, compressed)! + if (notifyProgress) notifyProgress(bytesRead); //throw X! + + if (bytesRead > 0) + someBytesRead = true; + } + while (!eof); - //::BackupRead() silently fails reading encrypted files -> double check! - if (!someBytesRead && get64BitUInt(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh) != 0U) - //note: there is no guaranteed ordering relation beween bytes transferred and file size! Consider ADS (>) and compressed/sparse files (<)! - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: unknown error"); //user should never see this -> this method is called only if "canCopyAsSparse()" + //::BackupRead() silently fails reading encrypted files -> double check! + if (!someBytesRead && get64BitUInt(sourceInfo.nFileSizeLow, sourceInfo.nFileSizeHigh) != 0U) + //note: there is no guaranteed ordering relation beween bytes transferred and file size! Consider ADS (>) and compressed/sparse files (<)! + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(sourceFile)), L"BackupRead: unknown error"); //user should never see this -> this method is called only if "canCopyAsSparse()" + } - //time needs to be set at the end: BackupWrite() changes modification time - setFileTimeByHandle(hFileTarget, &fileInfoSource.ftCreationTime,fileInfoSource.ftLastWriteTime, targetFile); //throw FileError + //time needs to be set at the end: WriteFile/BackupWrite() change modification time + setFileTimeByHandle(fileOut.getHandle(), &sourceInfo.ftCreationTime,sourceInfo.ftLastWriteTime, targetFile); //throw FileError return newAttrib; } -DEFINE_NEW_FILE_ERROR(ErrorFallbackToCopyAsBackupStream); +DEFINE_NEW_FILE_ERROR(ErrorFallbackToCopyViaBackupRead); +DEFINE_NEW_FILE_ERROR(ErrorFallbackToCopyViaReadFile); struct CallbackData @@ -2127,7 +2152,7 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, #else if (canCopyAsSparse(cbd.fileInfoSrc.dwFileAttributes, cbd.targetFile_)) //throw () #endif - throw ErrorFallbackToCopyAsBackupStream(L"sparse, callback"); //use a different copy routine! + throw ErrorFallbackToCopyViaBackupRead(L"sparse, callback"); //use a different copy routine! //#################### copy file creation time ################################ ::SetFileTime(hDestinationFile, &cbd.fileInfoSrc.ftCreationTime, nullptr, nullptr); //no error handling! @@ -2170,8 +2195,8 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, } -InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorFallbackToCopyAsBackupStream - const Zstring& targetFile, +InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked, + const Zstring& targetFile, // ErrorFallbackToCopyViaReadFile, ErrorFallbackToCopyViaBackupRead const std::function<void(std::int64_t bytesDelta)>& notifyProgress) { //try to get backup read and write privileges: may help solve some "access denied" errors @@ -2205,27 +2230,28 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE nullptr, //__in_opt LPBOOL pbCancel, copyFlags) != FALSE; //__in DWORD dwCopyFlags if (cbd.exception) - std::rethrow_exception(cbd.exception); //throw ?, process errors in callback first! + std::rethrow_exception(cbd.exception); //throw X, process errors in callback first! if (!success) { const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! - //don't suppress "lastError == ERROR_REQUEST_ABORTED": a user aborted operation IS an error condition! + //don't suppress "lastError == ERROR_REQUEST_ABORTED": a user-aborted operation IS an error condition! //trying to copy huge sparse files may directly fail with ERROR_DISK_FULL before entering the callback function if (canCopyAsSparse(sourceFile, targetFile)) //noexcept - throw ErrorFallbackToCopyAsBackupStream(L"sparse, copy failure"); + throw ErrorFallbackToCopyViaBackupRead(L"sparse, copy failure"); if (ec == ERROR_ACCESS_DENIED && backupPrivilegesActive) - //chances are good this will work with copyFileWindowsBackupStream: http://www.freefilesync.org/forum/viewtopic.php?t=1714 - throw ErrorFallbackToCopyAsBackupStream(L"access denied"); + //chances are good this will work with copyFileWindowsStream: http://www.freefilesync.org/forum/viewtopic.php?t=1714 + throw ErrorFallbackToCopyViaReadFile(L"access denied"); - //copying ADS may incorrectly fail with ERROR_FILE_NOT_FOUND: http://www.freefilesync.org/forum/viewtopic.php?t=446 + //- copying ADS may incorrectly fail with ERROR_FILE_NOT_FOUND: http://www.freefilesync.org/forum/viewtopic.php?t=446 + //- even BackupWrite may fail => use ReadFile: http://www.freefilesync.org/forum/viewtopic.php?t=2321 if (ec == ERROR_FILE_NOT_FOUND && cbd.fileInfoSrc.nNumberOfLinks > 0 && cbd.fileInfoTrg.nNumberOfLinks > 0) - throw ErrorFallbackToCopyAsBackupStream(L"bogus file not found"); + throw ErrorFallbackToCopyViaReadFile(L"bogus file not found"); //assemble error message... const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtPath(sourceFile)), L"%y", L"\n" + fmtPath(targetFile)); @@ -2288,11 +2314,15 @@ InSyncAttributes copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Z { try { - return copyFileWindowsDefault(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorFallbackToCopyAsBackupStream + return copyFileWindowsDefault(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorFallbackToCopyViaReadFile, ErrorFallbackToCopyViaBackupRead + } + catch (ErrorFallbackToCopyViaReadFile&) + { + return copyFileWindowsStream(sourceFile, targetFile, StreamCopyType::READ_FILE, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked } - catch (ErrorFallbackToCopyAsBackupStream&) + catch (ErrorFallbackToCopyViaBackupRead&) { - return copyFileWindowsBackupStream(sourceFile, targetFile, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked + return copyFileWindowsStream(sourceFile, targetFile, StreamCopyType::BACKUP_READ, notifyProgress); //throw FileError, ErrorTargetExisting, ErrorFileLocked } } @@ -2325,7 +2355,7 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError const Zstring& targetFile, const std::function<void(std::int64_t bytesDelta)>& notifyProgress) { - FileInput fileIn(sourceFile); //throw FileError + FileInput fileIn(sourceFile); //throw FileError, (ErrorFileLocked -> Windows-only) if (notifyProgress) notifyProgress(0); //throw X! struct ::stat sourceInfo = {}; @@ -2409,16 +2439,16 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError #endif /* - ------------------ - |File Copy Layers| - ------------------ - copyNewFile - | - copyFileOsSpecific (solve 8.3 issue on Windows) - | - copyFileWindowsSelectRoutine - / \ -copyFileWindowsDefault(::CopyFileEx) copyFileWindowsBackupStream(::BackupRead/::BackupWrite) + ------------------ + |File Copy Layers| + ------------------ + copyNewFile + | + copyFileOsSpecific (solve 8.3 issue on Windows) + | + copyFileWindowsSelectRoutine + / \ +copyFileWindowsDefault(::CopyFileEx) copyFileWindowsStream(::BackupRead/::BackupWrite) */ } diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 6243980b..440a5d0d 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -26,13 +26,13 @@ using namespace zen; namespace { #if defined ZEN_LINUX || defined ZEN_MAC -//- "filepath" could be a named pipe which *blocks* forever for open()! +//- "filePath" could be a named pipe which *blocks* forever for open()! //- open() with O_NONBLOCK avoids the block, but opens successfully //- create sample pipe: "sudo mkfifo named_pipe" -void checkForUnsupportedType(const Zstring& filepath) //throw FileError +void checkForUnsupportedType(const Zstring& filePath) //throw FileError { struct ::stat fileInfo = {}; - if (::stat(filepath.c_str(), &fileInfo) != 0) //follows symlinks + if (::stat(filePath.c_str(), &fileInfo) != 0) //follows symlinks return; //let the caller handle errors like "not existing" if (!S_ISREG(fileInfo.st_mode) && @@ -49,7 +49,7 @@ void checkForUnsupportedType(const Zstring& filepath) //throw FileError const std::wstring numFmt = printNumber<std::wstring>(L"0%06o", m & S_IFMT); return name ? numFmt + L", " + name : numFmt; }; - throw FileError(replaceCpy(_("Type of item %x is not supported:"), L"%x", fmtPath(filepath)) + L" " + getTypeName(fileInfo.st_mode)); + throw FileError(replaceCpy(_("Type of item %x is not supported:"), L"%x", fmtPath(filePath)) + L" " + getTypeName(fileInfo.st_mode)); } } #endif @@ -66,11 +66,11 @@ FileHandle getInvalidHandle() } -FileInput::FileInput(FileHandle handle, const Zstring& filepath) : FileBase(filepath), fileHandle(handle) {} +FileInput::FileInput(FileHandle handle, const Zstring& filePath) : FileBase(filePath), fileHandle(handle) {} -FileInput::FileInput(const Zstring& filepath) : //throw FileError, ErrorFileLocked - FileBase(filepath), fileHandle(getInvalidHandle()) +FileInput::FileInput(const Zstring& filePath) : //throw FileError, ErrorFileLocked + FileBase(filePath), fileHandle(getInvalidHandle()) { #ifdef ZEN_WIN try { activatePrivilege(PrivilegeName::BACKUP); } @@ -78,12 +78,12 @@ FileInput::FileInput(const Zstring& filepath) : //throw FileError, ErrorFileLock auto createHandle = [&](DWORD dwShareMode) { - return ::CreateFile(applyLongPathPrefix(filepath).c_str(), //_In_ LPCTSTR lpFileName, - GENERIC_READ, //_In_ DWORD dwDesiredAccess, - dwShareMode, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - FILE_FLAG_SEQUENTIAL_SCAN | //_In_ DWORD dwFlagsAndAttributes, + return ::CreateFile(applyLongPathPrefix(filePath).c_str(), //_In_ LPCTSTR lpFileName, + GENERIC_READ, //_In_ DWORD dwDesiredAccess, + dwShareMode, //_In_ DWORD dwShareMode, + nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, + OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, + FILE_FLAG_SEQUENTIAL_SCAN | //_In_ DWORD dwFlagsAndAttributes, /* possible values: (Reference https://msdn.microsoft.com/en-us/library/aa363858#caching_behavior) FILE_FLAG_NO_BUFFERING FILE_FLAG_RANDOM_ACCESS @@ -122,14 +122,14 @@ FileInput::FileInput(const Zstring& filepath) : //throw FileError, ErrorFileLock if (fileHandle == INVALID_HANDLE_VALUE) { const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filepath)); + const std::wstring errorMsg = replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath)); std::wstring errorDescr = formatSystemError(L"CreateFile", ec); if (ec == ERROR_SHARING_VIOLATION || //-> enhance error message! ec == ERROR_LOCK_VIOLATION) { #ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message - const std::wstring procList = vista::getLockingProcesses(filepath); //noexcept + const std::wstring procList = vista::getLockingProcesses(filePath); //noexcept if (!procList.empty()) errorDescr = _("The file is locked by another process:") + L"\n" + procList; #endif @@ -140,12 +140,12 @@ FileInput::FileInput(const Zstring& filepath) : //throw FileError, ErrorFileLock } #elif defined ZEN_LINUX || defined ZEN_MAC - checkForUnsupportedType(filepath); //throw FileError; opening a named pipe would block forever! + checkForUnsupportedType(filePath); //throw FileError; opening a named pipe would block forever! //don't use O_DIRECT: http://yarchive.net/comp/linux/o_direct.html - fileHandle = ::open(filepath.c_str(), O_RDONLY); + fileHandle = ::open(filePath.c_str(), O_RDONLY); if (fileHandle == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filepath)), L"open"); + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath)), L"open"); #endif //------------------------------------------------------------------------------------------------------ @@ -160,7 +160,7 @@ FileInput::FileInput(const Zstring& filepath) : //throw FileError, ErrorFileLock #ifdef ZEN_LINUX //handle still un-owned => need constructor guard //optimize read-ahead on input file: if (::posix_fadvise(fileHandle, 0, 0, POSIX_FADV_SEQUENTIAL) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(filepath)), L"posix_fadvise"); + THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(filePath)), L"posix_fadvise"); #elif defined ZEN_MAC //"dtruss" doesn't show use of "fcntl() F_RDAHEAD/F_RDADVISE" for "cp") @@ -215,11 +215,11 @@ size_t FileInput::tryRead(void* buffer, size_t bytesToRead) //throw FileError; m //---------------------------------------------------------------------------------------------------- -FileOutput::FileOutput(FileHandle handle, const Zstring& filepath) : FileBase(filepath), fileHandle(handle) {} +FileOutput::FileOutput(FileHandle handle, const Zstring& filePath) : FileBase(filePath), fileHandle(handle) {} -FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : //throw FileError, ErrorTargetExisting - FileBase(filepath), fileHandle(getInvalidHandle()) +FileOutput::FileOutput(const Zstring& filePath, AccessFlag access) : //throw FileError, ErrorTargetExisting + FileBase(filePath), fileHandle(getInvalidHandle()) { #ifdef ZEN_WIN try { activatePrivilege(PrivilegeName::BACKUP); } @@ -231,7 +231,7 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : //throw Fil auto createHandle = [&](DWORD dwFlagsAndAttributes) { - return ::CreateFile(applyLongPathPrefix(filepath).c_str(), //_In_ LPCTSTR lpFileName, + return ::CreateFile(applyLongPathPrefix(filePath).c_str(), //_In_ LPCTSTR lpFileName, GENERIC_READ | GENERIC_WRITE, //_In_ DWORD dwDesiredAccess, /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858#files quote: When an application creates a file across a network, it is better @@ -256,7 +256,7 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : //throw Fil //CREATE_ALWAYS fails with ERROR_ACCESS_DENIED if the existing file is hidden or "system": https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858#files if (ec == ERROR_ACCESS_DENIED && dwCreationDisposition == CREATE_ALWAYS) { - const DWORD attrib = ::GetFileAttributes(applyLongPathPrefix(filepath).c_str()); + const DWORD attrib = ::GetFileAttributes(applyLongPathPrefix(filePath).c_str()); if (attrib != INVALID_FILE_ATTRIBUTES) { fileHandle = createHandle(attrib); //retry: alas this may still fail for hidden file, e.g. accessing shared folder in XP as Virtual Box guest! @@ -267,14 +267,14 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : //throw Fil //begin of "regular" error reporting if (fileHandle == INVALID_HANDLE_VALUE) { - const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filepath)); + const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filePath)); std::wstring errorDescr = formatSystemError(L"CreateFile", ec); #ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message if (ec == ERROR_SHARING_VIOLATION || //-> enhance error message! ec == ERROR_LOCK_VIOLATION) { - const std::wstring procList = vista::getLockingProcesses(filepath); //noexcept + const std::wstring procList = vista::getLockingProcesses(filePath); //noexcept if (!procList.empty()) errorDescr = _("The file is locked by another process:") + L"\n" + procList; } @@ -289,14 +289,14 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : //throw Fil } #elif defined ZEN_LINUX || defined ZEN_MAC - //checkForUnsupportedType(filepath); -> not needed, open() + O_WRONLY should fail fast + //checkForUnsupportedType(filePath); -> not needed, open() + O_WRONLY should fail fast - fileHandle = ::open(filepath.c_str(), O_WRONLY | O_CREAT | (access == FileOutput::ACC_CREATE_NEW ? O_EXCL : O_TRUNC), + fileHandle = ::open(filePath.c_str(), O_WRONLY | O_CREAT | (access == FileOutput::ACC_CREATE_NEW ? O_EXCL : O_TRUNC), S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); //0666 if (fileHandle == -1) { const int ec = errno; //copy before making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filepath)); + const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filePath)); const std::wstring errorDescr = formatSystemError(L"open", ec); if (ec == EEXIST) diff --git a/zen/file_io.h b/zen/file_io.h index 89cf77d5..4a135150 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -51,8 +51,8 @@ private: class FileInput : public FileBase { public: - FileInput(const Zstring& filepath); //throw FileError, ErrorFileLocked - FileInput(FileHandle handle, const Zstring& filepath); //takes ownership! + FileInput(const Zstring& filePath); //throw FileError, ErrorFileLocked + FileInput(FileHandle handle, const Zstring& filePath); //takes ownership! ~FileInput(); //Windows: better use 64kB ?? https://technet.microsoft.com/en-us/library/cc938632 @@ -76,8 +76,8 @@ public: ACC_CREATE_NEW }; - FileOutput(const Zstring& filepath, AccessFlag access); //throw FileError, ErrorTargetExisting - FileOutput(FileHandle handle, const Zstring& filepath); //takes ownership! + FileOutput(const Zstring& filePath, AccessFlag access); //throw FileError, ErrorTargetExisting + FileOutput(FileHandle handle, const Zstring& filePath); //takes ownership! ~FileOutput(); FileOutput(FileOutput&& tmp); diff --git a/zen/fixed_list.h b/zen/fixed_list.h index dba0996e..4376c13f 100644 --- a/zen/fixed_list.h +++ b/zen/fixed_list.h @@ -9,12 +9,13 @@ #include <cassert> #include <iterator> - +#include "stl_tools.h" namespace zen { -//std::list(C++11)-like class for inplace element construction supporting non-copyable/movable types -//may be replaced by C++11 std::list when available...or never... +//std::list(C++11)-like class for inplace element construction supporting non-copyable/non-movable types +//-> no iterator invalidation after emplace_back() + template <class T> class FixedList { @@ -23,7 +24,7 @@ class FixedList template <class... Args> Node(Args&& ... args) : val(std::forward<Args>(args)...) {} - Node* next = nullptr; //singly linked list is sufficient + Node* next = nullptr; //singly-linked list is sufficient T val; }; @@ -33,13 +34,13 @@ public: ~FixedList() { clear(); } template <class NodeT, class U> - class ListIterator : public std::iterator<std::forward_iterator_tag, U> + class FixedIterator : public std::iterator<std::forward_iterator_tag, U> { public: - ListIterator(NodeT* it = nullptr) : it_(it) {} - ListIterator& operator++() { it_ = it_->next; return *this; } - inline friend bool operator==(const ListIterator& lhs, const ListIterator& rhs) { return lhs.it_ == rhs.it_; } - inline friend bool operator!=(const ListIterator& lhs, const ListIterator& rhs) { return !(lhs == rhs); } + FixedIterator(NodeT* it = nullptr) : it_(it) {} + FixedIterator& operator++() { it_ = it_->next; return *this; } + inline friend bool operator==(const FixedIterator& lhs, const FixedIterator& rhs) { return lhs.it_ == rhs.it_; } + inline friend bool operator!=(const FixedIterator& lhs, const FixedIterator& rhs) { return !(lhs == rhs); } U& operator* () const { return it_->val; } U* operator->() const { return &it_->val; } private: @@ -47,48 +48,68 @@ public: }; using value_type = T; - using iterator = ListIterator< Node, T>; - using const_iterator = ListIterator<const Node, const T>; + using iterator = FixedIterator< Node, T>; + using const_iterator = FixedIterator<const Node, const T>; using reference = T&; using const_reference = const T&; - iterator begin() { return firstInsert; } + iterator begin() { return firstInsert_; } iterator end () { return iterator(); } - const_iterator begin() const { return firstInsert; } + const_iterator begin() const { return firstInsert_; } const_iterator end () const { return const_iterator(); } - //const_iterator cbegin() const { return firstInsert; } + //const_iterator cbegin() const { return firstInsert_; } //const_iterator cend () const { return const_iterator(); } - reference front() { return firstInsert->val; } - const_reference front() const { return firstInsert->val; } + reference front() { return firstInsert_->val; } + const_reference front() const { return firstInsert_->val; } - reference& back() { return lastInsert->val; } - const_reference& back() const { return lastInsert->val; } + reference& back() { return lastInsert_->val; } + const_reference& back() const { return lastInsert_->val; } template <class... Args> - void emplace_back(Args&& ... args) { pushNode(new Node(std::forward<Args>(args)...)); } + void emplace_back(Args&&... args) + { + Node* newNode = new Node(std::forward<Args>(args)...); + + if (!lastInsert_) + { + assert(!firstInsert_ && sz_ == 0); + firstInsert_ = lastInsert_ = newNode; + } + else + { + assert(lastInsert_->next == nullptr); + lastInsert_->next = newNode; + lastInsert_ = newNode; + } + ++sz_; + } template <class Predicate> void remove_if(Predicate pred) { Node* prev = nullptr; - Node* ptr = firstInsert; + Node* ptr = firstInsert_; while (ptr) if (pred(ptr->val)) { Node* next = ptr->next; - deleteNode(ptr); + + delete ptr; + assert(sz_ > 0); + --sz_; + ptr = next; if (prev) prev->next = next; else - firstInsert = next; + firstInsert_ = next; if (!next) - lastInsert = prev; + lastInsert_ = prev; } else { @@ -99,59 +120,119 @@ public: void clear() { - Node* ptr = firstInsert; + Node* ptr = firstInsert_; while (ptr) { Node* next = ptr->next; - deleteNode(ptr); + delete ptr; ptr = next; } - firstInsert = lastInsert = nullptr; - assert(sz == 0); + sz_ = 0; + firstInsert_ = lastInsert_ = nullptr; } - bool empty() const { return firstInsert == nullptr; } + bool empty() const { return sz_ == 0; } - size_t size() const { return sz; } + size_t size() const { return sz_; } void swap(FixedList& other) { - std::swap(firstInsert, other.firstInsert); - std::swap(lastInsert , other.lastInsert); - std::swap(sz , other.sz); + std::swap(firstInsert_, other.firstInsert_); + std::swap(lastInsert_ , other.lastInsert_); + std::swap(sz_ , other.sz_); } private: FixedList (const FixedList&) = delete; FixedList& operator=(const FixedList&) = delete; - void pushNode(Node* newNode) //throw() + Node* firstInsert_ = nullptr; + Node* lastInsert_ = nullptr; //point to last insertion; required by efficient emplace_back() + size_t sz_ = 0; +}; + + +//just as fast as FixedList, but simpler, more CPU-cache-friendly => superseeds FixedList! +template <class T> +class FixedVector +{ +public: + FixedVector() {} + + /* + class EndIterator {}; //just like FixedList: no iterator invalidation after emplace_back() + + template <class V> + class FixedIterator : public std::iterator<std::forward_iterator_tag, V> //could make this random-access if needed { - if (lastInsert == nullptr) - { - assert(firstInsert == nullptr && sz == 0); - firstInsert = lastInsert = newNode; - } - else - { - assert(lastInsert->next == nullptr); - lastInsert->next = newNode; - lastInsert = newNode; - } - ++sz; + public: + FixedIterator(std::vector<std::unique_ptr<T>>& cont, size_t pos) : cont_(cont), pos_(pos) {} + FixedIterator& operator++() { ++pos_; return *this; } + inline friend bool operator==(const FixedIterator& lhs, EndIterator) { return lhs.pos_ == lhs.cont_.size(); } + inline friend bool operator!=(const FixedIterator& lhs, EndIterator) { return !(lhs == EndIterator()); } + V& operator* () const { return *cont_[pos_]; } + V* operator->() const { return &*cont_[pos_]; } + private: + std::vector<std::unique_ptr<T>>& cont_; + size_t pos_ = 0; + }; + */ + + template <class IterImpl, class V> + class FixedIterator : public std::iterator<std::forward_iterator_tag, V> //could make this bidirectional if needed + { + public: + FixedIterator(IterImpl it) : it_(it) {} + FixedIterator& operator++() { ++it_; return *this; } + inline friend bool operator==(const FixedIterator& lhs, const FixedIterator& rhs) { return lhs.it_ == rhs.it_; } + inline friend bool operator!=(const FixedIterator& lhs, const FixedIterator& rhs) { return !(lhs == rhs); } + V& operator* () const { return **it_; } + V* operator->() const { return &**it_; } + private: + IterImpl it_; //TODO: avoid iterator invalidation after emplace_back(); caveat: end() must not store old length! + }; + + using value_type = T; + using iterator = FixedIterator<typename std::vector<std::unique_ptr<T>>::iterator , T>; + using const_iterator = FixedIterator<typename std::vector<std::unique_ptr<T>>::const_iterator, const T>; + using reference = T&; + using const_reference = const T&; + + iterator begin() { return items_.begin(); } + iterator end () { return items_.end (); } + + const_iterator begin() const { return items_.begin(); } + const_iterator end () const { return items_.end (); } + + reference front() { return *items_.front(); } + const_reference front() const { return *items_.front(); } + + reference& back() { return *items_.back(); } + const_reference& back() const { return *items_.back(); } + + template <class... Args> + void emplace_back(Args&&... args) + { + items_.push_back(std::make_unique<T>(std::forward<Args>(args)...)); } - void deleteNode(Node* oldNode) + template <class Predicate> + void remove_if(Predicate pred) { - assert(sz > 0); - --sz; - delete oldNode; + erase_if(items_, [&](const std::unique_ptr<T>& p){ return pred(*p); }); } - Node* firstInsert = nullptr; - Node* lastInsert = nullptr; //point to last insertion; required by efficient emplace_back() - size_t sz = 0; + void clear() { items_.clear(); } + bool empty() const { return items_.empty(); } + size_t size () const { return items_.size(); } + void swap(FixedVector& other) { items_.swap(other.items_); } + +private: + FixedVector (const FixedVector&) = delete; + FixedVector& operator=(const FixedVector&) = delete; + + std::vector<std::unique_ptr<T>> items_; }; } diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index 71bb8688..d87a1643 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -214,7 +214,7 @@ private: reinterpret_cast<LPTSTR>(&setting), //__out LPTSTR lpLCData, sizeof(setting) / sizeof(TCHAR)) > 0; //__in int cchData } - + static bool getUserSetting(LCTYPE lt, std::wstring& setting) { const int bufferSize = ::GetLocaleInfo(LOCALE_USER_DEFAULT, lt, nullptr, 0); @@ -261,7 +261,7 @@ std::wstring zen::ffs_Impl::includeNumberSeparator(const std::wstring& number) return &buffer[0]; //GetNumberFormat() returns char count *including* 0-termination! } } - assert(false); //what's the problem? + assert(false); //what's the problem? return number; #elif defined ZEN_LINUX || defined ZEN_MAC diff --git a/zen/globals.h b/zen/globals.h index ff8c890d..123028c7 100644 --- a/zen/globals.h +++ b/zen/globals.h @@ -18,10 +18,10 @@ template <class T> class Global { public: - Global() { static_assert(std::is_trivially_destructible<Pod>::value, "this memory needs to live forever"); } - explicit Global(std::unique_ptr<T>&& newInst) { set(std::move(newInst)); } - ~Global() { set(nullptr); } - + Global() { static_assert(std::is_trivially_destructible<Pod>::value, "this memory needs to live forever"); } + explicit Global(std::unique_ptr<T>&& newInst) { set(std::move(newInst)); } + ~Global() { set(nullptr); } + std::shared_ptr<T> get() //=> return std::shared_ptr to let instance life time be handled by caller (MT usage!) { while (pod.spinLock.exchange(true)) ; @@ -45,19 +45,19 @@ public: } private: - //avoid static destruction order fiasco: there may be accesses to "Global<T>::get()" during process shutdown - //e.g. _("") used by message in debug_minidump.cpp or by some detached thread assembling an error message! - //=> use trivially-destructible POD only!!! - struct Pod - { - std::shared_ptr<T>* inst = nullptr; - //serialize access; can't use std::mutex: has non-trival destructor - std::atomic<bool> spinLock { false }; - } pod; + //avoid static destruction order fiasco: there may be accesses to "Global<T>::get()" during process shutdown + //e.g. _("") used by message in debug_minidump.cpp or by some detached thread assembling an error message! + //=> use trivially-destructible POD only!!! + struct Pod + { + std::shared_ptr<T>* inst = nullptr; + //serialize access; can't use std::mutex: has non-trival destructor + std::atomic<bool> spinLock { false }; + } pod; }; #if defined _MSC_VER && _MSC_VER < 1900 -#error function scope static initialization is not yet thread-safe! + #error function scope static initialization is not yet thread-safe! #endif } @@ -100,7 +100,7 @@ std::wstring translate(const std::wstring& singular, const std::wstring& plural, inline Global<const TranslationHandler>& getGlobalTranslationHandler() { - //getTranslator() may be called even after static objects of this translation unit are destroyed! + //getTranslator() may be called even after static objects of this translation unit are destroyed! static Global<const TranslationHandler> inst; //external linkage even in header! return inst; } diff --git a/zen/process_priority.cpp b/zen/process_priority.cpp index 7ef7bc0f..826cf857 100644 --- a/zen/process_priority.cpp +++ b/zen/process_priority.cpp @@ -15,7 +15,7 @@ using namespace zen; #ifdef ZEN_WIN -struct PreventStandby::Pimpl {}; +struct PreventStandby::Impl {}; PreventStandby::PreventStandby() { @@ -30,7 +30,7 @@ PreventStandby::~PreventStandby() } -struct ScheduleForBackgroundProcessing::Pimpl {}; +struct ScheduleForBackgroundProcessing::Impl {}; ScheduleForBackgroundProcessing::ScheduleForBackgroundProcessing() @@ -46,13 +46,13 @@ ScheduleForBackgroundProcessing::~ScheduleForBackgroundProcessing() } #elif defined ZEN_LINUX -struct PreventStandby::Pimpl {}; +struct PreventStandby::Impl {}; PreventStandby::PreventStandby() {} PreventStandby::~PreventStandby() {} //solution for GNOME?: http://people.gnome.org/~mccann/gnome-session/docs/gnome-session.html#org.gnome.SessionManager.Inhibit -struct ScheduleForBackgroundProcessing::Pimpl {}; +struct ScheduleForBackgroundProcessing::Impl {}; ScheduleForBackgroundProcessing::ScheduleForBackgroundProcessing() {}; ScheduleForBackgroundProcessing::~ScheduleForBackgroundProcessing() {}; diff --git a/zen/process_priority.h b/zen/process_priority.h index ce2d0157..07679b0c 100644 --- a/zen/process_priority.h +++ b/zen/process_priority.h @@ -19,8 +19,8 @@ public: PreventStandby(); //throw FileError ~PreventStandby(); private: - struct Pimpl; - std::unique_ptr<Pimpl> pimpl; + struct Impl; + const std::unique_ptr<Impl> pimpl; }; //lower CPU and file I/O priorities @@ -30,8 +30,8 @@ public: ScheduleForBackgroundProcessing(); //throw FileError ~ScheduleForBackgroundProcessing(); private: - struct Pimpl; - std::unique_ptr<Pimpl> pimpl; + struct Impl; + const std::unique_ptr<Impl> pimpl; }; } diff --git a/zen/scope_guard.h b/zen/scope_guard.h index 8ab58901..853f51b9 100644 --- a/zen/scope_guard.h +++ b/zen/scope_guard.h @@ -95,24 +95,24 @@ public: explicit ScopeGuard( F&& fun) : fun_(std::move(fun)) {} ScopeGuard(ScopeGuard&& other) : fun_(std::move(other.fun_)), - exeptionCount(other.exeptionCount), - dismissed(other.dismissed) { other.dismissed = true; } + exeptionCount_(other.exeptionCount_), + dismissed_(other.dismissed_) { other.dismissed_ = true; } ~ScopeGuard() noexcept(runMode != ScopeGuardRunMode::ON_SUCCESS) { - if (!dismissed) - runScopeGuardDestructor(fun_, exeptionCount, StaticEnum<ScopeGuardRunMode, runMode>()); + if (!dismissed_) + runScopeGuardDestructor(fun_, exeptionCount_, StaticEnum<ScopeGuardRunMode, runMode>()); } - void dismiss() { dismissed = true; } + void dismiss() { dismissed_ = true; } private: ScopeGuard (const ScopeGuard&) = delete; ScopeGuard& operator=(const ScopeGuard&) = delete; F fun_; - const int exeptionCount = getUncaughtExceptionCount(); - bool dismissed = false; + const int exeptionCount_ = getUncaughtExceptionCount(); + bool dismissed_ = false; }; diff --git a/zen/shell_execute.h b/zen/shell_execute.h index 5ebdd04d..ee8203c3 100644 --- a/zen/shell_execute.h +++ b/zen/shell_execute.h @@ -82,7 +82,7 @@ void shellExecute(const Zstring& command, ExecutionType type) //throw FileError trim(commandTmp, true, false); //CommandLineToArgvW() does not like leading spaces std::vector<Zstring> argv; - if (!commandTmp.empty()) //::CommandLineToArgvW returns the path to the current executable if empty string is passed + if (!commandTmp.empty()) //::CommandLineToArgvW returns the path to the current executable if empty string is passed { int argc = 0; LPWSTR* tmp = ::CommandLineToArgvW(commandTmp.c_str(), &argc); diff --git a/zen/stl_tools.h b/zen/stl_tools.h index 07925981..064d5b51 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -61,8 +61,8 @@ template <class InputIterator1, class InputIterator2> bool equal(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2); -size_t hashBytes(const unsigned char* ptr, size_t len); -size_t hashBytesAppend(size_t hashVal, const unsigned char* ptr, size_t len); +template <class ByteIterator> size_t hashBytes (ByteIterator first, ByteIterator last); +template <class ByteIterator> size_t hashBytesAppend(size_t hashVal, ByteIterator first, ByteIterator last); //support for custom string classes in std::unordered_set/map @@ -72,7 +72,8 @@ struct StringHash size_t operator()(const String& str) const { const auto* strFirst = strBegin(str); - return hashBytes(reinterpret_cast<const unsigned char*>(strFirst), strLength(str) * sizeof(strFirst[0])); + return hashBytes(reinterpret_cast<const char*>(strFirst), + reinterpret_cast<const char*>(strFirst + strLength(str))); } }; @@ -211,36 +212,36 @@ bool equal(InputIterator1 first1, InputIterator1 last1, #endif -inline -size_t hashBytes(const unsigned char* ptr, size_t len) +template <class ByteIterator> inline +size_t hashBytes(ByteIterator first, ByteIterator last) { - //http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function + //FNV-1a: http://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function #ifdef ZEN_BUILD_32BIT const size_t basis = 2166136261U; #elif defined ZEN_BUILD_64BIT const size_t basis = 14695981039346656037ULL; #endif - return hashBytesAppend(basis, ptr, len); + return hashBytesAppend(basis, first, last); } -inline -size_t hashBytesAppend(size_t hashVal, const unsigned char* ptr, size_t len) +template <class ByteIterator> inline +size_t hashBytesAppend(size_t hashVal, ByteIterator first, ByteIterator last) { #ifdef ZEN_BUILD_32BIT const size_t prime = 16777619U; #elif defined ZEN_BUILD_64BIT const size_t prime = 1099511628211ULL; #endif + static_assert(sizeof(typename std::iterator_traits<ByteIterator>::value_type) == 1, ""); - for (size_t i = 0; i < len; ++i) + for (; first != last; ++first) { - hashVal ^= static_cast<size_t>(ptr[i]); + hashVal ^= static_cast<size_t>(*first); hashVal *= prime; } return hashVal; } - } #endif //STL_TOOLS_H_84567184321434 diff --git a/zen/string_base.h b/zen/string_base.h index b54c3b2e..1ee37563 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -26,7 +26,7 @@ Allocator Policy: */ class AllocatorOptimalSpeed //exponential growth + min size { -public: +protected: //::operator new/ ::operator delete show same performance characterisics like malloc()/free()! static void* allocate(size_t size) { return ::malloc(size); } //throw std::bad_alloc static void deallocate(void* ptr) { ::free(ptr); } @@ -38,7 +38,7 @@ public: class AllocatorOptimalMemory //no wasted memory, but more reallocations required when manipulating string { -public: +protected: static void* allocate(size_t size) { return ::malloc(size); } //throw std::bad_alloc static void deallocate(void* ptr) { ::free(ptr); } static size_t calcCapacity(size_t length) { return length; } @@ -81,8 +81,9 @@ protected: Char* clone(Char* ptr) { - Char* newData = create(length(ptr)); //throw std::bad_alloc - std::copy(ptr, ptr + length(ptr) + 1, newData); + const size_t len = length(ptr); + Char* newData = create(len); //throw std::bad_alloc + std::copy(ptr, ptr + len + 1, newData); return newData; } @@ -207,25 +208,28 @@ private: static const Descriptor* descr(const Char* ptr) { return reinterpret_cast<const Descriptor*>(ptr) - 1; } }; + +template <class Char> +using DefaultStoragePolicy = StorageRefCountThreadSafe<Char, AllocatorOptimalSpeed>; + + //################################################################################################################################################################ //perf note: interestingly StorageDeepCopy and StorageRefCountThreadSafe show same performance in FFS comparison -template <class Char, //Character Type - template <class, class> class SP = StorageRefCountThreadSafe, //Storage Policy - class AP = AllocatorOptimalSpeed> //Allocator Policy -class Zbase : public SP<Char, AP> +template <class Char, //Character Type + template <class> class SP = DefaultStoragePolicy> //Storage Policy +class Zbase : public SP<Char> { public: Zbase(); - Zbase(const Char* source); //implicit conversion from a C-string - Zbase(const Char* source, size_t length); - Zbase(const Zbase& source); + Zbase(const Char* str) : Zbase(str, str + strLength(str)) {} //implicit conversion from a C-string! + Zbase(const Char* str, size_t len) : Zbase(str, str + len) {} + Zbase(const Zbase& str); Zbase(Zbase&& tmp) noexcept; - //explicit Zbase(Char source); //dangerous if implicit: Char buffer[]; return buffer[0]; ups... forgot &, but not a compiler error! //-> non-standard extension!!! - - //allow explicit construction from different string type, prevent ambiguity via SFINAE - //template <class S> explicit Zbase(const S& other, typename S::value_type = 0); + template <class InputIterator> + Zbase(InputIterator first, InputIterator last); + //explicit Zbase(Char ch); //dangerous if implicit: Char buffer[]; return buffer[0]; ups... forgot &, but not a compiler error! //-> non-standard extension!!! ~Zbase(); @@ -238,19 +242,17 @@ public: using const_reference = const Char&; using value_type = Char; - Zbase(const_iterator first, const_iterator last); - Char* begin(); - Char* end (); - const Char* begin() const; - const Char* end () const; - const Char* cbegin() const { return begin(); } - const Char* cend () const { return end(); } + iterator begin(); + iterator end (); + const_iterator begin () const { return rawStr_; } + const_iterator end () const { return rawStr_ + length(); } + const_iterator cbegin() const { return begin(); } + const_iterator cend () const { return end (); } //std::string functions size_t length() const; size_t size () const { return length(); } - const Char* c_str() const { return rawStr; } //C-string format with 0-termination - const Char* data() const { return rawStr; } //internal representation, 0-termination not guaranteed + const Char* c_str() const { return rawStr_; } //C-string format with 0-termination const Char operator[](size_t pos) const; bool empty() const { return length() == 0; } void clear(); @@ -261,20 +263,24 @@ public: size_t rfind(const Char* str, size_t pos = npos) const; // //Zbase& replace(size_t pos1, size_t n1, const Zbase& str); void reserve(size_t minCapacity); - Zbase& assign(const Char* source, size_t len); - Zbase& append(const Char* source, size_t len); + Zbase& assign(const Char* str, size_t len) { return assign(str, str + len); } + Zbase& append(const Char* str, size_t len) { return append(str, str + len); } + + template <class InputIterator> Zbase& assign(InputIterator first, InputIterator last); + template <class InputIterator> Zbase& append(InputIterator first, InputIterator last); + void resize(size_t newSize, Char fillChar = 0); - void swap(Zbase& other); + void swap(Zbase& str) { std::swap(rawStr_, str.rawStr_); } void push_back(Char val) { operator+=(val); } //STL access void pop_back(); - Zbase& operator=(const Zbase& source); + Zbase& operator=(const Zbase& str); Zbase& operator=(Zbase&& tmp) noexcept; - Zbase& operator=(const Char* source); - Zbase& operator=(Char source); - Zbase& operator+=(const Zbase& other); - Zbase& operator+=(const Char* other); - Zbase& operator+=(Char ch); + Zbase& operator=(const Char* str) { return assign(str, strLength(str)); } + Zbase& operator=(Char ch) { return assign(&ch, 1); } + Zbase& operator+=(const Zbase& str) { return append(str.c_str(), str.length()); } + Zbase& operator+=(const Char* str) { return append(str, strLength(str)); } + Zbase& operator+=(Char ch) { return append(&ch, 1); } static const size_t npos = static_cast<size_t>(-1); @@ -284,32 +290,32 @@ private: Zbase& operator+=(int) = delete; // void push_back (int) = delete; // - Char* rawStr; + Char* rawStr_; }; -template <class Char, template <class, class> class SP, class AP> bool operator==(const Zbase<Char, SP, AP>& lhs, const Zbase<Char, SP, AP>& rhs); -template <class Char, template <class, class> class SP, class AP> bool operator==(const Zbase<Char, SP, AP>& lhs, const Char* rhs); -template <class Char, template <class, class> class SP, class AP> inline bool operator==(const Char* lhs, const Zbase<Char, SP, AP>& rhs) { return operator==(rhs, lhs); } +template <class Char, template <class> class SP> bool operator==(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs); +template <class Char, template <class> class SP> bool operator==(const Zbase<Char, SP>& lhs, const Char* rhs); +template <class Char, template <class> class SP> inline bool operator==(const Char* lhs, const Zbase<Char, SP>& rhs) { return operator==(rhs, lhs); } -template <class Char, template <class, class> class SP, class AP> inline bool operator!=(const Zbase<Char, SP, AP>& lhs, const Zbase<Char, SP, AP>& rhs) { return !operator==(lhs, rhs); } -template <class Char, template <class, class> class SP, class AP> inline bool operator!=(const Zbase<Char, SP, AP>& lhs, const Char* rhs) { return !operator==(lhs, rhs); } -template <class Char, template <class, class> class SP, class AP> inline bool operator!=(const Char* lhs, const Zbase<Char, SP, AP>& rhs) { return !operator==(lhs, rhs); } +template <class Char, template <class> class SP> inline bool operator!=(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs) { return !operator==(lhs, rhs); } +template <class Char, template <class> class SP> inline bool operator!=(const Zbase<Char, SP>& lhs, const Char* rhs) { return !operator==(lhs, rhs); } +template <class Char, template <class> class SP> inline bool operator!=(const Char* lhs, const Zbase<Char, SP>& rhs) { return !operator==(lhs, rhs); } -template <class Char, template <class, class> class SP, class AP> bool operator<(const Zbase<Char, SP, AP>& lhs, const Zbase<Char, SP, AP>& rhs); -template <class Char, template <class, class> class SP, class AP> bool operator<(const Zbase<Char, SP, AP>& lhs, const Char* rhs); -template <class Char, template <class, class> class SP, class AP> bool operator<(const Char* lhs, const Zbase<Char, SP, AP>& rhs); +template <class Char, template <class> class SP> bool operator<(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs); +template <class Char, template <class> class SP> bool operator<(const Zbase<Char, SP>& lhs, const Char* rhs); +template <class Char, template <class> class SP> bool operator<(const Char* lhs, const Zbase<Char, SP>& rhs); -template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(const Zbase<Char, SP, AP>& lhs, const Zbase<Char, SP, AP>& rhs) { return Zbase<Char, SP, AP>(lhs) += rhs; } -template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(const Zbase<Char, SP, AP>& lhs, const Char* rhs) { return Zbase<Char, SP, AP>(lhs) += rhs; } -template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(const Zbase<Char, SP, AP>& lhs, Char rhs) { return Zbase<Char, SP, AP>(lhs) += rhs; } +template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs) { return Zbase<Char, SP>(lhs) += rhs; } +template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(const Zbase<Char, SP>& lhs, const Char* rhs) { return Zbase<Char, SP>(lhs) += rhs; } +template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(const Zbase<Char, SP>& lhs, Char rhs) { return Zbase<Char, SP>(lhs) += rhs; } //don't use unified first argument but save one move-construction in the r-value case instead! -template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP>&& lhs, const Zbase<Char, SP, AP>& rhs) { return std::move(lhs += rhs); } //the move *is* needed!!! -template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP>&& lhs, const Char* rhs) { return std::move(lhs += rhs); } //lhs, is an l-value parameter... -template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP>&& lhs, Char rhs) { return std::move(lhs += rhs); } //and not a local variable => no copy elision +template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(Zbase<Char, SP>&& lhs, const Zbase<Char, SP>& rhs) { return std::move(lhs += rhs); } //the move *is* needed!!! +template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(Zbase<Char, SP>&& lhs, const Char* rhs) { return std::move(lhs += rhs); } //lhs, is an l-value parameter... +template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(Zbase<Char, SP>&& lhs, Char rhs) { return std::move(lhs += rhs); } //and not a local variable => no copy elision -template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+( Char lhs, const Zbase<Char, SP, AP>& rhs) { return Zbase<Char, SP, AP>(&lhs, 1) += rhs; } -template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(const Char* lhs, const Zbase<Char, SP, AP>& rhs) { return Zbase<Char, SP, AP>(lhs ) += rhs; } +template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+( Char lhs, const Zbase<Char, SP>& rhs) { return Zbase<Char, SP>(&lhs, 1) += rhs; } +template <class Char, template <class> class SP> inline Zbase<Char, SP> operator+(const Char* lhs, const Zbase<Char, SP>& rhs) { return Zbase<Char, SP>(lhs ) += rhs; } @@ -324,82 +330,51 @@ template <class Char, template <class, class> class SP, class AP> inline Zbase<C //################################# implementation ######################################## -template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>::Zbase() +template <class Char, template <class> class SP> inline +Zbase<Char, SP>::Zbase() { //resist the temptation to avoid this allocation by referening a static global: NO performance advantage, MT issues! - rawStr = this->create(0); - rawStr[0] = 0; -} - - -template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>::Zbase(const Char* source) -{ - const size_t sourceLen = strLength(source); - rawStr = this->create(sourceLen); - std::copy(source, source + sourceLen + 1, rawStr); //include null-termination + rawStr_ = this->create(0); + rawStr_[0] = 0; } -template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>::Zbase(const Char* source, size_t sourceLen) +template <class Char, template <class> class SP> +template <class InputIterator> inline +Zbase<Char, SP>::Zbase(InputIterator first, InputIterator last) { - rawStr = this->create(sourceLen); - std::copy(source, source + sourceLen, rawStr); - rawStr[sourceLen] = 0; + rawStr_ = this->create(std::distance(first, last)); + *std::copy(first, last, rawStr_) = 0; } -template <class Char, template <class, class> class SP, class AP> -Zbase<Char, SP, AP>::Zbase(const_iterator first, const_iterator last) +template <class Char, template <class> class SP> inline +Zbase<Char, SP>::Zbase(const Zbase<Char, SP>& str) { - assert(first <= last); - const size_t sourceLen = last - first; - rawStr = this->create(sourceLen); - std::copy(first, last, rawStr); - rawStr[sourceLen] = 0; + rawStr_ = this->clone(str.rawStr_); } -template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>::Zbase(const Zbase<Char, SP, AP>& source) +template <class Char, template <class> class SP> inline +Zbase<Char, SP>::Zbase(Zbase<Char, SP>&& tmp) noexcept { - rawStr = this->clone(source.rawStr); -} - - -template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>::Zbase(Zbase<Char, SP, AP>&& tmp) noexcept -{ - rawStr = tmp.rawStr; - tmp.rawStr = nullptr; //usually nullptr would violate the class invarants, but it is good enough for the destructor! + rawStr_ = tmp.rawStr_; + tmp.rawStr_ = nullptr; //usually nullptr would violate the class invarants, but it is good enough for the destructor! //caveat: do not increment ref-count of an unshared string! We'd lose optimization opportunity of reusing its memory! } -/* -template <class Char, template <class, class> class SP, class AP> -template <class S> inline -Zbase<Char, SP, AP>::Zbase(const S& other, typename S::value_type) -{ - const size_t sourceLen = other.size(); - rawStr = this->create(sourceLen); - std::copy(other.c_str(), other.c_str() + sourceLen, rawStr); - rawStr[sourceLen] = 0; -} -*/ -template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>::~Zbase() +template <class Char, template <class> class SP> inline +Zbase<Char, SP>::~Zbase() { static_assert(noexcept(this->~Zbase()), ""); //has exception spec of compiler-generated destructor by default - this->destroy(rawStr); //rawStr may be nullptr; see move constructor! + this->destroy(rawStr_); //rawStr_ may be nullptr; see move constructor! } -template <class Char, template <class, class> class SP, class AP> inline -size_t Zbase<Char, SP, AP>::find(const Zbase& str, size_t pos) const +template <class Char, template <class> class SP> inline +size_t Zbase<Char, SP>::find(const Zbase& str, size_t pos) const { assert(pos <= length()); const size_t len = length(); @@ -410,8 +385,8 @@ size_t Zbase<Char, SP, AP>::find(const Zbase& str, size_t pos) const } -template <class Char, template <class, class> class SP, class AP> inline -size_t Zbase<Char, SP, AP>::find(const Char* str, size_t pos) const +template <class Char, template <class> class SP> inline +size_t Zbase<Char, SP>::find(const Char* str, size_t pos) const { assert(pos <= length()); const size_t len = length(); @@ -422,8 +397,8 @@ size_t Zbase<Char, SP, AP>::find(const Char* str, size_t pos) const } -template <class Char, template <class, class> class SP, class AP> inline -size_t Zbase<Char, SP, AP>::find(Char ch, size_t pos) const +template <class Char, template <class> class SP> inline +size_t Zbase<Char, SP>::find(Char ch, size_t pos) const { assert(pos <= length()); const size_t len = length(); @@ -433,8 +408,8 @@ size_t Zbase<Char, SP, AP>::find(Char ch, size_t pos) const } -template <class Char, template <class, class> class SP, class AP> inline -size_t Zbase<Char, SP, AP>::rfind(Char ch, size_t pos) const +template <class Char, template <class> class SP> inline +size_t Zbase<Char, SP>::rfind(Char ch, size_t pos) const { assert(pos == npos || pos <= length()); const size_t len = length(); @@ -444,8 +419,8 @@ size_t Zbase<Char, SP, AP>::rfind(Char ch, size_t pos) const } -template <class Char, template <class, class> class SP, class AP> inline -size_t Zbase<Char, SP, AP>::rfind(const Char* str, size_t pos) const +template <class Char, template <class> class SP> inline +size_t Zbase<Char, SP>::rfind(const Char* str, size_t pos) const { assert(pos == npos || pos <= length()); const size_t strLen = strLength(str); @@ -457,126 +432,112 @@ size_t Zbase<Char, SP, AP>::rfind(const Char* str, size_t pos) const } -template <class Char, template <class, class> class SP, class AP> inline -void Zbase<Char, SP, AP>::resize(size_t newSize, Char fillChar) +template <class Char, template <class> class SP> inline +void Zbase<Char, SP>::resize(size_t newSize, Char fillChar) { const size_t oldSize = length(); - if (this->canWrite(rawStr, newSize)) + if (this->canWrite(rawStr_, newSize)) { if (oldSize < newSize) - std::fill(rawStr + oldSize, rawStr + newSize, fillChar); - rawStr[newSize] = 0; - this->setLength(rawStr, newSize); + std::fill(rawStr_ + oldSize, rawStr_ + newSize, fillChar); + rawStr_[newSize] = 0; + this->setLength(rawStr_, newSize); } else { Char* newStr = this->create(newSize); if (oldSize < newSize) { - std::copy(rawStr, rawStr + oldSize, newStr); + std::copy(rawStr_, rawStr_ + oldSize, newStr); std::fill(newStr + oldSize, newStr + newSize, fillChar); } else - std::copy(rawStr, rawStr + newSize, newStr); + std::copy(rawStr_, rawStr_ + newSize, newStr); newStr[newSize] = 0; - this->destroy(rawStr); - rawStr = newStr; + this->destroy(rawStr_); + rawStr_ = newStr; } } -template <class Char, template <class, class> class SP, class AP> inline -bool operator==(const Zbase<Char, SP, AP>& lhs, const Zbase<Char, SP, AP>& rhs) +template <class Char, template <class> class SP> inline +bool operator==(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs) { return lhs.length() == rhs.length() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); //respect embedded 0 } -template <class Char, template <class, class> class SP, class AP> inline -bool operator==(const Zbase<Char, SP, AP>& lhs, const Char* rhs) +template <class Char, template <class> class SP> inline +bool operator==(const Zbase<Char, SP>& lhs, const Char* rhs) { return lhs.length() == strLength(rhs) && std::equal(lhs.begin(), lhs.end(), rhs); //respect embedded 0 } -template <class Char, template <class, class> class SP, class AP> inline -bool operator<(const Zbase<Char, SP, AP>& lhs, const Zbase<Char, SP, AP>& rhs) +template <class Char, template <class> class SP> inline +bool operator<(const Zbase<Char, SP>& lhs, const Zbase<Char, SP>& rhs) { return std::lexicographical_compare(lhs.begin(), lhs.end(), //respect embedded 0 rhs.begin(), rhs.end()); } -template <class Char, template <class, class> class SP, class AP> inline -bool operator<(const Zbase<Char, SP, AP>& lhs, const Char* rhs) +template <class Char, template <class> class SP> inline +bool operator<(const Zbase<Char, SP>& lhs, const Char* rhs) { return std::lexicographical_compare(lhs.begin(), lhs.end(), //respect embedded 0 rhs, rhs + strLength(rhs)); } -template <class Char, template <class, class> class SP, class AP> inline -bool operator<(const Char* lhs, const Zbase<Char, SP, AP>& rhs) +template <class Char, template <class> class SP> inline +bool operator<(const Char* lhs, const Zbase<Char, SP>& rhs) { return std::lexicographical_compare(lhs, lhs + strLength(lhs), //respect embedded 0 rhs.begin(), rhs.end()); } -template <class Char, template <class, class> class SP, class AP> inline -size_t Zbase<Char, SP, AP>::length() const +template <class Char, template <class> class SP> inline +size_t Zbase<Char, SP>::length() const { - return SP<Char, AP>::length(rawStr); + return SP<Char>::length(rawStr_); } -template <class Char, template <class, class> class SP, class AP> inline -const Char Zbase<Char, SP, AP>::operator[](size_t pos) const +template <class Char, template <class> class SP> inline +const Char Zbase<Char, SP>::operator[](size_t pos) const { assert(pos < length()); //design by contract! no runtime check! - return rawStr[pos]; -} - - -template <class Char, template <class, class> class SP, class AP> inline -const Char* Zbase<Char, SP, AP>::begin() const -{ - return rawStr; -} - - -template <class Char, template <class, class> class SP, class AP> inline -const Char* Zbase<Char, SP, AP>::end() const -{ - return rawStr + length(); + return rawStr_[pos]; } -template <class Char, template <class, class> class SP, class AP> inline -Char* Zbase<Char, SP, AP>::begin() +template <class Char, template <class> class SP> inline +auto Zbase<Char, SP>::begin() -> iterator { reserve(length()); //make unshared! - return rawStr; + return rawStr_; } -template <class Char, template <class, class> class SP, class AP> inline -Char* Zbase<Char, SP, AP>::end() +template <class Char, template <class> class SP> inline +auto Zbase<Char, SP>::end() -> iterator { return begin() + length(); } -template <class Char, template <class, class> class SP, class AP> inline -void Zbase<Char, SP, AP>::clear() +template <class Char, template <class> class SP> inline +void Zbase<Char, SP>::clear() { if (!empty()) { - if (this->canWrite(rawStr, 0)) + if (this->canWrite(rawStr_, 0)) { - rawStr[0] = 0; //keep allocated memory - this->setLength(rawStr, 0); // + rawStr_[0] = 0; //keep allocated memory + this->setLength(rawStr_, 0); // } else *this = Zbase(); @@ -584,111 +545,70 @@ void Zbase<Char, SP, AP>::clear() } -template <class Char, template <class, class> class SP, class AP> inline -void Zbase<Char, SP, AP>::swap(Zbase<Char, SP, AP>& other) +template <class Char, template <class> class SP> inline +void Zbase<Char, SP>::reserve(size_t minCapacity) //make unshared and check capacity { - std::swap(rawStr, other.rawStr); -} - - -template <class Char, template <class, class> class SP, class AP> inline -void Zbase<Char, SP, AP>::reserve(size_t minCapacity) //make unshared and check capacity -{ - if (!this->canWrite(rawStr, minCapacity)) + if (!this->canWrite(rawStr_, minCapacity)) { //allocate a new string const size_t len = length(); Char* newStr = this->create(len, std::max(len, minCapacity)); //reserve() must NEVER shrink the string: logical const! - std::copy(rawStr, rawStr + len + 1, newStr); //include 0-termination + std::copy(rawStr_, rawStr_ + len + 1, newStr); //include 0-termination - this->destroy(rawStr); - rawStr = newStr; + this->destroy(rawStr_); + rawStr_ = newStr; } } -template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::assign(const Char* source, size_t len) +template <class Char, template <class> class SP> +template <class InputIterator> inline +Zbase<Char, SP>& Zbase<Char, SP>::assign(InputIterator first, InputIterator last) { - if (this->canWrite(rawStr, len)) + const size_t len = std::distance(first, last); + if (this->canWrite(rawStr_, len)) { - std::copy(source, source + len, rawStr); - rawStr[len] = 0; //include null-termination - this->setLength(rawStr, len); + *std::copy(first, last, rawStr_) = 0; + this->setLength(rawStr_, len); } else - *this = Zbase(source, len); + *this = Zbase(first, last); return *this; } -template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::append(const Char* source, size_t len) +template <class Char, template <class> class SP> +template <class InputIterator> inline +Zbase<Char, SP>& Zbase<Char, SP>::append(InputIterator first, InputIterator last) { + const size_t len = std::distance(first, last); const size_t thisLen = length(); reserve(thisLen + len); //make unshared and check capacity - std::copy(source, source + len, rawStr + thisLen); - rawStr[thisLen + len] = 0; - this->setLength(rawStr, thisLen + len); + *std::copy(first, last, rawStr_ + thisLen) = 0; + this->setLength(rawStr_, thisLen + len); return *this; } -template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator=(const Zbase<Char, SP, AP>& other) +template <class Char, template <class> class SP> inline +Zbase<Char, SP>& Zbase<Char, SP>::operator=(const Zbase<Char, SP>& str) { - Zbase<Char, SP, AP>(other).swap(*this); + Zbase<Char, SP>(str).swap(*this); return *this; } -template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator=(Zbase<Char, SP, AP>&& tmp) noexcept +template <class Char, template <class> class SP> inline +Zbase<Char, SP>& Zbase<Char, SP>::operator=(Zbase<Char, SP>&& tmp) noexcept { swap(tmp); //don't use unifying assignment but save one move-construction in the r-value case instead! return *this; } - -template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator=(const Char* source) -{ - return assign(source, strLength(source)); -} - - -template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator=(Char ch) -{ - return assign(&ch, 1); -} - - -template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator+=(const Zbase<Char, SP, AP>& other) -{ - return append(other.c_str(), other.length()); -} - - -template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator+=(const Char* other) -{ - return append(other, strLength(other)); -} - - -template <class Char, template <class, class> class SP, class AP> inline -Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::operator+=(Char ch) -{ - return append(&ch, 1); -} - - -template <class Char, template <class, class> class SP, class AP> inline -void Zbase<Char, SP, AP>::pop_back() +template <class Char, template <class> class SP> inline +void Zbase<Char, SP>::pop_back() { const size_t len = length(); assert(len > 0); diff --git a/zen/string_tools.h b/zen/string_tools.h index 525227d6..9b8e7328 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -56,7 +56,8 @@ template <class S, class T, class Num> S printNumber(const T& format, const Num& //string to string conversion: converts string-like type into char-compatible target string class template <class T, class S> T copyStringTo(S&& str); - +//case-sensitive comparison +template <class S, class T> int cmpString(const S& lhs, const T& rhs); @@ -233,7 +234,7 @@ std::vector<S> split(const S& str, const T& delimiter) const size_t delimLen = strLength(delimiter); if (delimLen == 0) - return { str }; + return { str }; else { const auto* const delimFirst = strBegin(delimiter); @@ -241,8 +242,8 @@ std::vector<S> split(const S& str, const T& delimiter) const auto* blockStart = strBegin(str); const auto* const strLast = blockStart + strLength(str); - - std::vector<S> output; + + std::vector<S> output; for (;;) { @@ -251,7 +252,7 @@ std::vector<S> split(const S& str, const T& delimiter) output.emplace_back(blockStart, blockEnd - blockStart); if (blockEnd == strLast) //clients expect: if delimiter not found, return str - return output; + return output; blockStart = blockEnd + delimLen; } } @@ -263,11 +264,11 @@ namespace impl ZEN_INIT_DETECT_MEMBER(append); //either call operator+=(S(str, len)) or append(str, len) -template <class S, class Char> inline -typename EnableIf<HasMember_append<S>::value>::Type stringAppend(S& str, const Char* other, size_t len) { str.append(other, len); } +template <class S, class InputIterator> inline +typename EnableIf<HasMember_append<S>::value>::Type stringAppend(S& str, InputIterator first, InputIterator last) { str.append(first, last); } -template <class S, class Char> inline -typename EnableIf<!HasMember_append<S>::value>::Type stringAppend(S& str, const Char* other, size_t len) { str += S(other, len); } +template <class S, class InputIterator> inline +typename EnableIf<!HasMember_append<S>::value>::Type stringAppend(S& str, InputIterator first, InputIterator last) { str += S(first, last); } } @@ -289,20 +290,20 @@ S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll) const auto* const oldBegin = strBegin(oldTerm); const auto* const oldEnd = oldBegin + oldLen; - //optimize "oldTerm not found" + //optimize "oldTerm not found": return ref-counted copy const auto* strMatch = std::search(strPos, strEnd, oldBegin, oldEnd); if (strMatch == strEnd) return str; - const size_t newLen = strLength(newTerm); const auto* const newBegin = strBegin(newTerm); + const auto* const newEnd = newBegin + strLength(newTerm); S output; for (;;) { - impl::stringAppend(output, strPos, strMatch - strPos); - impl::stringAppend(output, newBegin, newLen); + impl::stringAppend(output, strPos, strMatch); + impl::stringAppend(output, newBegin, newEnd); strPos = strMatch + oldLen; @@ -314,7 +315,7 @@ S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll) if (strMatch == strEnd) break; } - impl::stringAppend(output, strPos, strEnd - strPos); + impl::stringAppend(output, strPos, strEnd); return output; } @@ -380,6 +381,28 @@ template <class T, class S> inline T copyStringTo(S&& str) { return impl::CopyStringToString<std::decay_t<S>, T>().copy(std::forward<S>(str)); } +template <class S, class T> inline +int cmpString(const S& lhs, const T& rhs) +{ + const size_t lenL = strLength(lhs); + const size_t lenR = strLength(rhs); + + const auto* strPosL = strBegin(lhs); + const auto* strPosR = strBegin(rhs); + + const auto* const strPosLLast = strPosL + std::min(lenL, lenR); + + while (strPosL != strPosLLast) + { + const auto charL = static_cast<unsigned int>(*strPosL++); //unsigned char-comparison is the convention! + const auto charR = static_cast<unsigned int>(*strPosR++); + if (charL != charR) + return static_cast<int>(charL) - static_cast<int>(charR); + } + return static_cast<int>(lenL) - static_cast<int>(lenR); +} + + namespace impl { template <class Num> inline @@ -635,7 +658,6 @@ Num stringTo(const S& str) return impl::stringTo<Num>(str, TypeTag()); } - } #endif //STRING_TOOLS_H_213458973046 diff --git a/zen/sys_error.h b/zen/sys_error.h index 5897b413..4798b959 100644 --- a/zen/sys_error.h +++ b/zen/sys_error.h @@ -13,8 +13,8 @@ #include "scope_guard.h" #ifdef ZEN_WIN - #include "win.h" //includes "windows.h" - + #include "win.h" //tame WinINet.h include + #include <WinINet.h> #elif defined ZEN_LINUX || defined ZEN_MAC #include <cstring> #include <cerrno> @@ -50,6 +50,22 @@ private: +#ifdef _MSC_VER +#define THROW_LAST_SYS_ERROR(functionName) \ + do \ + { \ + const ErrorCode ecInternal = getLastError(); \ + throw SysError(formatSystemError(functionName, ecInternal)); \ + \ + __pragma(warning(suppress: 4127)) /*"conditional expression is constant"*/ \ + } while (false) + +#else //same thing witout "__pragma": +#define THROW_LAST_SYS_ERROR(functionName) \ + do { const ErrorCode ecInternal = getLastError(); throw SysError(formatSystemError(functionName, ecInternal)); } while (false) +#endif + + @@ -78,10 +94,26 @@ std::wstring formatSystemErrorRaw(ErrorCode ec) //return empty string on error ZEN_ON_SCOPE_EXIT(::SetLastError(currentError)); //this function must not change active system error variable! LPWSTR buffer = nullptr; - if (::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_MAX_WIDTH_MASK | - FORMAT_MESSAGE_IGNORE_INSERTS | //important: without this flag ::FormatMessage() will fail if message contains placeholders - FORMAT_MESSAGE_ALLOCATE_BUFFER, nullptr, ec, 0, reinterpret_cast<LPWSTR>(&buffer), 0, nullptr) != 0) + const DWORD rv = [&] + { + if (INTERNET_ERROR_BASE <= ec && ec <= INTERNET_ERROR_LAST) + return ::FormatMessage(FORMAT_MESSAGE_FROM_HMODULE | + FORMAT_MESSAGE_MAX_WIDTH_MASK | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_ALLOCATE_BUFFER, ::GetModuleHandle(L"WinINet.dll"), ec, 0, reinterpret_cast<LPWSTR>(&buffer), 0, nullptr); + else + return ::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_MAX_WIDTH_MASK | + FORMAT_MESSAGE_IGNORE_INSERTS | //important: without this flag ::FormatMessage() will fail if message contains placeholders + FORMAT_MESSAGE_ALLOCATE_BUFFER, //_In_ DWORD dwFlags, + nullptr, //_In_opt_ LPCVOID lpSource, + ec, //_In_ DWORD dwMessageId, + 0, //_In_ DWORD dwLanguageId, + reinterpret_cast<LPWSTR>(&buffer), //_Out_ LPTSTR lpBuffer, + 0, //_In_ DWORD nSize, + nullptr); //_In_opt_ va_list *Arguments + }(); + if (rv != 0) if (buffer) //"don't trust nobody" { ZEN_ON_SCOPE_EXIT(::LocalFree(buffer)); @@ -102,9 +134,9 @@ std::wstring formatSystemErrorRaw(ErrorCode ec) //return empty string on error std::wstring formatSystemError(const std::wstring& functionName, long long lastError) = delete; //intentional overload ambiguity to catch usage errors with HRESULT! inline -std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec) +std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec) { - return formatSystemError(functionName, numberTo<std::wstring>(ec), formatSystemErrorRaw(ec)); + return formatSystemError(functionName, numberTo<std::wstring>(ec), formatSystemErrorRaw(ec)); } diff --git a/zen/thread.h b/zen/thread.h index fd9dc76d..ac94da6a 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -65,6 +65,9 @@ void interruptibleSleep(const std::chrono::duration<Rep, Period>& relTime); //th #ifdef ZEN_WIN void setCurrentThreadName(const char* threadName); #endif + +std::uint64_t getThreadId(); //simple integer thread id, unlike boost::thread::id: https://svn.boost.org/trac/boost/ticket/5754 + //------------------------------------------------------------------------------------------ /* @@ -335,7 +338,7 @@ private: } std::atomic<bool> interrupted{ false }; //std:atomic is uninitialized by default!!! - //"The default constructor is trivial: no initialization takes place other than zero initialization of static and thread-local objects." + //"The default constructor is trivial: no initialization takes place other than zero initialization of static and thread-local objects." std::condition_variable* activeCondition = nullptr; std::mutex lockConditionPtr; //serialize pointer access (only!) @@ -447,6 +450,28 @@ void setCurrentThreadName(const char* threadName) __except (EXCEPTION_EXECUTE_HANDLER) {} } #endif + + +inline +std::uint64_t getThreadId() +{ +#ifdef ZEN_WIN + static_assert(sizeof(std::uint64_t) >= sizeof(DWORD), ""); + return ::GetCurrentThreadId(); //no-fail + +#elif defined ZEN_LINUX + //obviously "gettid()" is not available on Ubuntu/Debian/Suse => use the OpenSSL approach: + static_assert(sizeof(std::uint64_t) >= sizeof(void*), ""); + return reinterpret_cast<std::uint64_t>(static_cast<void*>(&errno)); + +#elif defined ZEN_MAC + uint64_t tid = 0; + const int rv = ::pthread_threadid_np(nullptr, &tid); //yeah, theoretically no-fail, too :> http://opensource.apple.com//source/Libc/Libc-583/pthreads/pthread.c + assert(rv == 0); + (void)rv; + return tid; +#endif +} } #endif //THREAD_H_7896323423432235246427 @@ -261,12 +261,12 @@ template <class String, class String2> inline String formatTime(const String2& format, const TimeComp& comp) { using FormatTag = typename SelectIf< - IsSameType<String2, FormatDateTag >::value || - IsSameType<String2, FormatTimeTag >::value || - IsSameType<String2, FormatDateTimeTag >::value || - IsSameType<String2, FormatIsoDateTag >::value || - IsSameType<String2, FormatIsoTimeTag >::value || - IsSameType<String2, FormatIsoDateTimeTag>::value, implementation::PredefinedFormatTag, implementation::UserDefinedFormatTag>::Type; + IsSameType<String2, FormatDateTag >::value || + IsSameType<String2, FormatTimeTag >::value || + IsSameType<String2, FormatDateTimeTag >::value || + IsSameType<String2, FormatIsoDateTag >::value || + IsSameType<String2, FormatIsoTimeTag >::value || + IsSameType<String2, FormatIsoDateTimeTag>::value, implementation::PredefinedFormatTag, implementation::UserDefinedFormatTag>::Type; return implementation::formatTime<String>(format, comp, FormatTag()); } @@ -417,16 +417,16 @@ CharString wideToUtf8(const WideString& str, Int2Type<4>) //other OS: convert ut template <class CharString> inline -bool isValidUtf8(const CharString& str) +bool isValidUtf8(const CharString& str) { - using namespace implementation; - bool valid = true; + using namespace implementation; + bool valid = true; utf8ToCodePoint(strBegin(str), strBegin(str) + strLength(str), - [&](CodePoint cp) - { - if (cp == REPLACEMENT_CHAR) - valid = false; //perf: should we use an (expensive) exception for iteration break? - }); + [&](CodePoint cp) + { + if (cp == REPLACEMENT_CHAR) + valid = false; //perf: should we use an (expensive) exception for iteration break? + }); return valid; } diff --git a/zen/warn_static.h b/zen/warn_static.h deleted file mode 100644 index 44d7bd73..00000000 --- a/zen/warn_static.h +++ /dev/null @@ -1,33 +0,0 @@ -// ***************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * -// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * -// ***************************************************************************** - -#ifndef WARN_STATIC_H_08724567834560832745 -#define WARN_STATIC_H_08724567834560832745 - -/* -Portable Compile-Time Warning ------------------------------ -Usage: - warn_static("my message") -*/ - -#ifdef _MSC_VER -#define STATIC_WARNING_MAKE_STRINGIZE_SUB(NUM) #NUM -#define STATIC_WARNING_MAKE_STRINGIZE(NUM) STATIC_WARNING_MAKE_STRINGIZE_SUB(NUM) - -#define warn_static(TXT) \ - __pragma(message(__FILE__ "(" STATIC_WARNING_MAKE_STRINGIZE(__LINE__) "): Warning: " ## TXT)) - -#elif defined __GNUC__ -#define STATIC_WARNING_CONCAT_SUB(X, Y) X ## Y -#define STATIC_WARNING_CONCAT(X, Y) STATIC_WARNING_CONCAT_SUB(X, Y) - -#define warn_static(TXT) \ - typedef int STATIC_WARNING_87903124 __attribute__ ((deprecated)); \ - enum { STATIC_WARNING_CONCAT(warn_static_dummy_value, __LINE__) = sizeof(STATIC_WARNING_87903124) }; -#endif - -#endif //WARN_STATIC_H_08724567834560832745 diff --git a/zen/zstring.h b/zen/zstring.h index 792b92db..902da80e 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -23,7 +23,7 @@ //"The reason for all the fuss above" - Loki/SmartPtr //a high-performance string for interfacing with native OS APIs in multithreaded contexts -using Zstring = zen::Zbase<Zchar, zen::StorageRefCountThreadSafe, zen::AllocatorOptimalSpeed>; +using Zstring = zen::Zbase<Zchar>; int cmpStringNoCase(const wchar_t* lhs, size_t lhsLen, const wchar_t* rhs, size_t rhsLen); @@ -95,6 +95,9 @@ bool pathEndsWith(const S& str, const T& postfix) } +template <class S, class T, class U> +S pathReplaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll = true); + @@ -195,6 +198,41 @@ int cmpFilePath(const char* lhs, size_t lhsLen, const char* rhs, size_t rhsLen) #endif +template <class S, class T, class U> inline +S pathReplaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll) +{ + assert(!contains(str, Zchar('\0'))); + +#if defined ZEN_WIN || defined ZEN_MAC + using namespace zen; + + S strU = makeUpperCopy(str); //S required to be a string class + S oldTermU = makeUpperCopy<S>(oldTerm); //[!] T not required to be a string class + assert(strLength(strU ) == strLength(str )); + assert(strLength(oldTermU) == strLength(oldTerm)); + + replace(strU, oldTermU, Zchar('\0'), replaceAll); + + S output; + + size_t i = 0; + for (auto c : strU) + if (c == Zchar('\0')) + { + output += newTerm; + i += oldTermU.size(); + } + else + output += str[i++]; + + return output; + +#elif defined ZEN_LINUX + return replaceCpy(str, oldTerm, newTerm, replaceAll); +#endif +} + + //--------------------------------------------------------------------------- //ZEN macro consistency checks: #ifdef ZEN_WIN |