summaryrefslogtreecommitdiff
path: root/zen
diff options
context:
space:
mode:
Diffstat (limited to 'zen')
-rw-r--r--zen/dir_watcher.cpp7
-rw-r--r--zen/file_access.cpp379
-rw-r--r--zen/file_id_def.h2
-rw-r--r--zen/file_io.cpp65
-rw-r--r--zen/perf.h8
-rw-r--r--zen/recycler.cpp195
-rw-r--r--zen/recycler.h2
-rw-r--r--zen/serialize.h8
-rw-r--r--zen/shell_execute.h66
-rw-r--r--zen/string_tools.h130
-rw-r--r--zen/string_traits.h27
-rw-r--r--zen/sys_error.h41
-rw-r--r--zen/win_ver.h135
-rw-r--r--zen/zstring.cpp131
-rw-r--r--zen/zstring.h118
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))
diff --git a/zen/perf.h b/zen/perf.h
index f43bc00f..e52bf662 100644
--- a/zen/perf.h
+++ b/zen/perf.h
@@ -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
bgstack15