diff options
Diffstat (limited to 'zen')
-rw-r--r-- | zen/FindFilePlus/FindFilePlus.vcxproj | 7 | ||||
-rw-r--r-- | zen/FindFilePlus/find_file_plus.cpp | 2 | ||||
-rw-r--r-- | zen/IFileOperation/FileOperation_Vista.vcxproj | 7 | ||||
-rw-r--r-- | zen/IFileOperation/file_op.cpp | 14 | ||||
-rw-r--r-- | zen/basic_math.h | 14 | ||||
-rw-r--r-- | zen/debug_memory_leaks.cpp | 31 | ||||
-rw-r--r-- | zen/debug_minidump.cpp (renamed from zen/debug_new.cpp) | 10 | ||||
-rw-r--r-- | zen/debug_minidump.h (renamed from zen/debug_new.h) | 0 | ||||
-rw-r--r-- | zen/dir_watcher.cpp | 50 | ||||
-rw-r--r-- | zen/error_log.h | 44 | ||||
-rw-r--r-- | zen/file_handling.cpp | 98 | ||||
-rw-r--r-- | zen/file_io.cpp | 170 | ||||
-rw-r--r-- | zen/file_io.h | 69 | ||||
-rw-r--r-- | zen/file_io_base.h | 64 | ||||
-rw-r--r-- | zen/file_traverser.cpp | 14 | ||||
-rw-r--r-- | zen/perf.h | 2 | ||||
-rw-r--r-- | zen/scroll_window_under_cursor.cpp | 15 | ||||
-rw-r--r-- | zen/serialize.h | 2 | ||||
-rw-r--r-- | zen/string_base.h | 20 | ||||
-rw-r--r-- | zen/string_tools.h | 33 | ||||
-rw-r--r-- | zen/thread.h | 27 | ||||
-rw-r--r-- | zen/tick_count.h | 30 | ||||
-rw-r--r-- | zen/time.h | 9 | ||||
-rw-r--r-- | zen/zstring.h | 2 |
24 files changed, 514 insertions, 220 deletions
diff --git a/zen/FindFilePlus/FindFilePlus.vcxproj b/zen/FindFilePlus/FindFilePlus.vcxproj index a50239ab..b94174ac 100644 --- a/zen/FindFilePlus/FindFilePlus.vcxproj +++ b/zen/FindFilePlus/FindFilePlus.vcxproj @@ -102,6 +102,7 @@ <DebugInformationFormat>EditAndContinue</DebugInformationFormat> <DisableSpecificWarnings>4100</DisableSpecificWarnings> <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories> + <SmallerTypeCheck>true</SmallerTypeCheck> </ClCompile> <Link> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> @@ -113,6 +114,7 @@ </ProfileGuidedDatabase> <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX86</TargetMachine> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> @@ -135,6 +137,7 @@ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DisableSpecificWarnings>4100</DisableSpecificWarnings> <AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories> + <SmallerTypeCheck>true</SmallerTypeCheck> </ClCompile> <Link> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> @@ -146,6 +149,7 @@ </ProfileGuidedDatabase> <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX64</TargetMachine> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> @@ -179,6 +183,7 @@ </ProfileGuidedDatabase> <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX86</TargetMachine> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> @@ -215,9 +220,11 @@ </ProfileGuidedDatabase> <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX64</TargetMachine> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemGroup> + <ClCompile Include="..\debug_memory_leaks.cpp" /> <ClCompile Include="dll_main.cpp"> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> </PrecompiledHeader> diff --git a/zen/FindFilePlus/find_file_plus.cpp b/zen/FindFilePlus/find_file_plus.cpp index 5fc1a538..ec56b0bc 100644 --- a/zen/FindFilePlus/find_file_plus.cpp +++ b/zen/FindFilePlus/find_file_plus.cpp @@ -321,7 +321,7 @@ void FileSearcher::readDirImpl(FileInformation& output) //throw NtFileError nextEntryOffset += dirInfo.NextEntryOffset; - auto toFileTime = [](const LARGE_INTEGER & rawTime) -> FILETIME + auto toFileTime = [](const LARGE_INTEGER& rawTime) -> FILETIME { FILETIME tmp = { rawTime.LowPart, rawTime.HighPart }; return tmp; diff --git a/zen/IFileOperation/FileOperation_Vista.vcxproj b/zen/IFileOperation/FileOperation_Vista.vcxproj index 4bd5b509..73bfd56a 100644 --- a/zen/IFileOperation/FileOperation_Vista.vcxproj +++ b/zen/IFileOperation/FileOperation_Vista.vcxproj @@ -99,6 +99,7 @@ <DebugInformationFormat>EditAndContinue</DebugInformationFormat> <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings> <AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories> + <SmallerTypeCheck>true</SmallerTypeCheck> </ClCompile> <Link> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> @@ -111,6 +112,7 @@ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX86</TargetMachine> <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib</AdditionalLibraryDirectories> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> @@ -133,6 +135,7 @@ <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <DisableSpecificWarnings>4100;4996</DisableSpecificWarnings> <AdditionalIncludeDirectories>../..;C:\Program Files\C++\boost</AdditionalIncludeDirectories> + <SmallerTypeCheck>true</SmallerTypeCheck> </ClCompile> <Link> <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> @@ -145,6 +148,7 @@ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX64</TargetMachine> <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib</AdditionalLibraryDirectories> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> @@ -179,6 +183,7 @@ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX86</TargetMachine> <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage\lib</AdditionalLibraryDirectories> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> @@ -216,9 +221,11 @@ <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> <TargetMachine>MachineX64</TargetMachine> <AdditionalLibraryDirectories>C:\Program Files\C++\Boost\stage_x64\lib</AdditionalLibraryDirectories> + <AdditionalDependencies>%(AdditionalDependencies)</AdditionalDependencies> </Link> </ItemDefinitionGroup> <ItemGroup> + <ClCompile Include="..\debug_memory_leaks.cpp" /> <ClCompile Include="dll_main.cpp"> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> </PrecompiledHeader> diff --git a/zen/IFileOperation/file_op.cpp b/zen/IFileOperation/file_op.cpp index 3591de12..5d4cfdc9 100644 --- a/zen/IFileOperation/file_op.cpp +++ b/zen/IFileOperation/file_op.cpp @@ -173,16 +173,16 @@ void moveToRecycleBin(const wchar_t* fileNames[], //throw ComError // Set the operation flags. Turn off all UI from being shown to the user during the // operation. This includes error, confirmation and progress dialogs. - ZEN_CHECK_COM(fileOp->SetOperationFlags(FOF_ALLOWUNDO | + ZEN_CHECK_COM(fileOp->SetOperationFlags(FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_SILENT | //no progress dialog box FOF_NOERRORUI | - FOFX_EARLYFAILURE | - //without FOFX_EARLYFAILURE, IFileOperationProgressSink::PostDeleteItem() will always report success, even if deletion failed!!? WTF!? - //PerformOperations() will still succeed but set the uselessly generic GetAnyOperationsAborted() instead :((( - //=> always set FOFX_EARLYFAILURE since we prefer good error messages over "doing as much as possible" - //luckily for FreeFileSync we don't expect failures on individual files anyway: FreeFileSync moves files to be - //deleted to a temporary folder first, so there is no reason why a second move (the recycling itself) should fail + FOFX_EARLYFAILURE | + //without FOFX_EARLYFAILURE, IFileOperationProgressSink::PostDeleteItem() will always report success, even if deletion failed!!? WTF!? + //PerformOperations() will still succeed but set the uselessly generic GetAnyOperationsAborted() instead :((( + //=> always set FOFX_EARLYFAILURE since we prefer good error messages over "doing as much as possible" + //luckily for FreeFileSync we don't expect failures on individual files anyway: FreeFileSync moves files to be + //deleted to a temporary folder first, so there is no reason why a second move (the recycling itself) should fail FOF_NO_CONNECTED_ELEMENTS)); //use FOFX_RECYCLEONDELETE when Windows 8 is available!? diff --git a/zen/basic_math.h b/zen/basic_math.h index 7923dc5d..bd416d19 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -32,6 +32,8 @@ const T& max(const T& a, const T& b, const T& c); template <class T> void confine(T& val, const T& minVal, const T& maxVal); //make sure minVal <= val && val <= maxVal +template <class T> +T confineCpy(const T& val, const T& minVal, const T& maxVal); template <class InputIterator> std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, InputIterator last); @@ -97,6 +99,7 @@ const double ln2 = 0.693147180559945309417; template <class T> inline T abs(T value) { + //static_assert(std::is_signed<T>::value, ""); might not compile for non-built-in arithmetic types; anyway "-value" should emit compiler error or warning for unsigned types if (value < 0) return -value; // operator "?:" caveat: may be different type than "value" else @@ -132,6 +135,17 @@ const T& max(const T& a, const T& b, const T& c) template <class T> inline +T confineCpy(const T& val, const T& minVal, const T& maxVal) +{ + assert(minVal <= maxVal); + if (val < minVal) + return minVal; + else if (val > maxVal) + return maxVal; + return val; +} + +template <class T> inline void confine(T& val, const T& minVal, const T& maxVal) //name trim? { assert(minVal <= maxVal); diff --git a/zen/debug_memory_leaks.cpp b/zen/debug_memory_leaks.cpp new file mode 100644 index 00000000..8774d16f --- /dev/null +++ b/zen/debug_memory_leaks.cpp @@ -0,0 +1,31 @@ +// ************************************************************************** +// * 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 * +// ************************************************************************** + +//-----------Memory Leak Detection-------------------------- +//Usage: just include this file into a Visual Studio project + + +#ifndef NDEBUG +#define _CRTDBG_MAP_ALLOC // +#include <stdlib.h> //keep this order: "The #include statements must be in the order shown here. If you change the order, the functions you use may not work properly." +#include <crtdbg.h> //overwrites "operator new" ect; no need to include this in every compilation unit! +//http://msdn.microsoft.com/en-us/library/e5ewb1h3(v=vs.80).aspx + +namespace +{ +struct OnStartup +{ + OnStartup() + { + //note: wxWidgets also "activates" leak detection in the usual buggy way: it sets incomplete flags and incorrectly overwrites them rather than appending -> luckily it still seems to work! + int flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + flags |= _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF; + _CrtSetDbgFlag(flags); + } + +} dummy; +} +#endif
\ No newline at end of file diff --git a/zen/debug_new.cpp b/zen/debug_minidump.cpp index 40fb88c7..9429819f 100644 --- a/zen/debug_new.cpp +++ b/zen/debug_minidump.cpp @@ -4,9 +4,10 @@ // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** -#include "debug_new.h" +#include "debug_minidump.h" #include <string> #include <sstream> +#include <cassert> #include <cstdlib> //malloc(), free() #include "win.h" //includes "windows.h" #include "DbgHelp.h" //available for MSC only @@ -28,7 +29,7 @@ LONG WINAPI writeDumpOnException(EXCEPTION_POINTERS* pExceptionInfo) MINIDUMP_EXCEPTION_INFORMATION* exceptParam = pExceptionInfo ? &exInfo : nullptr; /*bool rv = */ - ::MiniDumpWriteDump(::GetCurrentProcess(), //__in HANDLE hProcess, + ::MiniDumpWriteDump(::GetCurrentProcess (), //__in HANDLE hProcess, ::GetCurrentProcessId(), //__in DWORD ProcessId, hFile, //__in HANDLE hFile, MiniDumpWithDataSegs, //__in MINIDUMP_TYPE DumpType, ->Standard: MiniDumpNormal, Medium: MiniDumpWithDataSegs, Full: MiniDumpWithFullMemory @@ -38,11 +39,12 @@ LONG WINAPI writeDumpOnException(EXCEPTION_POINTERS* pExceptionInfo) ::CloseHandle(hFile); } + assert(false); return EXCEPTION_EXECUTE_HANDLER; } //ensure that a dump-file is written for uncaught exceptions -struct Dummy { Dummy() { ::SetUnhandledExceptionFilter(writeDumpOnException); }} dummy; +struct OnStartup { OnStartup() { ::SetUnhandledExceptionFilter(writeDumpOnException); }} dummy; } @@ -105,7 +107,7 @@ void* operator new(size_t size) return ptr; debug_tools::writeMinidump(); - throw debug_tools::BadAllocDetailed(size); + throw ::BadAllocDetailed(size); } void operator delete(void* ptr) { ::free(ptr); } diff --git a/zen/debug_new.h b/zen/debug_minidump.h index 4ef0106e..4ef0106e 100644 --- a/zen/debug_new.h +++ b/zen/debug_minidump.h diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index c02453c6..2d249af8 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -97,7 +97,7 @@ public: { boost::lock_guard<boost::mutex> dummy(lockAccess); - //first check whether errors occured in thread + //first check whether errors occurred in thread if (!errorMsg.first.empty()) { const std::wstring msg = errorMsg.first.c_str(); @@ -125,7 +125,7 @@ private: boost::mutex lockAccess; std::vector<DirWatcher::Entry> changedFiles; - std::pair<BasicWString, DWORD> errorMsg; //non-empty if errors occured in thread + std::pair<BasicWString, DWORD> errorMsg; //non-empty if errors occurred in thread }; @@ -279,9 +279,12 @@ private: virtual void onRequestRemoval(HANDLE hnd) { //must release hDir immediately => stop monitoring! - worker_.interrupt(); - worker_.join(); //we assume precondition "worker.joinable()"!!! - //now hDir should have been released + if (worker_.joinable()) //= join() precondition: play safe; can't trust Windows to only call-back once + { + worker_.interrupt(); + worker_.join(); //we assume precondition "worker.joinable()"!!! + //now hDir should have been released + } removalRequested = true; } //don't throw! @@ -319,8 +322,13 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError DirWatcher::~DirWatcher() { - pimpl_->worker.interrupt(); - //pimpl_->worker.join(); -> we don't have time to wait... will take ~50ms anyway + if (pimpl_->worker.joinable()) //= thread::detach() precondition! -> may already be joined by HandleVolumeRemoval::onRequestRemoval() + { + pimpl_->worker.interrupt(); + //if (pimpl_->worker.joinable()) pimpl_->worker.join(); -> we don't have time to wait... will take ~50ms anyway + pimpl_->worker.detach(); //we have to be explicit since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!! + } + //caveat: exitting the app may simply kill this thread! } @@ -409,10 +417,11 @@ DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError //set non-blocking mode bool initSuccess = false; - int flags = ::fcntl(pimpl_->notifDescr, F_GETFL); - if (flags != -1) - initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) != -1; - + { + int flags = ::fcntl(pimpl_->notifDescr, F_GETFL); + if (flags != -1) + initSuccess = ::fcntl(pimpl_->notifDescr, F_SETFL, flags | O_NONBLOCK) != -1; + } if (!initSuccess) throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(dirname)) + L"\n\n" + getLastErrorFormatted()); @@ -455,13 +464,18 @@ DirWatcher::~DirWatcher() std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void()>&) //throw FileError { //non-blocking call, see O_NONBLOCK - std::vector<char> buffer(1024 * (sizeof(struct inotify_event) + 16)); - ssize_t bytesRead = ::read(pimpl_->notifDescr, &buffer[0], buffer.size()); + std::vector<char> buffer(1024 * (sizeof(struct ::inotify_event) + 16)); + + ssize_t bytesRead = 0; + do + { + bytesRead = ::read(pimpl_->notifDescr, &buffer[0], buffer.size()); + } + while (bytesRead < 0 && errno == EINTR); //"Interrupted function call; When this happens, you should try the call again." - if (bytesRead == -1) + if (bytesRead < 0) { - if (errno == EINTR || //Interrupted function call; When this happens, you should try the call again. - errno == EAGAIN) //Non-blocking I/O has been selected using O_NONBLOCK and no data was immediately available for reading + if (errno == EAGAIN) //this error is ignored in all inotify wrappers I found return std::vector<Entry>(); throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtFileName(pimpl_->dirname)) + L"\n\n" + getLastErrorFormatted()); @@ -472,7 +486,7 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() ssize_t bytePos = 0; while (bytePos < bytesRead) { - struct inotify_event& evt = reinterpret_cast<struct inotify_event&>(buffer[bytePos]); + struct ::inotify_event& evt = reinterpret_cast<struct ::inotify_event&>(buffer[bytePos]); if (evt.len != 0) //exclude case: deletion of "self", already reported by parent directory watch { @@ -497,7 +511,7 @@ std::vector<DirWatcher::Entry> DirWatcher::getChanges(const std::function<void() } } - bytePos += sizeof(struct inotify_event) + evt.len; + bytePos += sizeof(struct ::inotify_event) + evt.len; } return output; diff --git a/zen/error_log.h b/zen/error_log.h index 401581d7..490bb1f4 100644 --- a/zen/error_log.h +++ b/zen/error_log.h @@ -25,30 +25,35 @@ enum MessageType TYPE_FATAL_ERROR = 0x8, }; -typedef Zbase<wchar_t> MsgString; //std::wstring may employ small string optimization: we cannot accept bloating the "logEntries" memory block below (think 1 million entries) +typedef Zbase<wchar_t> MsgString; //std::wstring may employ small string optimization: we cannot accept bloating the "ErrorLog::entries" memory block below (think 1 million items) struct LogEntry { time_t time; MessageType type; - MsgString message; + MsgString message; }; -MsgString formatMessage(const LogEntry& msg); +template <class String> +String formatMessage(const LogEntry& entry); class ErrorLog { public: - template <class String> - void logMsg(const String& message, MessageType type); + template <class String> //a wchar_t-based string! + void logMsg(const String& text, MessageType type); int getItemCount(int typeFilter = TYPE_INFO | TYPE_WARNING | TYPE_ERROR | TYPE_FATAL_ERROR) const; - const std::vector<LogEntry>& getEntries() const { return logEntries; } + //subset of std::vector<> interface: + typedef std::vector<LogEntry>::const_iterator const_iterator; + const_iterator begin() const { return entries.begin(); } + const_iterator end () const { return entries.end (); } + bool empty() const { return entries.empty(); } private: - std::vector<LogEntry> logEntries; //list of non-resolved errors and warnings + std::vector<LogEntry> entries; //list of non-resolved errors and warnings }; @@ -59,29 +64,26 @@ private: - - - - //######################## implementation ########################## template <class String> inline -void ErrorLog::logMsg(const String& message, zen::MessageType type) +void ErrorLog::logMsg(const String& text, zen::MessageType type) { - const LogEntry newEntry = { std::time(nullptr), type, copyStringTo<MsgString>(message) }; - logEntries.push_back(newEntry); + const LogEntry newEntry = { std::time(nullptr), type, copyStringTo<MsgString>(text) }; + entries.push_back(newEntry); } inline int ErrorLog::getItemCount(int typeFilter) const { - return static_cast<int>(std::count_if(logEntries.begin(), logEntries.end(), [&](const LogEntry& e) { return e.type & typeFilter; })); + return static_cast<int>(std::count_if(entries.begin(), entries.end(), [&](const LogEntry& e) { return e.type & typeFilter; })); } namespace { -MsgString formatMessageImpl(const LogEntry& entry) //internal linkage +template <class String> +String formatMessageImpl(const LogEntry& entry) //internal linkage { auto getTypeName = [&]() -> std::wstring { @@ -96,11 +98,11 @@ MsgString formatMessageImpl(const LogEntry& entry) //internal linkage case TYPE_FATAL_ERROR: return _("Fatal Error"); } - assert(false); + assert(false); return std::wstring(); }; - MsgString formattedText = L"[" + formatTime<MsgString>(FORMAT_TIME, localTime(entry.time)) + L"] " + copyStringTo<MsgString>(getTypeName()) + L": "; + String formattedText = L"[" + formatTime<String>(FORMAT_TIME, localTime(entry.time)) + L"] " + copyStringTo<String>(getTypeName()) + L": "; const size_t prefixLen = formattedText.size(); for (auto iter = entry.message.begin(); iter != entry.message.end(); ) @@ -108,7 +110,7 @@ MsgString formatMessageImpl(const LogEntry& entry) //internal linkage { formattedText += L'\n'; - MsgString blanks; + String blanks; blanks.resize(prefixLen, L' '); formattedText += blanks; @@ -125,8 +127,8 @@ MsgString formatMessageImpl(const LogEntry& entry) //internal linkage } } -inline -MsgString formatMessage(const LogEntry& entry) { return formatMessageImpl(entry); } +template <class String> inline +String formatMessage(const LogEntry& entry) { return formatMessageImpl<String>(entry); } } #endif //ERRORLOGGING_H_INCLUDED diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp index 589057ad..fce85bcd 100644 --- a/zen/file_handling.cpp +++ b/zen/file_handling.cpp @@ -28,9 +28,8 @@ #elif defined FFS_LINUX #include <sys/stat.h> -#include <time.h> #include <utime.h> -#include <sys/time.h> +#include <sys/time.h> //futimes #include <sys/vfs.h> #ifdef HAVE_SELINUX @@ -926,7 +925,7 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr #elif defined FFS_LINUX if (procSl == SYMLINK_FOLLOW) { - struct utimbuf newTimes = {}; + struct ::utimbuf newTimes = {}; newTimes.actime = ::time(nullptr); newTimes.modtime = to<time_t>(modificationTime); @@ -936,12 +935,9 @@ void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, Pr } else { - struct timeval newTimes[2] = {}; - newTimes[0].tv_sec = ::time(nullptr); //seconds - newTimes[0].tv_usec = 0; //microseconds - - newTimes[1].tv_sec = to<time_t>(modificationTime); - newTimes[1].tv_usec = 0; + 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 (::lutimes(filename.c_str(), newTimes) != 0) throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); @@ -1315,8 +1311,8 @@ void createDirectoryStraight(const Zstring& directory, //throw FileError, ErrorT ::SetFileAttributes(applyLongPathPrefix(directory).c_str(), sourceAttr); //copy "read-only and system attributes": http://blogs.msdn.com/b/oldnewthing/archive/2003/09/30/55100.aspx - const bool isCompressed = (sourceAttr & FILE_ATTRIBUTE_COMPRESSED) != 0; - const bool isEncrypted = (sourceAttr & FILE_ATTRIBUTE_ENCRYPTED) != 0; + const bool isCompressed = (sourceAttr & FILE_ATTRIBUTE_COMPRESSED) != 0; + const bool isEncrypted = (sourceAttr & FILE_ATTRIBUTE_ENCRYPTED) != 0; if (isEncrypted) ::EncryptFile(directory.c_str()); //seems no long path is required (check passed!) @@ -1492,7 +1488,7 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool catch (...) {} }); - //file times: essential for a symlink: enforce this! (don't just try!) + //file times: essential for sync'ing a symlink: enforce this! (don't just try!) { const Int64 modTime = getFileTime(sourceLink, SYMLINK_DIRECT); //throw FileError setFileTime(targetLink, modTime, SYMLINK_DIRECT); //throw FileError @@ -1741,11 +1737,8 @@ void copyFileWindowsSparse(const Zstring& sourceFile, } //---------------------------------------------------------------------- - const DWORD BUFFER_SIZE = 512 * 1024; //512 kb seems to be a reasonable buffer size - must be greater than sizeof(WIN32_STREAM_ID) - static boost::thread_specific_ptr<std::vector<BYTE>> cpyBuf; - if (!cpyBuf.get()) - cpyBuf.reset(new std::vector<BYTE>(BUFFER_SIZE)); - std::vector<BYTE>& buffer = *cpyBuf; + const DWORD BUFFER_SIZE = 128 * 1024; //must be greater than sizeof(WIN32_STREAM_ID) + std::vector<BYTE> buffer(BUFFER_SIZE); LPVOID contextRead = nullptr; //manage context for BackupRead()/BackupWrite() LPVOID contextWrite = nullptr; // @@ -2172,23 +2165,21 @@ void copyFileLinux(const Zstring& sourceFile, FileAttrib* newAttrib) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting { zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (...) {} }); //transactional behavior: place guard before lifetime of FileOutput + + //open sourceFile for reading + FileInputUnbuffered fileIn(sourceFile); //throw FileError, ErrorNotExisting + + struct ::stat sourceInfo = {}; + 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()); + try { - //open sourceFile for reading - FileInput fileIn(sourceFile); //throw FileError - //create targetFile and open it for writing - FileOutput fileOut(targetFile, FileOutput::ACC_CREATE_NEW); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting - - std::vector<char>& buffer = []() -> std::vector<char>& - { - static boost::thread_specific_ptr<std::vector<char>> cpyBuf; - if (!cpyBuf.get()) - cpyBuf.reset(new std::vector<char>(512 * 1024)); //512 kb seems to be a reasonable buffer size - return *cpyBuf; - }(); + FileOutputUnbuffered fileOut(targetFile, sourceInfo.st_mode); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting //copy contents of sourceFile to targetFile + std::vector<char> buffer(128 * 1024); //see comment in FileInputUnbuffered::read do { const size_t bytesRead = fileIn.read(&buffer[0], buffer.size()); //throw FileError @@ -2200,39 +2191,34 @@ void copyFileLinux(const Zstring& sourceFile, callback->updateCopyStatus(Int64(bytesRead)); //throw X! } while (!fileIn.eof()); - } - catch (ErrorTargetExisting&) - { - guardTarget.dismiss(); //don't delete file that existed previously! - throw; - } - - //adapt file modification time: - { - struct ::stat srcInfo = {}; - if (::stat(sourceFile.c_str(), &srcInfo) != 0) //read file attributes from source directory - throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)) + L"\n\n" + getLastErrorFormatted()); - struct ::utimbuf newTimes = {}; - newTimes.actime = srcInfo.st_atime; - newTimes.modtime = srcInfo.st_mtime; - - //set new "last write time" - if (::utime(targetFile.c_str(), &newTimes) != 0) - throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted()); - - if (newAttrib) + //adapt target file modification time: { - struct ::stat trgInfo = {}; - if (::stat(targetFile.c_str(), &trgInfo) != 0) //read file attributes from source directory + 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) throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)) + L"\n\n" + getLastErrorFormatted()); - newAttrib->fileSize = UInt64(srcInfo.st_size); - newAttrib->modificationTime = srcInfo.st_mtime; - newAttrib->sourceFileId = extractFileID(srcInfo); - newAttrib->targetFileId = extractFileID(trgInfo); + if (newAttrib) + { + newAttrib->fileSize = UInt64(sourceInfo.st_size); + newAttrib->modificationTime = sourceInfo.st_mtime; + newAttrib->sourceFileId = extractFileID(sourceInfo); + newAttrib->targetFileId = extractFileID(targetInfo); + } } } + catch (ErrorTargetExisting&) + { + guardTarget.dismiss(); //don't delete file that existed previously! + throw; + } guardTarget.dismiss(); //target has been created successfully! } diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 0b5586b0..4880f6cc 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -8,20 +8,46 @@ #ifdef FFS_WIN #include "long_path_prefix.h" +#elif defined FFS_LINUX +#include <fcntl.h> //open, close +#include <unistd.h> //read, write #endif using namespace zen; -FileInput::FileInput(FileHandle handle, const Zstring& filename) : - eofReached(false), - fileHandle(handle), - filename_(filename) {} +FileInput::FileInput(FileHandle handle, const Zstring& filename) : FileInputBase(filename), fileHandle(handle) {} + +#ifdef FFS_LINUX +//"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 +{ + struct ::stat fileInfo = {}; + if (::stat(filename.c_str(), &fileInfo) != 0) //follows symlinks + return; //let the caller handle errors like "not existing" + + if (!S_ISREG(fileInfo.st_mode) && + !S_ISLNK(fileInfo.st_mode) && + !S_ISDIR(fileInfo.st_mode)) + { + auto getTypeName = [](mode_t m) -> std::wstring + { + const wchar_t* name = + S_ISCHR (m) ? L"character device": + 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); + 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)); + } +} +#endif FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExisting - eofReached(false), - filename_(filename) + FileInputBase(filename) { #ifdef FFS_WIN fileHandle = ::CreateFile(applyLongPathPrefix(filename).c_str(), @@ -57,6 +83,7 @@ FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExis nullptr); if (fileHandle == INVALID_HANDLE_VALUE) #elif defined FFS_LINUX + 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) #endif @@ -64,9 +91,9 @@ FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExis const ErrorCode lastError = getLastError(); if (errorCodeForNotExisting(lastError)) - throw ErrorNotExisting(replaceCpy(_("Cannot find file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + getLastErrorFormatted(lastError)); + throw ErrorNotExisting(replaceCpy(_("Cannot find file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted(lastError)); - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + getLastErrorFormatted(lastError) + L" (open)"); + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted(lastError) + L" (open)"); } } @@ -83,6 +110,8 @@ FileInput::~FileInput() size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number of bytes read; throw FileError { + assert(!eof()); + if (bytesToRead == 0) return 0; #ifdef FFS_WIN DWORD bytesRead = 0; if (!::ReadFile(fileHandle, //__in HANDLE hFile, @@ -94,33 +123,33 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number const size_t bytesRead = ::fread(buffer, 1, bytesToRead, fileHandle); if (::ferror(fileHandle) != 0) //checks status of stream, not fread()! #endif - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + getLastErrorFormatted() + L" (read)"); + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted() + L" (read)"); #ifdef FFS_WIN if (bytesRead < bytesToRead) //verify only! - eofReached = true; + setEof(); #elif defined FFS_LINUX if (::feof(fileHandle) != 0) - eofReached = true; + setEof(); if (bytesRead < bytesToRead) - if (!eofReached) //pathologic!? - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + L"Incomplete read!"); + if (!eof()) //pathologic!? + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + L"Incomplete read!"); #endif if (bytesRead > bytesToRead) - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + L"buffer overflow"); + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + L"buffer overflow"); return bytesRead; } -FileOutput::FileOutput(FileHandle handle, const Zstring& filename) : fileHandle(handle), filename_(filename) {} +FileOutput::FileOutput(FileHandle handle, const Zstring& filename) : FileOutputBase(filename), fileHandle(handle) {} FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting - filename_(filename) + FileOutputBase(filename) { #ifdef FFS_WIN const DWORD dwCreationDisposition = access == FileOutput::ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW; @@ -160,7 +189,7 @@ FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw Fil //"regular" error handling if (fileHandle == INVALID_HANDLE_VALUE) { - const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + zen::getLastErrorFormatted(lastError); + const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + zen::getLastErrorFormatted(lastError); if (lastError == ERROR_FILE_EXISTS || //confirmed to be used lastError == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6 @@ -174,13 +203,14 @@ FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw Fil } #elif defined FFS_LINUX + 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 access == ACC_OVERWRITE ? "w,type=record,noseek" : "wx,type=record,noseek"); if (!fileHandle) { const int lastError = errno; - const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + zen::getLastErrorFormatted(lastError); + const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + zen::getLastErrorFormatted(lastError); if (lastError == EEXIST) throw ErrorTargetExisting(errorMessage); @@ -216,8 +246,108 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro const size_t bytesWritten = ::fwrite(buffer, 1, bytesToWrite, fileHandle); if (::ferror(fileHandle) != 0) //checks status of stream, not fwrite()! #endif - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + getLastErrorFormatted() + L" (w)"); //w -> distinguish from fopen error message! + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted() + L" (w)"); //w -> distinguish from fopen error message! if (bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes! - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename_)) + L"\n\n" + L"Incomplete write!"); + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + L"Incomplete write!"); +} + + +#ifdef FFS_LINUX +//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 +{ + checkForUnsupportedType(filename); //throw FileError; reading a named pipe would block forever! + + fdFile = ::open(filename.c_str(), O_RDONLY); + if (fdFile < 0) + { + const ErrorCode lastError = getLastError(); + + if (errorCodeForNotExisting(lastError)) + throw ErrorNotExisting(replaceCpy(_("Cannot find file %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted(lastError)); + + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted(lastError) + L" (open)"); + } +} + + +FileInputUnbuffered::~FileInputUnbuffered() { ::close(fdFile); } + + +size_t FileInputUnbuffered::read(void* buffer, size_t bytesToRead) //throw FileError; returns actual number of bytes read +{ + assert(!eof()); + if (bytesToRead == 0) return 0; //[!] + + ssize_t bytesRead = 0; + do + { + bytesRead = ::read(fdFile, buffer, bytesToRead); + } + while (bytesRead < 0 && errno == EINTR); + + if (bytesRead < 0) + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted() + L" (read)"); + else if (bytesRead == 0) //"zero indicates end of file" + setEof(); + else if (bytesRead > static_cast<ssize_t>(bytesToRead)) //better safe than sorry + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + L"buffer overflow"); + //if ::read is interrupted (EINTR) right in the middle, it will return successfully with "bytesRead < bytesToRead"! + + return bytesRead; +} + + +FileOutputUnbuffered::FileOutputUnbuffered(const Zstring& filename, mode_t mode) : FileOutputBase(filename) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting +{ + //checkForUnsupportedType(filename); -> not needed, open() + O_EXCL shoul fail fast + + //overwrite is: O_CREAT | O_WRONLY | O_TRUNC + fdFile = ::open(filename.c_str(), O_CREAT | O_WRONLY | O_EXCL, mode & (S_IRWXU | S_IRWXG | S_IRWXO)); + if (fdFile < 0) + { + const int lastError = errno; + const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename)) + L"\n\n" + zen::getLastErrorFormatted(lastError); + if (lastError == EEXIST) + throw ErrorTargetExisting(errorMessage); + + if (lastError == ENOENT) + throw ErrorTargetPathMissing(errorMessage); + + throw FileError(errorMessage); + } } + + +FileOutputUnbuffered::~FileOutputUnbuffered() { ::close(fdFile); } + + +void FileOutputUnbuffered::write(const void* buffer, size_t bytesToWrite) //throw FileError +{ + while (bytesToWrite > 0) + { + ssize_t bytesWritten = 0; + do + { + bytesWritten = ::write(fdFile, buffer, bytesToWrite); + } + while (bytesWritten < 0 && errno == EINTR); + + if (bytesWritten <= 0) + { + if (bytesWritten == 0) //comment in safe-read.c suggests to treat this as an error due to buggy drivers + errno = ENOSPC; + + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted() + L" (w)"); + } + if (bytesWritten > static_cast<ssize_t>(bytesToWrite)) //better safe than sorry + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + L"buffer overflow"); + + //if ::write is interrupted (EINTR) right in the middle, it will return successfully with "bytesWritten < bytesToWrite"! + buffer = static_cast<const char*>(buffer) + bytesWritten; //suppress warning about pointer arithmetics on void* + bytesToWrite -= bytesWritten; + } +} +#endif diff --git a/zen/file_io.h b/zen/file_io.h index b4d58ea4..31373857 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -7,15 +7,16 @@ #ifndef FILEIO_H_INCLUDED #define FILEIO_H_INCLUDED +#include "file_io_base.h" +#include "file_error.h" + #ifdef FFS_WIN #include "win.h" //includes "windows.h" - #elif defined FFS_LINUX #include <cstdio> +#include <sys/stat.h> #endif -#include "zstring.h" -#include "file_error.h" namespace zen { @@ -25,7 +26,7 @@ static const char LINE_BREAK[] = "\r\n"; static const char LINE_BREAK[] = "\n"; #endif -//file IO optimized for sequential read/write accesses + better error reporting + long path support (following symlinks) +//buffered file IO optimized for sequential read/write accesses + better error reporting + long path support (following symlinks) #ifdef FFS_WIN typedef HANDLE FileHandle; @@ -33,52 +34,66 @@ typedef HANDLE FileHandle; typedef FILE* FileHandle; #endif -class FileInput +class FileInput : public FileInputBase { public: FileInput(const Zstring& filename); //throw FileError, ErrorNotExisting FileInput(FileHandle handle, const Zstring& filename); //takes ownership! ~FileInput(); - size_t read(void* buffer, size_t bytesToRead); //throw FileError; returns actual number of bytes read - bool eof() { return eofReached; } //end of file reached - - const Zstring& getFilename() const { return filename_; } + virtual size_t read(void* buffer, size_t bytesToRead); //throw FileError; returns actual number of bytes read + //expected to fill buffer completely unless "end of file" private: - FileInput(const FileInput&); - FileInput& operator=(const FileInput&); - - bool eofReached; FileHandle fileHandle; - const Zstring filename_; }; -class FileOutput +class FileOutput : public FileOutputBase { public: - enum AccessFlag - { - ACC_OVERWRITE, - ACC_CREATE_NEW - }; FileOutput(const Zstring& filename, AccessFlag access); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting FileOutput(FileHandle handle, const Zstring& filename); //takes ownership! ~FileOutput(); - void write(const void* buffer, size_t bytesToWrite); //throw FileError - - const Zstring& getFilename() const { return filename_; } + virtual void write(const void* buffer, size_t bytesToWrite); //throw FileError private: - FileOutput(const FileOutput&); - FileOutput& operator=(const FileOutput&); - FileHandle fileHandle; - const Zstring filename_; }; + +#ifdef FFS_LINUX +class FileInputUnbuffered : public FileInputBase +{ +public: + FileInputUnbuffered(const Zstring& filename); //throw FileError, ErrorNotExisting + ~FileInputUnbuffered(); + + //considering safe-read.c it seems buffer size should be a multiple of 8192 + virtual size_t read(void* buffer, size_t bytesToRead); //throw FileError; returns actual number of bytes read + //we should not rely on buffer being filled completely! + + int getDescriptor() { return fdFile;} + +private: + int fdFile; +}; + +class FileOutputUnbuffered : public FileOutputBase +{ +public: + //creates a new file (no overwrite allowed!) + FileOutputUnbuffered(const Zstring& filename, mode_t mode); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting + ~FileOutputUnbuffered(); + + virtual void write(const void* buffer, size_t bytesToWrite); //throw FileError + int getDescriptor() { return fdFile;} + +private: + int fdFile; +}; +#endif } #endif // FILEIO_H_INCLUDED diff --git a/zen/file_io_base.h b/zen/file_io_base.h new file mode 100644 index 00000000..f26cd8c2 --- /dev/null +++ b/zen/file_io_base.h @@ -0,0 +1,64 @@ +// ************************************************************************** +// * 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 FILEIO_BASE_H_INCLUDED_23432431789615314 +#define FILEIO_BASE_H_INCLUDED_23432431789615314 + +#include "zstring.h" + +namespace zen +{ +class FileBase +{ +public: + const Zstring& getFilename() const { return filename_; } + +protected: + FileBase(const Zstring& filename) : filename_(filename) {} + ~FileBase() {} + +private: + FileBase(const FileBase&); + FileBase& operator=(const FileBase&); + + const Zstring filename_; +}; + + +class FileInputBase : public FileBase +{ +public: + virtual size_t read(void* buffer, size_t bytesToRead) = 0; //throw FileError; returns actual number of bytes read + bool eof() const { return eofReached; } //end of file reached + +protected: + FileInputBase(const Zstring& filename) : FileBase(filename), eofReached(false) {} + ~FileInputBase() {} + void setEof() { eofReached = true; } + +private: + bool eofReached; +}; + + +class FileOutputBase : public FileBase +{ +public: + enum AccessFlag + { + ACC_OVERWRITE, + ACC_CREATE_NEW + }; + virtual void write(const void* buffer, size_t bytesToWrite) = 0; //throw FileError + +protected: + FileOutputBase(const Zstring& filename) : FileBase(filename) {} + ~FileOutputBase() {} +}; + +} + +#endif //FILEIO_BASE_H_INCLUDED_23432431789615314 diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index a95f5dee..2cea74d8 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -547,7 +547,6 @@ private: if (!dirEntry) //no more items or ignored error return; - //don't return "." and ".." const char* const shortName = dirEntry->d_name; //evaluate dirEntry *before* going into recursion => we use a single "buffer"! if (shortName[0] == '.' && @@ -597,12 +596,12 @@ private: if (const std::shared_ptr<TraverseCallback>& rv = sink.onDir(shortName, fullName)) traverse(fullName, *rv, level + 1); } - else //a file + else //a file or named pipe, ect. { TraverseCallback::FileInfo fileInfo; fileInfo.fileSize = zen::UInt64(statDataTrg.st_size); fileInfo.lastWriteTimeRaw = statDataTrg.st_mtime; //UTC time (time_t format); unit: 1 second - //fileInfo.id = extractFileID(statDataTrg); -> id from dereferenced symlink is problematic, since renaming will consider the link, not the target! + //fileInfo.id = extractFileID(statDataTrg); -> id from dereferenced symlink is problematic, since renaming would consider the link, not the target! sink.onFile(shortName, fullName, fileInfo); } } @@ -619,7 +618,7 @@ private: if (const std::shared_ptr<TraverseCallback>& rv = sink.onDir(shortName, fullName)) traverse(fullName, *rv, level + 1); } - else //a file + else //a file or named pipe, ect. { TraverseCallback::FileInfo fileInfo; fileInfo.fileSize = zen::UInt64(statData.st_size); @@ -628,6 +627,13 @@ private: sink.onFile(shortName, fullName, fileInfo); } + /* + It may be a good idea to not check "S_ISREG(statData.st_mode)" explicitly and to not issue an error message on other types to support these scenarios: + - RTS setup watch (essentially wants to read directories only) + - removeDirectory (wants to delete everything; pipes can be deleted just like files via "unlink") + + However an "open" on a pipe will block (https://sourceforge.net/p/freefilesync/bugs/221/), so the copy routines need to be smarter!! + */ } } @@ -48,7 +48,7 @@ public: if (!now.isValid()) throw TimerError(); - const auto delta = static_cast<long>(1000.0 * (now - startTime) / ticksPerSec_); + const auto delta = static_cast<long>(1000.0 * dist(startTime, now) / ticksPerSec_); #ifdef FFS_WIN std::ostringstream ss; ss << delta << " ms"; diff --git a/zen/scroll_window_under_cursor.cpp b/zen/scroll_window_under_cursor.cpp index 6031cd88..f201bb04 100644 --- a/zen/scroll_window_under_cursor.cpp +++ b/zen/scroll_window_under_cursor.cpp @@ -4,10 +4,6 @@ // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** -#include <cassert> -#include "win.h" //includes "windows.h" -#include "Windowsx.h" //WM_MOUSEWHEEL - //redirect mouse wheel events directly to window under cursor rather than window having input focus //implementing new Windows Vista UI guidelines: http://msdn.microsoft.com/en-us/library/bb545459.aspx#wheel //this is confirmed to be required for at least Windows 2000 to Windows 8 @@ -15,6 +11,11 @@ //Usage: just include this file into a Windows project +#include <cassert> +#include "win.h" //includes "windows.h" +#include "Windowsx.h" //WM_MOUSEWHEEL + + namespace { #ifndef WM_MOUSEHWHEEL //MinGW is clueless... @@ -25,7 +26,7 @@ LRESULT CALLBACK mouseInputHook(int nCode, WPARAM wParam, LPARAM lParam) { //"if nCode is less than zero, the hook procedure must pass the message to the CallNextHookEx function //without further processing and should return the value returned by CallNextHookEx" - if (nCode >= 0) + if (nCode == HC_ACTION) //the only valid value for this hook type { MSG& msgInfo = *reinterpret_cast<MSG*>(lParam); @@ -37,8 +38,7 @@ LRESULT CALLBACK mouseInputHook(int nCode, WPARAM wParam, LPARAM lParam) pt.y = GET_Y_LPARAM(msgInfo.lParam); // //visible child window directly under cursor; attention: not necessarily from our process! - //http://blogs.msdn.com/b/oldnewthing/archive/2010/12/30/10110077.aspx - if (HWND hWin = ::WindowFromPoint(pt)) + if (HWND hWin = ::WindowFromPoint(pt)) //http://blogs.msdn.com/b/oldnewthing/archive/2010/12/30/10110077.aspx if (msgInfo.hwnd != hWin && ::GetCapture() == nullptr) { DWORD winProcessId = 0; @@ -50,7 +50,6 @@ LRESULT CALLBACK mouseInputHook(int nCode, WPARAM wParam, LPARAM lParam) } } } - return ::CallNextHookEx(nullptr, nCode, wParam, lParam); } diff --git a/zen/serialize.h b/zen/serialize.h index d22e3cea..a9238359 100644 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -162,7 +162,7 @@ BinContainer loadBinStream(const Zstring& filename) //throw FileError, ErrorNotE FileInput fileIn(filename); //throw FileError, ErrorNotExisting BinContainer contOut; - const size_t blockSize = 64 * 1024; + const size_t blockSize = 128 * 1024; do { contOut.resize(contOut.size() + blockSize); diff --git a/zen/string_base.h b/zen/string_base.h index c3ddde36..05e5935e 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -142,7 +142,8 @@ protected: static void destroy(Char* ptr) { - if (--descr(ptr)->refCount == 0) + assert(descr(ptr)->refCount > 0); + if (--descr(ptr)->refCount == 0) //operator--() is overloaded to decrement and evaluate in a single atomic operation! { descr(ptr)->~Descriptor(); AP::deallocate(descr(ptr)); @@ -213,8 +214,8 @@ public: Char* end (); const Char* begin() const; const Char* end () const; - const Char* cbegin() const { return begin(); } - const Char* cend () const { return end(); } + const Char* cbegin() const { return begin(); } + const Char* cend () const { return end(); } //std::string functions size_t length() const; @@ -268,10 +269,15 @@ template <class Char, template <class, class> class SP, class AP> bool operator< template <class Char, template <class, class> class SP, class AP> bool operator<(const Zbase<Char, SP, AP>& lhs, const Char* rhs); template <class Char, template <class, class> class SP, class AP> bool operator<(const Char* lhs, const Zbase<Char, SP, AP>& rhs); -//rvalue references: unified first argument! -template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP> lhs, const Zbase<Char, SP, AP>& rhs) { return lhs += rhs; } -template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP> lhs, const Char* rhs) { return lhs += rhs; } -template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP> lhs, Char rhs) { return lhs += rhs; } +template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(const Zbase<Char, SP, AP>& lhs, const Zbase<Char, SP, AP>& rhs) { return Zbase<Char, SP, AP>(lhs) += rhs; } +template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(const Zbase<Char, SP, AP>& lhs, const Char* rhs) { return Zbase<Char, SP, AP>(lhs) += rhs; } +template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(const Zbase<Char, SP, AP>& lhs, Char rhs) { return Zbase<Char, SP, AP>(lhs) += rhs; } + +//don't use unified first argument but save one move-construction in the r-value case instead! +template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP>&& lhs, const Zbase<Char, SP, AP>& rhs) { return std::move(lhs += rhs); } //is the move really needed? +template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP>&& lhs, const Char* rhs) { return std::move(lhs += rhs); } //lhs, is an l-vlaue in the function body... +template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(Zbase<Char, SP, AP>&& lhs, Char rhs) { return std::move(lhs += rhs); } //and not a local variable => no copy elision + template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+( Char lhs, const Zbase<Char, SP, AP>& rhs) { return Zbase<Char, SP, AP>(lhs) += rhs; } template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP> operator+(const Char* lhs, const Zbase<Char, SP, AP>& rhs) { return Zbase<Char, SP, AP>(lhs) += rhs; } diff --git a/zen/string_tools.h b/zen/string_tools.h index 32d12119..c0bb1039 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -286,34 +286,45 @@ template <class S, class T, class U> inline S replaceCpy(const S& str, const T& oldTerm, const U& newTerm, bool replaceAll) { assert_static(IsStringLike<T>::value && IsStringLike<U>::value); - typedef typename GetCharType<S>::Type CharType; const size_t oldLen = strLength(oldTerm); - const size_t newLen = strLength(newTerm); - - S output; + if (oldLen == 0) + { + assert(false); + return str; + } const CharType* strPos = strBegin(str); const CharType* const strEnd = strPos + strLength(str); const CharType* const oldBegin = strBegin(oldTerm); + const CharType* const oldEnd = oldBegin + oldLen; + + //optimize "oldTerm not found" + const CharType* strMatch = std::search(strPos, strEnd, + oldBegin, oldEnd); + if (strMatch == strEnd) + return str; + + const size_t newLen = strLength(newTerm); const CharType* const newBegin = strBegin(newTerm); + S output; for (;;) { - const CharType* ptr = std::search(strPos, strEnd, - oldBegin, oldBegin + oldLen); - if (ptr == strEnd) - break; - - implementation::stringAppend(output, strPos, ptr - strPos); + implementation::stringAppend(output, strPos, strMatch - strPos); implementation::stringAppend(output, newBegin, newLen); - strPos = ptr + oldLen; + strPos = strMatch + oldLen; if (!replaceAll) break; + + strMatch = std::search(strPos, strEnd, + oldBegin, oldEnd); + if (strMatch == strEnd) + break; } implementation::stringAppend(output, strPos, strEnd - strPos); diff --git a/zen/thread.h b/zen/thread.h index 432a521e..f00c0298 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -9,7 +9,6 @@ //temporary solution until C++11 thread becomes fully available #include <memory> -#include "fixed_list.h" //fix this pathetic boost thread warning mess #ifdef __MINGW32__ @@ -64,12 +63,12 @@ public: bool timedWait(const Duration& duration) const; //true: "get()" is ready, false: time elapsed //return first value or none if all jobs failed; blocks until result is ready! - std::unique_ptr<T> get() const; //must be called only once! + std::unique_ptr<T> get() const; //may be called only once! private: class AsyncResult; - FixedList<boost::thread> workload; //note: we cannot use std::vector<boost::thread>: compiler error on GCC 4.7, probably a boost screw-up std::shared_ptr<AsyncResult> result; + size_t jobsTotal; }; @@ -93,11 +92,12 @@ private: #endif template <class T, class Function> inline -auto async2(Function fun) -> boost::unique_future<T> //workaround VS2010 bug: bool (*fun)(); decltype(fun()) == int! +auto async2(Function fun) -> boost::unique_future<T> //support for workaround of VS2010 bug: bool (*fun)(); decltype(fun()) == int! { - boost::packaged_task<T> pt([=] { return fun(); }); + boost::packaged_task<T> pt(fun); auto fut = pt.get_future(); - boost::thread(std::move(pt)); + boost::thread t(std::move(pt)); + t.detach(); //we have to be explicit since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!! return std::move(fut); //compiler error without "move", why needed??? } @@ -181,28 +181,27 @@ private: template <class T> inline -RunUntilFirstHit<T>::RunUntilFirstHit() : result(std::make_shared<AsyncResult>()) {} +RunUntilFirstHit<T>::RunUntilFirstHit() : result(std::make_shared<AsyncResult>()), jobsTotal(0) {} template <class T> template <class Fun> inline void RunUntilFirstHit<T>::addJob(Fun f) //f must return a std::unique_ptr<T> containing a value on success { - auto result2 = result; //VC11: this is ridiculous!!! - workload.emplace_back([result2, f] - { - result2->reportFinished(f()); - }); + auto result2 = result; //MSVC2010: this is ridiculous!!! + boost::thread t([result2, f] { result2->reportFinished(f()); }); + ++jobsTotal; + t.detach(); //we have to be explicit since C++11: [thread.thread.destr] ~thread() calls std::terminate() if joinable()!!! } template <class T> template <class Duration> inline -bool RunUntilFirstHit<T>::timedWait(const Duration& duration) const { return result->waitForResult(workload.size(), duration); } +bool RunUntilFirstHit<T>::timedWait(const Duration& duration) const { return result->waitForResult(jobsTotal, duration); } template <class T> inline -std::unique_ptr<T> RunUntilFirstHit<T>::get() const { return result->getResult(workload.size()); } +std::unique_ptr<T> RunUntilFirstHit<T>::get() const { return result->getResult(jobsTotal); } } #endif //BOOST_THREAD_WRAP_H diff --git a/zen/tick_count.h b/zen/tick_count.h index 4f1a047e..98e59ae5 100644 --- a/zen/tick_count.h +++ b/zen/tick_count.h @@ -8,8 +8,10 @@ #define ZEN_TICK_COUNT_HEADER_3807326 #include <cstdint> +#include <algorithm> #include "type_traits.h" #include "assert_static.h" +#include <cmath> #ifdef FFS_WIN #include "win.h" //includes "windows.h" @@ -21,7 +23,7 @@ namespace zen { //a portable "GetTickCount()" using "wall time equivalent" - e.g. no jumps due to ntp time corrections class TickVal; -std::int64_t operator-(const TickVal& lhs, const TickVal& rhs); +std::int64_t dist(const TickVal& lhs, const TickVal& rhs); //use absolute difference for paranoid security: even QueryPerformanceCounter "wraps-around" at *some* time std::int64_t ticksPerSec(); //return 0 on error TickVal getTicks(); //return invalid value on error: !TickVal::isValid() @@ -40,10 +42,6 @@ TickVal getTicks(); //return invalid value on error: !TickVal::isValid() - - - - //############################ implementation ############################## class TickVal { @@ -58,19 +56,31 @@ public: explicit TickVal(const NativeVal& val) : val_(val) {} inline friend - std::int64_t operator-(const TickVal& lhs, const TickVal& rhs) + std::int64_t dist(const TickVal& lhs, const TickVal& rhs) { #ifdef FFS_WIN assert_static(IsSignedInt<decltype(lhs.val_.QuadPart)>::value); - return lhs.val_.QuadPart - rhs.val_.QuadPart; + return std::abs(lhs.val_.QuadPart - rhs.val_.QuadPart); #elif defined FFS_LINUX assert_static(IsSignedInt<decltype(lhs.val_.tv_sec)>::value); assert_static(IsSignedInt<decltype(lhs.val_.tv_nsec)>::value); - return static_cast<std::int64_t>(lhs.val_.tv_sec - rhs.val_.tv_sec) * 1000000000.0 + lhs.val_.tv_nsec - rhs.val_.tv_nsec; + return std::abs(static_cast<std::int64_t>(lhs.val_.tv_sec - rhs.val_.tv_sec) * 1000000000.0 + (lhs.val_.tv_nsec - rhs.val_.tv_nsec)); +#endif + } + + inline friend + bool operator<(const TickVal& lhs, const TickVal& rhs) //evaluate directly rather than reuse operator- + { +#ifdef FFS_WIN + return lhs.val_.QuadPart < rhs.val_.QuadPart; +#elif defined FFS_LINUX + 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; #endif } - bool isValid() const { return *this - TickVal() != 0; } + bool isValid() const { return dist(*this, TickVal()) != 0; } private: NativeVal val_; @@ -82,7 +92,7 @@ std::int64_t ticksPerSec() //return 0 on error { #ifdef FFS_WIN LARGE_INTEGER frequency = {}; - if (!::QueryPerformanceFrequency(&frequency)) + if (!::QueryPerformanceFrequency(&frequency)) //MSDN promises: "The frequency cannot change while the system is running." return 0; assert_static(sizeof(std::int64_t) >= sizeof(frequency.QuadPart)); return frequency.QuadPart; @@ -69,15 +69,6 @@ bool parseTime(const String& format, const String& str, TimeComp& comp); //simil - - - - - - - - - //############################ implementation ############################## namespace implementation { diff --git a/zen/zstring.h b/zen/zstring.h index 9d93d2d3..df96df7b 100644 --- a/zen/zstring.h +++ b/zen/zstring.h @@ -7,8 +7,8 @@ #ifndef ZSTRING_H_INCLUDED #define ZSTRING_H_INCLUDED -#include "string_base.h" #include <cstring> //strcmp() +#include "string_base.h" #ifndef NDEBUG #include "thread.h" //includes <boost/thread.hpp> |