diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:20:50 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:20:50 +0200 |
commit | 7e706cf64654aea466c059c307e5723e2423ed5d (patch) | |
tree | e85f0d28d7c81b6d21419fc38e1a654cca2212b1 /zen | |
parent | 5.5 (diff) | |
download | FreeFileSync-7e706cf64654aea466c059c307e5723e2423ed5d.tar.gz FreeFileSync-7e706cf64654aea466c059c307e5723e2423ed5d.tar.bz2 FreeFileSync-7e706cf64654aea466c059c307e5723e2423ed5d.zip |
5.6
Diffstat (limited to 'zen')
-rw-r--r-- | zen/FindFilePlus/find_file_plus.cpp | 21 | ||||
-rw-r--r-- | zen/FindFilePlus/find_file_plus.h | 1 | ||||
-rw-r--r-- | zen/IFileOperation/file_op.cpp | 2 | ||||
-rw-r--r-- | zen/assert_static.h | 2 | ||||
-rw-r--r-- | zen/basic_math.h | 2 | ||||
-rw-r--r-- | zen/debug_log.h | 2 | ||||
-rw-r--r-- | zen/debug_new.h | 9 | ||||
-rw-r--r-- | zen/dir_watcher.cpp | 2 | ||||
-rw-r--r-- | zen/file_handling.cpp | 38 | ||||
-rw-r--r-- | zen/file_handling.h | 2 | ||||
-rw-r--r-- | zen/file_io.cpp | 71 | ||||
-rw-r--r-- | zen/file_traverser.cpp | 45 | ||||
-rw-r--r-- | zen/fixed_list.h | 2 | ||||
-rw-r--r-- | zen/recycler.h | 2 | ||||
-rw-r--r-- | zen/serialize.h | 244 | ||||
-rw-r--r-- | zen/string_base.h | 2 | ||||
-rw-r--r-- | zen/string_tools.h | 19 | ||||
-rw-r--r-- | zen/symlink_target.h | 64 |
18 files changed, 434 insertions, 96 deletions
diff --git a/zen/FindFilePlus/find_file_plus.cpp b/zen/FindFilePlus/find_file_plus.cpp index 19f43998..a6e82617 100644 --- a/zen/FindFilePlus/find_file_plus.cpp +++ b/zen/FindFilePlus/find_file_plus.cpp @@ -107,13 +107,13 @@ bool findplus::initDllBinding() //evaluate in ::DllMain() when attaching process class findplus::FileSearcher { public: - FileSearcher(const wchar_t* dirname); //throw FileError + FileSearcher(const wchar_t* dirname); //throw NtFileError ~FileSearcher(); - void readDir(FileInformation& output); //throw FileError + void readDir(FileInformation& output); //throw NtFileError private: - template <class QueryPolicy> void readDirImpl(FileInformation& output); //throw FileError + template <class QueryPolicy> void readDirImpl(FileInformation& output); //throw NtFileError UNICODE_STRING dirnameNt; //it seems hDir implicitly keeps a reference to this, at least ::FindFirstFile() does no cleanup before ::FindClose()! HANDLE hDir; @@ -136,7 +136,7 @@ FileSearcher::FileSearcher(const wchar_t* dirname) : dirnameNt.Length = 0; dirnameNt.MaximumLength = 0; - zen::ScopeGuard guardConstructor = zen::makeGuard([&]() { this->~FileSearcher(); }); + zen::ScopeGuard guardConstructor = zen::makeGuard([&] { this->~FileSearcher(); }); //-------------------------------------------------------------------------------------------------------------- //convert dosFileName, e.g. C:\Users or \\?\C:\Users to ntFileName \??\C:\Users @@ -222,11 +222,11 @@ struct DirQueryFileId inline -void FileSearcher::readDir(FileInformation& output) { readDirImpl<DirQueryFileId>(output); } //throw FileError +void FileSearcher::readDir(FileInformation& output) { readDirImpl<DirQueryFileId>(output); } //throw NtFileError template <class QueryPolicy> -void FileSearcher::readDirImpl(FileInformation& output) //throw FileError +void FileSearcher::readDirImpl(FileInformation& output) //throw NtFileError { //although FILE_ID_FULL_DIR_INFORMATION should suffice for our purposes, there are problems on Windows XP for certain directories, e.g. "\\Vboxsvr\build" //making NtQueryDirectoryFile() return with STATUS_INVALID_PARAMETER while other directories, e.g. "C:\" work fine for some reason @@ -335,6 +335,11 @@ void FileSearcher::readDirImpl(FileInformation& output) //throw FileError output.fileAttributes = dirInfo.FileAttributes; output.shortNameLength = dirInfo.FileNameLength / sizeof(TCHAR); //FileNameLength is in bytes! + if (dirInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) //analog to FindFirstFile(), confirmed for Win XP and Win 7 + output.reparseTag = dirInfo.EaSize; // + else + output.reparseTag = 0; + if (dirInfo.FileNameLength + sizeof(TCHAR) > sizeof(output.shortName)) //this may actually happen if ::NtQueryDirectoryFile() decides to return a throw NtFileError(STATUS_BUFFER_OVERFLOW); //short name of length MAX_PATH + 1, 0-termination is not required! @@ -352,7 +357,7 @@ FindHandle findplus::openDir(const wchar_t* dirname) { try { - return new FileSearcher(dirname); //throw FileError + return new FileSearcher(dirname); //throw NtFileError } catch (const NtFileError& e) { @@ -366,7 +371,7 @@ bool findplus::readDir(FindHandle hnd, FileInformation& output) { try { - hnd->readDir(output); //throw FileError + hnd->readDir(output); //throw NtFileError return true; } catch (const NtFileError& e) diff --git a/zen/FindFilePlus/find_file_plus.h b/zen/FindFilePlus/find_file_plus.h index 3799a1e1..49b18733 100644 --- a/zen/FindFilePlus/find_file_plus.h +++ b/zen/FindFilePlus/find_file_plus.h @@ -36,6 +36,7 @@ struct FileInformation ULARGE_INTEGER fileSize; ULARGE_INTEGER fileId; //optional: may be 0 if not supported DWORD fileAttributes; + DWORD reparseTag; //set if "fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT" DWORD shortNameLength; WCHAR shortName[MAX_PATH + 1]; //shortName is 0-terminated }; //no need for #pragma pack -> all members are perfectly 4, 8 byte aligned! diff --git a/zen/IFileOperation/file_op.cpp b/zen/IFileOperation/file_op.cpp index 7c75a8e8..b7bc9f9c 100644 --- a/zen/IFileOperation/file_op.cpp +++ b/zen/IFileOperation/file_op.cpp @@ -247,7 +247,7 @@ std::vector<std::wstring> getLockingProcesses(const wchar_t* filename) //throw W &buffer[0], //__out LPTSTR lpExeName, &bufferSize)) //__inout PDWORD lpdwSize if (bufferSize < buffer.size()) - processName += std::wstring(L" - ") + L"\"" + &buffer[0] + L"\""; + processName += std::wstring(L" - ") + L"\'" + &buffer[0] + L"\'"; } } output.push_back(processName); diff --git a/zen/assert_static.h b/zen/assert_static.h index b96b5909..d61dd1e3 100644 --- a/zen/assert_static.h +++ b/zen/assert_static.h @@ -37,7 +37,7 @@ struct CompileTimeError<true> {}; #endif */ -//C++11: at least get rid of this pointless string literal requirement +//C++11: please get rid of this pointless string literal requirement! #define assert_static(X) \ static_assert(X, "Static assert has failed!"); diff --git a/zen/basic_math.h b/zen/basic_math.h index dbc2d922..6678b91e 100644 --- a/zen/basic_math.h +++ b/zen/basic_math.h @@ -134,7 +134,7 @@ const T& max(const T& a, const T& b, const T& c) template <class T> inline -void confine(T& val, const T& minVal, const T& maxVal) +void confine(T& val, const T& minVal, const T& maxVal) //name trim? { assert(minVal <= maxVal); if (val < minVal) diff --git a/zen/debug_log.h b/zen/debug_log.h index 6be81e89..d65f5e36 100644 --- a/zen/debug_log.h +++ b/zen/debug_log.h @@ -56,7 +56,7 @@ public: const std::string& message) { const std::string logEntry = "[" + formatTime<std::string>(FORMAT_TIME) + "] " + afterLast(sourceFile, ZEN_FILE_NAME_SEPARATOR) + - ", line " + numberTo<std::string>(sourceRow) + ": " + message + "\n"; + " (" + numberTo<std::string>(sourceRow) + "): " + message + "\n"; const size_t bytesWritten = ::fwrite(logEntry.c_str(), 1, logEntry.size(), handle); if (std::ferror(handle) != 0 || bytesWritten != logEntry.size()) diff --git a/zen/debug_new.h b/zen/debug_new.h index ca46cc8e..8d616360 100644 --- a/zen/debug_new.h +++ b/zen/debug_new.h @@ -18,9 +18,14 @@ /*overwrite "operator new" to get more detailed error messages on bad_alloc, detect memory leaks and write memory dumps Usage: - Include everywhere before any other file: $(ProjectDir)\shared\debug_new.h + For Minidumps: -- Compile "debug_new.cpp" -- Compile with debugging symbols and optimization deactivated +------------- +1. Compile "debug_new.cpp" +2. Compile "release" build with: + - debugging symbols + - optimization deactivated + - do not suppress frame pointer(/Oy-) - avoid call stack mess up */ namespace mem_check diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index e54c1b5b..29a2a3cf 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -222,7 +222,7 @@ public: } catch (boost::thread_interrupted&) { - throw; //this is the only reasonable exception! + throw; //this is the only exception expected! } } diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp index ebfe4d19..e176f3a6 100644 --- a/zen/file_handling.cpp +++ b/zen/file_handling.cpp @@ -74,8 +74,14 @@ bool zen::dirExists(const Zstring& dirname) bool zen::symlinkExists(const Zstring& linkname) { #ifdef FFS_WIN - const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(linkname).c_str()); - return ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_REPARSE_POINT) != 0; + WIN32_FIND_DATA fileInfo = {}; + { + const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(linkname).c_str(), &fileInfo); + if (searchHandle == INVALID_HANDLE_VALUE) + return false; + ::FindClose(searchHandle); + } + return isSymlink(fileInfo); #elif defined FFS_LINUX struct stat fileInfo = {}; @@ -115,8 +121,7 @@ void getFileAttrib(const Zstring& filename, FileAttrib& attr, ProcSymlink procSl // GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, // &sourceAttr)) //__out LPVOID lpFileInformation - const bool isSymbolicLink = (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; - if (!isSymbolicLink || procSl == SYMLINK_DIRECT) + if (!isSymlink(fileInfo) || procSl == SYMLINK_DIRECT) { //####################################### DST hack ########################################### const bool isDirectory = (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; @@ -1531,7 +1536,21 @@ void createDirectoryRecursively(const Zstring& directory, const Zstring& templat void zen::makeNewDirectory(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions) //FileError, ErrorTargetExisting { - //remove trailing separator +#ifdef FFS_WIN + //special handling for volume root: trying to create existing root directory results in ERROR_ACCESS_DENIED rather than ERROR_ALREADY_EXISTS! + const Zstring dirTmp = removeLongPathPrefix(directory); + if (dirTmp.size() == 3 && + std::iswalpha(dirTmp[0]) && endsWith(dirTmp, L":\\")) + { + const ErrorCode lastError = dirExists(dirTmp) ? ERROR_ALREADY_EXISTS : ERROR_PATH_NOT_FOUND; + + const std::wstring msg = replaceCpy(_("Cannot create directory %x."), L"%x", fmtFileName(dirTmp)) + L"\n\n" + getLastErrorFormatted(lastError); + if (lastError == ERROR_ALREADY_EXISTS) + throw ErrorTargetExisting(msg); + throw FileError(msg); + } +#endif + //remove trailing separator (except for volume root directories!) const Zstring dirFormatted = endsWith(directory, FILE_NAME_SEPARATOR) ? beforeLast(directory, FILE_NAME_SEPARATOR) : directory; @@ -1550,11 +1569,14 @@ void zen::makeDirectory(const Zstring& directory) { makeNewDirectory(directory, Zstring(), false); //FileError, ErrorTargetExisting } - catch (const ErrorTargetExisting&) + catch (const FileError& e) { - if (dirExists(directory)) + assert(dynamic_cast<const ErrorTargetExisting*>(&e)); (void)e; + //could there be situations where a directory/network path exists, but creation fails with + //error different than "ErrorTargetExisting"?? => better catch all "FileError" and check existence again + if (dirExists(directory)) //technically a file system race-condition! return; - throw; //clash with file (dir symlink is okay) + throw; } } diff --git a/zen/file_handling.h b/zen/file_handling.h index d6444da3..d1dcca22 100644 --- a/zen/file_handling.h +++ b/zen/file_handling.h @@ -48,7 +48,7 @@ UInt64 getFilesize(const Zstring& filename); //throw FileError UInt64 getFreeDiskSpace(const Zstring& path); //throw FileError //file handling -bool removeFile(const Zstring& filename); //return "true" if file was actually deleted; throw FileError +bool removeFile(const Zstring& filename); //throw FileError; return "true" if file was actually deleted void removeDirectory(const Zstring& directory, CallbackRemoveDir* callback = nullptr); //throw FileError diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 77fcb691..f935de7a 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -24,7 +24,7 @@ FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExis filename_(filename) { #ifdef FFS_WIN - fileHandle = ::CreateFile(zen::applyLongPathPrefix(filename).c_str(), + fileHandle = ::CreateFile(applyLongPathPrefix(filename).c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, @@ -123,31 +123,54 @@ FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw Fil filename_(filename) { #ifdef FFS_WIN - fileHandle = ::CreateFile(zen::applyLongPathPrefix(filename).c_str(), - GENERIC_READ | GENERIC_WRITE, - /* http://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx - quote: When an application creates a file across a network, it is better - to use GENERIC_READ | GENERIC_WRITE for dwDesiredAccess than to use GENERIC_WRITE alone. - The resulting code is faster, because the redirector can use the cache manager and send fewer SMBs with more data. - This combination also avoids an issue where writing to a file across a network can occasionally return ERROR_ACCESS_DENIED. */ - FILE_SHARE_DELETE, //FILE_SHARE_DELETE is required to rename file while handle is open! - nullptr, - access == ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, - nullptr); + const DWORD dwCreationDisposition = access == FileOutput::ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW; + + auto getHandle = [&](DWORD dwFlagsAndAttributes) + { + return ::CreateFile(applyLongPathPrefix(filename).c_str(), + GENERIC_READ | GENERIC_WRITE, + /* http://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx + quote: When an application creates a file across a network, it is better + to use GENERIC_READ | GENERIC_WRITE for dwDesiredAccess than to use GENERIC_WRITE alone. + The resulting code is faster, because the redirector can use the cache manager and send fewer SMBs with more data. + This combination also avoids an issue where writing to a file across a network can occasionally return ERROR_ACCESS_DENIED. */ + FILE_SHARE_DELETE, //FILE_SHARE_DELETE is required to rename file while handle is open! + nullptr, + dwCreationDisposition, + dwFlagsAndAttributes | FILE_FLAG_SEQUENTIAL_SCAN, + nullptr); + }; + + fileHandle = getHandle(FILE_ATTRIBUTE_NORMAL); if (fileHandle == INVALID_HANDLE_VALUE) { - const DWORD lastError = ::GetLastError(); - const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename_)) + 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 - throw ErrorTargetExisting(errorMessage); - - if (lastError == ERROR_PATH_NOT_FOUND) - throw ErrorTargetPathMissing(errorMessage); - - throw FileError(errorMessage); + DWORD lastError = ::GetLastError(); + + //CREATE_ALWAYS fails with ERROR_ACCESS_DENIED if the existing file is hidden or "system" http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx + if (lastError == ERROR_ACCESS_DENIED && + dwCreationDisposition == CREATE_ALWAYS) + { + const DWORD attrib = ::GetFileAttributes(applyLongPathPrefix(filename).c_str()); + if (attrib != INVALID_FILE_ATTRIBUTES) + { + fileHandle = getHandle(attrib); //retry + lastError = ::GetLastError(); + } + } + //"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); + + 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 + throw ErrorTargetExisting(errorMessage); + + if (lastError == ERROR_PATH_NOT_FOUND) + throw ErrorTargetPathMissing(errorMessage); + + throw FileError(errorMessage); + } } #elif defined FFS_LINUX diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index aa46d4f0..986e0ad8 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -225,7 +225,6 @@ struct Win32Traverser return true; } - template <class FindData> static void extractFileInfo(const FindData& fileInfo, DWORD volumeSerial, TraverseCallback::FileInfo& output) { output.fileSize = UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); @@ -233,23 +232,12 @@ struct Win32Traverser output.id = FileId(); } - template <class FindData> - static Int64 getModTime(const FindData& fileInfo) { return toTimeT(fileInfo.ftLastWriteTime); } - - template <class FindData> - static const FILETIME& getModTimeRaw(const FindData& fileInfo) { return fileInfo.ftLastWriteTime; } - - template <class FindData> + static Int64 getModTime (const FindData& fileInfo) { return toTimeT(fileInfo.ftLastWriteTime); } + static const FILETIME& getModTimeRaw (const FindData& fileInfo) { return fileInfo.ftLastWriteTime; } static const FILETIME& getCreateTimeRaw(const FindData& fileInfo) { return fileInfo.ftCreationTime; } - - template <class FindData> - static const wchar_t* getShortName(const FindData& fileInfo) { return fileInfo.cFileName; } - - template <class FindData> - static bool isDirectory(const FindData& fileInfo) { return (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } - - template <class FindData> - static bool isSymlink(const FindData& fileInfo) { return (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; } + static const wchar_t* getShortName (const FindData& fileInfo) { return fileInfo.cFileName; } + static bool isDirectory (const FindData& fileInfo) { return (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } + static bool isSymlink (const FindData& fileInfo) { return zen::isSymlink(fileInfo); } //[!] keep namespace }; @@ -295,10 +283,10 @@ struct FilePlusTraverser //else we have a problem... report it: throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted(lastError) + L" (+)"); } + return true; } - template <class FindData> static void extractFileInfo(const FindData& fileInfo, DWORD volumeSerial, TraverseCallback::FileInfo& output) { output.fileSize = fileInfo.fileSize.QuadPart; @@ -306,23 +294,12 @@ struct FilePlusTraverser output.id = extractFileID(volumeSerial, fileInfo.fileId); } - template <class FindData> - static Int64 getModTime(const FindData& fileInfo) { return toTimeT(fileInfo.lastWriteTime); } - - template <class FindData> - static const FILETIME& getModTimeRaw(const FindData& fileInfo) { return fileInfo.lastWriteTime; } - - template <class FindData> + static Int64 getModTime (const FindData& fileInfo) { return toTimeT(fileInfo.lastWriteTime); } + static const FILETIME& getModTimeRaw (const FindData& fileInfo) { return fileInfo.lastWriteTime; } static const FILETIME& getCreateTimeRaw(const FindData& fileInfo) { return fileInfo.creationTime; } - - template <class FindData> - static const wchar_t* getShortName(const FindData& fileInfo) { return fileInfo.shortName; } - - template <class FindData> - static bool isDirectory(const FindData& fileInfo) { return (fileInfo.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } - - template <class FindData> - static bool isSymlink(const FindData& fileInfo) { return (fileInfo.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; } + static const wchar_t* getShortName (const FindData& fileInfo) { return fileInfo.shortName; } + static bool isDirectory (const FindData& fileInfo) { return (fileInfo.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } + static bool isSymlink (const FindData& fileInfo) { return zen::isSymlink(fileInfo.fileAttributes, fileInfo.reparseTag); } //[!] keep namespace }; diff --git a/zen/fixed_list.h b/zen/fixed_list.h index 04a680ad..60d7e0e7 100644 --- a/zen/fixed_list.h +++ b/zen/fixed_list.h @@ -19,7 +19,7 @@ class FixedList struct Node { Node() : next(nullptr), val() {} - //no variadic templates on VC2010... :( + //no variadic templates on VC2010... :( template <class A> Node(A&& a) : next(nullptr), val(std::forward<A>(a)) {} template <class A, class B> Node(A&& a, B&& b) : next(nullptr), val(std::forward<A>(a), std::forward<B>(b)) {} template <class A, class B, class C> Node(A&& a, B&& b, C&& c) : next(nullptr), val(std::forward<A>(a), std::forward<B>(b), std::forward<C>(c)) {} diff --git a/zen/recycler.h b/zen/recycler.h index 1778eb2e..955dfbee 100644 --- a/zen/recycler.h +++ b/zen/recycler.h @@ -29,7 +29,7 @@ Linker flags: `pkg-config --libs gio-2.0` Already included in package "gtk+-2.0"! */ -//move a file or folder to Recycle Bin (deletes permanently if recycle is not available) -> crappy semantics, but we have no choice thanks to Windows' design +//move a file or folder to Recycle Bin (deletes permanently if recycler is not available) -> crappy semantics, but we have no choice thanks to Windows' design bool recycleOrDelete(const Zstring& filename); //throw FileError, return "true" if file/dir was actually deleted diff --git a/zen/serialize.h b/zen/serialize.h new file mode 100644 index 00000000..ff9695b1 --- /dev/null +++ b/zen/serialize.h @@ -0,0 +1,244 @@ +// ************************************************************************** +// * 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 (zhnmju123 AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#ifndef SERIALIZE_H_INCLUDED +#define SERIALIZE_H_INCLUDED + +#include <cstdint> +#include <zen/string_base.h> +#include <zen/file_io.h> + + +namespace zen +{ +//high-performance unformatted serialization (avoiding wxMemoryOutputStream/wxMemoryInputStream inefficiencies) + +/* +-------------------------- +|Binary Container Concept| +-------------------------- +binary container for data storage: must support "basic" std::vector interface (e.g. std::vector<char>, std::string, Zbase<char>) +*/ + +//binary container reference implementations +typedef Zbase<char> Utf8String; //ref-counted + COW text stream + guaranteed performance: exponential growth +class BinaryStream; //ref-counted byte stream + guaranteed performance: exponential growth -> no COW, but 12% faster than Utf8String (due to no null-termination?) + +class BinaryStream //essentially a std::vector<char> with ref-counted semantics, but no COW! => *almost* value type semantics, but not quite +{ +public: + BinaryStream() : buffer(std::make_shared<std::vector<char>>()) {} + + typedef std::vector<char>::value_type value_type; + typedef std::vector<char>::iterator iterator; + typedef std::vector<char>::const_iterator const_iterator; + + iterator begin() { return buffer->begin(); } + iterator end () { return buffer->end (); } + + const_iterator begin() const { return buffer->begin(); } + const_iterator end () const { return buffer->end (); } + + void resize(size_t len) { buffer->resize(len); } + size_t size() const { return buffer->size(); } + bool empty() const { return buffer->empty(); } + + inline friend bool operator==(const BinaryStream& lhs, const BinaryStream& rhs) { return *lhs.buffer == *rhs.buffer; } + +private: + std::shared_ptr<std::vector<char>> buffer; //always bound! + //perf: shared_ptr indirection irrelevant: less than 1% slower! +}; + +//---------------------------------------------------------------------- +//functions based on binary container abstraction +template <class BinContainer> void saveBinStream(const Zstring& filename, const BinContainer& cont); //throw FileError +template <class BinContainer> BinContainer loadBinStream(const Zstring& filename); //throw FileError, ErrorNotExisting + + +/* +----------------------------- +|Binary Input Stream Concept| +----------------------------- +struct BinInputStream +{ + const void* requestRead(size_t len); //expect external read of len bytes +}; + +------------------------------ +|Binary Output Stream Concept| +------------------------------ +struct BinOutputStream +{ + void* requestWrite(size_t len); //expect external write of len bytes +}; +*/ + +//binary input/output stream reference implementation +class UnexpectedEndOfStreamError {}; + +struct BinStreamIn //throw UnexpectedEndOfStreamError +{ + BinStreamIn(const BinaryStream& cont) : buffer(cont), pos(0) {} //this better be cheap! + + const void* requestRead(size_t len) //throw UnexpectedEndOfStreamError + { + if (pos + len > buffer.size()) + throw UnexpectedEndOfStreamError(); + size_t oldPos = pos; + pos += len; + return &*buffer.begin() + oldPos; + } + +private: + const BinaryStream buffer; + size_t pos; +}; + +struct BinStreamOut +{ + void* requestWrite(size_t len) + { + size_t oldSize = buffer.size(); + buffer.resize(buffer.size() + len); + return &*buffer.begin() + oldSize; + } + + BinaryStream get() { return buffer; } + +private: + BinaryStream buffer; +}; + +//---------------------------------------------------------------------- +//functions based on binary stream abstraction +template <class N, class BinOutputStream> void writeNumber (BinOutputStream& stream, const N& num); // +template <class C, class BinOutputStream> void writeContainer(BinOutputStream& stream, const C& str); //throw () +template < class BinOutputStream> void writeArray (BinOutputStream& stream, const void* data, size_t len); // + +//---------------------------------------------------------------------- + +template <class N, class BinInputStream> N readNumber (BinInputStream& stream); // +template <class C, class BinInputStream> C readContainer(BinInputStream& stream); //throw UnexpectedEndOfStreamError (corrupted data) +template < class BinInputStream> void readArray (BinInputStream& stream, void* data, size_t len); // + + + + + + + + + + + + + + + + + + +//-----------------------implementation------------------------------- +template <class BinContainer> inline +void saveBinStream(const Zstring& filename, const BinContainer& cont) //throw FileError +{ + assert_static(sizeof(typename BinContainer::value_type) == 1); //expect: bytes (until further) + + FileOutput fileOut(filename, zen::FileOutput::ACC_OVERWRITE); //throw FileError + if (!cont.empty()) + fileOut.write(&*cont.begin(), cont.size()); //throw FileError +} + + +template <class BinContainer> inline +BinContainer loadBinStream(const Zstring& filename) //throw FileError, ErrorNotExisting +{ + assert_static(sizeof(typename BinContainer::value_type) == 1); //expect: bytes (until further) + + FileInput fileIn(filename); //throw FileError, ErrorNotExisting + + BinContainer contOut; + const size_t blockSize = 64 * 1024; + do + { + contOut.resize(contOut.size() + blockSize); + + const size_t bytesRead = fileIn.read(&*contOut.begin() + contOut.size() - blockSize, blockSize); //throw FileError + if (bytesRead < blockSize) + contOut.resize(contOut.size() - (blockSize - bytesRead)); //caveat: unsigned arithmetics + } + while (!fileIn.eof()); + + return contOut; +} + + +template <class BinOutputStream> inline +void writeArray(BinOutputStream& stream, const void* data, size_t len) +{ + std::copy(static_cast<const char*>(data), + static_cast<const char*>(data) + len, + static_cast< char*>(stream.requestWrite(len))); +} + + +template <class N, class BinOutputStream> inline +void writeNumber(BinOutputStream& stream, const N& num) +{ + assert_static((IsArithmetic<N>::value || IsSameType<N, bool>::value)); + writeArray(stream, &num, sizeof(N)); +} + + +template <class C, class BinOutputStream> inline +void writeContainer(BinOutputStream& stream, const C& cont) //don't even consider UTF8 conversions here, we're handling arbitrary binary data! +{ + const auto len = cont.size(); + writeNumber(stream, static_cast<std::uint32_t>(len)); + if (len > 0) + writeArray(stream, &*cont.begin(), sizeof(typename C::value_type) * len); //don't use c_str(), but access uniformly via STL interface +} + + +template <class BinInputStream> inline +void readArray(BinInputStream& stream, void* data, size_t len) +{ + const char* const src = static_cast<const char*>(stream.requestRead(len)); //expect external write of len bytes + std::copy(src, src + len, static_cast<char*>(data)); +} + + +template <class N, class BinInputStream> inline +N readNumber(BinInputStream& stream) +{ + assert_static((IsArithmetic<N>::value || IsSameType<N, bool>::value)); + N num = 0; + readArray(stream, &num, sizeof(N)); + return num; +} + + +template <class C, class BinInputStream> inline +C readContainer(BinInputStream& stream) +{ + C cont; + auto strLength = readNumber<std::uint32_t>(stream); + if (strLength > 0) + try + { + cont.resize(strLength); //throw std::bad_alloc + readArray(stream, &*cont.begin(), sizeof(typename C::value_type) * strLength); + } + catch (std::bad_alloc&) //most likely this is due to data corruption! + { + throw UnexpectedEndOfStreamError(); + } + return cont; +} +} + +#endif //SERIALIZE_H_INCLUDED diff --git a/zen/string_base.h b/zen/string_base.h index 4c339d56..e2e9f19a 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -196,7 +196,7 @@ public: Zbase(const Char* source, size_t length); Zbase(const Zbase& source); Zbase(Zbase&& tmp); - explicit Zbase(Char source); //dangerous if implicit: Char buffer[]; return buffer[0]; ups... forgot &, but no error + explicit Zbase(Char source); //dangerous if implicit: Char buffer[]; return buffer[0]; ups... forgot &, but not a compiler error! //allow explicit construction from different string type, prevent ambiguity via SFINAE template <class S> explicit Zbase(const S& other, typename S::value_type = 0); ~Zbase(); diff --git a/zen/string_tools.h b/zen/string_tools.h index 00eb50ee..80232086 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -458,30 +458,29 @@ S formatInteger(Num n, bool hasMinus) { assert(n >= 0); typedef typename GetCharType<S>::Type CharType; + CharType buffer[2 + 5 * sizeof(Num) / 2]; //it's generally faster to use a buffer than to rely on String::operator+=() (in)efficiency + //minimum required chars (+ sign char): 1 + ceil(ln_10 (256^sizeof(n))) =~ 1 + ceil(sizeof(n) * 2.4082) < 2 + floor(sizeof(n) * 2.5) - const size_t bufferSize = 64; //sufficient for signed 128-bit numbers - CharType buffer[bufferSize]; //it's generally faster to use a buffer than to rely on String::operator+=() (in)efficiency - assert_static(2 + 5 * sizeof(n) / 2 <= bufferSize); - //minimum required chars (+ sign char): 1 + ceil(ln_10 (256^sizeof(n))) =~ 1 + ceil(sizeof(n) * 2.4082) <= 2 + floor(sizeof(n) * 2.5) - - size_t startPos = bufferSize; + auto iter = std::end(buffer); do { - buffer[--startPos] = static_cast<char>('0' + n % 10); - n /= 10; + const Num tmp = n / 10; + *--iter = static_cast<CharType>('0' + (n - tmp * 10)); //8% faster than using modulus operator! + n = tmp; } while (n != 0); if (hasMinus) - buffer[--startPos] = static_cast<CharType>('-'); + *--iter = static_cast<CharType>('-'); - return S(buffer + startPos, bufferSize - startPos); + return S(&*iter, std::end(buffer) - iter); } template <class S, class Num> inline S numberTo(const Num& number, Int2Type<NUM_TYPE_SIGNED_INT>) { return formatInteger<S>(number < 0 ? -number : number, number < 0); + //bug for "INT_MIN"! technically -INT_MIN == INT_MIN -> not worth the trouble } diff --git a/zen/symlink_target.h b/zen/symlink_target.h index 17ae1916..6d44d845 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -21,6 +21,33 @@ #endif +namespace zen +{ +#ifdef FFS_WIN +bool isSymlink(const WIN32_FIND_DATA& data); //*not* a simple FILE_ATTRIBUTE_REPARSE_POINT check! +bool isSymlink(DWORD fileAttributes, DWORD reparseTag); +#endif + +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 { @@ -55,10 +82,11 @@ typedef struct _REPARSE_DATA_BUFFER //from ntifs.h #define REPARSE_DATA_BUFFER_HEADER_SIZE FIELD_OFFSET(REPARSE_DATA_BUFFER, GenericReparseBuffer) #endif + namespace { //retrieve raw target data of symlink or junction -Zstring getSymlinkRawTargetString(const Zstring& linkPath) //throw FileError +Zstring getSymlinkRawTargetString_impl(const Zstring& linkPath) //throw FileError { using namespace zen; #ifdef FFS_WIN @@ -136,4 +164,38 @@ Zstring getSymlinkRawTargetString(const Zstring& linkPath) //throw FileError } } + +namespace zen +{ +inline +Zstring getSymlinkRawTargetString(const Zstring& linkPath) { return getSymlinkRawTargetString_impl(linkPath); } + + +#ifdef FFS_WIN +/* + Reparse Point Tags + http://msdn.microsoft.com/en-us/library/windows/desktop/aa365511(v=vs.85).aspx + WIN32_FIND_DATA structure + http://msdn.microsoft.com/en-us/library/windows/desktop/aa365740(v=vs.85).aspx + + The only surrogate reparse points are; + IO_REPARSE_TAG_MOUNT_POINT + IO_REPARSE_TAG_SYMLINK +*/ + +inline +bool isSymlink(DWORD fileAttributes, DWORD reparseTag) +{ + return (fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && + IsReparseTagNameSurrogate(reparseTag); +} + +inline +bool isSymlink(const WIN32_FIND_DATA& data) +{ + return isSymlink(data.dwFileAttributes, data.dwReserved0); +} +#endif +} + #endif // SYMLINK_WIN_H_INCLUDED |