diff options
Diffstat (limited to 'zen')
-rw-r--r-- | zen/basic_math.h | 23 | ||||
-rw-r--r-- | zen/build_info.h | 2 | ||||
-rw-r--r-- | zen/com_error.h | 8 | ||||
-rw-r--r-- | zen/debug_log.h | 7 | ||||
-rw-r--r-- | zen/debug_minidump.cpp | 2 | ||||
-rw-r--r-- | zen/dir_watcher.cpp | 31 | ||||
-rw-r--r-- | zen/dll.h | 28 | ||||
-rw-r--r-- | zen/file_handling.cpp | 131 | ||||
-rw-r--r-- | zen/file_id.cpp | 6 | ||||
-rw-r--r-- | zen/file_id_def.h | 4 | ||||
-rw-r--r-- | zen/file_io.cpp | 22 | ||||
-rw-r--r-- | zen/file_io.h | 9 | ||||
-rw-r--r-- | zen/file_traverser.cpp | 7 | ||||
-rw-r--r-- | zen/file_traverser.h | 2 | ||||
-rw-r--r-- | zen/format_unit.cpp | 190 | ||||
-rw-r--r-- | zen/format_unit.h | 6 | ||||
-rw-r--r-- | zen/guid.h | 4 | ||||
-rw-r--r-- | zen/int64.h | 4 | ||||
-rw-r--r-- | zen/last_error.h | 51 | ||||
-rw-r--r-- | zen/process_priority.cpp | 149 | ||||
-rw-r--r-- | zen/process_priority.h | 68 | ||||
-rw-r--r-- | zen/read_txt.cpp | 2 | ||||
-rw-r--r-- | zen/recycler.cpp | 69 | ||||
-rw-r--r-- | zen/symlink_target.h | 12 | ||||
-rw-r--r-- | zen/thread.h | 19 | ||||
-rw-r--r-- | zen/tick_count.h | 34 | ||||
-rw-r--r-- | zen/warn_static.h | 7 | ||||
-rw-r--r-- | zen/zstring.cpp | 196 | ||||
-rw-r--r-- | zen/zstring.h | 168 |
29 files changed, 694 insertions, 567 deletions
diff --git a/zen/basic_math.h b/zen/basic_math.h index d83a7f77..89d9d6c0 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -10,6 +10,7 @@ #include <algorithm> #include <iterator> #include <limits> +#include <cmath> #include <functional> #include <cassert> @@ -40,6 +41,9 @@ std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, Input template <class InputIterator, class Compare> std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, InputIterator last, Compare comp); +template <class T, class InputIterator> //precondition: range must be sorted! +auto nearMatch(const T& val, InputIterator first, InputIterator last) -> typename std::iterator_traits<InputIterator>::value_type; + template <class T> bool isNull(T value); @@ -198,6 +202,25 @@ std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, Input } +template <class T, class InputIterator> inline +auto nearMatch(const T& val, InputIterator first, InputIterator last) -> typename std::iterator_traits<InputIterator>::value_type +{ + if (first == last) + return 0; + + assert(std::is_sorted(first, last)); + InputIterator it = std::lower_bound(first, last, val); + if (it == last) + return *--last; + if (it == first) + return *first; + + const auto nextVal = *it; + const auto prevVal = *--it; + return val - prevVal < nextVal - val ? prevVal : nextVal; +} + + template <class T> inline bool isNull(T value) { diff --git a/zen/build_info.h b/zen/build_info.h index d69a8c1c..406fef70 100644 --- a/zen/build_info.h +++ b/zen/build_info.h @@ -13,6 +13,8 @@ namespace zen //safer than checking for _WIN64 (defined on windows for 64-bit compilations only) while _WIN32 is always defined (even for x64 compiler!) static const bool is32BitBuild = sizeof(void*) == 4; static const bool is64BitBuild = sizeof(void*) == 8; + +static_assert(is32BitBuild || is64BitBuild, ""); } #endif //BUILDINFO_H_INCLUDED diff --git a/zen/com_error.h b/zen/com_error.h index cd643b49..e6f5b492 100644 --- a/zen/com_error.h +++ b/zen/com_error.h @@ -51,14 +51,6 @@ Equivalent to: - - - - - - - - //################# implementation ##################### std::wstring formatWin32Msg(DWORD dwMessageId) //return empty string on error { diff --git a/zen/debug_log.h b/zen/debug_log.h index 45283da5..6ee44ff5 100644 --- a/zen/debug_log.h +++ b/zen/debug_log.h @@ -25,15 +25,10 @@ namespace zen { #ifdef FFS_WIN const char ZEN_FILE_NAME_SEPARATOR = '\\'; - -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC const char ZEN_FILE_NAME_SEPARATOR = '/'; - -#else -#error specify platform! #endif - class DebugLog { public: diff --git a/zen/debug_minidump.cpp b/zen/debug_minidump.cpp index 011e1bf2..c229b4aa 100644 --- a/zen/debug_minidump.cpp +++ b/zen/debug_minidump.cpp @@ -90,7 +90,7 @@ public: private: template <class T> - static std::string numberToString(const T& number) //convert number to string the C++ way + static std::string numberToString(const T& number) //convert number to string the (slow) C++ way { std::ostringstream ss; ss << number; diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 7d5e4697..0b34f890 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -19,6 +19,9 @@ #include <sys/inotify.h> #include <fcntl.h> #include "file_traverser.h" + +#elif defined FFS_MAC +//#include <CoreFoundation/FSEvents.h> #endif using namespace zen; @@ -133,7 +136,7 @@ class ReadChangesAsync { public: //constructed in main thread! - ReadChangesAsync(const Zstring& directory, //make sure to not leak in thread-unsafe types! + ReadChangesAsync(const Zstring& directory, //make sure to not leak-in thread-unsafe types! const std::shared_ptr<SharedData>& shared) : shared_(shared), dirnamePf(appendSeparator(directory)), @@ -510,11 +513,35 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() output.push_back(Entry(ACTION_DELETE, fullname)); } } - bytePos += sizeof(struct ::inotify_event) + evt.len; } return output; } +#elif defined FFS_MAC + +warn_static("finish") +struct DirWatcher::Pimpl +{ + +}; + + +DirWatcher::DirWatcher(const Zstring& directory) //throw FileError +{ + +} + + +DirWatcher::~DirWatcher() +{ +} + + +std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>&) //throw FileError +{ + std::vector<Entry> output; + return output; +} #endif @@ -8,10 +8,15 @@ #define DLLLOADER_H_INCLUDED #include <memory> +#ifdef FFS_WIN #include <string> #include "scope_guard.h" #include "win.h" //includes "windows.h" +#elif defined FFS_LINUX || defined FFS_MAC +#include <dlfcn.h> +#endif + namespace zen { /* @@ -35,10 +40,15 @@ class DllFun public: DllFun() : fun(nullptr) {} +#ifdef FFS_WIN DllFun(const wchar_t* libraryName, const char* functionName) : hLibRef(::LoadLibrary(libraryName), ::FreeLibrary), fun(hLibRef ? reinterpret_cast<Func>(::GetProcAddress(static_cast<HMODULE>(hLibRef.get()), functionName)) : nullptr) {} - +#elif defined FFS_LINUX || defined FFS_MAC + DllFun(const char* libraryName, const char* functionName) : + hLibRef(::dlopen(libraryName, RTLD_LAZY), ::dlclose), + fun(hLibRef ? reinterpret_cast<Func>(::dlsym(hLibRef.get(), functionName)) : nullptr) {} +#endif operator Func() const { return fun; } private: @@ -46,6 +56,8 @@ private: Func fun; }; + +#ifdef FFS_WIN //if the dll is already part of the process space, e.g. "kernel32.dll" or "shell32.dll", we can use a faster variant: //NOTE: since the lifetime of the referenced library is *not* controlled, this is safe to use only for permanently loaded libraries like these! template <class Func> @@ -66,7 +78,6 @@ private: Func fun; }; - /* extract binary resources from .exe/.dll: @@ -77,12 +88,7 @@ extract binary resources from .exe/.dll: MY_BINARY_RESOURCE RCDATA "filename.dat" */ std::string getResourceStream(const std::wstring& libraryName, size_t resourceId); - - - - - - +#endif @@ -94,6 +100,7 @@ std::string getResourceStream(const std::wstring& libraryName, size_t resourceId //--------------- implementation--------------------------------------------------- +#ifdef FFS_WIN inline std::string getResourceStream(const wchar_t* libraryName, size_t resourceId) { @@ -102,16 +109,13 @@ std::string getResourceStream(const wchar_t* libraryName, size_t resourceId) ZEN_ON_SCOPE_EXIT(::FreeLibrary(module)); if (HRSRC res = ::FindResource(module, MAKEINTRESOURCE(resourceId), RT_RCDATA)) - { if (HGLOBAL resHandle = ::LoadResource(module, res)) - { if (const char* stream = static_cast<const char*>(::LockResource(resHandle))) return std::string(stream, static_cast<size_t>(::SizeofResource(module, res))); //size is 0 on error - } - } } return std::string(); } +#endif } #endif // DLLLOADER_H_INCLUDED diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp index 4f34814f..bf829010 100644 --- a/zen/file_handling.cpp +++ b/zen/file_handling.cpp @@ -27,14 +27,20 @@ #include "IFileOperation/file_op.h" #elif defined FFS_LINUX -#include <sys/stat.h> -#include <utime.h> -#include <sys/time.h> //futimes -#include <sys/vfs.h> - +#include <sys/vfs.h> //statfs +#include <fcntl.h> //AT_SYMLINK_NOFOLLOW, UTIME_OMIT #ifdef HAVE_SELINUX #include <selinux/selinux.h> #endif + +#elif defined FFS_MAC +#include <sys/mount.h> //statfs +#include <utime.h> +#endif + +#if defined FFS_LINUX || defined FFS_MAC +#include <sys/stat.h> +//#include <sys/time.h> #endif using namespace zen; @@ -47,7 +53,7 @@ bool zen::fileExists(const Zstring& filename) const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(filename).c_str()); return ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_DIRECTORY) == 0; //returns true for (file-)symlinks also -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC struct stat fileInfo = {}; return ::lstat(filename.c_str(), &fileInfo) == 0 && (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode)); //in Linux a symbolic link is neither file nor directory @@ -62,7 +68,7 @@ bool zen::dirExists(const Zstring& dirname) const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(dirname).c_str()); return ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for (dir-)symlinks also -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC struct stat dirInfo = {}; return ::lstat(dirname.c_str(), &dirInfo) == 0 && (S_ISLNK(dirInfo.st_mode) || S_ISDIR(dirInfo.st_mode)); //in Linux a symbolic link is neither file nor directory @@ -82,7 +88,7 @@ bool zen::symlinkExists(const Zstring& linkname) } return isSymlink(fileInfo); -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC struct stat fileInfo = {}; return ::lstat(linkname.c_str(), &fileInfo) == 0 && S_ISLNK(fileInfo.st_mode); //symbolic link @@ -96,7 +102,7 @@ bool zen::somethingExists(const Zstring& objname) const DWORD rv = ::GetFileAttributes(applyLongPathPrefix(objname).c_str()); return rv != INVALID_FILE_ATTRIBUTES || ::GetLastError() == ERROR_SHARING_VIOLATION; //"C:\pagefile.sys" -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC struct stat fileInfo = {}; return ::lstat(objname.c_str(), &fileInfo) == 0; #endif @@ -160,7 +166,7 @@ void getFileAttrib(const Zstring& filename, FileAttrib& attr, ProcSymlink procSl attr.modificationTime = toTimeT(fileInfoHnd.ftLastWriteTime); } -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC struct stat fileInfo = {}; const int rv = procSl == SYMLINK_FOLLOW ? @@ -204,7 +210,7 @@ UInt64 zen::getFreeDiskSpace(const Zstring& path) //throw FileError return UInt64(bytesFree.LowPart, bytesFree.HighPart); -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC struct statfs info = {}; if (::statfs(path.c_str(), &info) != 0) throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(path)) + L"\n\n" + getLastErrorFormatted()); @@ -269,7 +275,7 @@ DWORD retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! return volumeSerial; } -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC dev_t retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! { Zstring volumePathName = pathName; @@ -308,7 +314,7 @@ bool zen::removeFile(const Zstring& filename) //throw FileError #ifdef FFS_WIN const Zstring& filenameFmt = applyLongPathPrefix(filename); if (!::DeleteFile(filenameFmt.c_str())) -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC if (::unlink(filename.c_str()) != 0) #endif { @@ -419,7 +425,7 @@ void renameFile_sub(const Zstring& oldName, const Zstring& newName) //throw File throw FileError(errorMessage); } -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC if (::rename(oldName.c_str(), newName.c_str()) != 0) { const int lastError = errno; @@ -619,7 +625,7 @@ void removeDirectoryImpl(const Zstring& directory, CallbackRemoveDir* callback) if (callback) callback->onBeforeDirDeletion(directory); //once per symlink #ifdef FFS_WIN if (!::RemoveDirectory(directoryFmt.c_str())) -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC if (::unlink(directory.c_str()) != 0) #endif throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted()); @@ -653,7 +659,7 @@ void removeDirectoryImpl(const Zstring& directory, CallbackRemoveDir* callback) if (callback) callback->onBeforeDirDeletion(directory); //and once per folder #ifdef FFS_WIN if (!::RemoveDirectory(directoryFmt.c_str())) -#else +#elif defined FFS_LINUX || defined FFS_MAC if (::rmdir(directory.c_str()) != 0) #endif throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted()); @@ -676,11 +682,11 @@ void zen::removeDirectory(const Zstring& directory, CallbackRemoveDir* callback) -void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, ProcSymlink procSl) //throw FileError +void zen::setFileTime(const Zstring& filename, const Int64& modTime, ProcSymlink procSl) //throw FileError { #ifdef FFS_WIN FILETIME creationTime = {}; - FILETIME lastWriteTime = tofiletime(modificationTime); + FILETIME lastWriteTime = tofiletime(modTime); //####################################### DST hack ########################################### if (dst::isFatDrive(filename)) //throw() @@ -930,25 +936,23 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr #endif #elif defined FFS_LINUX - if (procSl == SYMLINK_FOLLOW) - { - struct ::utimbuf newTimes = {}; - newTimes.actime = ::time(nullptr); - newTimes.modtime = to<time_t>(modificationTime); + struct ::timespec newTimes[2] = {}; + newTimes[0].tv_nsec = UTIME_OMIT; //omit access time + newTimes[1].tv_sec = to<time_t>(modTime); //modification time (seconds) - // set new "last write time" - if (::utime(filename.c_str(), &newTimes) != 0) - throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); - } - else - { - struct ::timeval newTimes[2] = {}; - newTimes[0].tv_sec = ::time(nullptr); //access time (seconds) - newTimes[1].tv_sec = to<time_t>(modificationTime); //modification time (seconds) + if (::utimensat(AT_FDCWD, filename.c_str(), newTimes, procSl == SYMLINK_DIRECT ? AT_SYMLINK_NOFOLLOW : 0) != 0) + throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); - if (::lutimes(filename.c_str(), newTimes) != 0) - throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); - } +#elif defined FFS_MAC + struct ::timeval newTimes[2] = {}; + newTimes[0].tv_sec = ::time(nullptr); //access time (seconds) + newTimes[1].tv_sec = to<time_t>(modTime); //modification time (seconds) + + const int rv = procSl == SYMLINK_FOLLOW ? + :: utimes(filename.c_str(), newTimes) : //utimensat() not yet implemented on OS X + ::lutimes(filename.c_str(), newTimes); + if (rv != 0) + throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); #endif } @@ -976,7 +980,7 @@ bool zen::supportsPermissions(const Zstring& dirname) //throw FileError return (fsFlags & FILE_PERSISTENT_ACLS) != 0; -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC return true; #endif } @@ -1226,7 +1230,7 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym throw FileError */ -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC #ifdef HAVE_SELINUX //copy SELinux security context copySecurityContext(source, target, procSl); //throw FileError @@ -1277,7 +1281,7 @@ void createDirectoryStraight(const Zstring& directory, //throw FileError, ErrorT throw FileError(msg); } -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC if (::mkdir(directory.c_str(), 0755) != 0) //mode: drwxr-xr-x { const std::wstring msg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted(); @@ -1474,7 +1478,7 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool if (!createSymbolicLink(targetLink.c_str(), //__in LPTSTR lpSymlinkFileName, - seems no long path prefix is required... linkPath.c_str(), //__in LPTSTR lpTargetFileName, (isDirLink ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0))) //__in DWORD dwFlags -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC if (::symlink(linkPath.c_str(), targetLink.c_str()) != 0) #endif throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", fmtFileName(sourceLink)), L"%y", fmtFileName(targetLink)) + @@ -2166,14 +2170,13 @@ void copyFileWindows(const Zstring& sourceFile, const Zstring& targetFile, Callb } } -#elif defined FFS_LINUX -void copyFileLinux(const Zstring& sourceFile, - const Zstring& targetFile, - CallbackCopyFile* callback, - FileAttrib* newAttrib) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting -{ - zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (...) {} }); //transactional behavior: place guard before lifetime of FileOutput +#elif defined FFS_LINUX || defined FFS_MAC +void copyFileLinuxMac(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackCopyFile* callback, + FileAttrib* newAttrib) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting +{ //open sourceFile for reading FileInputUnbuffered fileIn(sourceFile); //throw FileError, ErrorNotExisting @@ -2181,6 +2184,7 @@ void copyFileLinux(const Zstring& sourceFile, if (::fstat(fileIn.getDescriptor(), &sourceInfo) != 0) //read file attributes from source throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)) + L"\n\n" + getLastErrorFormatted()); + zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (...) {} }); //transactional behavior: place guard before lifetime of FileOutput try { //create targetFile and open it for writing @@ -2202,12 +2206,6 @@ void copyFileLinux(const Zstring& sourceFile, //adapt target file modification time: { - struct ::timeval newTimes[2] = {}; - newTimes[0].tv_sec = sourceInfo.st_atime; - newTimes[1].tv_sec = sourceInfo.st_mtime; - if (::futimes(fileOut.getDescriptor(), newTimes) != 0) //by using the already open file handle, we avoid issues like: https://sourceforge.net/p/freefilesync/bugs/230/ - throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted()); - //read and return file statistics struct ::stat targetInfo = {}; if (::fstat(fileOut.getDescriptor(), &targetInfo) != 0) @@ -2222,12 +2220,19 @@ void copyFileLinux(const Zstring& sourceFile, } } } - catch (ErrorTargetExisting&) + catch (const ErrorTargetExisting&) { guardTarget.dismiss(); //don't delete file that existed previously! throw; } + //we cannot set the target file times while the file descriptor is open and being written: + //this triggers bugs on samba shares where the modification time is set to current time instead. + //http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236 + //http://comments.gmane.org/gmane.linux.file-systems.cifs/2854 + //on the other hand we thereby have to reopen https://sourceforge.net/p/freefilesync/bugs/230/ + setFileTime(targetFile, sourceInfo.st_mtime, SYMLINK_FOLLOW); //throw FileError + guardTarget.dismiss(); //target has been created successfully! } #endif @@ -2246,14 +2251,14 @@ Zstring findUnusedTempName(const Zstring& filename) /* - File Copy Layers - ================ - - copyFile (setup transactional behavior) - | - copyFileSelectOs - / \ -copyFileLinux copyFileWindows (solve 8.3 issue) + ------------------ + |File Copy Layers| + ------------------ + copyFile (setup transactional behavior) + | + copyFileSelectOs + / \ +copyFileLinuxMac copyFileWindows (solve 8.3 issue) | copyFileWindowsSelectRoutine / \ @@ -2266,8 +2271,8 @@ void copyFileSelectOs(const Zstring& sourceFile, const Zstring& targetFile, Call #ifdef FFS_WIN copyFileWindows(sourceFile, targetFile, callback, sourceAttr); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked -#elif defined FFS_LINUX - copyFileLinux(sourceFile, targetFile, callback, sourceAttr); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting +#elif defined FFS_LINUX || defined FFS_MAC + copyFileLinuxMac(sourceFile, targetFile, callback, sourceAttr); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting #endif } } diff --git a/zen/file_id.cpp b/zen/file_id.cpp index 3d04ce34..7e0fa81b 100644 --- a/zen/file_id.cpp +++ b/zen/file_id.cpp @@ -11,7 +11,7 @@ #include "long_path_prefix.h" #include "scope_guard.h" -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC #include <sys/stat.h> #endif @@ -40,9 +40,9 @@ zen::FileId zen::getFileID(const Zstring& filename) return extractFileID(fileInfo); } -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC struct ::stat fileInfo = {}; - if (::stat(filename.c_str(), &fileInfo) == 0) //stat() follows symlinks + if (::lstat(filename.c_str(), &fileInfo) == 0) return extractFileID(fileInfo); #endif diff --git a/zen/file_id_def.h b/zen/file_id_def.h index 5f485c68..54012b4f 100644 --- a/zen/file_id_def.h +++ b/zen/file_id_def.h @@ -13,7 +13,7 @@ #ifdef FFS_WIN #include "win.h" //includes "windows.h" -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC #include <sys/stat.h> #endif @@ -49,7 +49,7 @@ assert_static(sizeof(FileId().second) == sizeof(BY_HANDLE_FILE_INFORMATION().nFi assert_static(sizeof(FileId().second) == sizeof(ULARGE_INTEGER)); -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC namespace impl { typedef struct ::stat StatDummy; } //sigh... typedef decltype(impl::StatDummy::st_dev) DeviceId; diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 788288ce..6581dfbe 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -12,7 +12,7 @@ #include "win_ver.h" #include "dll.h" -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC #include <fcntl.h> //open, close #include <unistd.h> //read, write #endif @@ -45,7 +45,7 @@ Zstring getLockingProcessNames(const Zstring& filename) //throw(), empty string return Zstring(); } -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC //"filename" could be a named pipe which *blocks* forever during "open()"! https://sourceforge.net/p/freefilesync/bugs/221/ void checkForUnsupportedType(const Zstring& filename) //throw FileError { @@ -64,7 +64,7 @@ void checkForUnsupportedType(const Zstring& filename) //throw FileError S_ISBLK (m) ? L"block device" : S_ISFIFO(m) ? L"FIFO, named pipe" : S_ISSOCK(m) ? L"socket" : nullptr; - const std::wstring numFmt = printNumber<std::wstring>(L"0%06o", m & __S_IFMT); + const std::wstring numFmt = printNumber<std::wstring>(L"0%06o", m & S_IFMT); return name ? numFmt + L", " + name : numFmt; }; throw FileError(replaceCpy(_("Type of item %x is not supported:"), L"%x", fmtFileName(filename)) + L" " + getTypeName(fileInfo.st_mode)); @@ -113,7 +113,7 @@ FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExis */ nullptr); if (fileHandle == INVALID_HANDLE_VALUE) -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC checkForUnsupportedType(filename); //throw FileError; reading a named pipe would block forever! fileHandle = ::fopen(filename.c_str(), "r,type=record,noseek"); //utilize UTF-8 filename if (!fileHandle) @@ -147,7 +147,7 @@ FileInput::~FileInput() { #ifdef FFS_WIN ::CloseHandle(fileHandle); -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC ::fclose(fileHandle); //NEVER allow passing nullptr to fclose! -> crash!; fileHandle != nullptr in this context! #endif } @@ -164,7 +164,7 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number static_cast<DWORD>(bytesToRead), //__in DWORD nNumberOfBytesToRead, &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead, nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC const size_t bytesRead = ::fread(buffer, 1, bytesToRead, fileHandle); if (::ferror(fileHandle) != 0) //checks status of stream, not fread()! #endif @@ -174,7 +174,7 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number if (bytesRead < bytesToRead) //verify only! setEof(); -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC if (::feof(fileHandle) != 0) setEof(); @@ -257,7 +257,7 @@ FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw Fil } } -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC checkForUnsupportedType(filename); //throw FileError; writing a named pipe would block forever! fileHandle = ::fopen(filename.c_str(), //GNU extension: https://www.securecoding.cert.org/confluence/display/cplusplus/FIO03-CPP.+Do+not+make+assumptions+about+fopen()+and+file+creation @@ -282,7 +282,7 @@ FileOutput::~FileOutput() { #ifdef FFS_WIN ::CloseHandle(fileHandle); -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC ::fclose(fileHandle); //NEVER allow passing nullptr to fclose! -> crash! #endif } @@ -297,7 +297,7 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro static_cast<DWORD>(bytesToWrite), //__in DWORD nNumberOfBytesToWrite, &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten, nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC const size_t bytesWritten = ::fwrite(buffer, 1, bytesToWrite, fileHandle); if (::ferror(fileHandle) != 0) //checks status of stream, not fwrite()! #endif @@ -308,7 +308,7 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro } -#ifdef FFS_LINUX +#if defined FFS_LINUX || defined FFS_MAC //Compare copy_reg() in copy.c: ftp://ftp.gnu.org/gnu/coreutils/coreutils-5.0.tar.gz FileInputUnbuffered::FileInputUnbuffered(const Zstring& filename) : FileInputBase(filename) //throw FileError, ErrorNotExisting diff --git a/zen/file_io.h b/zen/file_io.h index 31373857..8e501172 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -12,7 +12,7 @@ #ifdef FFS_WIN #include "win.h" //includes "windows.h" -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC #include <cstdio> #include <sys/stat.h> #endif @@ -24,13 +24,15 @@ namespace zen static const char LINE_BREAK[] = "\r\n"; #elif defined FFS_LINUX static const char LINE_BREAK[] = "\n"; +#elif defined FFS_MAC +static const char LINE_BREAK[] = "\r"; #endif //buffered file IO optimized for sequential read/write accesses + better error reporting + long path support (following symlinks) #ifdef FFS_WIN typedef HANDLE FileHandle; -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC typedef FILE* FileHandle; #endif @@ -62,8 +64,7 @@ private: FileHandle fileHandle; }; - -#ifdef FFS_LINUX +#if defined FFS_LINUX || defined FFS_MAC class FileInputUnbuffered : public FileInputBase { public: diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index 9a02eb1e..b39f8416 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -17,7 +17,7 @@ #include "dll.h" #include "FindFilePlus/find_file_plus.h" -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC #include <sys/stat.h> #include <dirent.h> #endif @@ -498,8 +498,7 @@ private: const DWORD volumeSerial; //may be 0! }; - -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC class DirTraverser { public: @@ -515,7 +514,7 @@ public: the buffer whose address is passed in entry as follows: len = offsetof(struct dirent, d_name) + pathconf(dirpath, _PC_NAME_MAX) + 1 entryp = malloc(len); */ - const long maxPath = std::max<long>(::pathconf(directoryFormatted.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return -1 + const size_t maxPath = std::max<long>(::pathconf(directoryFormatted.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return long(-1) buffer.resize(offsetof(struct ::dirent, d_name) + maxPath + 1); traverse(directoryFormatted, sink, 0); diff --git a/zen/file_traverser.h b/zen/file_traverser.h index c8ef6550..7e566075 100644 --- a/zen/file_traverser.h +++ b/zen/file_traverser.h @@ -61,7 +61,7 @@ struct DstHackCallback virtual ~DstHackCallback() {} virtual void requestUiRefresh(const Zstring& filename) = 0; //applying DST hack imposes significant one-time performance drawback => callback to inform user }; -#else +#elif defined FFS_LINUX || defined FFS_MAC struct DstHackCallback; //DST hack not required on Linux #endif diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index 6455029f..7cc43e6b 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -16,7 +16,7 @@ #include <zen/win.h> //includes "windows.h" #include <zen/win_ver.h> -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC #include <clocale> //thousands separator #include <zen/utf.h> // #endif @@ -26,53 +26,50 @@ using namespace zen; std::wstring zen::filesizeToShortString(Int64 size) { - //if (size < 0) return _("Error"); -> really? there's at least one exceptional case: a failed rename operation falls-back to copy + delete, reducing "bytes transferred" to potentially < 0! + //if (size < 0) return _("Error"); -> really? if (numeric::abs(size) <= 999) - return replaceCpy(_P("1 Byte", "%x Bytes", to<int>(size)), - L"%x", - numberTo<std::wstring>(size)); - else - { - double filesize = to<double>(size); + return replaceCpy(_P("1 Byte", "%x Bytes", to<int>(size)), L"%x", numberTo<std::wstring>(size)); - filesize /= 1024; - std::wstring output = _("%x KB"); - if (numeric::abs(filesize) > 999) - { - filesize /= 1024; - output = _("%x MB"); - if (numeric::abs(filesize) > 999) - { - filesize /= 1024; - output = _("%x GB"); - if (numeric::abs(filesize) > 999) - { - filesize /= 1024; - output = _("%x TB"); - if (numeric::abs(filesize) > 999) - { - filesize /= 1024; - output = _("%x PB"); - } - } - } - } + auto formatUnitSize = [](double sizeInUnit, const std::wstring& unitTxt) -> std::wstring + { //print just three significant digits: 0,01 | 0,11 | 1,11 | 11,1 | 111 - const size_t fullunits = static_cast<size_t>(numeric::abs(filesize)); + const size_t fullunits = static_cast<size_t>(numeric::abs(sizeInUnit)); const int precisionDigits = fullunits < 10 ? 2 : fullunits < 100 ? 1 : 0; //sprintf requires "int" wchar_t buffer[50]; #ifdef __MINGW32__ - int charsWritten = ::snwprintf(buffer, 50, L"%.*f", precisionDigits, filesize); //MinGW does not comply to the C standard here + int charsWritten = ::_snwprintf(buffer, 50, L"%.*f", precisionDigits, sizeInUnit); //MinGW does not comply to the C standard here #else - int charsWritten = std::swprintf(buffer, 50, L"%.*f", precisionDigits, filesize); + int charsWritten = std::swprintf(buffer, 50, L"%.*f", precisionDigits, sizeInUnit); #endif - return charsWritten > 0 ? replaceCpy(output, L"%x", std::wstring(buffer, charsWritten)) : _("Error"); - } + return charsWritten > 0 ? replaceCpy(unitTxt, L"%x", std::wstring(buffer, charsWritten)) : _("Error"); + }; + + double sizeInUnit = to<double>(size); + sizeInUnit /= 1024; + if (numeric::abs(sizeInUnit) <= 999) + return formatUnitSize(sizeInUnit, _("%x KB")); + + sizeInUnit /= 1024; + if (numeric::abs(sizeInUnit) <= 999) + return formatUnitSize(sizeInUnit, _("%x MB")); + + sizeInUnit /= 1024; + if (numeric::abs(sizeInUnit) <= 999) + return formatUnitSize(sizeInUnit, _("%x GB")); + + sizeInUnit /= 1024; + if (numeric::abs(sizeInUnit) <= 999) + return formatUnitSize(sizeInUnit, _("%x TB")); + + sizeInUnit /= 1024; + return formatUnitSize(sizeInUnit, _("%x PB")); } +namespace +{ enum UnitRemTime { URT_SEC, @@ -81,56 +78,75 @@ enum UnitRemTime URT_DAY }; -std::wstring zen::remainingTimeToShortString(double timeInSec) -{ - double remainingTime = timeInSec; - - //determine preferred unit - UnitRemTime unit = URT_SEC; - if (remainingTime > 59) - { - unit = URT_MIN; - remainingTime /= 60; - if (remainingTime > 59) - { - unit = URT_HOUR; - remainingTime /= 60; - if (remainingTime > 23) - { - unit = URT_DAY; - remainingTime /= 24; - } - } - } - - int formattedTime = numeric::round(remainingTime); - - //reduce precision to 5 seconds - if (unit == URT_SEC) - formattedTime = static_cast<int>(std::ceil(formattedTime / 5.0) * 5); - //generate output message - std::wstring output; +std::wstring formatUnitTime(int val, UnitRemTime unit) +{ + auto subst = [&](const std::wstring& output) { return replaceCpy(output, L"%x", zen::numberTo<std::wstring>(val)); }; switch (unit) { case URT_SEC: - output = _P("1 sec", "%x sec", formattedTime); - break; + return subst(_P("1 sec", "%x sec", val)); case URT_MIN: - output = _P("1 min", "%x min", formattedTime); - break; + return subst(_P("1 min", "%x min", val)); case URT_HOUR: - output = _P("1 hour", "%x hours", formattedTime); - break; + return subst(_P("1 hour", "%x hours", val)); case URT_DAY: - output = _P("1 day", "%x days", formattedTime); - break; + return subst(_P("1 day", "%x days", val)); } - return replaceCpy(output, L"%x", zen::numberTo<std::wstring>(formattedTime)); + assert(false); + return _("Error"); +} + + +template <int M, int N> +std::wstring roundToBlock(double timeHigh, + UnitRemTime unitHigh, const int (&stepsHigh)[M], + int unitLowPerHigh, + UnitRemTime unitLow, const int (&stepsLow)[N]) +{ + assert(unitLowPerHigh > 0); + const double granularity = 0.1; + const double timeLow = timeHigh * unitLowPerHigh; + const int blockSizeLow = granularity * timeHigh < 1 ? + numeric::nearMatch(granularity * timeLow, std::begin(stepsLow), std::end(stepsLow)): + numeric::nearMatch(granularity * timeHigh, std::begin(stepsHigh), std::end(stepsHigh)) * unitLowPerHigh; + const int roundedTimeLow = numeric::round(timeLow / blockSizeLow) * blockSizeLow; + + std::wstring output = formatUnitTime(roundedTimeLow / unitLowPerHigh, unitHigh); + if (unitLowPerHigh > blockSizeLow) + output += L" " + formatUnitTime(roundedTimeLow % unitLowPerHigh, unitLow); + return output; +}; } -std::wstring zen::fractionToShortString(double fraction) +std::wstring zen::remainingTimeToString(double timeInSec) +{ + const int steps10[] = { 1, 2, 5, 10 }; + const int steps24[] = { 1, 2, 3, 4, 6, 8, 12, 24 }; + const int steps60[] = { 1, 2, 5, 10, 15, 20, 30, 60 }; + + //determine preferred unit + double timeInUnit = timeInSec; + if (timeInUnit <= 60) + return roundToBlock(timeInUnit, URT_SEC, steps60, 1, URT_SEC, steps60); + + timeInUnit /= 60; + if (timeInUnit <= 60) + return roundToBlock(timeInUnit, URT_MIN, steps60, 60, URT_SEC, steps60); + + timeInUnit /= 60; + if (timeInUnit <= 24) + return roundToBlock(timeInUnit, URT_HOUR, steps24, 60, URT_MIN, steps60); + + timeInUnit /= 24; + return roundToBlock(timeInUnit, URT_DAY, steps10, 24, URT_HOUR, steps24); + //note: for 10% granularity steps10 yields a valid blocksize only up to timeInUnit == 100! + //for larger time sizes this results in a finer granularity than expected: 10 days -> should not be a problem considering "usual" remaining time for synchronization +} + + +std::wstring zen::fractionToString(double fraction) { //return replaceCpy(_("%x%"), L"%x", printNumber<std::wstring>(L"%3.2f", fraction * 100.0), false); return printNumber<std::wstring>(L"%3.2f", fraction * 100.0) + L'%'; //no need to internationalize fraction!? @@ -236,7 +252,7 @@ std::wstring zen::ffs_Impl::includeNumberSeparator(const std::wstring& number) } return number; -#else +#elif defined FFS_LINUX || defined FFS_MAC //we have to include thousands separator ourselves; this doesn't work for all countries (e.g india), but is better than nothing //::setlocale (LC_ALL, ""); -> implicitly called by wxLocale @@ -261,28 +277,6 @@ std::wstring zen::ffs_Impl::includeNumberSeparator(const std::wstring& number) #endif } -/* -#include <wx/scrolwin.h> - -void zen::scrollToBottom(wxScrolledWindow* scrWindow) -{ - int height = 0; - scrWindow->GetClientSize(nullptr, &height); - - int pixelPerLine = 0; - scrWindow->GetScrollPixelsPerUnit(nullptr, &pixelPerLine); - - if (height > 0 && pixelPerLine > 0) - { - const int scrollLinesTotal = scrWindow->GetScrollLines(wxVERTICAL); - const int scrollLinesOnScreen = height / pixelPerLine; - const int scrollPosBottom = scrollLinesTotal - scrollLinesOnScreen; - - if (0 <= scrollPosBottom) - scrWindow->Scroll(0, scrollPosBottom); - } -} -*/ #ifdef FFS_WIN namespace @@ -333,7 +327,7 @@ std::wstring zen::utcToLocalTimeString(Int64 utcTime) loc.minute = systemTimeLocal.wMinute; loc.second = systemTimeLocal.wSecond; -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC zen::TimeComp loc = zen::localTime(to<time_t>(utcTime)); #endif diff --git a/zen/format_unit.h b/zen/format_unit.h index f634e5e5..86477999 100644 --- a/zen/format_unit.h +++ b/zen/format_unit.h @@ -8,14 +8,14 @@ #define FMT_UNIT_8702184019487324 #include <string> -#include <zen/string_tools.h> #include <zen/int64.h> +#include <zen/string_tools.h> namespace zen { std::wstring filesizeToShortString(Int64 filesize); -std::wstring remainingTimeToShortString(double timeInSec); -std::wstring fractionToShortString(double fraction); //within [0, 1] +std::wstring remainingTimeToString(double timeInSec); +std::wstring fractionToString(double fraction); //within [0, 1] template <class NumberType> std::wstring toGuiString(NumberType number); //format integer number including thousands separator @@ -10,7 +10,7 @@ #include <string> #include <boost/uuid/uuid.hpp> -#ifdef __MINGW32__ //boost should start cleaning this mess up +#ifdef __GNUC__ //boost should start cleaning this mess up #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wshadow" #pragma GCC diagnostic ignored "-Wuninitialized" @@ -18,7 +18,7 @@ #include <boost/uuid/uuid_generators.hpp> -#ifdef __MINGW32__ +#ifdef __GNUC__ #pragma GCC diagnostic pop #endif diff --git a/zen/int64.h b/zen/int64.h index 238efb14..3e967df8 100644 --- a/zen/int64.h +++ b/zen/int64.h @@ -182,7 +182,7 @@ public: inline friend bool operator>=(const UInt64& lhs, const UInt64& rhs) { return lhs.value >= rhs.value; } //checked conversion to arbitrary target integer type - template <class T> inline friend T to(UInt64 number) { checkRange<T>(number.value); return static_cast<T>(number.value); } + template <class T> friend T to(UInt64 number); template <class T> inline friend std::basic_istream<T>& operator>>(std::basic_istream<T>& lhs, UInt64& rhs) { lhs >> rhs.value; return lhs; } template <class T> inline friend std::basic_ostream<T>& operator<<(std::basic_ostream<T>& lhs, const UInt64& rhs) { lhs << rhs.value; return lhs; } @@ -191,6 +191,8 @@ private: std::uint64_t value; }; +template <class T> inline T to(UInt64 number) { checkRange<T>(number.value); return static_cast<T>(number.value); } //Clang 3.2 doesn't properly handle inline-friends defined in class definition + inline UInt64 operator+ (const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) += rhs; } inline UInt64 operator- (const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) -= rhs; } inline UInt64 operator* (const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) *= rhs; } diff --git a/zen/last_error.h b/zen/last_error.h index 72d54d48..ddee552f 100644 --- a/zen/last_error.h +++ b/zen/last_error.h @@ -14,7 +14,7 @@ #ifdef FFS_WIN #include "win.h" //includes "windows.h" -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC #include <cstring> #include <cerrno> #endif @@ -25,7 +25,7 @@ namespace zen //evaluate GetLastError()/errno and assemble specific error message #ifdef FFS_WIN typedef DWORD ErrorCode; -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC typedef int ErrorCode; #endif @@ -42,40 +42,13 @@ bool errorCodeForNotExisting(ErrorCode lastError); //check for "not existing" al - - - - - - - - - - - - - - - - - - - - - - - - - - - //######################## implementation ######################## inline ErrorCode getLastError() { #ifdef FFS_WIN return ::GetLastError(); -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC return errno; //don't use "::", errno is a macro! #endif } @@ -87,17 +60,16 @@ std::wstring getLastErrorFormatted(ErrorCode lastError) if (lastError == 0) lastError = getLastError(); -#ifdef FFS_WIN - std::wstring output = _("Windows Error Code %x:"); + std::wstring output = _("Error Code %x:"); replace(output, L"%x", numberTo<std::wstring>(lastError)); - +#ifdef FFS_WIN LPWSTR buffer = nullptr; if (::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_MAX_WIDTH_MASK | FORMAT_MESSAGE_IGNORE_INSERTS | //important: without this flag ::FormatMessage() will fail if message contains placeholders FORMAT_MESSAGE_ALLOCATE_BUFFER, nullptr, lastError, 0, reinterpret_cast<LPWSTR>(&buffer), 0, nullptr) != 0) { - if (buffer) //just to be sure + if (buffer) //"don't trust nobody" { output += L" "; output += buffer; @@ -105,18 +77,14 @@ std::wstring getLastErrorFormatted(ErrorCode lastError) } } ::SetLastError(lastError); //restore last error - return output; - -#elif defined FFS_LINUX - std::wstring output = _("Linux Error Code %x:"); - replace(output, L"%x", numberTo<std::wstring>(lastError)); +#elif defined FFS_LINUX || defined FFS_MAC output += L" "; output += utfCvrtTo<std::wstring>(::strerror(lastError)); errno = lastError; //restore errno - return output; #endif + return output; } @@ -128,8 +96,7 @@ bool errorCodeForNotExisting(ErrorCode lastError) lastError == ERROR_PATH_NOT_FOUND || lastError == ERROR_BAD_NETPATH || lastError == ERROR_NETNAME_DELETED; - -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC return lastError == ENOENT; #endif } diff --git a/zen/process_priority.cpp b/zen/process_priority.cpp new file mode 100644 index 00000000..96a6c2de --- /dev/null +++ b/zen/process_priority.cpp @@ -0,0 +1,149 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#include "process_priority.h" +#include <zen/last_error.h> +#include <zen/i18n.h> + +#ifdef FFS_WIN +#include "win.h" //includes "windows.h" + +#elif defined FFS_LINUX +//#include <sys/syscall.h> + +#elif defined FFS_MAC +#include <sys/resource.h> //getiopolicy_np +#include <IOKit/pwr_mgt/IOPMLib.h> //keep in .cpp file to not pollute global namespace! e.g. with UInt64 +#endif + +using namespace zen; + + +#ifdef FFS_WIN +struct PreventStandby::Pimpl {}; + +PreventStandby::PreventStandby() +{ + if (::SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) == 0) + throw FileError(_("Failed to suspend system sleep mode.")); //no GetLastError() support? +} + + +PreventStandby::~PreventStandby() +{ + ::SetThreadExecutionState(ES_CONTINUOUS); +} + + +#ifndef PROCESS_MODE_BACKGROUND_BEGIN +#define PROCESS_MODE_BACKGROUND_BEGIN 0x00100000 // Windows Server 2003 and Windows XP/2000: This value is not supported! +#define PROCESS_MODE_BACKGROUND_END 0x00200000 // +#endif + +struct ScheduleForBackgroundProcessing::Pimpl {}; + + +ScheduleForBackgroundProcessing::ScheduleForBackgroundProcessing() +{ + if (!::SetPriorityClass(::GetCurrentProcess(), PROCESS_MODE_BACKGROUND_BEGIN)) //this call lowers CPU priority, too!! + throw FileError(_("Cannot change process I/O priorities.") + L"\n\n" + getLastErrorFormatted()); +} + + +ScheduleForBackgroundProcessing::~ScheduleForBackgroundProcessing() +{ + ::SetPriorityClass(::GetCurrentProcess(), PROCESS_MODE_BACKGROUND_END); +} + +#elif defined FFS_LINUX +struct PreventStandby::Pimpl {}; +PreventStandby::PreventStandby() {} +PreventStandby::~PreventStandby() {} + +//solution for GNOME?: http://people.gnome.org/~mccann/gnome-session/docs/gnome-session.html#org.gnome.SessionManager.Inhibit + +struct ScheduleForBackgroundProcessing::Pimpl {}; +ScheduleForBackgroundProcessing::ScheduleForBackgroundProcessing() {}; +ScheduleForBackgroundProcessing::~ScheduleForBackgroundProcessing() {}; + +/* +struct ScheduleForBackgroundProcessing +{ + - required functions ioprio_get/ioprio_set are not part of glibc: http://linux.die.net/man/2/ioprio_set + - and probably never will: http://sourceware.org/bugzilla/show_bug.cgi?id=4464 + - /usr/include/linux/ioprio.h not available on Ubuntu, so we can't use it instead + + ScheduleForBackgroundProcessing() : oldIoPrio(getIoPriority(IOPRIO_WHO_PROCESS, ::getpid())) + { + if (oldIoPrio != -1) + setIoPriority(::getpid(), IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0)); + } + ~ScheduleForBackgroundProcessing() + { + if (oldIoPrio != -1) + setIoPriority(::getpid(), oldIoPrio); + } + +private: + static int getIoPriority(pid_t pid) + { + return ::syscall(SYS_ioprio_get, IOPRIO_WHO_PROCESS, pid); + } + static int setIoPriority(pid_t pid, int ioprio) + { + return ::syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, pid, ioprio); + } + + const int oldIoPrio; +}; +*/ + +#elif defined FFS_MAC +//https://developer.apple.com/library/mac/#qa/qa1340 +struct PreventStandby::Pimpl +{ + Pimpl() : assertionID() {} + IOPMAssertionID assertionID; +}; + +PreventStandby::PreventStandby() : pimpl(make_unique<Pimpl>()) +{ + if (::IOPMAssertionCreateWithName(kIOPMAssertionTypeNoIdleSleep, + kIOPMAssertionLevelOn, + CFSTR("FreeFileSync"), + &pimpl->assertionID) != kIOReturnSuccess) + throw FileError(_("Failed to suspend system sleep mode.")); +} + +PreventStandby::~PreventStandby() +{ + ::IOPMAssertionRelease(pimpl->assertionID); +} + + +struct ScheduleForBackgroundProcessing::Pimpl +{ + Pimpl() : oldIoPrio() {} + int oldIoPrio; +}; + + +ScheduleForBackgroundProcessing::ScheduleForBackgroundProcessing() : pimpl(make_unique<Pimpl>()) +{ + pimpl->oldIoPrio = ::getiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS); + if (pimpl->oldIoPrio == -1) + throw FileError(_("Cannot change process I/O priorities.") + L" (r)" + L"\n\n" + getLastErrorFormatted()); + + if (::setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_THROTTLE) != 0) + throw FileError(_("Cannot change process I/O priorities.") + L" (w)" + L"\n\n" + getLastErrorFormatted()); +} + + +ScheduleForBackgroundProcessing::~ScheduleForBackgroundProcessing() +{ + ::setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, pimpl->oldIoPrio); +} +#endif
\ No newline at end of file diff --git a/zen/process_priority.h b/zen/process_priority.h index c0bae667..78e49cd0 100644 --- a/zen/process_priority.h +++ b/zen/process_priority.h @@ -1,58 +1,36 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** #ifndef PREVENTSTANDBY_H_INCLUDED #define PREVENTSTANDBY_H_INCLUDED -#ifdef FFS_WIN -#include "win.h" //includes "windows.h" - -#elif defined FFS_LINUX -#endif +#include <memory> +#include <zen/file_error.h> namespace zen { -struct PreventStandby //signal a "busy" state to the operating system +//signal a "busy" state to the operating system +class PreventStandby { -#ifdef FFS_WIN - PreventStandby () { ::SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED /* | ES_AWAYMODE_REQUIRED*/ ); } - ~PreventStandby() { ::SetThreadExecutionState(ES_CONTINUOUS); } -#endif +public: + PreventStandby(); //throw FileError + ~PreventStandby(); +private: + struct Pimpl; + std::unique_ptr<Pimpl> pimpl; }; - -struct ScheduleForBackgroundProcessing //lower CPU and file I/O priorities +//lower CPU and file I/O priorities +class ScheduleForBackgroundProcessing { -#ifdef FFS_WIN -#ifndef PROCESS_MODE_BACKGROUND_BEGIN -#define PROCESS_MODE_BACKGROUND_BEGIN 0x00100000 // Windows Server 2003 and Windows XP/2000: This value is not supported! -#define PROCESS_MODE_BACKGROUND_END 0x00200000 // -#endif - - ScheduleForBackgroundProcessing () { ::SetPriorityClass(::GetCurrentProcess(), PROCESS_MODE_BACKGROUND_BEGIN); } //this call lowers CPU priority, too!! - ~ScheduleForBackgroundProcessing() { ::SetPriorityClass(::GetCurrentProcess(), PROCESS_MODE_BACKGROUND_END ); } - -#elif defined FFS_LINUX - /* - CPU prio: - int getpriority(PRIO_PROCESS, 0); - errno caveat! - int ::setpriority(PRIO_PROCESS, 0, int prio); //a zero value for "who" denotes the calling process - - priority can be decreased, but NOT increased :( - - file I/O prio: - ScheduleForBackgroundProcessing() : oldIoPrio(::ioprio_get(IOPRIO_WHO_PROCESS, ::getpid())) - { - if (oldIoPrio != -1) - ::ioprio_set(IOPRIO_WHO_PROCESS, ::getpid(), IOPRIO_PRIO_VALUE(IOPRIO_CLASS_IDLE, 0)); - } - ~ScheduleForBackgroundProcessing() - { - if (oldIoPrio != -1) - ::ioprio_set(IOPRIO_WHO_PROCESS, ::getpid(), oldIoPrio); - } - - private: - const int oldIoPrio; - */ -#endif +public: + ScheduleForBackgroundProcessing(); //throw FileError + ~ScheduleForBackgroundProcessing(); +private: + struct Pimpl; + std::unique_ptr<Pimpl> pimpl; }; } diff --git a/zen/read_txt.cpp b/zen/read_txt.cpp index 0fd310c2..7566ff14 100644 --- a/zen/read_txt.cpp +++ b/zen/read_txt.cpp @@ -5,6 +5,7 @@ using namespace zen; namespace { +warn_static("superfluous method") std::string detectLineBreak(const Zstring& filename) //throw FileError { //read a (hopefully) significant portion of data @@ -51,6 +52,7 @@ ExtractLines::ExtractLines(const Zstring& filename, const std::string& lineBreak bool ExtractLines::getLine(std::string& output) //throw FileError { + warn_static("don't use lineBreak, but support any of r, n, rn!!!") for (;;) { //check if full line is in buffer diff --git a/zen/recycler.cpp b/zen/recycler.cpp index 07803e50..2a82cd24 100644 --- a/zen/recycler.cpp +++ b/zen/recycler.cpp @@ -5,14 +5,13 @@ // ************************************************************************** #include "recycler.h" -#include <stdexcept> -#include <iterator> +//#include <stdexcept> +//#include <iterator> #include <zen/file_handling.h> #ifdef FFS_WIN -#include <algorithm> -#include <functional> -#include <vector> +//#include <algorithm> +//#include <functional> #include <zen/dll.h> #include <zen/win.h> //includes "windows.h" #include <zen/assert_static.h> @@ -24,6 +23,9 @@ #include <zen/scope_guard.h> #include <sys/stat.h> #include <gio/gio.h> + +#elif defined FFS_MAC +#include <CoreServices/CoreServices.h> #endif using namespace zen; @@ -142,7 +144,7 @@ void zen::recycleOrDelete(const std::vector<Zstring>& filenames, CallbackRecycli bool zen::recycleOrDelete(const Zstring& filename) //throw FileError { - if (!somethingExists(filename)) + if (!somethingExists(filename)) //[!] do not optimize away, OS X needs this for reliable detection of "recycle bin missing" return false; //neither file nor any other object with that name existing: no error situation, manual deletion relies on it! #ifdef FFS_WIN @@ -151,13 +153,13 @@ bool zen::recycleOrDelete(const Zstring& filename) //throw FileError recycleOrDelete(filenames, nullptr); //throw FileError #elif defined FFS_LINUX - GFile* file = g_file_new_for_path(filename.c_str()); //never fails according to docu + GFile* file = ::g_file_new_for_path(filename.c_str()); //never fails according to docu ZEN_ON_SCOPE_EXIT(g_object_unref(file);) GError* error = nullptr; - ZEN_ON_SCOPE_EXIT(if (error) g_error_free(error);); + ZEN_ON_SCOPE_EXIT(if (error) ::g_error_free(error);); - if (!g_file_trash(file, nullptr, &error)) + if (!::g_file_trash(file, nullptr, &error)) { const std::wstring shortMsg = replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filename)); @@ -181,6 +183,53 @@ bool zen::recycleOrDelete(const Zstring& filename) //throw FileError throw FileError(shortMsg + L"\n\n" + L"Glib Error Code " + numberTo<std::wstring>(error->code) + /* L", " + g_quark_to_string(error->domain) + */ L": " + utfCvrtTo<std::wstring>(error->message)); } + +#elif defined FFS_MAC + //we cannot use FSPathMoveObjectToTrashSync directly since it follows symlinks! + + assert_static(sizeof(Zchar) == sizeof(char)); + const UInt8* filenameUtf8 = reinterpret_cast<const UInt8*>(filename.c_str()); + + auto throwFileError = [&](OSStatus oss) + { + std::wstring msg = replaceCpy(_("Unable to move %x to the Recycle Bin!"), L"%x", fmtFileName(filename)) + L"\n\n" + + L"Result Code " + numberTo<std::wstring>(oss); + const char* description = GetMacOSStatusCommentString(oss); + if (description) //found no documentation for proper use of GetMacOSStatusCommentString + msg += L": " + utfCvrtTo<std::wstring>(description); + throw FileError(msg); + }; + + FSRef objectRef; + OSStatus rv = ::FSPathMakeRefWithOptions(filenameUtf8, //const UInt8 *path, + kFSPathMakeRefDoNotFollowLeafSymlink, //OptionBits options, + &objectRef, //FSRef *ref, + nullptr); //Boolean *isDirectory + if (rv != noErr) + throwFileError(rv); + + //deprecated since OS X 10.8!!! "trashItemAtURL" should be used instead + OSStatus rv2 = ::FSMoveObjectToTrashSync(&objectRef, //const FSRef *source, + nullptr, //FSRef *target, + kFSFileOperationDefaultOptions); //OptionBits options + if (rv2 != noErr) + { + //implement same behavior as in Windows: if recycler is not existing, delete permanently + if (rv2 == -120) //=="Directory not found or incomplete pathname." but should really be "recycle bin directory not found"! + { + struct stat fileInfo = {}; + if (::lstat(filename.c_str(), &fileInfo) != 0) + return false; + + if (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode)) + removeFile(filename); //throw FileError + else if (S_ISDIR(fileInfo.st_mode)) + removeDirectory(filename); //throw FileError + return true; + } + + throwFileError(rv2); + } #endif return true; } @@ -275,7 +324,7 @@ StatusRecycler zen::recycleBinStatus(const Zstring& pathName) */ } -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC /* We really need access to a similar function to check whether a directory supports trashing and emit a warning if it does not! diff --git a/zen/symlink_target.h b/zen/symlink_target.h index 1a616559..95aa84fb 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -16,7 +16,7 @@ #include "privilege.h" #include "long_path_prefix.h" -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC #include <unistd.h> #endif @@ -39,14 +39,6 @@ Zstring getSymlinkRawTargetString(const Zstring& linkPath); //throw FileError - - - - - - - - //################################ implementation ################################ #ifdef _MSC_VER //I don't have Windows Driver Kit at hands right now, so unfortunately we need to redefine this structures and cross fingers... typedef struct _REPARSE_DATA_BUFFER //from ntifs.h @@ -145,7 +137,7 @@ Zstring getSymlinkRawTargetString_impl(const Zstring& linkPath) //throw FileErro return output; -#elif defined FFS_LINUX +#elif defined FFS_LINUX || defined FFS_MAC const int BUFFER_SIZE = 10000; std::vector<char> buffer(BUFFER_SIZE); diff --git a/zen/thread.h b/zen/thread.h index 43917d13..ae865cc8 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -11,7 +11,7 @@ #include <memory> //fix this pathetic boost thread warning mess -#ifdef __MINGW32__ +#ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-enum" #pragma GCC diagnostic ignored "-Wstrict-aliasing" @@ -24,7 +24,7 @@ #include <boost/thread.hpp> -#ifdef __MINGW32__ +#ifdef __GNUC__ #pragma GCC diagnostic pop #endif #ifdef _MSC_VER @@ -34,8 +34,8 @@ namespace zen { /* -std::async replacement without crappy semantics: - 1. guaranteed to run asynchronous +std::async replacement without crappy semantics: + 1. guaranteed to run asynchronous 2. does not follow C++11 [futures.async], Paragraph 5, where std::future waits for thread in destructor Example: @@ -79,15 +79,6 @@ private: - - - - - - - - - //###################### implementation ###################### #ifndef BOOST_HAS_THREADS #error just some paranoia check... @@ -99,7 +90,7 @@ auto async2(Function fun) -> boost::unique_future<T> //support for workaround of #if defined BOOST_THREAD_PROVIDES_SIGNATURE_PACKAGED_TASK //mirror "boost/thread/future.hpp", hopefully they know what they're doing boost::packaged_task<T()> pt(std::move(fun)); //packaged task seems to even require r-value reference: https://sourceforge.net/p/freefilesync/bugs/234/ #else - boost::packaged_task<T> pt(std::move(fun)); + boost::packaged_task<T> pt(std::move(fun)); #endif auto fut = pt.get_future(); boost::thread(std::move(pt)).detach(); //we have to explicitly detach since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!! diff --git a/zen/tick_count.h b/zen/tick_count.h index 04ac0902..be4839ca 100644 --- a/zen/tick_count.h +++ b/zen/tick_count.h @@ -8,9 +8,18 @@ #define ZEN_TICK_COUNT_HEADER_3807326 #include <cstdint> -//#include <algorithm> #include "type_traits.h" #include "basic_math.h" +#ifdef FFS_WIN +#include "win.h" //includes "windows.h" + +#elif defined FFS_LINUX +#include <time.h> //Posix ::clock_gettime() + +#elif defined FFS_MAC +#include <mach/mach_time.h> +#endif +//#include <algorithm> //#include "assert_static.h" //#include <cmath> //template <class T> inline @@ -20,11 +29,6 @@ //} -#ifdef FFS_WIN -#include "win.h" //includes "windows.h" -#elif defined FFS_LINUX -#include <time.h> //Posix ::clock_gettime() -#endif namespace zen { @@ -57,6 +61,8 @@ public: typedef LARGE_INTEGER NativeVal; #elif defined FFS_LINUX typedef timespec NativeVal; +#elif defined FFS_MAC + typedef uint64_t NativeVal; #endif TickVal() : val_() {} @@ -67,7 +73,6 @@ public: { #ifdef FFS_WIN return numeric::dist(lhs.val_.QuadPart, rhs.val_.QuadPart); //std::abs(a - b) can lead to overflow! - #elif defined FFS_LINUX const auto distSec = numeric::dist(lhs.val_.tv_sec, rhs.val_.tv_sec); const auto distNsec = numeric::dist(lhs.val_.tv_nsec, rhs.val_.tv_nsec); @@ -75,11 +80,13 @@ public: if (distSec > (std::numeric_limits<std::int64_t>::max() - distNsec) / 1000000000) //truncate instead of overflow! return std::numeric_limits<std::int64_t>::max(); return distSec * 1000000000 + distNsec; +#elif defined FFS_MAC + return numeric::dist(lhs.val_, rhs.val_); #endif } inline friend - bool operator<(const TickVal& lhs, const TickVal& rhs) //evaluate directly rather than reuse operator- + bool operator<(const TickVal& lhs, const TickVal& rhs) { #ifdef FFS_WIN return lhs.val_.QuadPart < rhs.val_.QuadPart; @@ -87,6 +94,8 @@ public: if (lhs.val_.tv_sec != rhs.val_.tv_sec) return lhs.val_.tv_sec < rhs.val_.tv_sec; return lhs.val_.tv_nsec < rhs.val_.tv_nsec; +#elif defined FFS_MAC + return lhs.val_ < rhs.val_; #endif } @@ -109,6 +118,12 @@ std::int64_t ticksPerSec() //return 0 on error #elif defined FFS_LINUX return 1000000000; //precision: nanoseconds + +#elif defined FFS_MAC + mach_timebase_info_data_t tbi = {}; + if (::mach_timebase_info(&tbi) != KERN_SUCCESS) + return 0; + return 1000000000 * tbi.denom / tbi.numer; #endif } @@ -126,6 +141,9 @@ TickVal getTicks() //return 0 on error timespec now = {}; if (::clock_gettime(CLOCK_MONOTONIC_RAW, &now) != 0) //CLOCK_MONOTONIC measures time reliably across processors! return TickVal(); + +#elif defined FFS_MAC + uint64_t now = ::mach_absolute_time(); //can this call fail??? #endif return TickVal(now); } diff --git a/zen/warn_static.h b/zen/warn_static.h index 70679a12..737a56fb 100644 --- a/zen/warn_static.h +++ b/zen/warn_static.h @@ -26,9 +26,8 @@ Usage: #define ZEN_CONCAT(X, Y) ZEN_CONCAT_SUB(X, Y) #define warn_static(TXT) \ - typedef int STATIC_WARNING __attribute__ ((deprecated)); \ - enum { ZEN_CONCAT(warn_static_dummy_value, __LINE__) = sizeof(STATIC_WARNING) }; + typedef int STATIC_WARNING_87903124 __attribute__ ((deprecated)); \ + enum { ZEN_CONCAT(warn_static_dummy_value, __LINE__) = sizeof(STATIC_WARNING_87903124) }; #endif - -#endif //WARN_STATIC_HEADER_08724567834560832745
\ No newline at end of file +#endif //WARN_STATIC_HEADER_08724567834560832745 diff --git a/zen/zstring.cpp b/zen/zstring.cpp index 7f4e79db..b371e598 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -6,93 +6,141 @@ #include "zstring.h" #include <stdexcept> -#include <zen/stl_tools.h> #ifdef FFS_WIN #include "dll.h" #include "win_ver.h" + +#elif defined FFS_MAC +#include <ctype.h> //toupper() #endif #ifndef NDEBUG +#include "thread.h" //includes <boost/thread.hpp> #include <iostream> -#include <cstdlib> #endif using namespace zen; #ifndef NDEBUG -LeakChecker::~LeakChecker() +namespace { - if (!activeStrings.empty()) +class LeakChecker //small test for memory leaks +{ +public: + void insert(const void* ptr, size_t size) { - std::string leakingStrings; + boost::lock_guard<boost::mutex> dummy(lockActStrings); + if (activeStrings.find(ptr) != activeStrings.end()) + reportProblem("Fatal Error: New memory points into occupied space: " + rawMemToString(ptr, size)); - int items = 0; - for (auto it = activeStrings.begin(); it != activeStrings.end() && items < 20; ++it, ++items) - leakingStrings += "\"" + rawMemToString(it->first, it->second) + "\"\n"; + activeStrings[ptr] = size; + } - const std::string message = std::string("Memory leak detected!") + "\n\n" - + "Candidates:\n" + leakingStrings; -#ifdef FFS_WIN - MessageBoxA(nullptr, message.c_str(), "Error", 0); -#else - std::cerr << message; - std::abort(); -#endif + void remove(const void* ptr) + { + boost::lock_guard<boost::mutex> dummy(lockActStrings); + if (activeStrings.find(ptr) == activeStrings.end()) + reportProblem("Fatal Error: No memory available for deallocation at this location!"); + + activeStrings.erase(ptr); } -} + static LeakChecker& instance() { static LeakChecker inst; return inst; } -LeakChecker& LeakChecker::instance() -{ - static LeakChecker inst; - return inst; -} +private: + LeakChecker() {} + ~LeakChecker() + { + if (!activeStrings.empty()) + { + std::string leakingStrings; -//caveat: function scope static initialization is not thread-safe in VS 2010! => make sure to call at app start! -namespace -{ -const LeakChecker& dummy = LeakChecker::instance(); -} + 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 FFS_WIN + MessageBoxA(nullptr, message.c_str(), "Error", 0); +#else + std::cerr << message; + std::abort(); +#endif + } + } -std::string LeakChecker::rawMemToString(const void* ptr, size_t size) -{ - std::string output = std::string(reinterpret_cast<const char*>(ptr), size); - vector_remove_if(output, [](char& c) { return c == 0; }); //remove intermediate 0-termination - if (output.size() > 100) - output.resize(100); - return output; -} + LeakChecker(const LeakChecker&); + LeakChecker& operator=(const LeakChecker&); + static std::string rawMemToString(const void* ptr, size_t size) + { + std::string output(reinterpret_cast<const char*>(ptr), size); + vector_remove_if(output, [](char& c) { return c == 0; }); //remove intermediate 0-termination + if (output.size() > 100) + output.resize(100); + return output; + } -void LeakChecker::reportProblem(const std::string& message) //throw std::logic_error -{ + void reportProblem(const std::string& message) //throw std::logic_error + { #ifdef FFS_WIN - ::MessageBoxA(nullptr, message.c_str(), "Error", 0); + ::MessageBoxA(nullptr, message.c_str(), "Error", 0); #else - std::cerr << message; + std::cerr << message; #endif - throw std::logic_error("Memory leak! " + message); + throw std::logic_error("Memory leak! " + message); + } + + boost::mutex lockActStrings; + zen::hash_map<const void*, size_t> activeStrings; +}; + +//caveat: function scope static initialization is not thread-safe in VS 2010! => make sure to call at app start! +const LeakChecker& dummy = LeakChecker::instance(); } + +void z_impl::leakCheckerInsert(const void* ptr, size_t size) { LeakChecker::instance().insert(ptr, size); } +void z_impl::leakCheckerRemove(const void* ptr ) { LeakChecker::instance().remove(ptr); } #endif //NDEBUG +/* +Perf test: compare strings 10 mio times; 64 bit build +----------------------------------------------------- + string a = "Fjk84$%kgfj$%T\\\\Gffg\\gsdgf\\fgsx----------d-" + string b = "fjK84$%kgfj$%T\\\\gfFg\\gsdgf\\fgSy----------dfdf" + +Windows (UTF16 wchar_t) + 4 ns | wcscmp + 67 ns | CompareStringOrdinalFunc+ + bIgnoreCase +314 ns | LCMapString + wmemcmp + +OS X (UTF8 char) + 6 ns | strcmp + 98 ns | strcasecmp + 120 ns | strncasecmp + std::min(sizeLhs, sizeRhs); + 856 ns | CFStringCreateWithCString + CFStringCompare(kCFCompareCaseInsensitive) +1110 ns | CFStringCreateWithCStringNoCopy + CFStringCompare(kCFCompareCaseInsensitive) +________________________ +time per call | function +*/ + + #ifdef FFS_WIN namespace { -#ifndef LOCALE_INVARIANT //invariant locale has been introduced with XP -#define LOCALE_INVARIANT 0x007f +#ifndef LOCALE_INVARIANT //not known to MinGW +#define LOCALE_INVARIANT 0x007f #endif - //warning: LOCALE_INVARIANT is NOT available with Windows 2000, so we have to make yet another distinction... const LCID ZSTRING_INVARIANT_LOCALE = zen::winXpOrLater() ? LOCALE_INVARIANT : MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); //see: http://msdn.microsoft.com/en-us/goglobal/bb688122.aspx - //try to call "CompareStringOrdinal" for low-level string comparison: unfortunately available not before Windows Vista! //by a factor ~3 faster than old string comparison using "LCMapString" typedef int (WINAPI* CompareStringOrdinalFunc)(LPCWSTR lpString1, @@ -104,7 +152,7 @@ const SysDllFun<CompareStringOrdinalFunc> compareStringOrdinal = SysDllFun<Compa } -int z_impl::compareFilenamesWin(const wchar_t* lhs, const wchar_t* rhs, size_t sizeLhs, size_t sizeRhs) +int z_impl::compareFilenamesNoCase(const wchar_t* lhs, const wchar_t* rhs, size_t sizeLhs, size_t sizeRhs) { //caveat: function scope static initialization is not thread-safe in VS 2010! if (compareStringOrdinal) //this additional test has no noticeable performance impact @@ -165,26 +213,12 @@ int z_impl::compareFilenamesWin(const wchar_t* lhs, const wchar_t* rhs, size_t s return rv; } } - return static_cast<int>(sizeLhs) - static_cast<int>(sizeRhs); } - - // const int rv = CompareString( - // invariantLocale, //locale independent - // NORM_IGNORECASE | SORT_STRINGSORT, //comparison-style options - // a, //pointer to first string - // aCount, //size, in bytes or characters, of first string - // b, //pointer to second string - // bCount); //size, in bytes or characters, of second string - // - // if (rv == 0) - // throw std::runtime_error("Error comparing strings!"); - // else - // return rv - 2; //convert to C-style string compare result } -void z_impl::makeUpperCaseWin(wchar_t* str, size_t size) +void z_impl::makeFilenameUpperCase(wchar_t* str, size_t size) { if (size == 0) //LCMapString does not allow input sizes of 0! return; @@ -194,38 +228,10 @@ void z_impl::makeUpperCaseWin(wchar_t* str, size_t size) throw std::runtime_error("Error converting to upper case! (LCMapString)"); } - -/* -#include <fstream> - extern std::wofstream statShared; -extern std::wofstream statLowCapacity; -extern std::wofstream statOverwrite; - -std::wstring p(ptr); -p.erase(std::remove(p.begin(), p.end(), L'\n'), p.end()); -p.erase(std::remove(p.begin(), p.end(), L','), p.end()); - - if (descr(ptr)->refCount > 1) - statShared << - minCapacity << L"," << - descr(ptr)->refCount << L"," << - descr(ptr)->length << L"," << - descr(ptr)->capacity << L"," << - p << L"\n"; -else if (minCapacity > descr(ptr)->capacity) - statLowCapacity << - minCapacity << L"," << - descr(ptr)->refCount << L"," << - descr(ptr)->length << L"," << - descr(ptr)->capacity << L"," << - p << L"\n"; -else - statOverwrite << - minCapacity << L"," << - descr(ptr)->refCount << L"," << - descr(ptr)->length << L"," << - descr(ptr)->capacity << L"," << - p << L"\n"; -*/ - -#endif //FFS_WIN +#elif defined FFS_MAC +void z_impl::makeFilenameUpperCase(char* str, size_t size) +{ + std::for_each(str, str + size, [](char& c) { c = static_cast<char>(::toupper(static_cast<unsigned char>(c))); }); //locale-dependent! + //result of toupper() is an unsigned char mapped to int range, so the char representation is in the last 8 bits and we need not care about signedness! +} +#endif diff --git a/zen/zstring.h b/zen/zstring.h index df96df7b..f4a79181 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -7,72 +7,38 @@ #ifndef ZSTRING_H_INCLUDED #define ZSTRING_H_INCLUDED -#include <cstring> //strcmp() #include "string_base.h" - -#ifndef NDEBUG -#include "thread.h" //includes <boost/thread.hpp> -#include <map> +#ifdef FFS_LINUX +#include <cstring> //strcmp +#elif defined FFS_MAC +#include <strings.h> //strcasecmp #endif #ifndef NDEBUG -class LeakChecker //small test for memory leaks +namespace z_impl { -public: - void insert(const void* ptr, size_t size) - { - boost::lock_guard<boost::mutex> dummy(lockActStrings); - if (activeStrings.find(ptr) != activeStrings.end()) - reportProblem("Fatal Error: New memory points into occupied space: " + rawMemToString(ptr, size)); - - activeStrings[ptr] = size; - } - - void remove(const void* ptr) - { - boost::lock_guard<boost::mutex> dummy(lockActStrings); - if (activeStrings.find(ptr) == activeStrings.end()) - reportProblem("Fatal Error: No memory available for deallocation at this location!"); - - activeStrings.erase(ptr); - } - - static LeakChecker& instance(); - -private: - LeakChecker() {} - LeakChecker(const LeakChecker&); - LeakChecker& operator=(const LeakChecker&); - ~LeakChecker(); - - static std::string rawMemToString(const void* ptr, size_t size); - void reportProblem(const std::string& message); //throw std::logic_error - - boost::mutex lockActStrings; - zen::hash_map<const void*, size_t> activeStrings; -}; +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 { -#ifndef NDEBUG void* newMem = ::operator new(size); - LeakChecker::instance().insert(newMem, size); //test Zbase for memory leaks - return newMem; -#else - return ::operator new(size); +#ifndef NDEBUG + z_impl::leakCheckerInsert(newMem, size); //test Zbase for memory leaks #endif + return newMem; } static void deallocate(void* ptr) { #ifndef NDEBUG - LeakChecker::instance().remove(ptr); //check for memory leaks + z_impl::leakCheckerRemove(ptr); //check for memory leaks #endif ::operator delete(ptr); } @@ -82,66 +48,50 @@ public: //############################## helper functions ############################################# -#if defined(FFS_WIN) || defined(FFS_LINUX) -//Compare filenames: Windows does NOT distinguish between upper/lower-case, while Linux DOES -template <class T, template <class, class> class SP, class AP> int cmpFileName(const zen::Zbase<T, SP, AP>& lhs, const zen::Zbase<T, SP, AP>& rhs); - -struct LessFilename //case-insensitive on Windows, case-sensitive on Linux -{ - template <class T, template <class, class> class SP, class AP> - bool operator()(const zen::Zbase<T, SP, AP>& lhs, const zen::Zbase<T, SP, AP>& rhs) const; -}; - -struct EqualFilename //case-insensitive on Windows, case-sensitive on Linux -{ - template <class T, template <class, class> class SP, class AP> - bool operator()(const zen::Zbase<T, SP, AP>& lhs, const zen::Zbase<T, SP, AP>& rhs) const; -}; -#endif - -#ifdef FFS_WIN -template <template <class, class> class SP, class AP> -void makeUpper(zen::Zbase<wchar_t, SP, AP>& str); -#endif #ifdef FFS_WIN //Windows encodes Unicode as UTF-16 wchar_t typedef wchar_t Zchar; #define Zstr(x) L ## x const Zchar FILE_NAME_SEPARATOR = L'\\'; -#elif defined FFS_LINUX //Linux uses UTF-8 +#elif defined FFS_LINUX || defined FFS_MAC //Linux uses UTF-8 typedef char Zchar; #define Zstr(x) x const Zchar FILE_NAME_SEPARATOR = '/'; - -#else -#error define your platform: FFS_WIN or FFS_LINUX #endif //"The reason for all the fuss above" - Loki/SmartPtr -//a high-performance string for use as file name in multithreaded contexts +//a high-performance string for interfacing with native OS APIs and multithreaded contexts typedef zen::Zbase<Zchar, zen::StorageRefCountThreadSafe, AllocatorFreeStoreChecked> Zstring; -inline -Zstring appendSeparator(Zstring path) //support rvalue references! -{ - return endsWith(path, FILE_NAME_SEPARATOR) ? path : (path += FILE_NAME_SEPARATOR); -} - - - - - - - - - +//Compare filenames: Windows does NOT distinguish between upper/lower-case, while Linux DOES +template <template <class, class> class SP, class AP> +int cmpFileName(const zen::Zbase<Zchar, SP, AP>& lhs, const zen::Zbase<Zchar, SP, AP>& rhs); +struct LessFilename //case-insensitive on Windows, case-sensitive on Linux +{ + template <template <class, class> class SP, class AP> + bool operator()(const zen::Zbase<Zchar, SP, AP>& lhs, const zen::Zbase<Zchar, SP, AP>& rhs) const { return cmpFileName(lhs, rhs) < 0; } +}; +struct EqualFilename //case-insensitive on Windows, case-sensitive on Linux +{ + template <template <class, class> class SP, class AP> + bool operator()(const zen::Zbase<Zchar, SP, AP>& lhs, const zen::Zbase<Zchar, SP, AP>& rhs) const { return cmpFileName(lhs, rhs) == 0; } +}; +#if defined FFS_WIN || defined FFS_MAC +template <template <class, class> class SP, class AP> +void makeUpper(zen::Zbase<Zchar, SP, AP>& str); +#endif +inline +Zstring appendSeparator(Zstring path) //support rvalue references! +{ + return endsWith(path, FILE_NAME_SEPARATOR) ? path : (path += FILE_NAME_SEPARATOR); +} @@ -149,53 +99,35 @@ Zstring appendSeparator(Zstring path) //support rvalue references! //################################# inline implementation ######################################## -#if defined(FFS_WIN) || defined(FFS_LINUX) namespace z_impl { -int compareFilenamesWin(const wchar_t* lhs, const wchar_t* rhs, size_t sizeLhs, size_t sizeRhs); -void makeUpperCaseWin(wchar_t* str, size_t size); -} - - -template <class T, template <class, class> class SP, class AP> inline -int cmpFileName(const zen::Zbase<T, SP, AP>& lhs, const zen::Zbase<T, SP, AP>& rhs) -{ -#ifdef FFS_WIN - return z_impl::compareFilenamesWin(lhs.data(), rhs.data(), lhs.length(), rhs.length()); -#elif defined FFS_LINUX - return ::strcmp(lhs.c_str(), rhs.c_str()); //POSIX filenames don't have embedded 0 +#if defined FFS_WIN +int compareFilenamesNoCase(const Zchar* lhs, const Zchar* rhs, size_t sizeLhs, size_t sizeRhs); #endif -} - - -template <class T, template <class, class> class SP, class AP> inline -bool LessFilename::operator()(const zen::Zbase<T, SP, AP>& lhs, const zen::Zbase<T, SP, AP>& rhs) const -{ -#ifdef FFS_WIN - return z_impl::compareFilenamesWin(lhs.data(), rhs.data(), lhs.length(), rhs.length()) < 0; -#elif defined FFS_LINUX - return ::strcmp(lhs.c_str(), rhs.c_str()) < 0; //POSIX filenames don't have embedded 0 +#if defined FFS_WIN || defined FFS_MAC +void makeFilenameUpperCase(Zchar* str, size_t size); #endif } -template <class T, template <class, class> class SP, class AP> inline -bool EqualFilename::operator()(const zen::Zbase<T, SP, AP>& lhs, const zen::Zbase<T, SP, AP>& rhs) const +template <template <class, class> class SP, class AP> inline +int cmpFileName(const zen::Zbase<Zchar, SP, AP>& lhs, const zen::Zbase<Zchar, SP, AP>& rhs) { -#ifdef FFS_WIN - return z_impl::compareFilenamesWin(lhs.data(), rhs.data(), lhs.length(), rhs.length()) == 0; +#if defined FFS_WIN + return z_impl::compareFilenamesNoCase(lhs.data(), rhs.data(), lhs.length(), rhs.length()); #elif defined FFS_LINUX - return ::strcmp(lhs.c_str(), rhs.c_str()) == 0; //POSIX filenames don't have embedded 0 + return std::strcmp(lhs.c_str(), rhs.c_str()); //POSIX filenames don't have embedded 0 +#elif defined FFS_MAC + return ::strcasecmp(lhs.c_str(), rhs.c_str()); //locale-dependent! #endif } -#endif //defined(FFS_WIN) || defined(FFS_LINUX) -#ifdef FFS_WIN +#if defined FFS_WIN || defined FFS_MAC template <template <class, class> class SP, class AP> inline -void makeUpper(zen::Zbase<wchar_t, SP, AP>& str) +void makeUpper(zen::Zbase<Zchar, SP, AP>& str) { - z_impl::makeUpperCaseWin(str.begin(), str.length()); + z_impl::makeFilenameUpperCase(str.begin(), str.length()); } #endif |