diff options
Diffstat (limited to 'zen')
-rw-r--r-- | zen/dir_watcher.cpp | 7 | ||||
-rw-r--r-- | zen/file_access.cpp | 379 | ||||
-rw-r--r-- | zen/file_id_def.h | 2 | ||||
-rw-r--r-- | zen/file_io.cpp | 65 | ||||
-rw-r--r-- | zen/perf.h | 8 | ||||
-rw-r--r-- | zen/recycler.cpp | 195 | ||||
-rw-r--r-- | zen/recycler.h | 2 | ||||
-rw-r--r-- | zen/serialize.h | 8 | ||||
-rw-r--r-- | zen/shell_execute.h | 66 | ||||
-rw-r--r-- | zen/string_tools.h | 130 | ||||
-rw-r--r-- | zen/string_traits.h | 27 | ||||
-rw-r--r-- | zen/sys_error.h | 41 | ||||
-rw-r--r-- | zen/win_ver.h | 135 | ||||
-rw-r--r-- | zen/zstring.cpp | 131 | ||||
-rw-r--r-- | zen/zstring.h | 118 |
15 files changed, 552 insertions, 762 deletions
diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index a948a5e8..a97ea80d 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -220,8 +220,11 @@ public: zen::ScopeGuard guardAio = zen::makeGuard([&] { //Canceling Pending I/O Operations: http://msdn.microsoft.com/en-us/library/aa363789(v=vs.85).aspx - //if (::CancelIoEx(hDir, &overlapped) /*!= FALSE*/ || ::GetLastError() != ERROR_NOT_FOUND) -> Vista only +#ifdef ZEN_WIN_VISTA_AND_LATER + if (::CancelIoEx(hDir, &overlapped) /*!= FALSE*/ || ::GetLastError() != ERROR_NOT_FOUND) +#else if (::CancelIo(hDir) /*!= FALSE*/ || ::GetLastError() != ERROR_NOT_FOUND) +#endif { DWORD bytesWritten = 0; ::GetOverlappedResult(hDir, &overlapped, &bytesWritten, true); //wait until cancellation is complete @@ -441,7 +444,7 @@ DirWatcher::DirWatcher(const Zstring& dirPath) : //throw FileError const auto ec = getLastError(); if (ec == ENOSPC) //fix misleading system message "No space left on device" throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subDirPath)), - formatSystemError(L"inotify_add_watch", ec, L"The user limit on the total number of inotify watches was reached or the kernel failed to allocate a needed resource.")); + formatSystemError(L"inotify_add_watch", ec, L"The user limit on the total number of inotify watches was reached or the kernel failed to allocate a needed resource.")); throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(subDirPath)), formatSystemError(L"inotify_add_watch", ec)); } diff --git a/zen/file_access.cpp b/zen/file_access.cpp index b1b781ee..96aac081 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -18,10 +18,12 @@ #ifdef ZEN_WIN #include <Aclapi.h> #include "privilege.h" - #include "dll.h" #include "long_path_prefix.h" #include "win_ver.h" - #include "IFileOperation/file_op.h" + #ifdef ZEN_WIN_VISTA_AND_LATER + #include <zen/vista_file_op.h> + #endif + #elif defined ZEN_LINUX #include <sys/vfs.h> //statfs @@ -166,27 +168,6 @@ bool isFatDrive(const Zstring& filePath) //throw() return &buffer[0] == Zstring(L"FAT") || &buffer[0] == Zstring(L"FAT32"); } - - -//(try to) enhance error messages by showing which processes lock the file -Zstring getLockingProcessNames(const Zstring& filepath) //throw(), empty string if none found or error occurred -{ - if (vistaOrLater()) - { - using namespace fileop; - const DllFun<FunType_getLockingProcesses> getLockingProcesses(getDllName(), funName_getLockingProcesses); - const DllFun<FunType_freeString> freeString (getDllName(), funName_freeString); - - const wchar_t* processList = nullptr; - if (getLockingProcesses && freeString) - if (getLockingProcesses(filepath.c_str(), processList)) - { - ZEN_ON_SCOPE_EXIT(freeString(processList)); - return processList; - } - } - return Zstring(); -} #endif } @@ -248,13 +229,13 @@ std::uint64_t zen::getFreeDiskSpace(const Zstring& path) //throw FileError, retu nullptr)) //__out_opt PULARGE_INTEGER lpTotalNumberOfFreeBytes throwFileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtFileName(path)), L"GetDiskFreeSpaceEx", getLastError()); - //return 0 if info is not available: "The GetDiskFreeSpaceEx function returns zero for lpFreeBytesAvailable for all CD requests" + //return 0 if info is not available: "The GetDiskFreeSpaceEx function returns zero for lpFreeBytesAvailable for all CD requests" return get64BitUInt(bytesFree.LowPart, bytesFree.HighPart); #elif defined ZEN_LINUX || defined ZEN_MAC struct ::statfs info = {}; if (::statfs(path.c_str(), &info) != 0) - throwFileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtFileName(path)), L"statfs", getLastError()); + throwFileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtFileName(path)), L"statfs", getLastError()); return static_cast<std::uint64_t>(info.f_bsize) * info.f_bavail; #endif @@ -290,13 +271,13 @@ bool zen::removeFile(const Zstring& filepath) //throw FileError const std::wstring errorMsg = replaceCpy(_("Cannot delete file %x."), L"%x", fmtFileName(filepath)); std::wstring errorDescr = formatSystemError(functionName, lastError); -#ifdef ZEN_WIN +#ifdef ZEN_WIN_VISTA_AND_LATER if (lastError == ERROR_SHARING_VIOLATION || //-> enhance error message! lastError == ERROR_LOCK_VIOLATION) { - const Zstring procList = getLockingProcessNames(filepath); //throw() + const std::wstring procList = vista::getLockingProcesses(filepath); //noexcept if (!procList.empty()) - errorDescr = _("The file is locked by another process:") + L"\n" + procList; + errorDescr = _("The file is locked by another process:") + L"\n" + procList; } #endif throw FileError(errorMsg, errorDescr); @@ -315,55 +296,56 @@ namespace Fix8Dot3NameClash() */ //wrapper for file system rename function: -void renameFile_sub(const Zstring& oldName, const Zstring& newName) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting +void renameFile_sub(const Zstring& pathSource, const Zstring& pathTarget) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting { #ifdef ZEN_WIN - const Zstring oldNameFmt = applyLongPathPrefix(oldName); - const Zstring newNameFmt = applyLongPathPrefix(newName); + const Zstring pathSourceFmt = applyLongPathPrefix(pathSource); + const Zstring pathTargetFmt = applyLongPathPrefix(pathTarget); - if (!::MoveFileEx(oldNameFmt.c_str(), //__in LPCTSTR lpExistingFileName, - newNameFmt.c_str(), //__in_opt LPCTSTR lpNewFileName, + if (!::MoveFileEx(pathSourceFmt.c_str(), //__in LPCTSTR lpExistingFileName, + pathTargetFmt.c_str(), //__in_opt LPCTSTR lpNewFileName, 0)) //__in DWORD dwFlags { DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls! if (lastError == ERROR_ACCESS_DENIED) //MoveFileEx may fail to rename a read-only file on a SAMBA-share -> (try to) handle this { - const DWORD oldAttr = ::GetFileAttributes(oldNameFmt.c_str()); + const DWORD oldAttr = ::GetFileAttributes(pathSourceFmt.c_str()); if (oldAttr != INVALID_FILE_ATTRIBUTES && (oldAttr & FILE_ATTRIBUTE_READONLY)) { - if (::SetFileAttributes(oldNameFmt.c_str(), FILE_ATTRIBUTE_NORMAL)) //remove readonly-attribute + if (::SetFileAttributes(pathSourceFmt.c_str(), FILE_ATTRIBUTE_NORMAL)) //remove readonly-attribute { //try again... - if (::MoveFileEx(oldNameFmt.c_str(), //__in LPCTSTR lpExistingFileName, - newNameFmt.c_str(), //__in_opt LPCTSTR lpNewFileName, + if (::MoveFileEx(pathSourceFmt.c_str(), //__in LPCTSTR lpExistingFileName, + pathTargetFmt.c_str(), //__in_opt LPCTSTR lpNewFileName, 0)) //__in DWORD dwFlags { //(try to) restore file attributes - ::SetFileAttributes(newNameFmt.c_str(), oldAttr); //don't handle error + ::SetFileAttributes(pathTargetFmt.c_str(), oldAttr); //don't handle error return; } else { lastError = ::GetLastError(); //use error code from second call to ::MoveFileEx() - //cleanup: (try to) restore file attributes: assume oldName is still existing - ::SetFileAttributes(oldNameFmt.c_str(), oldAttr); + //cleanup: (try to) restore file attributes: assume pathSource is still existing + ::SetFileAttributes(pathSourceFmt.c_str(), oldAttr); } } } } //begin of "regular" error reporting - const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtFileName(oldName)), L"%y", L"\n" + fmtFileName(newName)); + const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtFileName(pathSource)), L"%y", L"\n" + fmtFileName(pathTarget)); std::wstring errorDescr = formatSystemError(L"MoveFileEx", lastError); - //try to enhance error message: +#ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message if (lastError == ERROR_SHARING_VIOLATION || lastError == ERROR_LOCK_VIOLATION) { - const Zstring procList = getLockingProcessNames(oldName); //throw() + const std::wstring procList = vista::getLockingProcesses(pathSource); //noexcept if (!procList.empty()) errorDescr = _("The file is locked by another process:") + L"\n" + procList; } +#endif if (lastError == ERROR_NOT_SAME_DEVICE) throw ErrorDifferentVolume(errorMsg, errorDescr); @@ -375,10 +357,10 @@ void renameFile_sub(const Zstring& oldName, const Zstring& newName) //throw File } #elif defined ZEN_LINUX || defined ZEN_MAC - if (::rename(oldName.c_str(), newName.c_str()) != 0) + if (::rename(pathSource.c_str(), pathTarget.c_str()) != 0) { const int lastError = errno; //copy before directly or indirectly making other system calls! - const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtFileName(oldName)), L"%y", L"\n" + fmtFileName(newName)); + const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot move file %x to %y."), L"%x", L"\n" + fmtFileName(pathSource)), L"%y", L"\n" + fmtFileName(pathTarget)); const std::wstring errorDescr = formatSystemError(L"rename", lastError); if (lastError == EXDEV) @@ -498,21 +480,21 @@ private: //rename file: no copying!!! -void zen::renameFile(const Zstring& itemPathOld, const Zstring& itemPathNew) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting +void zen::renameFile(const Zstring& pathSource, const Zstring& pathTarget) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting { try { - renameFile_sub(itemPathOld, itemPathNew); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting + renameFile_sub(pathSource, pathTarget); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting } catch (const ErrorTargetExisting&) { #ifdef ZEN_WIN //try to handle issues with already existing short 8.3 file names on Windows - if (have8dot3NameClash(itemPathNew)) + if (have8dot3NameClash(pathTarget)) { - Fix8Dot3NameClash dummy(itemPathNew); //throw FileError; move clashing filepath to the side + Fix8Dot3NameClash dummy(pathTarget); //throw FileError; move clashing filepath to the side //now try again... - renameFile_sub(itemPathOld, itemPathNew); //throw FileError + renameFile_sub(pathSource, pathTarget); //throw FileError return; } #endif @@ -840,6 +822,53 @@ void setFileTimeRaw(const Zstring& filepath, #endif } + +#elif defined ZEN_LINUX +DEFINE_NEW_FILE_ERROR(ErrorLinuxFallbackToUtimes); + +void setFileTimeRaw(const Zstring& filePath, const struct ::timespec& modTime, ProcSymlink procSl) //throw FileError, ErrorLinuxFallbackToUtimes +{ + /* + [2013-05-01] sigh, we can't use utimensat() on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit??? + => fallback to "retarded-idiot version"! -- DarkByte + + [2015-03-09] + - cannot reproduce issues with NTFS and utimensat() on Ubuntu + - utimensat() is supposed to obsolete utime/utimes and is also used by "cp" and "touch" + - solves utimes() EINVAL bug for certain CIFS/NTFS drives: https://sourceforge.net/p/freefilesync/discussion/help/thread/1ace042d/ + => don't use utimensat() directly, but open file descriptor manually, else EINVAL, again! + + => let's give utimensat another chance: + */ + struct ::timespec newTimes[2] = {}; + newTimes[0].tv_sec = ::time(nullptr); //access time; using UTIME_OMIT for tv_nsec would trigger even more bugs!! + //https://sourceforge.net/p/freefilesync/discussion/open-discussion/thread/218564cf/ + newTimes[1] = modTime; //modification time + + //=> using open()/futimens() for regular files and utimensat(AT_SYMLINK_NOFOLLOW) for symlinks is consistent with "cp" and "touch"! + if (procSl == ProcSymlink::FOLLOW) + { + const int fdFile = ::open(filePath.c_str(), O_WRONLY, 0); //"if O_CREAT is not specified, then mode is ignored" + if (fdFile == -1) + { + if (errno == EACCES) //bullshit, access denied even with 0777 permissions! => utimes should work! + throw ErrorLinuxFallbackToUtimes(L""); + + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filePath)), L"open", getLastError()); + } + ZEN_ON_SCOPE_EXIT(::close(fdFile)); + + if (::futimens(fdFile, newTimes) != 0) + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filePath)), L"futimens", getLastError()); + } + else + { + if (::utimensat(AT_FDCWD, filePath.c_str(), newTimes, AT_SYMLINK_NOFOLLOW) != 0) + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filePath)), L"utimensat", getLastError()); + } +} + + #elif defined ZEN_MAC struct AttrBufFileTimes { @@ -922,63 +951,40 @@ void zen::removeDirectory(const Zstring& directory, //throw FileError } -void zen::setFileTime(const Zstring& filepath, std::int64_t modTime, ProcSymlink procSl) //throw FileError +void zen::setFileTime(const Zstring& filePath, std::int64_t modTime, ProcSymlink procSl) //throw FileError { #ifdef ZEN_WIN - setFileTimeRaw(filepath, nullptr, timetToFileTime(modTime), procSl); //throw FileError + setFileTimeRaw(filePath, nullptr, timetToFileTime(modTime), procSl); //throw FileError #elif defined ZEN_LINUX - //[2013-05-01] sigh, we can't use utimensat() on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit??? - //=> fallback to "retarded-idiot version"! -- DarkByte - // - //[2015-03-09] - // - cannot reproduce issues with NTFS and utimensat() on Ubuntu - // - utimensat() is supposed to obsolete utime/utimes and is also used by "touch" - //=> let's give utimensat another chance: - struct ::timespec newTimes[2] = {}; - newTimes[0].tv_sec = modTime; //access time: using UTIME_OMIT for tv_nsec would trigger even more bugs!! https://sourceforge.net/p/freefilesync/discussion/open-discussion/thread/218564cf/ - newTimes[1].tv_sec = modTime; //modification time (seconds) - - if (procSl == ProcSymlink::FOLLOW) + try { - //don't use utimensat() directly, but open file descriptor manually: - //=> solves EINVAL bug for certain CIFS/NTFS drives: https://sourceforge.net/p/freefilesync/discussion/help/thread/1ace042d/ - //=> using utimensat(AT_SYMLINK_NOFOLLOW) for symlinks and open()/futimens() for regular files is consistent with "cp" and "touch"! - const int fdFile = ::open(filepath.c_str(), O_WRONLY, 0); //"if O_CREAT is not specified, then mode is ignored" - if (fdFile == -1) - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"open", getLastError()); - ZEN_ON_SCOPE_EXIT(::close(fdFile)); - - if (::futimens(fdFile, newTimes) != 0) - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"futimens", getLastError()); + struct ::timespec writeTime = {}; + writeTime.tv_sec = modTime; + setFileTimeRaw(filePath, writeTime, procSl); //throw FileError, ErrorLinuxFallbackToUtimes } - else + catch (ErrorLinuxFallbackToUtimes&) { - if (::utimensat(AT_FDCWD, filepath.c_str(), newTimes, AT_SYMLINK_NOFOLLOW) != 0) - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"utimensat", getLastError()); - } + struct ::timeval writeTime[2] = {}; + writeTime[0].tv_sec = ::time(nullptr); //access time (seconds) + writeTime[1].tv_sec = modTime; //modification time (seconds) - /* - struct ::timeval newTimes[2] = {}; - newTimes[0].tv_sec = ::time(nullptr); //access time (seconds) - newTimes[1].tv_sec = modTime; //modification time (seconds) - - if (procSl == ProcSymlink::FOLLOW) - { - if (::utimes(filepath.c_str(), newTimes) != 0) - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"utimes", getLastError()); - } - else - { - if (::lutimes(filepath.c_str(), newTimes) != 0) - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"lutimes", getLastError()); - } - */ + if (procSl == ProcSymlink::FOLLOW) + { + if (::utimes(filePath.c_str(), writeTime) != 0) + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filePath)), L"utimes", getLastError()); + } + else + { + if (::lutimes(filePath.c_str(), writeTime) != 0) + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filePath)), L"lutimes", getLastError()); + } + } #elif defined ZEN_MAC struct ::timespec writeTime = {}; writeTime.tv_sec = modTime; - setFileTimeRaw(filepath, nullptr, writeTime, procSl); //throw FileError + setFileTimeRaw(filePath, nullptr, writeTime, procSl); //throw FileError #endif } @@ -1083,11 +1089,11 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P //Note: trying to copy SACL (SACL_SECURITY_INFORMATION) may return ERROR_PRIVILEGE_NOT_HELD (1314) on Samba shares. This is not due to missing privileges! //However, this is okay, since copying NTFS permissions doesn't make sense in this case anyway - //enable privilege: required to copy owner information - activatePrivilege(SE_RESTORE_NAME); //throw FileError - //the following privilege may be required according to http://msdn.microsoft.com/en-us/library/aa364399(VS.85).aspx (although not needed nor active in my tests) activatePrivilege(SE_BACKUP_NAME); //throw FileError + + //enable privilege: required to copy owner information + activatePrivilege(SE_RESTORE_NAME); //throw FileError } catch (const FileError& e)//add some more context description (e.g. user is not an admin) { @@ -1100,8 +1106,8 @@ void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, P { DWORD bytesNeeded = 0; if (::GetFileSecurity(applyLongPathPrefix(sourceResolved).c_str(), //__in LPCTSTR lpFileName, -> long path prefix IS needed, although it is NOT mentioned on MSDN!!! - OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | - DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, //__in SECURITY_INFORMATION RequestedInformation, + DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | //__in SECURITY_INFORMATION RequestedInformation, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION, reinterpret_cast<PSECURITY_DESCRIPTOR>(&buffer[0]), //__out_opt PSECURITY_DESCRIPTOR pSecurityDescriptor, static_cast<DWORD>(buffer.size()), //__in DWORD nLength, &bytesNeeded)) //__out LPDWORD lpnLengthNeeded @@ -1297,7 +1303,7 @@ void makeDirectoryRecursively(const Zstring& directory) //FileError, ErrorTarget { makeDirectoryRecursively(dirParent); //throw FileError, (ErrorTargetExisting) } - catch (const ErrorTargetExisting&) {} //parent directory created externally in the meantime? => NOT AN ERROR; not a directory? fail in next step! + catch (const ErrorTargetExisting&) {} //parent directory created externally in the meantime? => NOT AN ERROR; not a directory? fail in next step! //now try again... copyNewDirectory(Zstring(), directory, false /*copyFilePermissions*/); //throw FileError, (ErrorTargetExisting), (ErrorTargetPathMissing) @@ -1322,14 +1328,14 @@ void zen::makeNewDirectory(const Zstring& directory) //throw FileError, ErrorTar } catch (const ErrorTargetExisting&) //*something* existing: folder or FILE! { - //avoid any file system race-condition by *not* checking existence again here!!! - throw; + //avoid any file system race-condition by *not* checking existence again here!!! + throw; } } void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, //throw FileError, ErrorTargetExisting, ErrorTargetPathMissing - bool copyFilePermissions) + bool copyFilePermissions) { #ifdef ZEN_WIN //special handling for volume root: trying to create existing root directory results in ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS! @@ -1337,7 +1343,7 @@ void zen::copyNewDirectory(const Zstring& sourcePath, const Zstring& targetPath, beforeLast(targetPath, FILE_NAME_SEPARATOR) : targetPath); if (dirTmp.size() == 2 && - std::iswalpha(dirTmp[0]) && dirTmp[1] == L':') + isAlpha(dirTmp[0]) && dirTmp[1] == L':') { dirTmp += FILE_NAME_SEPARATOR; //we do not support "C:" to represent a relative path! @@ -1582,7 +1588,7 @@ ADS YES YES NO Encrypted YES NO(silent fail!) NO Compressed NO NO NO Sparse NO YES NO -Nonstandard FS YES UNKNOWN -> issues writing ADS to Samba, issues reading from NAS, error copying files having "blocked" state... ect. +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() @@ -1593,11 +1599,11 @@ 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 copyFileWindowsSparse + --S | --S -CS E-S copyFileWindowsBackupStream -C- | -C- -C- E-- copyFileWindowsDefault - -CS | -CS -CS E-S copyFileWindowsSparse + -CS | -CS -CS E-S copyFileWindowsBackupStream E-- | E-- E-- E-- copyFileWindowsDefault - E-S | E-- (NOK) E-- (NOK) E-- (NOK) copyFileWindowsDefault -> may fail with ERROR_DISK_FULL!! + E-S | E-- (NOK) E-- (NOK) E-- (NOK) copyFileWindowsDefault -> may fail with ERROR_DISK_FULL for large sparse files!! tf := target folder E := encrypted @@ -1611,7 +1617,8 @@ Note: - if target parent folder is compressed or encrypted, both attributes are //due to issues on non-NTFS volumes, we should use the copy-as-sparse routine only if required and supported! -bool canCopyAsSparse(DWORD fileAttrSource, const Zstring& targetFile) //throw () +template <class Function> +bool canCopyAsSparse(DWORD fileAttrSource, Function getTargetFsFlags) //throw () { const bool sourceIsEncrypted = (fileAttrSource & FILE_ATTRIBUTE_ENCRYPTED) != 0; const bool sourceIsSparse = (fileAttrSource & FILE_ATTRIBUTE_SPARSE_FILE) != 0; @@ -1619,33 +1626,57 @@ bool canCopyAsSparse(DWORD fileAttrSource, const Zstring& targetFile) //throw () if (sourceIsEncrypted || !sourceIsSparse) //BackupRead() silently fails reading encrypted files! return false; //small perf optimization: don't check "targetFile" if not needed - //------------------------------------------------------------------------------------ - const DWORD bufferSize = MAX_PATH + 1; - std::vector<wchar_t> buffer(bufferSize); - - //full pathName need not yet exist! - if (!::GetVolumePathName(targetFile.c_str(), //__in LPCTSTR lpszFileName, - &buffer[0], //__out LPTSTR lpszVolumePathName, - bufferSize)) //__in DWORD cchBufferLength + DWORD targetFsFlags = 0; + if (!getTargetFsFlags(targetFsFlags)) return false; + assert(targetFsFlags != 0); - const Zstring volumePath = appendSeparator(&buffer[0]); + return (targetFsFlags & FILE_SUPPORTS_SPARSE_FILES) != 0; +} - DWORD fsFlagsTarget = 0; - if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName - nullptr, //__out_opt LPTSTR lpVolumeNameBuffer, - 0, //__in DWORD nVolumeNameSize, - nullptr, //__out_opt LPDWORD lpVolumeSerialNumber, - nullptr, //__out_opt LPDWORD lpMaximumComponentLength, - &fsFlagsTarget, //__out_opt LPDWORD lpFileSystemFlags, - nullptr, //__out LPTSTR lpFileSystemNameBuffer, - 0)) //__in DWORD nFileSystemNameSize - return false; - const bool targetSupportSparse = (fsFlagsTarget & FILE_SUPPORTS_SPARSE_FILES) != 0; +#ifdef ZEN_WIN_VISTA_AND_LATER +bool canCopyAsSparse(DWORD fileAttrSource, HANDLE hTargetFile) //throw () +{ + return canCopyAsSparse(fileAttrSource, [&](DWORD& targetFsFlags) -> bool + { + return ::GetVolumeInformationByHandleW(hTargetFile, //_In_ HANDLE hFile, + nullptr, //_Out_writes_opt_(nVolumeNameSize) LPWSTR lpVolumeNameBuffer, + 0, //_In_ DWORD nVolumeNameSize, + nullptr, //_Out_opt_ LPDWORD lpVolumeSerialNumber, + nullptr, //_Out_opt_ LPDWORD lpMaximumComponentLength, + &targetFsFlags, //_Out_opt_ LPDWORD lpFileSystemFlags, + nullptr, //_Out_writes_opt_(nFileSystemNameSize) LPWSTR lpFileSystemNameBuffer, + 0) != 0; //_In_ DWORD nFileSystemNameSize + }); +} +#endif + + +bool canCopyAsSparse(DWORD fileAttrSource, const Zstring& targetFile) //throw () +{ + return canCopyAsSparse(fileAttrSource, [&targetFile](DWORD& targetFsFlags) -> bool + { + const DWORD bufferSize = MAX_PATH + 1; + std::vector<wchar_t> buffer(bufferSize); + + //full pathName need not yet exist! + if (!::GetVolumePathName(targetFile.c_str(), //__in LPCTSTR lpszFileName, + &buffer[0], //__out LPTSTR lpszVolumePathName, + bufferSize)) //__in DWORD cchBufferLength + return false; - return targetSupportSparse; - //both source and target must not be FAT since copyFileWindowsSparse() does no DST hack! implicitly guaranteed at this point! + const Zstring volumePath = appendSeparator(&buffer[0]); + + return ::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName + nullptr, //__out_opt LPTSTR lpVolumeNameBuffer, + 0, //__in DWORD nVolumeNameSize, + nullptr, //__out_opt LPDWORD lpVolumeSerialNumber, + nullptr, //__out_opt LPDWORD lpMaximumComponentLength, + &targetFsFlags, //__out_opt LPDWORD lpFileSystemFlags, + nullptr, //__out LPTSTR lpFileSystemNameBuffer, + 0) != 0; //__in DWORD nFileSystemNameSize + }); } @@ -1672,15 +1703,14 @@ bool canCopyAsSparse(const Zstring& sourceFile, const Zstring& targetFile) //thr return canCopyAsSparse(fileInfoSource.dwFileAttributes, targetFile); //throw () } +//============================================================================================= -//precondition: canCopyAsSparse() must return "true"! -InSyncAttributes copyFileWindowsSparse(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked - const Zstring& targetFile, - const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus) +InSyncAttributes copyFileWindowsBackupStream(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked + const Zstring& targetFile, + const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus) { - assert(canCopyAsSparse(sourceFile, targetFile)); - - //try to get backup read and write privileges: who knows, maybe this helps solve some obscure "access denied" errors + //try to get backup read and write privileges: help solve most "access denied" errors with FILE_FLAG_BACKUP_SEMANTICS: + //https://sourceforge.net/p/freefilesync/discussion/open-discussion/thread/1998ebf2/ try { activatePrivilege(SE_BACKUP_NAME); } catch (const FileError&) {} try { activatePrivilege(SE_RESTORE_NAME); } @@ -1708,9 +1738,11 @@ InSyncAttributes copyFileWindowsSparse(const Zstring& sourceFile, //throw FileEr if (lastError == ERROR_SHARING_VIOLATION || lastError == ERROR_LOCK_VIOLATION) { - const Zstring procList = getLockingProcessNames(sourceFile); //throw() +#ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message + const std::wstring procList = vista::getLockingProcesses(sourceFile); //noexcept if (!procList.empty()) errorDescr = _("The file is locked by another process:") + L"\n" + procList; +#endif throw ErrorFileLocked(errorMsg, errorDescr); } @@ -1723,7 +1755,12 @@ InSyncAttributes copyFileWindowsSparse(const Zstring& sourceFile, //throw FileEr if (!::GetFileInformationByHandle(hFileSource, &fileInfoSource)) throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)), L"GetFileInformationByHandle", getLastError()); + //encrypted files cannot be read with BackupRead which would fail silently! + const bool sourceIsEncrypted = (fileInfoSource.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; + if (sourceIsEncrypted) + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), L"BackupRead: Source file is encrypted."); //---------------------------------------------------------------------- + const DWORD validAttribs = FILE_ATTRIBUTE_NORMAL | //"This attribute is valid only if used alone." FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | @@ -1801,7 +1838,11 @@ InSyncAttributes copyFileWindowsSparse(const Zstring& sourceFile, //throw FileEr //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 (sourceIsSparse && targetSupportsSparse) -> no need to check, this is our precondition! +#ifdef ZEN_WIN_VISTA_AND_LATER + if (canCopyAsSparse(fileInfoSource.dwFileAttributes, hFileTarget)) //throw () +#else + if (canCopyAsSparse(fileInfoSource.dwFileAttributes, targetFile)) //throw () +#endif { DWORD bytesReturned = 0; if (!::DeviceIoControl(hFileTarget, //_In_ HANDLE hDevice, @@ -1828,7 +1869,7 @@ InSyncAttributes copyFileWindowsSparse(const Zstring& sourceFile, //throw FileEr //stream-copy sourceFile to targetFile bool eof = false; - bool someBytesWritten = false; //try to detect failure reading encrypted files + bool someBytesRead = false; //try to detect failure reading encrypted files do { DWORD bytesRead = 0; @@ -1861,18 +1902,15 @@ InSyncAttributes copyFileWindowsSparse(const Zstring& sourceFile, //throw FileEr throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(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)! - - //invoke callback method to update progress indicators - if (onUpdateCopyStatus) - onUpdateCopyStatus(bytesRead); //throw X! + if (onUpdateCopyStatus) onUpdateCopyStatus(bytesRead); //throw X! if (bytesRead > 0) - someBytesWritten = true; + someBytesRead = true; } while (!eof); //::BackupRead() silently fails reading encrypted files -> double check! - if (!someBytesWritten && get64BitUInt(fileInfoSource.nFileSizeLow, fileInfoSource.nFileSizeHigh) != 0U) + 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", fmtFileName(sourceFile)), L"BackupRead: unknown error"); //user should never see this -> this method is called only if "canCopyAsSparse()" @@ -1888,7 +1926,7 @@ InSyncAttributes copyFileWindowsSparse(const Zstring& sourceFile, //throw FileEr } -DEFINE_NEW_FILE_ERROR(ErrorShouldCopyAsSparse); +DEFINE_NEW_FILE_ERROR(ErrorFallbackToCopyAsBackupStream); struct CallbackData @@ -1960,8 +1998,12 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(cbd.targetFile_)), L"GetFileInformationByHandle", ::GetLastError()); //#################### switch to sparse file copy if req. ####################### +#ifdef ZEN_WIN_VISTA_AND_LATER + if (canCopyAsSparse(cbd.fileInfoSrc.dwFileAttributes, hDestinationFile)) //throw () +#else if (canCopyAsSparse(cbd.fileInfoSrc.dwFileAttributes, cbd.targetFile_)) //throw () - throw ErrorShouldCopyAsSparse(L"sparse dummy value"); //use a different copy routine! +#endif + throw ErrorFallbackToCopyAsBackupStream(L"sparse, callback"); //use a different copy routine! //#################### copy file creation time ################################ ::SetFileTime(hDestinationFile, &cbd.fileInfoSrc.ftCreationTime, nullptr, nullptr); //no error handling! @@ -2009,15 +2051,16 @@ const bool supportNonEncryptedDestination = winXpOrLater(); //encrypted destinat //caveat: function scope static initialization is not thread-safe in VS 2010! -> still not sufficient if multiple threads access during static init!!! -InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse +InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorFallbackToCopyAsBackupStream const Zstring& targetFile, const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus) { - //try to get backup read and write privileges: who knows, maybe this helps solve some obscure "access denied" errors + //try to get backup read and write privileges: may help solve some "access denied" errors + bool backupPrivilegesActive = true; try { activatePrivilege(SE_BACKUP_NAME); } - catch (const FileError&) {} + catch (const FileError&) { backupPrivilegesActive = false; } try { activatePrivilege(SE_RESTORE_NAME); } - catch (const FileError&) {} + catch (const FileError&) { backupPrivilegesActive = false; } zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); //transactional behavior: guard just before starting copy, we don't trust ::CopyFileEx(), do we? ;) @@ -2053,7 +2096,17 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE //trying to copy huge sparse files may directly fail with ERROR_DISK_FULL before entering the callback function if (canCopyAsSparse(sourceFile, targetFile)) //noexcept - throw ErrorShouldCopyAsSparse(L"sparse dummy value2"); + throw ErrorFallbackToCopyAsBackupStream(L"sparse, copy failure"); + + if (lastError == ERROR_ACCESS_DENIED && backupPrivilegesActive) + //chances are good this will work with copyFileWindowsBackupStream: https://sourceforge.net/p/freefilesync/discussion/open-discussion/thread/1998ebf2/ + throw ErrorFallbackToCopyAsBackupStream(L"access denied"); + + //copying ADS may incorrectly fail with ERROR_FILE_NOT_FOUND: https://sourceforge.net/p/freefilesync/discussion/help/thread/a18a2c02/ + if (lastError == ERROR_FILE_NOT_FOUND && + cbd.fileInfoSrc.nNumberOfLinks > 0 && + cbd.fileInfoTrg.nNumberOfLinks > 0) + throw ErrorFallbackToCopyAsBackupStream(L"bogus file not found"); //assemble error message... const std::wstring errorMsg = replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)); @@ -2063,9 +2116,11 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE if (lastError == ERROR_SHARING_VIOLATION || lastError == ERROR_LOCK_VIOLATION) { - const Zstring procList = getLockingProcessNames(sourceFile); //throw() -> enhance error message! +#ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message + const std::wstring procList = vista::getLockingProcesses(sourceFile); //noexcept if (!procList.empty()) errorDescr = _("The file is locked by another process:") + L"\n" + procList; +#endif throw ErrorFileLocked(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(sourceFile)), errorDescr); } @@ -2110,17 +2165,17 @@ InSyncAttributes copyFileWindowsDefault(const Zstring& sourceFile, //throw FileE } -//another layer to support copying sparse files +//another layer to support copying sparse files and handle some access denied errors inline InSyncAttributes copyFileWindowsSelectRoutine(const Zstring& sourceFile, const Zstring& targetFile, const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus) { try { - return copyFileWindowsDefault(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorShouldCopyAsSparse + return copyFileWindowsDefault(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked, ErrorFallbackToCopyAsBackupStream } - catch (ErrorShouldCopyAsSparse&) //we quickly check for this condition within callback of ::CopyFileEx()! + catch (ErrorFallbackToCopyAsBackupStream&) { - return copyFileWindowsSparse(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked + return copyFileWindowsBackupStream(sourceFile, targetFile, onUpdateCopyStatus); //throw FileError, ErrorTargetExisting, ErrorFileLocked } } @@ -2209,7 +2264,7 @@ InSyncAttributes copyFileOsSpecific(const Zstring& sourceFile, //throw FileError throwFileError(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)), L"copyfile", getLastError()); #endif -fileOut.close(); //throw FileError -> optional, but good place to catch errors when closing stream! + fileOut.close(); //throw FileError -> optional, but good place to catch errors when closing stream! } //close output file handle before setting file time //we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation: @@ -2240,7 +2295,7 @@ fileOut.close(); //throw FileError -> optional, but good place to catch errors w | copyFileWindowsSelectRoutine / \ -copyFileWindowsDefault(::CopyFileEx) copyFileWindowsSparse(::BackupRead/::BackupWrite) +copyFileWindowsDefault(::CopyFileEx) copyFileWindowsBackupStream(::BackupRead/::BackupWrite) */ } diff --git a/zen/file_id_def.h b/zen/file_id_def.h index 7a2059a6..c33edf81 100644 --- a/zen/file_id_def.h +++ b/zen/file_id_def.h @@ -23,7 +23,7 @@ namespace zen typedef DWORD DeviceId; typedef ULONGLONG FileIndex; -typedef std::pair<DeviceId, FileIndex> FileId; //optional! (however, always set on Linux, and *generally* available on Windows) +typedef std::pair<DeviceId, FileIndex> FileId; //optional! (however, always set on Linux, and *generally* available on Windows) inline diff --git a/zen/file_io.cpp b/zen/file_io.cpp index a5412f19..5d7fa8c5 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -9,9 +9,10 @@ #ifdef ZEN_WIN #include "long_path_prefix.h" - #include "IFileOperation/file_op.h" - #include "win_ver.h" - #include "dll.h" +#include "privilege.h" + #ifdef ZEN_WIN_VISTA_AND_LATER + #include "vista_file_op.h" + #endif #elif defined ZEN_LINUX || defined ZEN_MAC #include <sys/stat.h> @@ -24,28 +25,7 @@ using namespace zen; namespace { -#ifdef ZEN_WIN -//(try to) enhance error messages by showing which processes lock the file -Zstring getLockingProcessNames(const Zstring& filepath) //throw(), empty string if none found or error occurred -{ - if (vistaOrLater()) - { - using namespace fileop; - const DllFun<FunType_getLockingProcesses> getLockingProcesses(getDllName(), funName_getLockingProcesses); - const DllFun<FunType_freeString> freeString (getDllName(), funName_freeString); - - const wchar_t* processList = nullptr; - if (getLockingProcesses && freeString) - if (getLockingProcesses(filepath.c_str(), processList)) - { - ZEN_ON_SCOPE_EXIT(freeString(processList)); - return processList; - } - } - return Zstring(); -} - -#elif defined ZEN_LINUX || defined ZEN_MAC +#if defined ZEN_LINUX || defined ZEN_MAC //- "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" @@ -82,6 +62,9 @@ FileInput::FileInput(FileHandle handle, const Zstring& filepath) : FileBase(file FileInput::FileInput(const Zstring& filepath) : FileBase(filepath) //throw FileError, ErrorFileLocked { #ifdef ZEN_WIN + try { activatePrivilege(SE_BACKUP_NAME); } + catch (const FileError&) {} + auto createHandle = [&](DWORD dwShareMode) { return ::CreateFile(applyLongPathPrefix(filepath).c_str(), //_In_ LPCTSTR lpFileName, @@ -89,7 +72,7 @@ FileInput::FileInput(const Zstring& filepath) : FileBase(filepath) //throw FileE dwShareMode, //_In_ DWORD dwShareMode, nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - FILE_FLAG_SEQUENTIAL_SCAN, //_In_ DWORD dwFlagsAndAttributes, + FILE_FLAG_SEQUENTIAL_SCAN //_In_ DWORD dwFlagsAndAttributes, /* possible values: (Reference http://msdn.microsoft.com/en-us/library/aa363858(VS.85).aspx#caching_behavior) FILE_FLAG_NO_BUFFERING FILE_FLAG_RANDOM_ACCESS @@ -114,6 +97,7 @@ FileInput::FileInput(const Zstring& filepath) : FileBase(filepath) //throw FileE for FFS most comparisons are probably between different disks => let's use FILE_FLAG_SEQUENTIAL_SCAN */ + | FILE_FLAG_BACKUP_SEMANTICS, nullptr); //_In_opt_ HANDLE hTemplateFile }; fileHandle = createHandle(FILE_SHARE_READ | FILE_SHARE_DELETE); @@ -133,9 +117,11 @@ FileInput::FileInput(const Zstring& filepath) : FileBase(filepath) //throw FileE if (ec == ERROR_SHARING_VIOLATION || //-> enhance error message! ec == ERROR_LOCK_VIOLATION) { - const Zstring procList = getLockingProcessNames(filepath); //throw() +#ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message + const std::wstring procList = vista::getLockingProcesses(filepath); //noexcept if (!procList.empty()) errorDescr = _("The file is locked by another process:") + L"\n" + procList; +#endif throw ErrorFileLocked(errorMsg, errorDescr); } throw FileError(errorMsg, errorDescr); @@ -150,10 +136,14 @@ FileInput::FileInput(const Zstring& filepath) : FileBase(filepath) //throw FileE if (fileHandle == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle throwFileError(replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filepath)), L"open", getLastError()); -#ifndef ZEN_MAC //posix_fadvise not supported on OS X (and "dtruss" doesn't show alternative use of "fcntl() F_RDAHEAD/F_RDADVISE" for "cp") + +#ifdef ZEN_LINUX //optimize read-ahead on input file: if (::posix_fadvise(fileHandle, 0, 0, POSIX_FADV_SEQUENTIAL) != 0) throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filepath)), L"posix_fadvise", getLastError()); + +#elif defined ZEN_MAC + //"dtruss" doesn't show use of "fcntl() F_RDAHEAD/F_RDADVISE" for "cp") #endif #endif } @@ -217,6 +207,11 @@ FileOutput::FileOutput(FileHandle handle, const Zstring& filepath) : FileBase(fi FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : FileBase(filepath) //throw FileError, ErrorTargetExisting { #ifdef ZEN_WIN + try { activatePrivilege(SE_BACKUP_NAME); } + catch (const FileError&) {} + try { activatePrivilege(SE_RESTORE_NAME); } + catch (const FileError&) {} + const DWORD dwCreationDisposition = access == FileOutput::ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW; auto createHandle = [&](DWORD dwFlagsAndAttributes) @@ -233,7 +228,8 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : FileBase(fi nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, dwCreationDisposition, //_In_ DWORD dwCreationDisposition, dwFlagsAndAttributes | - FILE_FLAG_SEQUENTIAL_SCAN, //_In_ DWORD dwFlagsAndAttributes, + FILE_FLAG_SEQUENTIAL_SCAN //_In_ DWORD dwFlagsAndAttributes, + | FILE_FLAG_BACKUP_SEMANTICS, nullptr); //_In_opt_ HANDLE hTemplateFile }; @@ -259,14 +255,15 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : FileBase(fi const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(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 Zstring procList = getLockingProcessNames(filepath); //throw() + const std::wstring procList = vista::getLockingProcesses(filepath); //noexcept if (!procList.empty()) errorDescr = _("The file is locked by another process:") + L"\n" + procList; } - +#endif if (ec == ERROR_FILE_EXISTS || //confirmed to be used ec == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6 throw ErrorTargetExisting(errorMsg, errorDescr); @@ -320,15 +317,15 @@ FileOutput::~FileOutput() } catch (FileError&) { assert(false); } } - - + + void FileOutput::close() //throw FileError { if (fileHandle == getInvalidHandle()) throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilePath())), L"Contract error: close() called more than once."); ZEN_ON_SCOPE_EXIT(fileHandle = getInvalidHandle()); - //no need to clean-up on failure here (just like there is no clean on FileOutput::write failure!) => FileOutput is not transactional! + //no need to clean-up on failure here (just like there is no clean on FileOutput::write failure!) => FileOutput is not transactional! #ifdef ZEN_WIN if (!::CloseHandle(fileHandle)) @@ -41,7 +41,7 @@ public: throw TimerError(); } - ~PerfTimer() { if (!resultShown) try { showResult(); } catch (TimerError&){} } + ~PerfTimer() { if (!resultShown) try { showResult(); } catch (TimerError&) {} } void pause() { @@ -67,7 +67,7 @@ public: paused = false; elapsedUntilPause = 0; } - + int64_t timeMs() const { int64_t ticksTotal = elapsedUntilPause; @@ -78,8 +78,8 @@ public: void showResult() { - const bool wasRunning = !paused; - if (wasRunning) pause(); //don't include call to MessageBox()! + const bool wasRunning = !paused; + if (wasRunning) pause(); //don't include call to MessageBox()! ZEN_ON_SCOPE_EXIT(if (wasRunning) resume()); #ifdef ZEN_WIN diff --git a/zen/recycler.cpp b/zen/recycler.cpp index ed6669ef..a4f6c128 100644 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -9,10 +9,10 @@ #ifdef ZEN_WIN #include "thread.h" - #include "dll.h" - #include "win_ver.h" - #include "long_path_prefix.h" - #include "IFileOperation/file_op.h" + + #ifdef ZEN_WIN_VISTA_AND_LATER + #include "vista_file_op.h" + #endif #elif defined ZEN_LINUX #include <sys/stat.h> @@ -27,122 +27,55 @@ using namespace zen; #ifdef ZEN_WIN -namespace -{ -/* -Performance test: delete 1000 files ------------------------------------- -SHFileOperation - single file 33s -SHFileOperation - multiple files 2,1s -IFileOperation - single file 33s -IFileOperation - multiple files 2,1s - -=> SHFileOperation and IFileOperation have nearly IDENTICAL performance characteristics! - -Nevertheless, let's use IFileOperation for better error reporting (including details on locked files)! -*/ - -struct CallbackData +void zen::recycleOrDelete(const std::vector<Zstring>& itempaths, const std::function<void (const Zstring& currentItem)>& onRecycleItem) { - CallbackData(const std::function<void (const Zstring& currentItem)>& notifyDeletionStatus) : - notifyDeletionStatus_(notifyDeletionStatus) {} - - const std::function<void (const Zstring& currentItem)>& notifyDeletionStatus_; //in, optional - std::exception_ptr exception; //out -}; - - -bool onRecyclerCallback(const wchar_t* itempath, void* sink) -{ - CallbackData& cbd = *static_cast<CallbackData*>(sink); //sink is NOT optional here - - if (cbd.notifyDeletionStatus_) - try - { - cbd.notifyDeletionStatus_(itempath); //throw ? - } - catch (...) - { - cbd.exception = std::current_exception(); - return false; - } - return true; -} -} - - -void zen::recycleOrDelete(const std::vector<Zstring>& itempaths, const std::function<void (const Zstring& currentItem)>& notifyDeletionStatus) -{ - if (itempaths.empty()) - return; + if (itempaths.empty()) return; //warning: moving long file paths to recycler does not work! //both ::SHFileOperation() and ::IFileOperation cannot delete a folder named "System Volume Information" with normal attributes but shamelessly report success //both ::SHFileOperation() and ::IFileOperation can't handle \\?\-prefix! - if (vistaOrLater()) //new recycle bin usage: available since Vista - { -#define DEF_DLL_FUN(name) const DllFun<fileop::FunType_##name> name(fileop::getDllName(), fileop::funName_##name); - DEF_DLL_FUN(moveToRecycleBin); - DEF_DLL_FUN(getLastErrorMessage); -#undef DEF_DLL_FUN - - if (!moveToRecycleBin || !getLastErrorMessage) - throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtFileName(itempaths[0])), - replaceCpy(_("Cannot load file %x."), L"%x", fmtFileName(fileop::getDllName()))); - - std::vector<const wchar_t*> cNames; - for (auto it = itempaths.begin(); it != itempaths.end(); ++it) //CAUTION: do not create temporary strings here!! - cNames.push_back(it->c_str()); - - - - CallbackData cbd(notifyDeletionStatus); - if (!moveToRecycleBin(&cNames[0], cNames.size(), onRecyclerCallback, &cbd)) - { - if (cbd.exception) - std::rethrow_exception(cbd.exception); + /* + Performance test: delete 1000 files + ------------------------------------ + SHFileOperation - single file 33s + SHFileOperation - multiple files 2,1s + IFileOperation - single file 33s + IFileOperation - multiple files 2,1s - if (cNames.size() == 1) - throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtFileName(itempaths[0])), getLastErrorMessage()); + => SHFileOperation and IFileOperation have nearly IDENTICAL performance characteristics! - //batch recycling failed: retry one-by-one to get a better error message; see FileOperation.dll - for (size_t i = 0; i < cNames.size(); ++i) - { - if (notifyDeletionStatus) notifyDeletionStatus(itempaths[i]); + Nevertheless, let's use IFileOperation for better error reporting (including details on locked files)! + */ +#ifdef ZEN_WIN_VISTA_AND_LATER + vista::moveToRecycleBin(itempaths, onRecycleItem); //throw FileError - if (!moveToRecycleBin(&cNames[i], 1, nullptr, nullptr)) - throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", fmtFileName(itempaths[i])), getLastErrorMessage()); //already includes details about locking errors! - } - } - } - else //regular recycle bin usage: available since XP: 1. bad error reporting 2. early failure +#else //regular recycle bin usage: available since XP: 1. bad error reporting 2. early failure + Zstring itempathsDoubleNull; + for (const Zstring& itempath : itempaths) { - Zstring itempathsDoubleNull; - for (const Zstring& itempath : itempaths) - { - itempathsDoubleNull += itempath; - itempathsDoubleNull += L'\0'; - } + itempathsDoubleNull += itempath; + itempathsDoubleNull += L'\0'; + } - SHFILEOPSTRUCT fileOp = {}; - fileOp.hwnd = nullptr; - fileOp.wFunc = FO_DELETE; - fileOp.pFrom = itempathsDoubleNull.c_str(); - fileOp.pTo = nullptr; - fileOp.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI; - fileOp.fAnyOperationsAborted = false; - fileOp.hNameMappings = nullptr; - fileOp.lpszProgressTitle = nullptr; - - //"You should use fully-qualified path names with this function. Using it with relative path names is not thread safe." - if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) - { - std::wstring itempathFmt = fmtFileName(itempaths[0]); //probably not the correct file name for file lists larger than 1! - if (itempaths.size() > 1) - itempathFmt += L", ..."; //give at least some hint that there are multiple files, and the error need not be related to the first one - throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", itempathFmt)); - } + SHFILEOPSTRUCT fileOp = {}; + fileOp.hwnd = nullptr; + fileOp.wFunc = FO_DELETE; + fileOp.pFrom = itempathsDoubleNull.c_str(); + fileOp.pTo = nullptr; + fileOp.fFlags = FOF_ALLOWUNDO | FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI; + fileOp.fAnyOperationsAborted = false; + fileOp.hNameMappings = nullptr; + fileOp.lpszProgressTitle = nullptr; + + //"You should use fully-qualified path names with this function. Using it with relative path names is not thread safe." + if (::SHFileOperation(&fileOp) != 0 || fileOp.fAnyOperationsAborted) + { + std::wstring itempathFmt = fmtFileName(itempaths[0]); //probably not the correct file name for file lists larger than 1! + if (itempaths.size() > 1) + itempathFmt += L", ..."; //give at least some hint that there are multiple files, and the error need not be related to the first one + throw FileError(replaceCpy(_("Unable to move %x to the recycle bin."), L"%x", itempathFmt)); } +#endif } #endif @@ -241,39 +174,25 @@ bool zen::recycleOrDelete(const Zstring& itempath) //throw FileError #ifdef ZEN_WIN bool zen::recycleBinExists(const Zstring& dirpath, const std::function<void ()>& onUpdateGui) //throw FileError { - if (vistaOrLater()) - { - using namespace fileop; - const DllFun<FunType_getRecycleBinStatus> getRecycleBinStatus(getDllName(), funName_getRecycleBinStatus); - const DllFun<FunType_getLastErrorMessage> getLastErrorMessage(getDllName(), funName_getLastErrorMessage); +#ifdef ZEN_WIN_VISTA_AND_LATER + return vista::supportsRecycleBin(dirpath); //throw FileError - if (!getRecycleBinStatus || !getLastErrorMessage) - throw FileError(replaceCpy(_("Checking recycle bin failed for folder %x."), L"%x", fmtFileName(dirpath)), - replaceCpy(_("Cannot load file %x."), L"%x", fmtFileName(getDllName()))); - - bool hasRecycler = false; - if (!getRecycleBinStatus(dirpath.c_str(), hasRecycler)) - throw FileError(replaceCpy(_("Checking recycle bin failed for folder %x."), L"%x", fmtFileName(dirpath)), getLastErrorMessage()); - - return hasRecycler; - } - else +#else + //excessive runtime if recycle bin exists, is full and drive is slow: + auto ft = async([dirpath]() { - //excessive runtime if recycle bin exists, is full and drive is slow: - auto ft = async([dirpath]() - { - SHQUERYRBINFO recInfo = {}; - recInfo.cbSize = sizeof(recInfo); - return ::SHQueryRecycleBin(dirpath.c_str(), //__in_opt LPCTSTR pszRootPath, - &recInfo); //__inout LPSHQUERYRBINFO pSHQueryRBInfo - }); + SHQUERYRBINFO recInfo = {}; + recInfo.cbSize = sizeof(recInfo); + return ::SHQueryRecycleBin(dirpath.c_str(), //__in_opt LPCTSTR pszRootPath, + &recInfo); //__inout LPSHQUERYRBINFO pSHQueryRBInfo + }); - while (!ft.timed_wait(boost::posix_time::milliseconds(50))) - if (onUpdateGui) - onUpdateGui(); //may throw! + while (!ft.timed_wait(boost::posix_time::milliseconds(50))) + if (onUpdateGui) + onUpdateGui(); //may throw! - return ft.get() == S_OK; - } + return ft.get() == S_OK; +#endif //1. ::SHQueryRecycleBin() is excessive: traverses whole $Recycle.Bin directory tree each time!!!! But it's safe and correct. diff --git a/zen/recycler.h b/zen/recycler.h index 5112444d..3f48452e 100644 --- a/zen/recycler.h +++ b/zen/recycler.h @@ -39,7 +39,7 @@ bool recycleOrDelete(const Zstring& itempath); //throw FileError, return "true" bool recycleBinExists(const Zstring& dirpath, const std::function<void ()>& onUpdateGui); //throw FileError void recycleOrDelete(const std::vector<Zstring>& filepaths, //throw FileError, return "true" if file/dir was actually deleted - const std::function<void (const Zstring& currentItem)>& notifyDeletionStatus); //optional; currentItem may be empty + const std::function<void (const Zstring& currentItem)>& onRecycleItem); //optional; currentItem may be empty #endif } diff --git a/zen/serialize.h b/zen/serialize.h index 4af12af1..a41745e4 100644 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -91,7 +91,7 @@ struct MemoryStreamIn const size_t bytesRead = std::min(len, buffer.size() - pos); auto itFirst = buffer.begin() + pos; std::copy(itFirst, itFirst + bytesRead, static_cast<char*>(data)); - pos += bytesRead; + pos += bytesRead; return bytesRead; } @@ -144,7 +144,7 @@ template <class BinInputStream, class BinOutputStream> inline void copyStream(BinInputStream& streamIn, BinOutputStream& streamOut, size_t blockSize, const std::function<void(std::int64_t bytesDelta)>& onNotifyCopyStatus) //optional { - assert(blockSize > 0); + assert(blockSize > 0); std::vector<char> buffer(blockSize); for (;;) { @@ -167,7 +167,7 @@ void saveBinStream(const Zstring& filepath, //throw FileError { MemoryStreamIn<BinContainer> streamIn(cont); FileOutput streamOut(filepath, zen::FileOutput::ACC_OVERWRITE); //throw FileError, (ErrorTargetExisting) - if (onUpdateStatus) onUpdateStatus(0); //throw X! + if (onUpdateStatus) onUpdateStatus(0); //throw X! copyStream(streamIn, streamOut, streamOut.optimalBlockSize(), onUpdateStatus); //throw FileError } @@ -177,7 +177,7 @@ BinContainer loadBinStream(const Zstring& filepath, //throw FileError const std::function<void(std::int64_t bytesDelta)>& onUpdateStatus) //optional { FileInput streamIn(filepath); //throw FileError, ErrorFileLocked - if (onUpdateStatus) onUpdateStatus(0); //throw X! + if (onUpdateStatus) onUpdateStatus(0); //throw X! MemoryStreamOut<BinContainer> streamOut; copyStream(streamIn, streamOut, streamIn.optimalBlockSize(), onUpdateStatus); //throw FileError return streamOut.ref(); diff --git a/zen/shell_execute.h b/zen/shell_execute.h index 4eebcca2..628e957a 100644 --- a/zen/shell_execute.h +++ b/zen/shell_execute.h @@ -31,6 +31,47 @@ enum ExecutionType namespace { +#ifdef ZEN_WIN +template <class Function> +bool shellExecuteImpl(Function fillExecInfo, ExecutionType type) +{ + SHELLEXECUTEINFO execInfo = {}; + execInfo.cbSize = sizeof(execInfo); + execInfo.lpVerb = nullptr; + execInfo.nShow = SW_SHOWNORMAL; + execInfo.fMask = type == EXEC_TYPE_SYNC ? (SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC) : 0; + //don't use SEE_MASK_ASYNCOK -> different async mode than the default which returns successful despite errors! + execInfo.fMask |= SEE_MASK_FLAG_NO_UI; //::ShellExecuteEx() shows a non-blocking pop-up dialog on errors -> we want a blocking one + //for the record, SEE_MASK_UNICODE does nothing: http://blogs.msdn.com/b/oldnewthing/archive/2014/02/27/10503519.aspx + + fillExecInfo(execInfo); + + if (!::ShellExecuteEx(&execInfo)) //__inout LPSHELLEXECUTEINFO lpExecInfo + return false; + + if (execInfo.hProcess) + { + ZEN_ON_SCOPE_EXIT(::CloseHandle(execInfo.hProcess)); + + if (type == EXEC_TYPE_SYNC) + ::WaitForSingleObject(execInfo.hProcess, INFINITE); + } + return true; +} + + +void shellExecute(const void* /*PCIDLIST_ABSOLUTE*/ shellItemPidl, const Zstring& displayPath, ExecutionType type) //throw FileError +{ + if (!shellExecuteImpl([&](SHELLEXECUTEINFO& execInfo) +{ + execInfo.fMask |= SEE_MASK_IDLIST; + execInfo.lpIDList = const_cast<void*>(shellItemPidl); //lpIDList is documented as PCIDLIST_ABSOLUTE! + }, type)) //throw FileError + throwFileError(_("Incorrect command line:") + L"\n" + fmtFileName(displayPath), L"ShellExecuteEx", ::GetLastError()); +} +#endif + + void shellExecute(const Zstring& command, ExecutionType type) //throw FileError { #ifdef ZEN_WIN @@ -56,27 +97,12 @@ void shellExecute(const Zstring& command, ExecutionType type) //throw FileError (iter->empty() || std::any_of(iter->begin(), iter->end(), &isWhiteSpace<wchar_t>) ? L"\"" + *iter + L"\"" : *iter); } - SHELLEXECUTEINFO execInfo = {}; - execInfo.cbSize = sizeof(execInfo); - - //SEE_MASK_NOASYNC is equal to SEE_MASK_FLAG_DDEWAIT, but former is defined not before Win SDK 6.0 - execInfo.fMask = type == EXEC_TYPE_SYNC ? (SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_DDEWAIT) : 0; //don't use SEE_MASK_ASYNCOK -> returns successful despite errors! - execInfo.fMask |= SEE_MASK_UNICODE | SEE_MASK_FLAG_NO_UI; //::ShellExecuteEx() shows a non-blocking pop-up dialog on errors -> we want a blocking one - execInfo.lpVerb = nullptr; + if (!shellExecuteImpl([&](SHELLEXECUTEINFO& execInfo) +{ execInfo.lpFile = filepath.c_str(); - execInfo.lpParameters = arguments.c_str(); - execInfo.nShow = SW_SHOWNORMAL; - - if (!::ShellExecuteEx(&execInfo)) //__inout LPSHELLEXECUTEINFO lpExecInfo - throwFileError(_("Incorrect command line:") + L"\nFile: " + filepath + L"\nArg: " + arguments, L"ShellExecuteEx", ::GetLastError()); - - if (execInfo.hProcess) - { - ZEN_ON_SCOPE_EXIT(::CloseHandle(execInfo.hProcess)); - - if (type == EXEC_TYPE_SYNC) - ::WaitForSingleObject(execInfo.hProcess, INFINITE); - } + execInfo.lpParameters = arguments.c_str(); + }, type)) + throwFileError(_("Incorrect command line:") + L"\nFile: " + fmtFileName(filepath) + L"\nArg: " + arguments, L"ShellExecuteEx", ::GetLastError()); #elif defined ZEN_LINUX || defined ZEN_MAC /* diff --git a/zen/string_tools.h b/zen/string_tools.h index c8591522..03094c96 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -24,6 +24,7 @@ namespace zen { template <class Char> bool isWhiteSpace(Char ch); template <class Char> bool isDigit (Char ch); //not exactly the same as "std::isdigit" -> we consider '0'-'9' only! +template <class Char> bool isAlpha (Char ch); template <class S, class T> bool startsWith(const S& str, const T& prefix); // template <class S, class T> bool endsWith (const S& str, const T& postfix); //both S and T can be strings or char/wchar_t arrays or simple char/wchar_t @@ -87,19 +88,20 @@ bool isDigit(Char ch) //similar to implmenetation of std::::isdigit()! return static_cast<Char>('0') <= ch && ch <= static_cast<Char>('9'); } +template <> bool isAlpha(char ch) = delete; //probably not a good idea with UTF-8 anyway... + +template <> inline bool isAlpha(wchar_t ch) { return std::iswalpha(ch) != 0; } + template <class S, class T> inline bool startsWith(const S& str, const T& prefix) { - static_assert(IsStringLike<S>::value && IsStringLike<T>::value, ""); - typedef typename GetCharType<S>::Type CharType; - - const size_t pfLength = strLength(prefix); - if (strLength(str) < pfLength) + const size_t pfLen = strLength(prefix); + if (strLength(str) < pfLen) return false; - const CharType* const strFirst = strBegin(str); - return std::equal(strFirst, strFirst + pfLength, + const auto* const cmpFirst = strBegin(str); + return std::equal(cmpFirst, cmpFirst + pfLen, strBegin(prefix)); } @@ -107,15 +109,12 @@ bool startsWith(const S& str, const T& prefix) template <class S, class T> inline bool endsWith(const S& str, const T& postfix) { - static_assert(IsStringLike<S>::value && IsStringLike<T>::value, ""); - typedef typename GetCharType<S>::Type CharType; - const size_t strLen = strLength(str); const size_t pfLen = strLength(postfix); if (strLen < pfLen) return false; - const CharType* const cmpFirst = strBegin(str) + strLen - pfLen; + const auto* const cmpFirst = strBegin(str) + strLen - pfLen; return std::equal(cmpFirst, cmpFirst + pfLen, strBegin(postfix)); } @@ -124,17 +123,14 @@ bool endsWith(const S& str, const T& postfix) template <class S, class T> inline bool contains(const S& str, const T& term) { - static_assert(IsStringLike<S>::value && IsStringLike<T>::value, ""); - typedef typename GetCharType<S>::Type CharType; - const size_t strLen = strLength(str); const size_t termLen = strLength(term); if (strLen < termLen) return false; - const CharType* const strFirst = strBegin(str); - const CharType* const strLast = strFirst + strLen; - const CharType* const termFirst = strBegin(term); + const auto* const strFirst = strBegin(str); + const auto* const strLast = strFirst + strLen; + const auto* const termFirst = strBegin(term); return std::search(strFirst, strLast, termFirst, termFirst + termLen) != strLast; @@ -145,17 +141,14 @@ bool contains(const S& str, const T& term) template <class S, class T> inline S afterLast(const S& str, const T& term) { - static_assert(IsStringLike<T>::value, ""); - typedef typename GetCharType<S>::Type CharType; - const size_t termLen = strLength(term); - const CharType* const strFirst = strBegin(str); - const CharType* const strLast = strFirst + strLength(str); - const CharType* const termFirst = strBegin(term); + const auto* const strFirst = strBegin(str); + const auto* const strLast = strFirst + strLength(str); + const auto* const termFirst = strBegin(term); - const CharType* iter = search_last(strFirst, strLast, - termFirst, termFirst + termLen); + const auto* iter = search_last(strFirst, strLast, + termFirst, termFirst + termLen); if (iter == strLast) return str; @@ -168,15 +161,12 @@ S afterLast(const S& str, const T& term) template <class S, class T> inline S beforeLast(const S& str, const T& term) { - static_assert(IsStringLike<T>::value, ""); - typedef typename GetCharType<S>::Type CharType; - - const CharType* const strFirst = strBegin(str); - const CharType* const strLast = strFirst + strLength(str); - const CharType* const termFirst = strBegin(term); + const auto* const strFirst = strBegin(str); + const auto* const strLast = strFirst + strLength(str); + const auto* const termFirst = strBegin(term); - const CharType* iter = search_last(strFirst, strLast, - termFirst, termFirst + strLength(term)); + const auto* iter = search_last(strFirst, strLast, + termFirst, termFirst + strLength(term)); if (iter == strLast) return S(); @@ -188,16 +178,13 @@ S beforeLast(const S& str, const T& term) template <class S, class T> inline S afterFirst(const S& str, const T& term) { - static_assert(IsStringLike<T>::value, ""); - typedef typename GetCharType<S>::Type CharType; - const size_t termLen = strLength(term); - const CharType* const strFirst = strBegin(str); - const CharType* const strLast = strFirst + strLength(str); - const CharType* const termFirst = strBegin(term); + const auto* const strFirst = strBegin(str); + const auto* const strLast = strFirst + strLength(str); + const auto* const termFirst = strBegin(term); - const CharType* iter = std::search(strFirst, strLast, - termFirst, termFirst + termLen); + const auto* iter = std::search(strFirst, strLast, + termFirst, termFirst + termLen); if (iter == strLast) return S(); iter += termLen; @@ -210,11 +197,8 @@ S afterFirst(const S& str, const T& term) template <class S, class T> inline S beforeFirst(const S& str, const T& term) { - static_assert(IsStringLike<T>::value, ""); - typedef typename GetCharType<S>::Type CharType; - - const CharType* const strFirst = strBegin(str); - const CharType* const termFirst = strBegin(term); + const auto* const strFirst = strBegin(str); + const auto* const termFirst = strBegin(term); return S(strFirst, std::search(strFirst, strFirst + strLength(str), termFirst, termFirst + strLength(term)) - strFirst); @@ -224,9 +208,6 @@ S beforeFirst(const S& str, const T& term) template <class S, class T> inline std::vector<S> split(const S& str, const T& delimiter) { - static_assert(IsStringLike<T>::value, ""); - typedef typename GetCharType<S>::Type CharType; - std::vector<S> output; const size_t delimLen = strLength(delimiter); @@ -235,16 +216,16 @@ std::vector<S> split(const S& str, const T& delimiter) output.push_back(str); else { - const CharType* const delimFirst = strBegin(delimiter); - const CharType* const delimLast = delimFirst + delimLen; + const auto* const delimFirst = strBegin(delimiter); + const auto* const delimLast = delimFirst + delimLen; - const CharType* blockStart = strBegin(str); - const CharType* const strLast = blockStart + strLength(str); + const auto* blockStart = strBegin(str); + const auto* const strLast = blockStart + strLength(str); for (;;) { - const CharType* const blockEnd = std::search(blockStart, strLast, - delimFirst, delimLast); + const auto* const blockEnd = std::search(blockStart, strLast, + delimFirst, delimLast); output.emplace_back(blockStart, blockEnd - blockStart); if (blockEnd == strLast) @@ -272,9 +253,6 @@ typename EnableIf<!HasMember_append<S>::value>::Type stringAppend(S& str, const template <class S, class T, class U> inline S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll) { - static_assert(IsStringLike<T>::value && IsStringLike<U>::value, ""); - typedef typename GetCharType<S>::Type CharType; - const size_t oldLen = strLength(oldTerm); if (oldLen == 0) { @@ -282,20 +260,20 @@ S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll) return str; } - const CharType* strPos = strBegin(str); - const CharType* const strEnd = strPos + strLength(str); + const auto* strPos = strBegin(str); + const auto* const strEnd = strPos + strLength(str); - const CharType* const oldBegin = strBegin(oldTerm); - const CharType* const oldEnd = oldBegin + oldLen; + const auto* const oldBegin = strBegin(oldTerm); + const auto* const oldEnd = oldBegin + oldLen; //optimize "oldTerm not found" - const CharType* strMatch = std::search(strPos, strEnd, - oldBegin, oldEnd); + const auto* strMatch = std::search(strPos, strEnd, + oldBegin, oldEnd); if (strMatch == strEnd) return str; const size_t newLen = strLength(newTerm); - const CharType* const newBegin = strBegin(newTerm); + const auto* const newBegin = strBegin(newTerm); S output; for (;;) @@ -330,11 +308,10 @@ template <class S> inline void trim(S& str, bool fromLeft, bool fromRight) { assert(fromLeft || fromRight); - typedef typename GetCharType<S>::Type CharType; //don't use value_type! (wxString, Glib::ustring) - const CharType* const oldBegin = strBegin(str); - const CharType* newBegin = oldBegin; - const CharType* newEnd = oldBegin + strLength(str); + const auto* const oldBegin = strBegin(str); + const auto* newBegin = oldBegin; + const auto* newEnd = oldBegin + strLength(str); if (fromRight) while (newBegin != newEnd && isWhiteSpace(newEnd[-1])) @@ -354,10 +331,10 @@ void trim(S& str, bool fromLeft, bool fromRight) template <class S> inline S trimCpy(const S& str, bool fromLeft, bool fromRight) { - //implementing trimCpy() in terms of trim(), instead of the other way round, avoids memory allocations when trimming from right! - S tmp = str; - trim(tmp, fromLeft, fromRight); - return tmp; + //implementing trimCpy() in terms of trim(), instead of the other way round, avoids memory allocations when trimming from right! + S tmp = str; + trim(tmp, fromLeft, fromRight); + return tmp; } @@ -406,16 +383,11 @@ int saferPrintf(wchar_t* buffer, size_t bufferSize, const wchar_t* format, const template <class S, class T, class Num> inline S printNumber(const T& format, const Num& number) //format a single number using ::sprintf { - static_assert(IsStringLike<T>::value, ""); - static_assert(IsSameType< - typename GetCharType<S>::Type, - typename GetCharType<T>::Type>::value, ""); - typedef typename GetCharType<S>::Type CharType; const int BUFFER_SIZE = 128; CharType buffer[BUFFER_SIZE]; - const int charsWritten = implementation::saferPrintf(buffer, BUFFER_SIZE, format, number); + const int charsWritten = implementation::saferPrintf(buffer, BUFFER_SIZE, strBegin(format), number); return charsWritten > 0 ? S(buffer, charsWritten) : S(); } diff --git a/zen/string_traits.h b/zen/string_traits.h index 8c4775f4..12a7f87c 100644 --- a/zen/string_traits.h +++ b/zen/string_traits.h @@ -173,10 +173,8 @@ size_t cStringLength(const C* str) //naive implementation seems somewhat faster ++len; return len; } -} - -template <class S, typename = typename EnableIf<implementation::StringTraits<S>::isStringClass>::Type> inline +template <class S, typename = typename EnableIf<StringTraits<S>::isStringClass>::Type> inline const typename GetCharType<S>::Type* strBegin(const S& str) //SFINAE: T must be a "string" { return str.c_str(); @@ -190,18 +188,35 @@ inline const char* strBegin(const StringRef<char >& ref) { return ref.data( inline const wchar_t* strBegin(const StringRef<wchar_t>& ref) { return ref.data(); } -template <class S, typename = typename EnableIf<implementation::StringTraits<S>::isStringClass>::Type> inline +template <class S, typename = typename EnableIf<StringTraits<S>::isStringClass>::Type> inline size_t strLength(const S& str) //SFINAE: T must be a "string" { return str.length(); } -inline size_t strLength(const char* str) { return implementation::cStringLength(str); } -inline size_t strLength(const wchar_t* str) { return implementation::cStringLength(str); } +inline size_t strLength(const char* str) { return cStringLength(str); } +inline size_t strLength(const wchar_t* str) { return cStringLength(str); } inline size_t strLength(char) { return 1; } inline size_t strLength(wchar_t) { return 1; } inline size_t strLength(const StringRef<char >& ref) { return ref.length(); } inline size_t strLength(const StringRef<wchar_t>& ref) { return ref.length(); } } + +template <class S> inline +auto strBegin(S&& str) -> const typename GetCharType<S>::Type* +{ + static_assert(IsStringLike<S>::value, ""); + return implementation::strBegin(std::forward<S>(str)); +} + + +template <class S> inline +size_t strLength(S&& str) +{ + static_assert(IsStringLike<S>::value, ""); + return implementation::strLength(std::forward<S>(str)); +} +} + #endif //STRING_TRAITS_HEADER_813274321443234 diff --git a/zen/sys_error.h b/zen/sys_error.h index 9f7667db..7fb12d31 100644 --- a/zen/sys_error.h +++ b/zen/sys_error.h @@ -28,14 +28,12 @@ namespace zen typedef DWORD ErrorCode; #elif defined ZEN_LINUX || defined ZEN_MAC typedef int ErrorCode; -#else - #error define a platform! #endif ErrorCode getLastError(); -std::wstring formatSystemError(const std::wstring& functionName, ErrorCode lastError); -std::wstring formatSystemError(const std::wstring& functionName, ErrorCode lastError, const std::wstring& lastErrorMsg); +std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec); +std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec, const std::wstring& errorMsg); //A low-level exception class giving (non-translated) detail information only - same conceptional level like "GetLastError()"! class SysError @@ -67,16 +65,14 @@ ErrorCode getLastError() } -std::wstring formatSystemError(const std::wstring& functionName, long long lastError) = delete; //not implemented! intentional overload ambiguity to catch usage errors with HRESULT! - +std::wstring formatSystemErrorRaw(long long) = delete; //intentional overload ambiguity to catch usage errors inline -std::wstring formatSystemError(const std::wstring& functionName, ErrorCode lastError) +std::wstring formatSystemErrorRaw(ErrorCode ec) //return empty string on error { const ErrorCode currentError = getLastError(); //not necessarily == lastError - std::wstring lastErrorMsg; - + std::wstring errorMsg; #ifdef ZEN_WIN ZEN_ON_SCOPE_EXIT(::SetLastError(currentError)); //this function must not change active system error variable! @@ -84,37 +80,42 @@ std::wstring formatSystemError(const std::wstring& functionName, ErrorCode lastE 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, lastError, 0, reinterpret_cast<LPWSTR>(&buffer), 0, nullptr) != 0) + FORMAT_MESSAGE_ALLOCATE_BUFFER, nullptr, ec, 0, reinterpret_cast<LPWSTR>(&buffer), 0, nullptr) != 0) if (buffer) //"don't trust nobody" { ZEN_ON_SCOPE_EXIT(::LocalFree(buffer)); - lastErrorMsg = buffer; + errorMsg = buffer; } #elif defined ZEN_LINUX || defined ZEN_MAC ZEN_ON_SCOPE_EXIT(errno = currentError); - lastErrorMsg = utfCvrtTo<std::wstring>(::strerror(lastError)); + errorMsg = utfCvrtTo<std::wstring>(::strerror(ec)); #endif + trim(errorMsg); //Windows messages seem to end with a blank... - return formatSystemError(functionName, lastError, lastErrorMsg); + return errorMsg; } +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 lastError, const std::wstring& lastErrorMsg) +std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec) { return formatSystemError(functionName, ec, formatSystemErrorRaw(ec)); } + + +inline +std::wstring formatSystemError(const std::wstring& functionName, ErrorCode ec, const std::wstring& errorMsg) { - std::wstring output = replaceCpy(_("Error Code %x:"), L"%x", numberTo<std::wstring>(lastError)); + std::wstring output = replaceCpy(_("Error Code %x:"), L"%x", numberTo<std::wstring>(ec)); - if (!lastErrorMsg.empty()) + if (!errorMsg.empty()) { output += L" "; - output += lastErrorMsg; + output += errorMsg; } - if (!endsWith(output, L" ")) //Windows messages seem to end with a blank... - output += L" "; - output += L"(" + functionName + L")"; + output += L" (" + functionName + L")"; return output; } diff --git a/zen/win_ver.h b/zen/win_ver.h deleted file mode 100644 index 2e8f1ee7..00000000 --- a/zen/win_ver.h +++ /dev/null @@ -1,135 +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 gmx DOT de) - All Rights Reserved * -// ************************************************************************** - -#ifndef WINDOWS_VERSION_HEADER_238470348254325 -#define WINDOWS_VERSION_HEADER_238470348254325 - -#include <cassert> -#include <utility> -#include "win.h" //includes "windows.h" -#include "build_info.h" -#include "dll.h" - -namespace zen -{ -struct OsVersion -{ - OsVersion() : major(), minor() {} - OsVersion(DWORD high, DWORD low) : major(high), minor(low) {} - - DWORD major; - DWORD minor; -}; -inline bool operator< (const OsVersion& lhs, const OsVersion& rhs) { return lhs.major != rhs.major ? lhs.major < rhs.major : lhs.minor < rhs.minor; } -inline bool operator==(const OsVersion& lhs, const OsVersion& rhs) { return lhs.major == rhs.major && lhs.minor == rhs.minor; } - - -//version overview: http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx -const OsVersion osVersionWin10 (10, 0); -const OsVersion osVersionWin81 (6, 3); -const OsVersion osVersionWin8 (6, 2); -const OsVersion osVersionWin7 (6, 1); -const OsVersion osVersionWinVista (6, 0); -const OsVersion osVersionWinServer2003(5, 2); -const OsVersion osVersionWinXp (5, 1); -const OsVersion osVersionWin2000 (5, 0); - -/* - NOTE: there are two basic APIs to check Windows version: (empiric study following) - GetVersionEx -> reports version considering compatibility mode (and compatibility setting in app manifest since Windows 8.1) - VerifyVersionInfo -> always reports *real* Windows Version - *) Win10 Technical preview caveat: VerifyVersionInfo returns 6.3 unless manifest entry is added!!! -*/ - -//GetVersionEx()-based APIs: -OsVersion getOsVersion(); -inline bool win81OrLater () { using namespace std::rel_ops; return getOsVersion() >= osVersionWin81; } -inline bool win8OrLater () { using namespace std::rel_ops; return getOsVersion() >= osVersionWin8; } -inline bool win7OrLater () { using namespace std::rel_ops; return getOsVersion() >= osVersionWin7; } -inline bool vistaOrLater () { using namespace std::rel_ops; return getOsVersion() >= osVersionWinVista; } -inline bool winServer2003orLater() { using namespace std::rel_ops; return getOsVersion() >= osVersionWinServer2003; } -inline bool winXpOrLater () { using namespace std::rel_ops; return getOsVersion() >= osVersionWinXp; } - -//VerifyVersionInfo()-based APIs: -bool isRealOsVersion(const OsVersion& ver); - - -bool runningWOW64(); -bool running64BitWindows(); - - - - -//######################### implementation ######################### -inline -OsVersion getOsVersion() -{ - OSVERSIONINFO osvi = {}; - osvi.dwOSVersionInfoSize = sizeof(osvi); -#ifdef _MSC_VER -#pragma warning(suppress: 4996) //"'GetVersionExW': was declared deprecated" -#endif - if (!::GetVersionEx(&osvi)) //38 ns per call! (yes, that's nano!) -> we do NOT miss C++11 thread-safe statics right now... - { - assert(false); - return OsVersion(); - } - return OsVersion(osvi.dwMajorVersion, osvi.dwMinorVersion); -} - - -inline -bool isRealOsVersion(const OsVersion& ver) -{ - OSVERSIONINFOEX verInfo = {}; - verInfo.dwOSVersionInfoSize = sizeof(verInfo); - verInfo.dwMajorVersion = ver.major; - verInfo.dwMinorVersion = ver.minor; - - //Syntax: http://msdn.microsoft.com/en-us/library/windows/desktop/ms725491%28v=vs.85%29.aspx - DWORDLONG conditionMask = 0; - VER_SET_CONDITION(conditionMask, VER_MAJORVERSION, VER_EQUAL); - VER_SET_CONDITION(conditionMask, VER_MINORVERSION, VER_EQUAL); - - const bool rv = ::VerifyVersionInfo(&verInfo, VER_MAJORVERSION | VER_MINORVERSION, conditionMask) - == TRUE; //silence VC "performance warnings" - assert(rv || ::GetLastError() == ERROR_OLD_WIN_VERSION); - - return rv; -} - - -inline -bool runningWOW64() //test if process is running under WOW64: http://msdn.microsoft.com/en-us/library/ms684139(VS.85).aspx -{ - typedef BOOL (WINAPI* IsWow64ProcessFun)(HANDLE hProcess, PBOOL Wow64Process); - - const SysDllFun<IsWow64ProcessFun> isWow64Process(L"kernel32.dll", "IsWow64Process"); - if (isWow64Process) - { - BOOL isWow64 = FALSE; - if (isWow64Process(::GetCurrentProcess(), &isWow64)) - return isWow64 != FALSE; - } - return false; -} - - -template <bool is64BitBuild> inline -bool running64BitWindowsImpl() { return true; } - -template <> inline -bool running64BitWindowsImpl<false>() { return runningWOW64(); } - -inline -bool running64BitWindows() //http://blogs.msdn.com/b/oldnewthing/archive/2005/02/01/364563.aspx -{ - static_assert(zen::is32BitBuild || zen::is64BitBuild, ""); - return running64BitWindowsImpl<is64BitBuild>(); -} -} - -#endif //WINDOWS_VERSION_HEADER_238470348254325 diff --git a/zen/zstring.cpp b/zen/zstring.cpp index 73ef3ee9..68934e19 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -16,98 +16,8 @@ #include <ctype.h> //toupper() #endif -#ifndef NDEBUG - #include "thread.h" - #include <iostream> -#endif - using namespace zen; - -#ifndef NDEBUG -namespace -{ -class LeakChecker //small test for memory leaks -{ -public: - static LeakChecker& get() - { - //meyers singleton: avoid static initialization order problem in global namespace! - static LeakChecker inst; - return inst; - } - - void insert(const void* ptr, size_t size) - { - boost::lock_guard<boost::mutex> dummy(lockActStrings); - if (!activeStrings.emplace(ptr, size).second) - reportProblem("Serious Error: New memory points into occupied space: " + rawMemToString(ptr, size)); - } - - void remove(const void* ptr) - { - boost::lock_guard<boost::mutex> dummy(lockActStrings); - if (activeStrings.erase(ptr) != 1) - reportProblem("Serious Error: No memory available for deallocation at this location!"); - } - -private: - LeakChecker() {} - - ~LeakChecker() - { - if (!activeStrings.empty()) - { - std::string leakingStrings; - - int items = 0; - for (auto it = activeStrings.begin(); it != activeStrings.end() && items < 20; ++it, ++items) - leakingStrings += "\"" + rawMemToString(it->first, it->second) + "\"\n"; - - const std::string message = std::string("Memory leak detected!") + "\n\n" - + "Candidates:\n" + leakingStrings; -#ifdef ZEN_WIN - MessageBoxA(nullptr, message.c_str(), "Error", MB_SERVICE_NOTIFICATION | MB_ICONERROR); -#else - std::cerr << message; - std::abort(); -#endif - } - } - - LeakChecker (const LeakChecker&) = delete; - LeakChecker& operator=(const LeakChecker&) = delete; - - static std::string rawMemToString(const void* ptr, size_t size) - { - std::string output(reinterpret_cast<const char*>(ptr), std::min<size_t>(size, 100)); - replace(output, '\0', ' '); //don't stop at 0-termination - return output; - } - - void reportProblem(const std::string& message) //throw std::logic_error - { -#ifdef ZEN_WIN - ::MessageBoxA(nullptr, message.c_str(), "Error", MB_SERVICE_NOTIFICATION | MB_ICONERROR); -#else - std::cerr << message; -#endif - throw std::logic_error("Memory leak! " + message + "\n" + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); - } - - boost::mutex lockActStrings; - std::unordered_map<const void*, size_t> activeStrings; -}; - -//caveat: function scope static initialization is not thread-safe in VS 2010! -auto& dummy = LeakChecker::get(); //still not sufficient if multiple threads access during static init!!! -} - -void z_impl::leakCheckerInsert(const void* ptr, size_t size) { LeakChecker::get().insert(ptr, size); } -void z_impl::leakCheckerRemove(const void* ptr ) { LeakChecker::get().remove(ptr); } -#endif //NDEBUG - - /* Perf test: compare strings 10 mio times; 64 bit build ----------------------------------------------------- @@ -148,15 +58,18 @@ const SysDllFun<CompareStringOrdinalFunc> compareStringOrdinal = SysDllFun<Compa } -int cmpFileName(const Zstring& lhs, const Zstring& rhs) +int cmpFilePath(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) { + assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls! + assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); // + if (compareStringOrdinal) //this additional test has no noticeable performance impact { - const int rv = compareStringOrdinal(lhs.c_str(), //__in LPCWSTR lpString1, - static_cast<int>(lhs.size()), //__in int cchCount1, - rhs.c_str(), //__in LPCWSTR lpString2, - static_cast<int>(rhs.size()), //__in int cchCount2, - true); //__in BOOL bIgnoreCase + const int rv = compareStringOrdinal(lhs, //__in LPCWSTR lpString1, + static_cast<int>(lhsLen), //__in int cchCount1, + rhs, //__in LPCWSTR lpString2, + static_cast<int>(rhsLen), //__in int cchCount2, + true); //__in BOOL bIgnoreCase if (rv <= 0) throw std::runtime_error("Error comparing strings (CompareStringOrdinal). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); else @@ -167,13 +80,10 @@ int cmpFileName(const Zstring& lhs, const Zstring& rhs) //do NOT use "CompareString"; this function is NOT accurate (even with LOCALE_INVARIANT and SORT_STRINGSORT): for example "weiß" == "weiss"!!! //the only reliable way to compare filepaths (with XP) is to call "CharUpper" or "LCMapString": - const size_t sizeLhs = lhs.size(); - const size_t sizeRhs = rhs.size(); - - const auto minSize = std::min(sizeLhs, sizeRhs); + const auto minSize = std::min(lhsLen, rhsLen); if (minSize == 0) //LCMapString does not allow input sizes of 0! - return static_cast<int>(sizeLhs) - static_cast<int>(sizeRhs); + return static_cast<int>(lhsLen) - static_cast<int>(rhsLen); auto copyToUpperCase = [&](const wchar_t* strIn, wchar_t* strOut) { @@ -189,14 +99,14 @@ int cmpFileName(const Zstring& lhs, const Zstring& rhs) auto eval = [&](wchar_t* bufL, wchar_t* bufR) { - copyToUpperCase(lhs.c_str(), bufL); - copyToUpperCase(rhs.c_str(), bufR); + copyToUpperCase(lhs, bufL); + copyToUpperCase(rhs, bufR); - const int rv = ::wmemcmp(bufL, bufR, minSize); + const int rv = ::wcsncmp(bufL, bufR, minSize); if (rv != 0) return rv; - return static_cast<int>(sizeLhs) - static_cast<int>(sizeRhs); + return static_cast<int>(lhsLen) - static_cast<int>(rhsLen); }; if (minSize <= MAX_PATH) //performance optimization: stack @@ -238,10 +148,15 @@ Zstring makeUpperCopy(const Zstring& str) #elif defined ZEN_MAC -int cmpFileName(const Zstring& lhs, const Zstring& rhs) +int cmpFilePath(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) { - const int rv = ::strcasecmp(lhs.c_str(), rhs.c_str()); //locale-dependent! - return rv; + assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls! + assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); // + + const int rv = ::strncasecmp(lhs, rhs, std::min(lhsLen, rhsLen)); //locale-dependent! + if (rv != 0) + return rv; + return static_cast<int>(lhsLen) - static_cast<int>(rhsLen); } diff --git a/zen/zstring.h b/zen/zstring.h index 7dcfbb69..9822e504 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -10,43 +10,10 @@ #include "string_base.h" #ifdef ZEN_LINUX - #include <cstring> //strcmp + #include <cstring> //strncmp #endif -#ifndef NDEBUG -namespace z_impl -{ -void leakCheckerInsert(const void* ptr, size_t size); -void leakCheckerRemove(const void* ptr); -} -#endif //NDEBUG - -class AllocatorFreeStoreChecked -{ -public: - static void* allocate(size_t size) //throw std::bad_alloc - { - void* ptr = zen::AllocatorOptimalSpeed::allocate(size); -#ifndef NDEBUG - z_impl::leakCheckerInsert(ptr, size); //test Zbase for memory leaks -#endif - return ptr; - } - - static void deallocate(void* ptr) - { -#ifndef NDEBUG - z_impl::leakCheckerRemove(ptr); //check for memory leaks -#endif - zen::AllocatorOptimalSpeed::deallocate(ptr); - } - - static size_t calcCapacity(size_t length) { return zen::AllocatorOptimalSpeed::calcCapacity(length); } -}; - - -//############################## helper functions ############################################# #ifdef ZEN_WIN //Windows encodes Unicode as UTF-16 wchar_t typedef wchar_t Zchar; @@ -61,27 +28,32 @@ public: //"The reason for all the fuss above" - Loki/SmartPtr //a high-performance string for interfacing with native OS APIs and multithreaded contexts -typedef zen::Zbase<Zchar, zen::StorageRefCountThreadSafe, AllocatorFreeStoreChecked> Zstring; +typedef zen::Zbase<Zchar, zen::StorageRefCountThreadSafe, zen::AllocatorOptimalSpeed> Zstring; //Compare filepaths: Windows does NOT distinguish between upper/lower-case, while Linux DOES -int cmpFileName(const Zstring& lhs, const Zstring& rhs); +int cmpFilePath(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen); + struct LessFilePath //case-insensitive on Windows, case-sensitive on Linux { - bool operator()(const Zstring& lhs, const Zstring& rhs) const { return cmpFileName(lhs, rhs) < 0; } + template <class S, class T> + bool operator()(const S& lhs, const T& rhs) const { using namespace zen; return cmpFilePath(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) < 0; } }; struct EqualFilePath //case-insensitive on Windows, case-sensitive on Linux { - bool operator()(const Zstring& lhs, const Zstring& rhs) const { return cmpFileName(lhs, rhs) == 0; } + template <class S, class T> + bool operator()(const S& lhs, const T& rhs) const { using namespace zen; return cmpFilePath(strBegin(lhs), strLength(lhs), strBegin(rhs), strLength(rhs)) == 0; } }; + #if defined ZEN_WIN || defined ZEN_MAC Zstring makeUpperCopy(const Zstring& str); #endif + inline Zstring appendSeparator(Zstring path) //support rvalue references! { @@ -100,32 +72,82 @@ Zstring getFileExtension(const Zstring& filePath) } -inline -bool pathStartsWith(const Zstring& str, const Zstring& prefix) +template <class S, class T> inline +bool pathStartsWith(const S& str, const T& prefix) { - return str.size() >= prefix.size() && - EqualFilePath()(Zstring(str.begin(), str.begin() + prefix.size()), prefix); + using namespace zen; + const size_t pfLen = strLength(prefix); + if (strLength(str) < pfLen) + return false; + + return cmpFilePath(strBegin(str), pfLen, strBegin(prefix), pfLen) == 0; } -inline -bool pathEndsWith(const Zstring& str, const Zstring& postfix) +template <class S, class T> inline +bool pathEndsWith(const S& str, const T& postfix) { - return str.size() >= postfix.size() && - EqualFilePath()(Zstring(str.end() - postfix.size(), str.end()), postfix); + using namespace zen; + const size_t strLen = strLength(str); + const size_t pfLen = strLength(postfix); + if (strLen < pfLen) + return false; + + return cmpFilePath(strBegin(str) + strLen - pfLen, pfLen, strBegin(postfix), pfLen) == 0; } + //################################# inline implementation ######################################## #ifdef ZEN_LINUX inline -int cmpFileName(const Zstring& lhs, const Zstring& rhs) +int cmpFilePath(const Zchar* lhs, size_t lhsLen, const Zchar* rhs, size_t rhsLen) { - return std::strcmp(lhs.c_str(), rhs.c_str()); //POSIX filepaths don't have embedded 0 + assert(std::find(lhs, lhs + lhsLen, 0) == lhs + lhsLen); //don't expect embedded nulls! + assert(std::find(rhs, rhs + rhsLen, 0) == rhs + rhsLen); // + + const int rv = std::strncmp(lhs, rhs, std::min(lhsLen, rhsLen)); + if (rv != 0) + return rv; + return static_cast<int>(lhsLen) - static_cast<int>(rhsLen); } #endif + +//--------------------------------------------------------------------------- +//ZEN macro consistency checks: +#ifdef ZEN_WIN + #if defined ZEN_LINUX || defined ZEN_MAC + #error more than one target platform defined + #endif + + #ifdef ZEN_WIN_VISTA_AND_LATER + #ifdef ZEN_WIN_PRE_VISTA + #error choose only one of the two variants + #endif + #elif defined ZEN_WIN_PRE_VISTA + #ifdef ZEN_WIN_VISTA_AND_LATER + #error choose only one of the two variants + #endif + #else + #error choose one of the two variants + #endif + +#elif defined ZEN_LINUX + #if defined ZEN_WIN || defined ZEN_MAC + #error more than one target platform defined + #endif + +#elif defined ZEN_MAC + #if defined ZEN_WIN || defined ZEN_LINUX + #error more than one target platform defined + #endif + +#else + #error no target platform defined +#endif + #endif //ZSTRING_H_INCLUDED_73425873425789 |