diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:15:16 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:15:16 +0200 |
commit | bd6336c629841c6db3a6ca53a936d629d34db53b (patch) | |
tree | 3721ef997864108df175ce677a8a7d4342a6f1d2 /zen | |
parent | 4.0 (diff) | |
download | FreeFileSync-bd6336c629841c6db3a6ca53a936d629d34db53b.tar.gz FreeFileSync-bd6336c629841c6db3a6ca53a936d629d34db53b.tar.bz2 FreeFileSync-bd6336c629841c6db3a6ca53a936d629d34db53b.zip |
4.1
Diffstat (limited to 'zen')
55 files changed, 10145 insertions, 0 deletions
diff --git a/zen/assert_static.h b/zen/assert_static.h new file mode 100644 index 00000000..00c4c5c8 --- /dev/null +++ b/zen/assert_static.h @@ -0,0 +1,44 @@ +// ************************************************************************** +// * This file is part of the zenXML project. It is distributed under the * +// * Boost Software License, Version 1.0. See accompanying file * +// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. * +// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef ASSERTSTATIC_H_INCLUDED +#define ASSERTSTATIC_H_INCLUDED + +/* +//compile time assert based on Loki (http://loki-lib.sourceforge.net) + +#ifdef NDEBUG + +#define assert_static(x) //((void)0) -> leads to error when seen in namespace scope! + +#else // debugging enabled +namespace static_check_impl +{ +template<int> +struct CompileTimeError; + +template<> +struct CompileTimeError<true> {}; +} + +#define LOKI_CONCAT( X, Y ) LOKI_CONCAT_SUB( X, Y ) +#define LOKI_CONCAT_SUB( X, Y ) X##Y + +#define assert_static(expr) \ + enum { LOKI_CONCAT(loki_enum_dummy_value, __LINE__) = sizeof(StaticCheckImpl::CompileTimeError<static_cast<bool>(expr) >) } + +// #define assert_static(expr) \ +// { Loki::CompileTimeError<((expr) != 0)> Static_Assert_Has_Failed; (void)Static_Assert_Has_Failed; } + +#endif +*/ + +//C++11: +#define assert_static(X) \ + static_assert(X, "Static assert has failed!"); + +#endif //ASSERTSTATIC_H_INCLUDED diff --git a/zen/base64.h b/zen/base64.h new file mode 100644 index 00000000..4a81787c --- /dev/null +++ b/zen/base64.h @@ -0,0 +1,159 @@ +// ************************************************************************** +// * This file is part of the zenXML project. It is distributed under the * +// * Boost Software License, Version 1.0. See accompanying file * +// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. * +// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** +#ifndef BASE64_HEADER_08473021856321840873021487213453214 +#define BASE64_HEADER_08473021856321840873021487213453214 + +#include <iterator> +#include <cassert> +#include "assert_static.h" + +namespace zen +{ +//http://en.wikipedia.org/wiki/Base64 +/* +Usage: + const std::string input = "Sample text"; + std::string output; + zen::encodeBase64(input.begin(), input.end(), std::back_inserter(output)); + //output contains "U2FtcGxlIHRleHQ=" +*/ +template <class InputIterator, class OutputIterator> +OutputIterator encodeBase64(InputIterator first, InputIterator last, OutputIterator result); //throw () + +template <class InputIterator, class OutputIterator> +OutputIterator decodeBase64(InputIterator first, InputIterator last, OutputIterator result); //throw () + + + + + + + + + + + + + + + + +//---------------------------------------- implementation ---------------------------------------- +namespace implementation +{ +const char ENCODING_MIME[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; //64 chars for base64 encoding + padding char +const signed char DECODING_MIME[] = +{ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 64, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 +}; +const size_t INDEX_PAD = 64; +} + + +//http://en.wikipedia.org/wiki/Base64 +template <class InputIterator, class OutputIterator> inline +OutputIterator encodeBase64(InputIterator first, InputIterator last, OutputIterator result) +{ + using namespace implementation; + assert_static(sizeof(std::iterator_traits<InputIterator>::value_type) == 1); + assert_static(sizeof(ENCODING_MIME) == 65 + 1); + + while (first != last) + { + const unsigned char a = *first++; + *result++ = ENCODING_MIME[a >> 2]; + + if (first == last) + { + *result++ = ENCODING_MIME[((a & 0x3) << 4)]; + *result++ = ENCODING_MIME[INDEX_PAD]; + *result++ = ENCODING_MIME[INDEX_PAD]; + break; + } + const unsigned char b = *first++; + *result++ = ENCODING_MIME[((a & 0x3) << 4) | (b >> 4)]; + + if (first == last) + { + *result++ = ENCODING_MIME[((b & 0xf) << 2)]; + *result++ = ENCODING_MIME[INDEX_PAD]; + break; + } + const unsigned char c = *first++; + *result++ = ENCODING_MIME[((b & 0xf) << 2) | (c >> 6)]; + *result++ = ENCODING_MIME[c & 0x3f]; + } + + return result; +} + + +template <class InputIterator, class OutputIterator> inline +OutputIterator decodeBase64(InputIterator first, InputIterator last, OutputIterator result) +{ + using namespace implementation; + assert_static(sizeof(std::iterator_traits<InputIterator>::value_type) == 1); + assert_static(sizeof(DECODING_MIME) == 128); + + const int INDEX_END = INDEX_PAD + 1; + + auto readIndex = [&]() -> unsigned char //return index within [0, 64] or INDEX_END if end of input + { + while (true) + { + if (first == last) + return INDEX_END; + + unsigned char ch = *first++; + if (ch < 128) //skip all unknown characters (including carriage return, line-break, tab) + { + const int index = implementation::DECODING_MIME[ch]; + if (0 <= index && index <= INDEX_PAD) //respect padding + return index; + } + } + }; + + while (true) + { + const unsigned char index1 = readIndex(); + const unsigned char index2 = readIndex(); + if (index1 >= INDEX_PAD || index2 >= INDEX_PAD) + { + assert(index1 == INDEX_END && index2 == INDEX_END); + break; + } + *result++ = (index1 << 2) | (index2 >> 4); + + const unsigned char index3 = readIndex(); + if (index3 >= INDEX_PAD) //padding + { + assert(index3 == INDEX_PAD); + break; + } + *result++ = ((index2 & 0xf) << 4) | (index3 >> 2); + + const unsigned char index4 = readIndex(); + if (index4 >= INDEX_PAD) //padding + { + assert(index4 == INDEX_PAD); + break; + } + *result++ = ((index3 & 0x3) << 6) | index4; + } + return result; +} +} + +#endif //BASE64_HEADER_08473021856321840873021487213453214 diff --git a/zen/basic_math.h b/zen/basic_math.h new file mode 100644 index 00000000..24bcf27a --- /dev/null +++ b/zen/basic_math.h @@ -0,0 +1,356 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef BASIC_MATH_HEADER_34726398432 +#define BASIC_MATH_HEADER_34726398432 + +#include <algorithm> +#include <iterator> +#include <limits> +#include <functional> + + +namespace numeric +{ +template <class T> +T abs(T value); + +template <class T> +T dist(T a, T b); + +template <class T> +int sign(T value); //returns -1/0/1 + +template <class T> +const T& min(const T& a, const T& b, const T& c); + +template <class T> +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 InputIterator> +std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, InputIterator last); +template <class InputIterator, class Compare> +std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, InputIterator last, Compare comp); + +template <class T> +bool isNull(T value); + +int round(double d); //little rounding function + +template <size_t N, class T> +T power(const T& value); + +double radToDeg(double rad); //convert unit [rad] into [°] +double degToRad(double degree); //convert unit [°] into [rad] + +template <class InputIterator> +double arithmeticMean(InputIterator first, InputIterator last); + +template <class RandomAccessIterator> +double median(RandomAccessIterator first, RandomAccessIterator last); //note: invalidates input range! + +template <class InputIterator> +double stdDeviation(InputIterator first, InputIterator last, double* mean = NULL); //estimate standard deviation (and thereby arithmetic mean) + +//median absolute deviation: "mad / 0.6745" is a robust measure for standard deviation of a normal distribution +template <class RandomAccessIterator> +double mad(RandomAccessIterator first, RandomAccessIterator last); //note: invalidates input range! + + +template <class InputIterator> +double norm2(InputIterator first, InputIterator last); + +//constants +const double pi = 3.14159265358979323846; +const double e = 2.71828182845904523536; +const double sqrt2 = 1.41421356237309504880; +const double ln2 = 0.693147180559945309417; +//---------------------------------------------------------------------------------- + + + + + + + + + + + + + + + + + + + + + + +//################# inline implementation ######################### +template <class T> inline +T abs(T value) +{ + return value < 0 ? -1 * value : value; +} + +template <class T> inline +T dist(T a, T b) +{ + return a > b ? a - b : b - a; +} + + +template <class T> inline +int sign(T value) //returns -1/0/1 +{ + return value < 0 ? -1 : (value > 0 ? 1 : 0); +} + + +template <class T> inline +const T& min(const T& a, const T& b, const T& c) +{ + return std::min(std::min(a, b), c); +} + + +template <class T> inline +const T& max(const T& a, const T& b, const T& c) +{ + return std::max(std::max(a, b), c); +} + + +template <class T> inline +void confine(T& val, const T& minVal, const T& maxVal) +{ + assert(minVal <= maxVal); + if (val < minVal) + val = minVal; + else if (val > maxVal) + val = maxVal; +} + + +template <class InputIterator, class Compare> inline +std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, InputIterator last, Compare comp) +{ + InputIterator lowest = first; + InputIterator largest = first; + + if (first != last) + while (++first != last) + { + if (comp(*largest, *first)) // or: if (comp(*largest,*lowest)) for the comp version + largest = first; + else if (comp(*first, *lowest)) + lowest = first; + } + return std::make_pair(lowest, largest); +} + + +template <class InputIterator> inline +std::pair<InputIterator, InputIterator> minMaxElement(InputIterator first, InputIterator last) +{ + return minMaxElement(first, last, std::less<typename std::iterator_traits<InputIterator>::value_type>()); +} + + +template <class T> inline +bool isNull(T value) +{ + return abs(value) <= std::numeric_limits<T>::epsilon(); //epsilon is 0 für integral types => less-equal +} + + +inline +int round(double d) +{ + return static_cast<int>(d < 0 ? d - 0.5 : d + 0.5); +} + + +namespace +{ +template <size_t N, class T> +struct PowerImpl +{ + static T result(const T& value) + { + return PowerImpl<N - 1, T>::result(value) * value; + } +}; + +template <class T> +struct PowerImpl<2, T> +{ + static T result(const T& value) + { + return value * value; + } +}; + +template <class T> +struct PowerImpl<0, T>; //not defined: invalidates power<0> and power<1> + +template <class T> +struct PowerImpl<10, T>; //not defined: invalidates power<N> for N >= 10 +} + +template <size_t N, class T> inline +T power(const T& value) +{ + return PowerImpl<N, T>::result(value); +} + + +inline +double radToDeg(double rad) +{ + return rad * 180.0 / numeric::pi; +} + + +inline +double degToRad(double degree) +{ + return degree * numeric::pi / 180.0; +} + + +template <class InputIterator> inline +double arithmeticMean(InputIterator first, InputIterator last) +{ + //low level implementation to avoid random-access requirement on iterator + size_t n = 0; + double sum_xi = 0; + + for (; first != last; ++first, ++n) + sum_xi += *first; + + return n == 0 ? 0 : sum_xi / n; +} + + +template <class RandomAccessIterator> inline +double median(RandomAccessIterator first, RandomAccessIterator last) //note: invalidates input range! +{ + const size_t n = last - first; + if (n > 0) + { + std::nth_element(first, first + n / 2, last); //complexity: O(n) + const double midVal = *(first + n / 2); + + if (n % 2 != 0) + return midVal; + else //n is even and >= 2 in this context: return mean of two middle values + return 0.5 * (*std::max_element(first, first + n / 2) + midVal); //this operation is the reason why median() CANNOT support a comparison predicate!!! + } + return 0; +} + + +class LessMinusMedAbs : public std::binary_function<double, double, bool> +{ +public: + LessMinusMedAbs(double median) : median_(median) {} + bool operator()(double lhs, double rhs) const + { + return abs(lhs - median_) < abs(rhs - median_); + } +private: + double median_; +}; + + +template <class RandomAccessIterator> inline +double mad(RandomAccessIterator first, RandomAccessIterator last) //note: invalidates input range! +{ + //http://en.wikipedia.org/wiki/Median_absolute_deviation + + const size_t n = last - first; + if (n > 0) + { + const double m = median(first, last); + + //the second median needs to operate on absolute residuals => avoid transforming input range as it may decrease precision! + + std::nth_element(first, first + n / 2, last, LessMinusMedAbs(m)); //complexity: O(n) + const double midVal = abs(*(first + n / 2) - m); + + if (n % 2 != 0) + return midVal; + else //n is even and >= 2 in this context: return mean of two middle values + { + const double midVal2 = abs(*std::max_element(first, first + n / 2, LessMinusMedAbs(m)) - m); + return 0.5 * (midVal2 + midVal); + } + } + return 0; +} + + +template <class InputIterator> inline +double stdDeviation(InputIterator first, InputIterator last, double* arithMean) +{ + //implementation minimizing rounding errors, see: http://en.wikipedia.org/wiki/Standard_deviation + //combined with techinque avoiding overflow, see: http://www.netlib.org/blas/dnrm2.f -> only 10% performance degregation + + size_t n = 0; + double mean = 0; + double q = 0; + double scale = 1; + + for (; first != last; ++first) + { + ++n; + const double val = *first - mean; + + if (abs(val) > scale) + { + q = (n - 1.0) / n + q * power<2>(scale / val); + scale = abs(val); + } + else + q += (n - 1.0) * power<2>(val / scale) / n; + + mean += val / n; + } + + if (arithMean) + *arithMean = mean; + + return n <= 1 ? 0 : std::sqrt(q / (n - 1)) * scale; +} + + +template <class InputIterator> inline +double norm2(InputIterator first, InputIterator last) +{ + double result = 0; + double scale = 1; + for (; first != last; ++first) + { + const double tmp = abs(*first); + if (tmp > scale) + { + result = 1 + result * power<2>(scale / tmp); + scale = tmp; + } + else + result += power<2>(tmp / scale); + } + return std::sqrt(result) * scale; +} +} + +#endif //BASIC_MATH_HEADER_34726398432 diff --git a/zen/build_info.h b/zen/build_info.h new file mode 100644 index 00000000..e57d0c77 --- /dev/null +++ b/zen/build_info.h @@ -0,0 +1,18 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef BUILDINFO_H_INCLUDED +#define BUILDINFO_H_INCLUDED + +namespace zen +{ +//determine build info +//safer than checking for _WIN64 (defined on windows for 64-bit compilations only) while _WIN32 is always defined (even for x64 compiler!) +static const bool is32BitBuild = sizeof(void*) == 4; +static const bool is64BitBuild = sizeof(void*) == 8; +} + +#endif //BUILDINFO_H_INCLUDED diff --git a/zen/com_error.h b/zen/com_error.h new file mode 100644 index 00000000..2ba76c0f --- /dev/null +++ b/zen/com_error.h @@ -0,0 +1,206 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef COM_ERROR_HEADER +#define COM_ERROR_HEADER + +#include <string> +#include <cstdio> +#include "win.h" //includes "windows.h" + +namespace zen +{ +std::wstring generateErrorMsg(const std::wstring& input, HRESULT hr); +std::wstring formatWin32Msg(DWORD dwMessageId); //return empty string on error + + + + + + + + + + + + + + + + + + + + + +//################# implementation ##################### +std::wstring formatWin32Msg(DWORD dwMessageId) //return empty string on error +{ + std::wstring output; + LPWSTR buffer = NULL; + if (::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_MAX_WIDTH_MASK | + FORMAT_MESSAGE_IGNORE_INSERTS | //important: without this flag ::FormatMessage() will fail if message contains placeholders + FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, dwMessageId, 0, reinterpret_cast<LPWSTR>(&buffer), 0, NULL) != 0) + { + if (buffer) //just to be sure + { + output = buffer; + ::LocalFree(buffer); + } + } + return output; +} + +namespace +{ +std::wstring formatFacility(HRESULT hr) +{ + switch (HRESULT_FACILITY(hr)) + { + case FACILITY_XPS: + return L"XPS"; + case FACILITY_WINRM: + return L"Windows Resource Manager"; + case FACILITY_WINDOWSUPDATE: + return L"Windows Update"; + case FACILITY_WINDOWS_DEFENDER: + return L"Windows Defender Component"; + case FACILITY_WINDOWS_CE: + return L"Windows CE"; + case FACILITY_WINDOWS: + return L"Windows Subsystem"; + case FACILITY_USERMODE_VOLMGR: + return L"User Mode Volume Manager"; + case FACILITY_USERMODE_VIRTUALIZATION: + return L"User Mode Virtualization Subsystem"; + case FACILITY_USERMODE_VHD: + return L"User Mode Virtual Hard Disk Support"; + case FACILITY_URT: + return L".NET CLR"; + case FACILITY_UMI: + return L"Ubiquitous Memoryintrospection Service"; + case FACILITY_UI: + return L"UI"; + case FACILITY_TPM_SOFTWARE: + return L"Trusted Platform Module Applications"; + case FACILITY_TPM_SERVICES: + return L"Trusted Platform Module Services"; + case FACILITY_SXS: + return L"Side-by-side Servicing"; + case FACILITY_STORAGE: + return L"OLE Storage"; + case FACILITY_STATE_MANAGEMENT: + return L"State Management Services"; + case FACILITY_SCARD: + return L"Smart-card Subsystem"; + case FACILITY_SHELL: + return L"User Shell"; + case FACILITY_SETUPAPI: + return L"Setup API"; + case FACILITY_SECURITY: + return L"Security API Layer"; + case FACILITY_SDIAG: + return L"System Diagnostics"; + case FACILITY_RPC: + return L"RPC Subsystem"; + case FACILITY_RAS: + return L"RAS"; + case FACILITY_PLA: + return L"Performance Logs and Alerts"; + case FACILITY_OPC: + return L"Open Connectivity Service"; + case FACILITY_WIN32: + return L"Win32"; + case FACILITY_CONTROL: + return L"Control Mechanism"; + case FACILITY_WEBSERVICES: + return L"Web Services"; + case FACILITY_NDIS: + return L"Network Driver Interface"; + case FACILITY_METADIRECTORY: + return L"Microsoft Identity Server"; + case FACILITY_MSMQ: + return L"Microsoft Message Queue"; + case FACILITY_MEDIASERVER: + return L"Windows Media Server"; + case FACILITY_MBN: + return L"MBN"; + case FACILITY_INTERNET: + return L"Wininet"; + case FACILITY_ITF: + return L"COM/OLE Interface Management"; + case FACILITY_USERMODE_HYPERVISOR: + return L"Usermode Hypervisor Components"; + case FACILITY_HTTP: + return L"HTTP Support"; + case FACILITY_GRAPHICS: + return L"Graphics Drivers"; + case FACILITY_FWP: + return L"Firewall Platform"; + case FACILITY_FVE: + return L"Full volume encryption"; + case FACILITY_USERMODE_FILTER_MANAGER: + return L"User Mode Filter Manager"; + case FACILITY_DPLAY: + return L"Direct Play"; + case FACILITY_DISPATCH: + return L"COM Dispatch"; + case FACILITY_DIRECTORYSERVICE: + return L"Active Directory"; + case FACILITY_CONFIGURATION: + return L"Configuration Services"; + case FACILITY_COMPLUS: + return L"COM+"; + case FACILITY_USERMODE_COMMONLOG: + return L"Common Logging Support"; + case FACILITY_CMI: + return L"Configuration Management Infrastructure"; + case FACILITY_CERT: + return L"Certificate"; + case FACILITY_BCD: + return L"Boot Configuration Database"; + case FACILITY_BACKGROUNDCOPY: + return L"Background Copy Control"; + case FACILITY_ACS: + return L"Audit Collection Service"; + case FACILITY_AAF: + return L"Microsoft Agent"; + default: + return L"Unknown"; + } +} +} + +inline +std::wstring numberToHexString(long number) +{ + wchar_t result[100]; + ::swprintf(result, 100, L"0x%08x", number); + return std::wstring(result); +} + + +inline +std::wstring generateErrorMsg(const std::wstring& input, HRESULT hr) +{ + std::wstring output(input); + output += L"\n"; + output += L"HRESULT: " + numberToHexString(hr) + L",\n"; + + //don't use _com_error(hr).ErrorMessage(): internally this is nothing more than a call to ::FormatMessage() + std::wstring win32Msg = formatWin32Msg(hr); + if (!win32Msg.empty()) //empty string on error + output += win32Msg; + else + { + output += L"Facility: " + formatFacility(hr) + L",\n"; + output += L"Win32 Error: " + formatWin32Msg(HRESULT_CODE(hr)); //interpret hr as a Win32 code; this is often useful... + } + return output; +} +} +#endif //COM_ERROR_HEADER diff --git a/zen/com_ptr.h b/zen/com_ptr.h new file mode 100644 index 00000000..380f4536 --- /dev/null +++ b/zen/com_ptr.h @@ -0,0 +1,123 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef SMART_COM_PTR_H +#define SMART_COM_PTR_H + +#include <algorithm> +#include <Objbase.h> + +namespace zen +{ +/* +ComPtr: RAII class handling COM objects + +Example: + -------- + ComPtr<IUPnPDeviceFinder> devFinder; + if (FAILED(::CoCreateInstance(CLSID_UPnPDeviceFinder, + NULL, + CLSCTX_ALL, + IID_PPV_ARGS(devFinder.init())))) + return -1; + + ComPtr<IEnumUnknown> devEnum = com_dynamic_cast<IEnumUnknown>(devColl); + if (!devEnum) + return -1; +*/ + +template <class T> +class ComPtr +{ +public: + ComPtr() : ptr(NULL) {} + + ComPtr(const ComPtr& other) : ptr(other.ptr) { if (ptr) ptr->AddRef(); } + ComPtr( ComPtr&& other) : ptr(other.release()) {} + + ComPtr& operator=(const ComPtr& other) { ComPtr(other).swap(*this); return *this; } + ComPtr& operator=( ComPtr&& other) { swap(other); return *this; } + + ~ComPtr() { if (ptr) ptr->Release(); } + + T** init() //get pointer for use with ::CoCreateInstance() + { + ComPtr<T>().swap(*this); + return &ptr; + } + + T* get() const { return ptr; } + + T* release() //throw() + { + T* tmp = ptr; + ptr = NULL; + return tmp; + } + + void swap(ComPtr& rhs) { std::swap(ptr, rhs.ptr); } //throw() + + T* operator->() const { return ptr; } + +private: + T* ptr; + + struct ConversionToBool { int dummy; }; +public: + //use member pointer as implicit conversion to bool (C++ Templates - Vandevoorde/Josuttis; chapter 20) + operator int ConversionToBool::* () const { return ptr != NULL ? &ConversionToBool::dummy : NULL; } +}; + + +template <class S, class T> +ComPtr<S> com_dynamic_cast(const ComPtr<T>& other); //throw() + + + + + + + + + + + + + + + + + + + + + + + + + +//################# Inline Implementation ############################# + +//we cannot specialize std::swap() for a class template and are not allowed to overload it => offer swap in own namespace +template <class T> inline +void swap(zen::ComPtr<T>& lhs, zen::ComPtr<T>& rhs) +{ + lhs.swap(rhs); +} + + +template <class S, class T> inline +ComPtr<S> com_dynamic_cast(const ComPtr<T>& other) //throw() +{ + ComPtr<S> outPtr; + if (other) + other->QueryInterface(IID_PPV_ARGS(outPtr.init())); + return outPtr; +} +} + + +#endif //SMART_COM_PTR_H
\ No newline at end of file diff --git a/zen/com_util.h b/zen/com_util.h new file mode 100644 index 00000000..db51404b --- /dev/null +++ b/zen/com_util.h @@ -0,0 +1,121 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef COM_UTILITY_HEADER +#define COM_UTILITY_HEADER + +#include "com_ptr.h" +#include <string> +#include <cassert> + +namespace zen +{ +//get an enumeration interface as a std::vector of bound(!) ComPtr(s) +template <class T, class U> +std::vector<ComPtr<T> > convertEnum(const ComPtr<U>& enumObj); //enumObj: must have the "_NewEnum" property that supports the IEnumUnknown interface + +/* +extract text from com object member function returning a single BSTR: HRESULT ComInterface::MemFun([out] BSTR *pbstr); + Example: ComPtr<...> comObj =...; + std::wstring description = getText(comObj, &IUPnPDevice::get_Description); +*/ +template <class T, class MemFun> +std::wstring getText(ComPtr<T> comObj, MemFun memFun); + + +//RAII class handling BSTR +class Bstring +{ +public: + Bstring(const std::wstring& str) { str_ = ::SysAllocStringLen(str.data(), str.length()); } //string::data() returns unmodified string potentially containing 0-values + ~Bstring() { if (str_) ::SysFreeString(str_); } + + const BSTR get() const { return str_; } + +private: + Bstring(const Bstring&); //not implemented + Bstring& operator=(const Bstring&); // + + BSTR str_; +}; + + + + + + + + + + + + + + + + + + + + + + + + + +//############################ inline implemenatation ################################## +template <class T, class U> inline +std::vector<ComPtr<T> > convertEnum(const ComPtr<U>& enumObj) +{ + std::vector<ComPtr<T> > output; + + if (enumObj) + { + ComPtr<IUnknown> unknown; + enumObj->get__NewEnum(unknown.init()); + ComPtr<IEnumUnknown> enumUnknown = com_dynamic_cast<IEnumUnknown>(unknown); + + assert(enumUnknown); //IEnumUnknown must be supported! + if (enumUnknown) + { + ComPtr<IUnknown> itemTmp; + while (enumUnknown->Next(1, itemTmp.init(), NULL) == S_OK) //returns S_FALSE == 1 when finished! Don't use SUCCEEDED()!!! + { + ComPtr<T> itemNew = com_dynamic_cast<T>(itemTmp); + if (itemNew) + output.push_back(itemNew); + } + } + } + + return output; +} + + +template <class T, class MemFun> inline +std::wstring getText(ComPtr<T> comObj, MemFun memFun) +{ + std::wstring text; + { + if (!comObj) + return std::wstring(); + + BSTR bstr = NULL; + if (FAILED((comObj.get()->*memFun)(&bstr))) + return std::wstring(); + + if (bstr) //NULL means "no text" + { + text = std::wstring(bstr, ::SysStringLen(bstr)); //correctly copy 0-characters + ::SysFreeString(bstr); + } + } + return text; +} +} + + +#endif //COM_UTILITY_HEADER
\ No newline at end of file diff --git a/zen/debug_log.h b/zen/debug_log.h new file mode 100644 index 00000000..d8871ef9 --- /dev/null +++ b/zen/debug_log.h @@ -0,0 +1,82 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef DEBUG_LOG_HEADER_017324601673246392184621895740256342 +#define DEBUG_LOG_HEADER_017324601673246392184621895740256342 + +#include "zstring.h" + +cleanup this mess + remove any wxWidgets dependency! + +//small macro for writing debug information into a logfile +#define WRITE_DEBUG_LOG(x) globalLogFile().write(getCodeLocation(__TFILE__, __LINE__) + x); +//speed alternative: wxLogDebug(wxT("text")) + DebugView + + +class DebugLog +{ +public: + wxDEPRECATED(DebugLog(const wxString& filePrefix = wxString())) + prefix(filePrefix), + lineCount(0) + { + logfileName = assembleFileName(); + logFile.Open(logfileName, wxFile::write); + } + + void write(const std::string& logText) + { + todo; + } + + void write(const wxString& logText) + { + ++lineCount; + if (lineCount % 50000 == 0) //prevent logfile from becoming too big + { + logFile.Close(); + wxRemoveFile(logfileName); + + logfileName = assembleFileName(); + logFile.Open(logfileName, wxFile::write); + } + +ersetze wxDateTime::Now() durch eigene lib: + z.b. iso_time.h + + logFile.Write(wxString(wxT("[")) + wxDateTime::Now().FormatTime() + wxT("] ")); + logFile.Write(logText + LINE_BREAK); + } + +private: + wxString assembleFileName() + { + wxString tmp = wxDateTime::Now().FormatISOTime(); + tmp.Replace(wxT(":"), wxEmptyString); + return prefix + wxString(wxT("DEBUG_")) + wxDateTime::Now().FormatISODate() + wxChar('_') + tmp + wxT(".log"); + } + + wxString logfileName; + wxString prefix; + int lineCount; + wxFile logFile; //logFile.close(); <- not needed +}; + +inline +DebugLog& globalLogFile() +{ + static DebugLog inst; //external linkage despite header definition! + return inst; +} + +inline +wxString getCodeLocation(const wxString& file, int line) +{ + return wxString(file).AfterLast(FILE_NAME_SEPARATOR) + wxT(", LINE ") + toString<wxString>(line) + wxT(" | "); +} + + +#endif //DEBUG_LOG_HEADER_017324601673246392184621895740256342 diff --git a/zen/debug_new.cpp b/zen/debug_new.cpp new file mode 100644 index 00000000..c830a36b --- /dev/null +++ b/zen/debug_new.cpp @@ -0,0 +1,54 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#include "debug_new.h" + +#include "win.h" //includes "windows.h" +#include "DbgHelp.h" //available for MSC only +#pragma comment(lib, "Dbghelp.lib") + + +namespace +{ +LONG WINAPI writeDumpOnException(EXCEPTION_POINTERS* pExceptionInfo) +{ + HANDLE hFile = ::CreateFile(L"exception.dmp", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + MINIDUMP_EXCEPTION_INFORMATION exInfo = {}; + exInfo.ThreadId = ::GetCurrentThreadId(); + exInfo.ExceptionPointers = pExceptionInfo; + exInfo.ClientPointers = NULL; + + MINIDUMP_EXCEPTION_INFORMATION* exceptParam = pExceptionInfo ? &exInfo : NULL; + + ::MiniDumpWriteDump(::GetCurrentProcess(), //__in HANDLE hProcess, + ::GetCurrentProcessId(), //__in DWORD ProcessId, + hFile, //__in HANDLE hFile, + MiniDumpWithDataSegs, //__in MINIDUMP_TYPE DumpType, ->Standard: MiniDumpNormal, Medium: MiniDumpWithDataSegs, Full: MiniDumpWithFullMemory + exceptParam, //__in PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + NULL, //__in PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + NULL); //__in PMINIDUMP_CALLBACK_INFORMATION CallbackParam + + ::CloseHandle(hFile); + + return EXCEPTION_EXECUTE_HANDLER; +} + + +struct WriteDumpOnUnhandledException +{ + WriteDumpOnUnhandledException() + { + ::SetUnhandledExceptionFilter(writeDumpOnException); + } +} dummy; //ensure that a dump-file is written for uncaught exceptions +} + + +void mem_check::writeMinidump() +{ + writeDumpOnException(NULL); +} diff --git a/zen/debug_new.h b/zen/debug_new.h new file mode 100644 index 00000000..2976d3d7 --- /dev/null +++ b/zen/debug_new.h @@ -0,0 +1,96 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef DEBUGNEW_H_INCLUDED +#define DEBUGNEW_H_INCLUDED + +#include <string> +#include <sstream> +#include <cstdlib> //malloc(), free() + +#ifndef _MSC_VER +#error currently for use with MSC only +#endif + +/*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 +*/ + +namespace mem_check +{ +class BadAllocDetailed : public std::bad_alloc +{ +public: + explicit BadAllocDetailed(size_t allocSize) + { + errorMsg = "Memory allocation failed: "; + errorMsg += numberToString(allocSize); + } + + ~BadAllocDetailed() throw() {} + + virtual const char* what() const throw() + { + return errorMsg.c_str(); + } + +private: + template <class T> + static std::string numberToString(const T& number) //convert number to string the C++ way + { + std::ostringstream ss; + ss << number; + return ss.str(); + } + + std::string errorMsg; +}; + +#ifdef _MSC_VER +void writeMinidump(); +#endif +} + +inline +void* operator new(size_t size) +{ + void* newMem = ::malloc(size); + if (!newMem) + { +#ifdef _MSC_VER + mem_check::writeMinidump(); +#endif + throw mem_check::BadAllocDetailed(size); + } + return newMem; +} + + +inline +void operator delete(void* ptr) +{ + ::free(ptr); +} + + +inline +void* operator new[](size_t size) +{ + return operator new(size); +} + + +inline +void operator delete[](void* ptr) +{ + operator delete(ptr); +} + +#endif // DEBUGNEW_H_INCLUDED diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp new file mode 100644 index 00000000..7b45b014 --- /dev/null +++ b/zen/dir_watcher.cpp @@ -0,0 +1,493 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#include "dir_watcher.h" +#include <algorithm> +#include <set> +#include "thread.h" //includes <boost/thread.hpp> +#include "scope_guard.h" + +#ifdef FFS_WIN +#include "notify_removal.h" +#include "win.h" //includes "windows.h" +#include "long_path_prefix.h" +#include "privilege.h" + +#elif defined FFS_LINUX +#include <sys/inotify.h> +#include <fcntl.h> +#include "file_traverser.h" +#endif + + +using namespace zen; + +#ifdef FFS_WIN +namespace +{ +inline +bool errorCodeForNotExisting(const DWORD lastError) +{ + return lastError == ERROR_PATH_NOT_FOUND || + lastError == ERROR_BAD_NETPATH || + lastError == ERROR_NETNAME_DELETED; +} + + +class SharedData +{ +public: + //context of worker thread + void addChanges(const char* buffer, DWORD bytesWritten, const Zstring& dirname) //throw () + { + boost::lock_guard<boost::mutex> dummy(lockAccess); + + std::set<Zstring>& output = changedFiles; + + if (bytesWritten == 0) //according to docu this may happen in case of internal buffer overflow: report some "dummy" change + output.insert(L"Overflow!"); + else + { + const char* bufPos = &buffer[0]; + for(;;) + { + const FILE_NOTIFY_INFORMATION& notifyInfo = reinterpret_cast<const FILE_NOTIFY_INFORMATION&>(*bufPos); + + const Zstring fullname = dirname + Zstring(notifyInfo.FileName, notifyInfo.FileNameLength / sizeof(WCHAR)); + + //skip modifications sent by changed directories: reason for change, child element creation/deletion, will notify separately! + [&]() + { + if (notifyInfo.Action == FILE_ACTION_RENAMED_OLD_NAME) //reporting FILE_ACTION_RENAMED_NEW_NAME should suffice + return; + + if (notifyInfo.Action == FILE_ACTION_MODIFIED) + { + //note: this check will not work if top watched directory has been renamed + const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(fullname).c_str()); + if (ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_DIRECTORY)) //returns true for (dir-)symlinks also + return; + } + + output.insert(fullname); + }(); + + if (notifyInfo.NextEntryOffset == 0) + break; + bufPos += notifyInfo.NextEntryOffset; + } + } + } + + //context of main thread + void addChange(const Zstring& dirname) //throw () + { + boost::lock_guard<boost::mutex> dummy(lockAccess); + changedFiles.insert(dirname); + } + + + //context of main thread + void getChanges(std::vector<Zstring>& output) //throw FileError, ErrorNotExisting + { + boost::lock_guard<boost::mutex> dummy(lockAccess); + + //first check whether errors occured in thread + if (!errorMsg.first.empty()) + { + const std::wstring msg = errorMsg.first.c_str(); + const DWORD lastError = errorMsg.second; + + if (errorCodeForNotExisting(lastError)) + throw ErrorNotExisting(msg); + throw FileError(msg); + } + + output.assign(changedFiles.begin(), changedFiles.end()); + changedFiles.clear(); + } + + + //context of worker thread + void reportError(const std::wstring& msg, DWORD errorCode) //throw() + { + boost::lock_guard<boost::mutex> dummy(lockAccess); + errorMsg = std::make_pair(cvrtString<BasicWString>(msg), errorCode); + } + +private: + typedef Zbase<wchar_t> BasicWString; //thread safe string class for UI texts + + boost::mutex lockAccess; + std::set<Zstring> changedFiles; //get rid of duplicate entries (actually occur!) + std::pair<BasicWString, DWORD> errorMsg; //non-empty if errors occured in thread +}; + + +class ReadChangesAsync +{ +public: + //constructed in main thread! + ReadChangesAsync(const Zstring& directory, //make sure to not leak in thread-unsafe types! + const std::shared_ptr<SharedData>& shared) : + shared_(shared), + dirname(directory) + { + if (!endsWith(dirname, FILE_NAME_SEPARATOR)) + dirname += FILE_NAME_SEPARATOR; + + //these two privileges are required by ::CreateFile FILE_FLAG_BACKUP_SEMANTICS according to + //http://msdn.microsoft.com/en-us/library/aa363858(v=vs.85).aspx + try + { + Privileges::getInstance().ensureActive(SE_BACKUP_NAME); //throw FileError + Privileges::getInstance().ensureActive(SE_RESTORE_NAME); // + } + catch (const FileError&) {} + + hDir = ::CreateFile(applyLongPathPrefix(dirname.c_str()).c_str(), + FILE_LIST_DIRECTORY, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, + NULL); + if (hDir == INVALID_HANDLE_VALUE ) + { + const std::wstring errorMsg = _("Could not initialize directory monitoring:") + "\n\"" + utf8CvrtTo<std::wstring>(dirname) + "\"" + "\n\n" + zen::getLastErrorFormatted(); + if (errorCodeForNotExisting(::GetLastError())) + throw ErrorNotExisting(errorMsg); + throw FileError(errorMsg); + } + + //end of constructor, no need to start managing "hDir" + } + + ~ReadChangesAsync() + { + if (hDir != INVALID_HANDLE_VALUE) + ::CloseHandle(hDir); + } + + void operator()() //thread entry + { + try + { + std::vector<char> buffer(64 * 1024); //needs to be aligned on a DWORD boundary; maximum buffer size restricted by some networks protocols (according to docu) + + for(;;) + { + boost::this_thread::interruption_point(); + + //actual work + OVERLAPPED overlapped = {}; + overlapped.hEvent = ::CreateEvent(NULL, //__in_opt LPSECURITY_ATTRIBUTES lpEventAttributes, + true, //__in BOOL bManualReset, + false, //__in BOOL bInitialState, + NULL); //__in_opt LPCTSTR lpName + if (overlapped.hEvent == NULL) + return shared_->reportError(_("Error when monitoring directories.") + " (CreateEvent)" + "\n\n" + getLastErrorFormatted(), ::GetLastError()); + ZEN_ON_BLOCK_EXIT(::CloseHandle(overlapped.hEvent)); + + //asynchronous variant: runs on this thread's APC queue! + if (!::ReadDirectoryChangesW(hDir, // __in HANDLE hDirectory, + &buffer[0], // __out LPVOID lpBuffer, + static_cast<DWORD>(buffer.size()), // __in DWORD nBufferLength, + true, // __in BOOL bWatchSubtree, + FILE_NOTIFY_CHANGE_FILE_NAME | + FILE_NOTIFY_CHANGE_DIR_NAME | + FILE_NOTIFY_CHANGE_SIZE | + FILE_NOTIFY_CHANGE_LAST_WRITE, // __in DWORD dwNotifyFilter, + NULL, // __out_opt LPDWORD lpBytesReturned, + &overlapped, // __inout_opt LPOVERLAPPED lpOverlapped, + NULL)) // __in_opt LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine + return shared_->reportError(_("Error when monitoring directories.") + " (ReadDirectoryChangesW)" + "\n\n" + getLastErrorFormatted(), ::GetLastError()); + + //async I/O is a resource that needs to be guarded since it will write to local variable "buffer"! + zen::ScopeGuard lockAio = zen::makeGuard([&]() + { + //http://msdn.microsoft.com/en-us/library/aa363789(v=vs.85).aspx + if (::CancelIo(hDir) == TRUE) //cancel all async I/O related to this handle and thread + { + DWORD bytesWritten = 0; + ::GetOverlappedResult(hDir, &overlapped, &bytesWritten, true); //wait until cancellation is complete + } + }); + + DWORD bytesWritten = 0; + + //wait for results + while (!::GetOverlappedResult(hDir, //__in HANDLE hFile, + &overlapped, //__in LPOVERLAPPED lpOverlapped, + &bytesWritten, //__out LPDWORD lpNumberOfBytesTransferred, + false)) //__in BOOL bWait + { + if (::GetLastError() != ERROR_IO_INCOMPLETE) + return shared_->reportError(_("Error when monitoring directories.") + " (GetOverlappedResult)" + "\n\n" + getLastErrorFormatted(), ::GetLastError()); + + //execute asynchronous procedure calls (APC) queued on this thread + ::SleepEx(50, // __in DWORD dwMilliseconds, + true); // __in BOOL bAlertable + + boost::this_thread::interruption_point(); + } + lockAio.dismiss(); + + shared_->addChanges(&buffer[0], bytesWritten, dirname); //throw () + } + } + catch (boost::thread_interrupted&) + { + throw; //this is the only reasonable exception! + } + } + + ReadChangesAsync(ReadChangesAsync && other) : + hDir(INVALID_HANDLE_VALUE) + { + shared_ = std::move(other.shared_); + dirname = std::move(other.dirname); + std::swap(hDir, other.hDir); + } + + HANDLE getDirHandle() const { return hDir; } //for reading purposes only, don't abuse (e.g. close handle)! + +private: + //shared between main and worker: + std::shared_ptr<SharedData> shared_; + //worker thread only: + Zstring dirname; //thread safe! + HANDLE hDir; +}; + + +class HandleVolumeRemoval : public NotifyRequestDeviceRemoval +{ +public: + HandleVolumeRemoval(HANDLE hDir, + boost::thread& worker, + const std::shared_ptr<SharedData>& shared, + const Zstring& dirname) : + NotifyRequestDeviceRemoval(hDir), //throw FileError + worker_(worker), + shared_(shared), + dirname_(dirname), + removalRequested(false), + operationComplete(false) {} + + //all functions are called by main thread! + + bool requestReceived() const { return removalRequested; } + bool finished() const { return operationComplete; } + +private: + virtual void onRequestRemoval(HANDLE hnd) + { + //must release hDir immediately => stop monitoring! + worker_.interrupt(); + worker_.join(); + //now hDir should have been released + + //report removal as change to main directory + shared_->addChange(dirname_); + + removalRequested = true; + } //don't throw! + virtual void onRemovalFinished(HANDLE hnd, bool successful) { operationComplete = true; } //throw()! + + boost::thread& worker_; + std::shared_ptr<SharedData> shared_; + Zstring dirname_; + bool removalRequested; + bool operationComplete; +}; +} + + +struct DirWatcher::Pimpl +{ + boost::thread worker; + std::shared_ptr<SharedData> shared; + + std::unique_ptr<HandleVolumeRemoval> volRemoval; +}; + + +DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError + pimpl_(new Pimpl) +{ + pimpl_->shared = std::make_shared<SharedData>(); + + ReadChangesAsync reader(directory, pimpl_->shared); //throw FileError + pimpl_->volRemoval.reset(new HandleVolumeRemoval(reader.getDirHandle(), pimpl_->worker, pimpl_->shared, directory)); //throw FileError + pimpl_->worker = boost::thread(std::move(reader)); +} + + +DirWatcher::~DirWatcher() +{ + pimpl_->worker.interrupt(); + //pimpl_->worker.join(); -> we don't have time to wait... will take ~50ms anyway + //caveat: exitting the app may simply kill this thread! + + //wait until device removal is confirmed, to (hopefully!) prevent locking hDir again by new watch! + if (pimpl_->volRemoval->requestReceived()) + { + const boost::system_time maxwait = boost::get_system_time() + boost::posix_time::seconds(3); //HandleVolumeRemoval::finished() not guaranteed! + + while (!pimpl_->volRemoval->finished() && boost::get_system_time() < maxwait) + boost::thread::sleep(boost::get_system_time() + boost::posix_time::milliseconds(50)); + } +} + + +std::vector<Zstring> DirWatcher::getChanges() //throw FileError +{ + std::vector<Zstring> output; + pimpl_->shared->getChanges(output); //throw FileError + return output; +} + + +#elif defined FFS_LINUX +struct DirWatcher::Pimpl +{ + int notifDescr; + std::map<int, Zstring> watchDescrs; //watch descriptor and corresponding directory name (postfixed with separator!) +}; + + +namespace +{ +class DirsOnlyTraverser : public zen::TraverseCallback +{ +public: + DirsOnlyTraverser(std::vector<Zstring>& dirs) : dirs_(dirs) {} + + virtual void onFile (const Zchar* shortName, const Zstring& fullName, const FileInfo& details) {} + virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) {} + virtual ReturnValDir onDir (const Zchar* shortName, const Zstring& fullName) + { + dirs_.push_back(fullName); + return ReturnValDir(zen::Int2Type<ReturnValDir::TRAVERSING_DIR_CONTINUE>(), *this); + } + virtual HandleError onError(const std::wstring& errorText) { throw FileError(errorText); } + +private: + std::vector<Zstring>& dirs_; +}; +} + + +DirWatcher::DirWatcher(const Zstring& directory) : //throw FileError + pimpl_(new Pimpl) +{ + //still in main thread + Zstring dirname = directory; + if (endsWith(dirname, FILE_NAME_SEPARATOR)) + dirname.resize(dirname.size() - 1); + + //get all subdirectories + std::vector<Zstring> fullDirList; + fullDirList.push_back(dirname); + + DirsOnlyTraverser traverser(fullDirList); //throw FileError + zen::traverseFolder(dirname, false, traverser); //don't traverse into symlinks (analog to windows build) + + //init + pimpl_->notifDescr = ::inotify_init(); + if (pimpl_->notifDescr == -1) + throw FileError(_("Could not initialize directory monitoring:") + "\n\"" + dirname + "\"" + "\n\n" + getLastErrorFormatted()); + + zen::ScopeGuard guardDescr = zen::makeGuard([&]() { ::close(pimpl_->notifDescr); }); + + //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; + + if (!initSuccess) + throw FileError(_("Could not initialize directory monitoring:") + "\n\"" + dirname + "\"" + "\n\n" + getLastErrorFormatted()); + + //add watches + std::for_each(fullDirList.begin(), fullDirList.end(), + [&](Zstring subdir) + { + int wd = ::inotify_add_watch(pimpl_->notifDescr, subdir.c_str(), + IN_ONLYDIR | //watch directories only + IN_DONT_FOLLOW | //don't follow symbolic links + IN_MODIFY | + IN_CLOSE_WRITE | + IN_MOVE | + IN_CREATE | + IN_DELETE | + IN_DELETE_SELF | + IN_MOVE_SELF); + if (wd == -1) + { + std::wstring errorMsg = _("Could not initialize directory monitoring:") + "\n\"" + subdir + "\"" + "\n\n" + getLastErrorFormatted(); + if (errno == ENOENT) + throw ErrorNotExisting(errorMsg); + throw FileError(errorMsg); + } + + if (!endsWith(subdir, FILE_NAME_SEPARATOR)) + subdir += FILE_NAME_SEPARATOR; + pimpl_->watchDescrs.insert(std::make_pair(wd, subdir)); + }); + + guardDescr.dismiss(); +} + + +DirWatcher::~DirWatcher() +{ + ::close(pimpl_->notifDescr); //associated watches are removed automatically! +} + + +std::vector<Zstring> DirWatcher::getChanges() //throw FileError +{ + std::vector<char> buffer(1024 * (sizeof(struct inotify_event) + 16)); + + //non-blocking call, see O_NONBLOCK + ssize_t bytesRead = ::read(pimpl_->notifDescr, &buffer[0], buffer.size()); + + if (bytesRead == -1) + { + 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 + return std::vector<Zstring>(); + + throw FileError(_("Error when monitoring directories.") + "\n\n" + getLastErrorFormatted()); + } + + std::set<Zstring> tmp; //get rid of duplicate entries (actually occur!) + + ssize_t bytePos = 0; + while (bytePos < bytesRead) + { + 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 + { + auto iter = pimpl_->watchDescrs.find(evt.wd); + if (iter != pimpl_->watchDescrs.end()) + { + //Note: evt.len is NOT the size of the evt.name c-string, but the array size including all padding 0 characters! + //It may be even 0 in which case evt.name must not be used! + tmp.insert(iter->second + evt.name); + } + } + + bytePos += sizeof(struct inotify_event) + evt.len; + } + + return std::vector<Zstring>(tmp.begin(), tmp.end()); +} + +#endif diff --git a/zen/dir_watcher.h b/zen/dir_watcher.h new file mode 100644 index 00000000..df3c444c --- /dev/null +++ b/zen/dir_watcher.h @@ -0,0 +1,50 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef DIR_WATCHER_348577025748023458 +#define DIR_WATCHER_348577025748023458 + +#include <vector> +#include <memory> +#include "file_error.h" + +namespace zen +{ +//Windows: ReadDirectoryChangesW http://msdn.microsoft.com/en-us/library/aa365465(v=vs.85).aspx +//Linux: inotify http://linux.die.net/man/7/inotify + +//watch directory including subdirectories +/* +!Note handling of directories!: + Linux: newly added subdirectories are reported but not automatically added for watching! -> reset Dirwatcher! + removal of top watched directory is NOT notified! + Windows: removal of top watched directory also NOT notified (e.g. brute force usb stick removal) + however manual unmount IS notified (e.g. usb stick removal, then re-insert), but watching is stopped! + Renaming of top watched directory handled incorrectly: Not notified(!) + changes in subfolders + report FILE_ACTION_MODIFIED for directory (check that should prevent this fails!) + + Overcome all issues portably: check existence of watched directory externally + reinstall watch after changes in directory structure (added directories) are possible +*/ +class DirWatcher +{ +public: + DirWatcher(const Zstring& directory); //throw FileError, ErrorNotExisting + ~DirWatcher(); + + //extract accumulated changes since last call + std::vector<Zstring> getChanges(); //throw FileError + +private: + DirWatcher(const DirWatcher&); + DirWatcher& operator=(const DirWatcher&); + + struct Pimpl; + std::unique_ptr<Pimpl> pimpl_; +}; + +} + +#endif diff --git a/zen/disable_standby.h b/zen/disable_standby.h new file mode 100644 index 00000000..ec112427 --- /dev/null +++ b/zen/disable_standby.h @@ -0,0 +1,27 @@ +#ifndef PREVENTSTANDBY_H_INCLUDED +#define PREVENTSTANDBY_H_INCLUDED + +#ifdef FFS_WIN +#include "win.h" //includes "windows.h" +#endif + +namespace zen +{ +class DisableStandby +{ +public: +#ifdef FFS_WIN + DisableStandby() + { + ::SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED /* | ES_AWAYMODE_REQUIRED*/ ); + } + + ~DisableStandby() + { + ::SetThreadExecutionState(ES_CONTINUOUS); + } +#endif +}; +} + +#endif // PREVENTSTANDBY_H_INCLUDED diff --git a/zen/dll.h b/zen/dll.h new file mode 100644 index 00000000..302a3ac8 --- /dev/null +++ b/zen/dll.h @@ -0,0 +1,120 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef DLLLOADER_H_INCLUDED +#define DLLLOADER_H_INCLUDED + +#include <memory> +#include <string> +#include "scope_guard.h" +#include "win.h" //includes "windows.h" + +namespace zen +{ +/* +Manage DLL function and library ownership + - thread safety: like built-in type + - full value semantics + + Usage: + typedef BOOL (WINAPI *IsWow64ProcessFun)(HANDLE hProcess, PBOOL Wow64Process); + const zen::DllFun<IsWow64ProcessFun> isWow64Process(L"kernel32.dll", "IsWow64Process"); + if (isWow64Process) ... use function ptr ... +*/ + +template <class Func> +class DllFun +{ +public: + DllFun() : fun(NULL) {} + + DllFun(const wchar_t* libraryName, const char* functionName) : + hLibRef(new HMODULE(::LoadLibrary(libraryName)), deleter), + fun(*hLibRef ? reinterpret_cast<Func>(::GetProcAddress(*hLibRef, functionName)) : NULL) {} + + operator Func() const { return fun; } + +private: + static void deleter(HMODULE* ptr) { if (*ptr) ::FreeLibrary(*ptr); delete ptr; } + + std::shared_ptr<const HMODULE> hLibRef; + Func fun; +}; + +//if the dll is already part of the process space, e.g. "kernel32.dll" or "shell32.dll", we can use a faster variant: +//NOTE: since the lifetime of the referenced library is *not* controlled, this is safe to use only for permanently loaded libraries like these! +template <class Func> +class SysDllFun +{ +public: + SysDllFun() : fun(NULL) {} + + SysDllFun(const wchar_t* systemLibrary, const char* functionName) : + fun(reinterpret_cast<Func>(::GetProcAddress(::GetModuleHandle(systemLibrary), functionName))) {} + + operator Func() const { return fun; } + +private: + Func fun; +}; + + +/* +extract binary resources from .exe/.dll: + +-- resource.h -- +#define MY_BINARY_RESOURCE 1337 + +-- resource.rc -- +MY_BINARY_RESOURCE RCDATA "filename.dat" +*/ +std::string getResourceStream(const std::wstring& libraryName, size_t resourceId); + + + + + + + + + + + + + + + + +//---------------Inline Implementation--------------------------------------------------- +inline +std::string getResourceStream(const wchar_t* libraryName, size_t resourceId) +{ + std::string output; + HMODULE module = ::LoadLibrary(libraryName); + if (module) + { + ZEN_ON_BLOCK_EXIT(::FreeLibrary(module)); + + const HRSRC res = ::FindResource(module, MAKEINTRESOURCE(resourceId), RT_RCDATA); + if (res != NULL) + { + const HGLOBAL resHandle = ::LoadResource(module, res); + if (resHandle != NULL) + { + const char* stream = static_cast<const char*>(::LockResource(resHandle)); + if (stream) + { + const DWORD streamSize = ::SizeofResource(module, res); + output.assign(stream, streamSize); + } + } + } + } + return output; +} +} + +#endif // DLLLOADER_H_INCLUDED diff --git a/zen/dst_hack.cpp b/zen/dst_hack.cpp new file mode 100644 index 00000000..f6579441 --- /dev/null +++ b/zen/dst_hack.cpp @@ -0,0 +1,369 @@ +#include "dst_hack.h" +#include <bitset> +#include "basic_math.h" +#include "long_path_prefix.h" +#include "utf8.h" +#include "assert_static.h" +#include "int64.h" +#include "file_error.h" +#include "dll.h" + +using namespace zen; + + +namespace +{ +//fast ::GetVolumePathName() clone: let's hope it's not too simple (doesn't honor mount points) +Zstring getVolumeName(const Zstring& filename) +{ + //this call is expensive: ~1.5 ms! + // if (!::GetVolumePathName(applyLongPathPrefix(filename).c_str(), //__in LPCTSTR lpszFileName, + // fsName, //__out LPTSTR lpszVolumePathName, + // BUFFER_SIZE)) //__in DWORD cchBufferLength + // ... + // Zstring volumePath = fsName; + // if (!volumePath.EndsWith(FILE_NAME_SEPARATOR)) //a trailing backslash is required + // volumePath += FILE_NAME_SEPARATOR; + + Zstring nameFmt = removeLongPathPrefix(filename); //throw() + if (!endsWith(nameFmt, FILE_NAME_SEPARATOR)) + nameFmt += FILE_NAME_SEPARATOR; //GetVolumeInformation expects trailing backslash + + if (startsWith(nameFmt, Zstr("\\\\"))) //UNC path: "\\ComputerName\SharedFolder\" + { + size_t nameSize = nameFmt.size(); + const size_t posFirstSlash = nameFmt.find(Zstr("\\"), 2); + if (posFirstSlash != Zstring::npos) + { + nameSize = posFirstSlash + 1; + const size_t posSecondSlash = nameFmt.find(Zstr("\\"), posFirstSlash + 1); + if (posSecondSlash != Zstring::npos) + nameSize = posSecondSlash + 1; + } + return Zstring(nameFmt.c_str(), nameSize); //include trailing backslash! + } + else //local path: "C:\Folder\" + { + const size_t pos = nameFmt.find(Zstr(":\\")); + if (pos == 1) //expect single letter volume + return Zstring(nameFmt.c_str(), 3); + } + + return Zstring(); +} +} + + +bool dst::isFatDrive(const Zstring& fileName) //throw() +{ + const size_t BUFFER_SIZE = MAX_PATH + 1; + wchar_t fsName[BUFFER_SIZE]; + + const Zstring volumePath = getVolumeName(fileName); + if (volumePath.empty()) + return false; + + //suprisingly fast: ca. 0.03 ms per call! + if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName, + NULL, //__out LPTSTR lpVolumeNameBuffer, + 0, //__in DWORD nVolumeNameSize, + NULL, //__out_opt LPDWORD lpVolumeSerialNumber, + NULL, //__out_opt LPDWORD lpMaximumComponentLength, + NULL, //__out_opt LPDWORD lpFileSystemFlags, + fsName, //__out LPTSTR lpFileSystemNameBuffer, + BUFFER_SIZE)) //__in DWORD nFileSystemNameSize + { + assert(false); //shouldn't happen + return false; + } + //DST hack seems to be working equally well for FAT and FAT32 (in particular creation time has 10^-2 s precision as advertised) + return fsName == Zstring(L"FAT") || + fsName == Zstring(L"FAT32"); +} + + +bool dst::isFatDrive(HANDLE hFile) //throw() +{ + //dynamically load windows API function + typedef BOOL (WINAPI *GetVolumeInformationByHandleWFunc)(HANDLE hFile, + LPWSTR lpVolumeNameBuffer, + DWORD nVolumeNameSize, + LPDWORD lpVolumeSerialNumber, + LPDWORD lpMaximumComponentLength, + LPDWORD lpFileSystemFlags, + LPWSTR lpFileSystemNameBuffer, + DWORD nFileSystemNameSize); + + const DllFun<GetVolumeInformationByHandleWFunc> getVolumeInformationByHandle(L"kernel32.dll", "GetVolumeInformationByHandleW"); + if (!getVolumeInformationByHandle) + { + assert(false); + return false; + } + + const size_t BUFFER_SIZE = MAX_PATH + 1; + wchar_t fsName[BUFFER_SIZE]; + + if (!getVolumeInformationByHandle(hFile, //__in HANDLE hFile, + NULL, //__out LPTSTR lpVolumeNameBuffer, + 0, //__in DWORD nVolumeNameSize, + NULL, //__out_opt LPDWORD lpVolumeSerialNumber, + NULL, //__out_opt LPDWORD lpMaximumComponentLength, + NULL, //__out_opt LPDWORD lpFileSystemFlags, + fsName, //__out LPTSTR lpFileSystemNameBuffer, + BUFFER_SIZE)) //__in DWORD nFileSystemNameSize + { + assert(false); //shouldn't happen + return false; + } + //DST hack seems to be working equally well for FAT and FAT32 (in particular creation time has 10^-2 s precision as advertised) + return fsName == Zstring(L"FAT") || + fsName == Zstring(L"FAT32"); +} + + +namespace +{ +//convert UInt64 and Int64 to FILETIME +inline +FILETIME toFiletime(Int64 number) +{ + const UInt64 unsig = to<UInt64>(number); + + FILETIME output = {}; + output.dwLowDateTime = unsig.getLo(); + output.dwHighDateTime = unsig.getHi(); + return output; +} + +FILETIME toFiletime(UInt64 number) +{ + FILETIME output = {}; + output.dwLowDateTime = number.getLo(); + output.dwHighDateTime = number.getHi(); + return output; +} + +inline +UInt64 toUInt64(const FILETIME& fileTime) +{ + return UInt64(fileTime.dwLowDateTime, fileTime.dwHighDateTime); +} + + +inline +Int64 toInt64(const FILETIME& fileTime) +{ + return to<Int64>(UInt64(fileTime.dwLowDateTime, fileTime.dwHighDateTime)); +} + + +FILETIME utcToLocal(const FILETIME& utcTime) //throw (std::runtime_error) +{ + //treat binary local time representation (which is invariant under DST time zone shift) as logical UTC: + FILETIME localTime = {}; + if (!::FileTimeToLocalFileTime( + &utcTime, //__in const FILETIME *lpFileTime, + &localTime)) //__out LPFILETIME lpLocalFileTime + { + const std::wstring errorMessage = _("Conversion error:") + " FILETIME -> local FILETIME: " + "(" + + "High: " + toString<std::wstring>(utcTime.dwHighDateTime) + " " + + "Low: " + toString<std::wstring>(utcTime.dwLowDateTime) + ") " + "\n\n" + getLastErrorFormatted(); + throw std::runtime_error(wideToUtf8<std::string>(errorMessage)); + } + return localTime; +} + + +FILETIME localToUtc(const FILETIME& localTime) //throw (std::runtime_error) +{ + //treat binary local time representation (which is invariant under DST time zone shift) as logical UTC: + FILETIME utcTime = {}; + if (!::LocalFileTimeToFileTime( + &localTime, //__in const FILETIME *lpLocalFileTime, + &utcTime)) //__out LPFILETIME lpFileTime + { + const std::wstring errorMessage = _("Conversion error:") + " local FILETIME -> FILETIME: " + "(" + + "High: " + toString<std::wstring>(localTime.dwHighDateTime) + " " + + "Low: " + toString<std::wstring>(localTime.dwLowDateTime) + ") " + "\n\n" + getLastErrorFormatted(); + throw std::runtime_error(wideToUtf8<std::string>(errorMessage)); + } + return utcTime; +} + + +//struct FILETIME {DWORD dwLowDateTime; DWORD dwHighDateTime;}; +const FILETIME FAT_MIN_TIME = { 13374976, 27846544 }; //1980 \ both are valid max/min FAT dates for 2 second precision +const FILETIME FAT_MAX_TIME = { 14487552, 37251238 }; //2107 / + +//http://en.wikipedia.org/wiki/File_Allocation_Table +const size_t PRECISION_WRITE_TIME = 20000000; //number of 100 ns per step -> 2 s +const size_t PRECISION_CREATE_TIME = 100000; // -> 1/100 s + +/* +Number of bits of information in create time: ln_2((FAT_MAX_TIME - FAT_MIN_TIME) / PRECISION_CREATE_TIME) = 38.55534023 +Number of bits of information in write time: 30.91148404 +*/ +//total size available to store data: +const size_t CREATE_TIME_INFO_BITS = 38; +// I. indicator that offset in II) is present +const size_t INDICATOR_EXISTING_BITS = 1; +// II. local<->UTC time offset +const size_t UTC_LOCAL_OFFSET_BITS = 7; +// III. indicator that offset in II) corresponds to current local write time (this could be a hash of the write time) +const size_t WRITE_TIME_HASH_BITS = CREATE_TIME_INFO_BITS - INDICATOR_EXISTING_BITS - UTC_LOCAL_OFFSET_BITS; + + +template <size_t precision> +FILETIME encodeRawInformation(UInt64 rawInfo) +{ + rawInfo *= precision; + rawInfo += toUInt64(FAT_MIN_TIME); + + assert(rawInfo <= toUInt64(FAT_MAX_TIME)); + return toFiletime(rawInfo); +} + + +template <size_t precision> +UInt64 extractRawInformation(const FILETIME& createTime) +{ + assert(toUInt64(FAT_MIN_TIME) <= toUInt64(createTime)); + assert(toUInt64(createTime) <= toUInt64(FAT_MAX_TIME)); + + //FAT create time ranges from 1980 - 2107 (2^7 years) with 1/100 seconds precision + UInt64 rawInfo = toUInt64(createTime); + + rawInfo -= toUInt64(FAT_MIN_TIME); + rawInfo /= precision; //reduce precision (FILETIME has unit 10^-7 s) + + assert(toUInt64(encodeRawInformation<precision>(rawInfo)) == toUInt64(createTime)); //must be reversible + return rawInfo; +} + + +//convert write time to it's minimal representation (no restriction to FAT range "1980 - 2107") +UInt64 extractRawWriteTime(const FILETIME& writeTime) +{ + UInt64 rawInfo = toUInt64(writeTime); + assert(rawInfo % PRECISION_WRITE_TIME == 0U); + rawInfo /= PRECISION_WRITE_TIME; //reduce precision (FILETIME has unit 10^-7 s) + return rawInfo; +} + + +//files with different resolution than 2 seconds are rounded up when written to FAT +FILETIME roundToFatWriteTime(const FILETIME& writeTime) +{ + UInt64 rawData = toUInt64(writeTime); + + if (rawData % PRECISION_WRITE_TIME != 0U) + rawData += PRECISION_WRITE_TIME; + + rawData /= PRECISION_WRITE_TIME; + rawData *= PRECISION_WRITE_TIME; + return toFiletime(rawData); +} + + +//get 7-bit value representing time shift in number of quarter-hours +std::bitset<UTC_LOCAL_OFFSET_BITS> getUtcLocalShift() +{ + FILETIME utcTime = FAT_MIN_TIME; + utcTime.dwHighDateTime += 5000000; //some arbitrary valid time + + const FILETIME localTime = utcToLocal(utcTime); + + const int timeShiftSec = to<int>((toInt64(localTime) - toInt64(utcTime)) / 10000000); //time shift in seconds + + const int timeShiftQuarter = timeShiftSec / (60 * 15); //time shift in quarter-hours + + const int absValue = numeric::abs(timeShiftQuarter); //MSVC C++0x bug: std::bitset<>(unsigned long) is ambiguous + + if (std::bitset < UTC_LOCAL_OFFSET_BITS - 1 > (absValue).to_ulong() != static_cast<unsigned long>(absValue) || //time shifts that big shouldn't be possible! + timeShiftSec % (60 * 15) != 0) //all known time shift have at least 15 minute granularity! + { + const std::wstring errorMessage = _("Conversion error:") + " Unexpected UTC <-> local time shift: " + + "(" + toString<std::wstring>(timeShiftSec) + ") " + "\n\n" + getLastErrorFormatted(); + throw std::runtime_error(wideToUtf8<std::string>(errorMessage)); + } + + std::bitset<UTC_LOCAL_OFFSET_BITS> output(absValue); + output[UTC_LOCAL_OFFSET_BITS - 1] = timeShiftQuarter < 0; //sign bit + return output; +} + + +//get time-zone shift in seconds +inline +int convertUtcLocalShift(std::bitset<UTC_LOCAL_OFFSET_BITS> rawShift) +{ + const bool hasSign = rawShift[UTC_LOCAL_OFFSET_BITS - 1]; + rawShift[UTC_LOCAL_OFFSET_BITS - 1] = false; + + assert_static(UTC_LOCAL_OFFSET_BITS <= sizeof(unsigned long) * 8); + return hasSign ? + static_cast<int>(rawShift.to_ulong()) * 15 * 60 * -1 : + static_cast<int>(rawShift.to_ulong()) * 15 * 60; +} +} + + +bool dst::fatHasUtcEncoded(const RawTime& rawTime) //"createTimeRaw" as retrieved by ::FindFirstFile() and ::GetFileAttributesEx(); throw (std::runtime_error) +{ + if (toUInt64(rawTime.createTimeRaw) < toUInt64(FAT_MIN_TIME) || + toUInt64(FAT_MAX_TIME) < toUInt64(rawTime.createTimeRaw)) + { + assert(false); //shouldn't be possible according to FAT specification + return false; + } + + const UInt64 rawInfo = extractRawInformation<PRECISION_CREATE_TIME>(utcToLocal(rawTime.createTimeRaw)); + + assert_static(WRITE_TIME_HASH_BITS == 30); + return (extractRawWriteTime(utcToLocal(rawTime.writeTimeRaw)) & 0x3FFFFFFFU) == (rawInfo & 0x3FFFFFFFU) && //ensure write time wasn't changed externally + rawInfo >> (CREATE_TIME_INFO_BITS - INDICATOR_EXISTING_BITS) == 1U; //extended data available +} + + +dst::RawTime dst::fatEncodeUtcTime(const FILETIME& writeTimeRealUtc) //throw (std::runtime_error) +{ + const FILETIME fatWriteTimeUtc = roundToFatWriteTime(writeTimeRealUtc); //writeTimeRealUtc may have incompatible precision (NTFS) + + //create time lets us store 40 bit of information + + //indicator that utc time is encoded -> hopefully results in a date long way in the future; but even if this bit is accidentally set, we still have the hash! + UInt64 data = 1U; + + const std::bitset<UTC_LOCAL_OFFSET_BITS> utcShift = getUtcLocalShift(); + data <<= UTC_LOCAL_OFFSET_BITS; + data |= utcShift.to_ulong(); + + data <<= WRITE_TIME_HASH_BITS; + data |= extractRawWriteTime(utcToLocal(fatWriteTimeUtc)) & 0x3FFFFFFFU; //trim to last 30 bit of information + assert_static(WRITE_TIME_HASH_BITS == 30); + + const FILETIME encodedData = localToUtc(encodeRawInformation<PRECISION_CREATE_TIME>(data)); //localToUtc: make sure data is physically saved as FAT local time + assert(toUInt64(FAT_MIN_TIME) <= toUInt64(encodedData)); + assert(toUInt64(encodedData) <= toUInt64(FAT_MAX_TIME)); + + return RawTime(encodedData, fatWriteTimeUtc); //keep compatible with other applications, at least until DST shift actually occurs +} + + +FILETIME dst::fatDecodeUtcTime(const RawTime& rawTime) //return real UTC time; throw (std::runtime_error) +{ + if (!fatHasUtcEncoded(rawTime)) + return rawTime.writeTimeRaw; + + const UInt64 rawInfo = extractRawInformation<PRECISION_CREATE_TIME>(utcToLocal(rawTime.createTimeRaw)); + + const std::bitset<UTC_LOCAL_OFFSET_BITS> rawShift(to<int>((rawInfo >> WRITE_TIME_HASH_BITS) & 0x7FU)); //static_cast<int>: a shame MSC... "unsigned long" should be supported instead! + assert_static(UTC_LOCAL_OFFSET_BITS == 7); + + const int timeShiftSec = convertUtcLocalShift(rawShift); + const FILETIME writeTimeLocal = utcToLocal(rawTime.writeTimeRaw); + + const Int64 realUTC = toInt64(writeTimeLocal) - Int64(timeShiftSec) * 10000000; + return toFiletime(realUTC); +} diff --git a/zen/dst_hack.h b/zen/dst_hack.h new file mode 100644 index 00000000..07d08dc5 --- /dev/null +++ b/zen/dst_hack.h @@ -0,0 +1,40 @@ +#ifndef DST_HACK_H_INCLUDED +#define DST_HACK_H_INCLUDED + +#include "win.h" //includes "windows.h" +#include "zstring.h" +#include <stdexcept> + + +namespace dst +{ +/* +Solve DST +-1h and time zone shift issues on FAT drives +------------------------------------------------------- +- (local) last write time is not touched! +- all additional metadata is encoded in local create time: + I. indicator that offset in II) is present + II. local<->UTC time offset + III. indicator that offset in II) corresponds to current local write time (a hash of local last write time) +*/ + +bool isFatDrive(const Zstring& fileName); //throw () +bool isFatDrive(HANDLE hFile); //throw() -> call ONLY if vistaOrLater() == true! +bool vistaOrLater(); + +//all subsequent functions may throw the std::runtime_error exception! + +struct RawTime //time as retrieved by ::FindFirstFile() and ::GetFileAttributesEx() +{ + RawTime(const FILETIME& create, const FILETIME& lastWrite) : createTimeRaw(create), writeTimeRaw(lastWrite) {} + FILETIME createTimeRaw; + FILETIME writeTimeRaw; +}; +//save UTC time resistant against DST/time zone shifts +bool fatHasUtcEncoded(const RawTime& rawTime); //as retrieved by ::FindFirstFile() and ::GetFileAttributesEx(); throw (std::runtime_error) + +RawTime fatEncodeUtcTime(const FILETIME& writeTimeRealUtc); //throw (std::runtime_error) +FILETIME fatDecodeUtcTime(const RawTime& rawTime); //return last write time in real UTC; throw (std::runtime_error) +} + +#endif // DST_HACK_H_INCLUDED diff --git a/zen/file_error.h b/zen/file_error.h new file mode 100644 index 00000000..8c49937c --- /dev/null +++ b/zen/file_error.h @@ -0,0 +1,48 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef FILEERROR_H_INCLUDED +#define FILEERROR_H_INCLUDED + +#include <string> +#include "zstring.h" +#include "utf8.h" +#include "last_error.h" //we'll need this later anyway! + +namespace zen +{ +class FileError //Exception base class used to notify file/directory copy/delete errors +{ +public: + FileError(const std::wstring& message) : errorMessage(message) {} + virtual ~FileError() {} + + const std::wstring& msg() const { return errorMessage; } + +private: + std::wstring errorMessage; +}; + +#define DEFINE_NEW_FILE_ERROR(X) struct X : public FileError { X(const std::wstring& message) : FileError(message) {} }; + +DEFINE_NEW_FILE_ERROR(ErrorNotExisting); +DEFINE_NEW_FILE_ERROR(ErrorTargetExisting); +DEFINE_NEW_FILE_ERROR(ErrorTargetPathMissing); +DEFINE_NEW_FILE_ERROR(ErrorFileLocked); + + + +//----------- facilitate usage of std::wstring for error messages -------------------- + +//allow implicit UTF8 conversion: since std::wstring models a GUI string, convenience is more important than performance +inline std::wstring operator+(const std::wstring& lhs, const Zstring& rhs) { return std::wstring(lhs) += zen::utf8CvrtTo<std::wstring>(rhs); } + +//we musn't put our overloads in namespace std, but namespace zen (+ using directive) is sufficient +inline std::wstring operator+(const std::wstring& lhs, const char* rhs) { return std::wstring(lhs) += utf8CvrtTo<std::wstring>(rhs); } +inline std::wstring operator+(const std::wstring& lhs, const std::string& rhs) { return std::wstring(lhs) += utf8CvrtTo<std::wstring>(rhs); } +} + +#endif // FILEERROR_H_INCLUDED diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp new file mode 100644 index 00000000..7b46181b --- /dev/null +++ b/zen/file_handling.cpp @@ -0,0 +1,2060 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#include "file_handling.h" +#include <map> +#include <algorithm> +#include <stdexcept> +#include "file_traverser.h" +#include "scope_guard.h" +#include "symlink_target.h" +#include "file_io.h" +#include "assert_static.h" +#include <boost/thread/tss.hpp> +#include <boost/thread/once.hpp> +#include "file_id_internal.h" + +#ifdef FFS_WIN +#include "privilege.h" +#include "dll.h" +#include "win.h" //includes "windows.h" +#include "long_path_prefix.h" +#include <Aclapi.h> +#include "dst_hack.h" +#include "file_update_handle.h" +#include "win_ver.h" + +#elif defined FFS_LINUX +#include <sys/stat.h> +#include <time.h> +#include <utime.h> +#include <cerrno> +#include <sys/time.h> + +#ifdef HAVE_SELINUX +#include <selinux/selinux.h> +#endif +#endif + +using namespace zen; + + +bool zen::fileExists(const Zstring& filename) +{ + //symbolic links (broken or not) are also treated as existing files! +#ifdef FFS_WIN + const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(filename).c_str()); + return ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_DIRECTORY) == 0; //returns true for (file-)symlinks also + +#elif defined FFS_LINUX + struct stat fileInfo = {}; + return ::lstat(filename.c_str(), &fileInfo) == 0 && + (S_ISLNK(fileInfo.st_mode) || S_ISREG(fileInfo.st_mode)); //in Linux a symbolic link is neither file nor directory +#endif +} + + +bool zen::dirExists(const Zstring& dirname) +{ + //symbolic links (broken or not) are also treated as existing directories! +#ifdef FFS_WIN + const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(dirname).c_str()); + return ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for (dir-)symlinks also + +#elif defined FFS_LINUX + struct stat dirInfo = {}; + return ::lstat(dirname.c_str(), &dirInfo) == 0 && + (S_ISLNK(dirInfo.st_mode) || S_ISDIR(dirInfo.st_mode)); //in Linux a symbolic link is neither file nor directory +#endif +} + + +bool zen::symlinkExists(const Zstring& objname) +{ +#ifdef FFS_WIN + const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(objname).c_str()); + return ret != INVALID_FILE_ATTRIBUTES && (ret & FILE_ATTRIBUTE_REPARSE_POINT) != 0; + +#elif defined FFS_LINUX + struct stat fileInfo = {}; + return ::lstat(objname.c_str(), &fileInfo) == 0 && + S_ISLNK(fileInfo.st_mode); //symbolic link +#endif +} + + +bool zen::somethingExists(const Zstring& objname) //throw() check whether any object with this name exists +{ +#ifdef FFS_WIN + return ::GetFileAttributes(applyLongPathPrefix(objname).c_str()) != INVALID_FILE_ATTRIBUTES; + +#elif defined FFS_LINUX + struct stat fileInfo = {}; + return ::lstat(objname.c_str(), &fileInfo) == 0; +#endif +} + + +namespace +{ +void getFileAttrib(const Zstring& filename, FileAttrib& attr, ProcSymlink procSl) //throw FileError +{ +#ifdef FFS_WIN + WIN32_FIND_DATA fileInfo = {}; + { + const HANDLE searchHandle = ::FindFirstFile(applyLongPathPrefix(filename).c_str(), &fileInfo); + if (searchHandle == INVALID_HANDLE_VALUE) + throw FileError(_("Error reading file attributes:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted()); + ::FindClose(searchHandle); + } + // WIN32_FILE_ATTRIBUTE_DATA sourceAttr = {}; + // if (!::GetFileAttributesEx(applyLongPathPrefix(sourceObj).c_str(), //__in LPCTSTR lpFileName, + // 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) + { + //####################################### DST hack ########################################### + const bool isDirectory = (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; + if (!isDirectory && dst::isFatDrive(filename)) //throw() + { + const dst::RawTime rawTime(fileInfo.ftCreationTime, fileInfo.ftLastWriteTime); + if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error) + { + fileInfo.ftLastWriteTime = dst::fatDecodeUtcTime(rawTime); //return last write time in real UTC, throw (std::runtime_error) + ::GetSystemTimeAsFileTime(&fileInfo.ftCreationTime); //real creation time information is not available... + } + } + //####################################### DST hack ########################################### + + attr.fileSize = UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); + attr.modificationTime = toTimeT(fileInfo.ftLastWriteTime); + } + else + { + const HANDLE hFile = ::CreateFile(applyLongPathPrefix(filename).c_str(), //open handle to target of symbolic link + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (hFile == INVALID_HANDLE_VALUE) + throw FileError(_("Error reading file attributes:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted()); + ZEN_ON_BLOCK_EXIT(::CloseHandle(hFile)); + + BY_HANDLE_FILE_INFORMATION fileInfoHnd = {}; + if (!::GetFileInformationByHandle(hFile, &fileInfoHnd)) + throw FileError(_("Error reading file attributes:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted()); + + attr.fileSize = UInt64(fileInfoHnd.nFileSizeLow, fileInfoHnd.nFileSizeHigh); + attr.modificationTime = toTimeT(fileInfoHnd.ftLastWriteTime); + } + +#elif defined FFS_LINUX + struct stat fileInfo = {}; + + const int rv = procSl == SYMLINK_FOLLOW ? + :: stat(filename.c_str(), &fileInfo) : + ::lstat(filename.c_str(), &fileInfo); + if (rv != 0) //follow symbolic links + throw FileError(_("Error reading file attributes:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted()); + + attr.fileSize = UInt64(fileInfo.st_size); + attr.modificationTime = fileInfo.st_mtime; +#endif +} +} + + +UInt64 zen::getFilesize(const Zstring& filename) //throw FileError +{ + FileAttrib attr; + getFileAttrib(filename, attr, SYMLINK_FOLLOW); //throw FileError + return attr.fileSize; +} + + +Int64 zen::getFileTime(const Zstring& filename, ProcSymlink procSl) //throw FileError +{ + FileAttrib attr; + getFileAttrib(filename, attr, procSl); //throw FileError + return attr.modificationTime; +} + + +namespace +{ + + +#ifdef FFS_WIN +DWORD retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! +{ + std::vector<wchar_t> buffer(10000); + + //full pathName need not yet exist! + if (!::GetVolumePathName(pathName.c_str(), //__in LPCTSTR lpszFileName, + &buffer[0], //__out LPTSTR lpszVolumePathName, + static_cast<DWORD>(buffer.size()))) //__in DWORD cchBufferLength + return 0; + + Zstring volumePath = &buffer[0]; + if (!endsWith(volumePath, FILE_NAME_SEPARATOR)) + volumePath += FILE_NAME_SEPARATOR; + + DWORD volumeSerial = 0; + if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName, + NULL, //__out LPTSTR lpVolumeNameBuffer, + 0, //__in DWORD nVolumeNameSize, + &volumeSerial, //__out_opt LPDWORD lpVolumeSerialNumber, + NULL, //__out_opt LPDWORD lpMaximumComponentLength, + NULL, //__out_opt LPDWORD lpFileSystemFlags, + NULL, //__out LPTSTR lpFileSystemNameBuffer, + 0)) //__in DWORD nFileSystemNameSize + return 0; + + return volumeSerial; +} +#elif defined FFS_LINUX + +dev_t retrieveVolumeSerial(const Zstring& pathName) //return 0 on error! +{ + Zstring volumePathName = pathName; + + //remove trailing slash + if (volumePathName.size() > 1 && endsWith(volumePathName, FILE_NAME_SEPARATOR)) //exception: allow '/' + volumePathName = beforeLast(volumePathName, FILE_NAME_SEPARATOR); + + struct stat fileInfo = {}; + while (::lstat(volumePathName.c_str(), &fileInfo) != 0) //go up in folder hierarchy until existing folder is found + { + volumePathName = beforeLast(volumePathName, FILE_NAME_SEPARATOR); //returns empty string if ch not found + if (volumePathName.empty()) + return 0; //this includes path "/" also! + } + + return fileInfo.st_dev; +} +#endif +} + + +zen::ResponseSame zen::onSameVolume(const Zstring& folderLeft, const Zstring& folderRight) //throw() +{ + const auto serialLeft = retrieveVolumeSerial(folderLeft); //returns 0 on error! + const auto serialRight = retrieveVolumeSerial(folderRight); //returns 0 on error! + if (serialLeft == 0 || serialRight == 0) + return IS_SAME_CANT_SAY; + + return serialLeft == serialRight ? IS_SAME_YES : IS_SAME_NO; +} + + +bool zen::removeFile(const Zstring& filename) //throw FileError; +{ +#ifdef FFS_WIN + //remove file, support for \\?\-prefix + const Zstring filenameFmt = applyLongPathPrefix(filename); + if (!::DeleteFile(filenameFmt.c_str())) +#elif defined FFS_LINUX + if (::unlink(filename.c_str()) != 0) +#endif + { +#ifdef FFS_WIN + //perf: apply ONLY when necessary! + if (::GetLastError() == ERROR_ACCESS_DENIED) //function fails if file is read-only + { + //(try to) normalize file attributes + ::SetFileAttributes(filenameFmt.c_str(), FILE_ATTRIBUTE_NORMAL); + + //now try again... + if (::DeleteFile(filenameFmt.c_str())) + return true; + } + //eval error code before next call + DWORD lastError = ::GetLastError(); +#elif defined FFS_LINUX + int lastError = errno; +#endif + + //no error situation if file is not existing! manual deletion relies on it! + //perf: check is placed in error handling block + //warning: this call changes error code!! + if (!somethingExists(filename)) + return false; //neither file nor any other object (e.g. broken symlink) with that name existing + + throw FileError(_("Error deleting file:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted(lastError)); + } + return true; +} + + +namespace +{ +DEFINE_NEW_FILE_ERROR(ErrorDifferentVolume); + +/* Usage overview: (avoid circular pattern!) + + renameFile() --> renameFile_sub() + | /|\ + \|/ | + Fix8Dot3NameClash() +*/ +//wrapper for file system rename function: +void renameFile_sub(const Zstring& oldName, const Zstring& newName) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting +{ +#ifdef FFS_WIN + const Zstring oldNameFmt = applyLongPathPrefix(oldName); + const Zstring newNameFmt = applyLongPathPrefix(newName); + + if (!::MoveFileEx(oldNameFmt.c_str(), //__in LPCTSTR lpExistingFileName, + newNameFmt.c_str(), //__in_opt LPCTSTR lpNewFileName, + 0)) //__in DWORD dwFlags + { + DWORD lastError = ::GetLastError(); + if (lastError == ERROR_ACCESS_DENIED) //MoveFileEx may fail to rename a read-only file on a SAMBA-share -> (try to) handle this + { + const DWORD oldAttr = ::GetFileAttributes(oldNameFmt.c_str()); + if (oldAttr != INVALID_FILE_ATTRIBUTES && (oldAttr & FILE_ATTRIBUTE_READONLY)) + { + if (::SetFileAttributes(oldNameFmt.c_str(), FILE_ATTRIBUTE_NORMAL)) //remove readonly-attribute + { + //try again... + if (::MoveFileEx(oldNameFmt.c_str(), //__in LPCTSTR lpExistingFileName, + newNameFmt.c_str(), //__in_opt LPCTSTR lpNewFileName, + 0)) //__in DWORD dwFlags + { + //(try to) restore file attributes + ::SetFileAttributes(newNameFmt.c_str(), oldAttr); //don't handle error + return; + } + else + { + lastError = ::GetLastError(); //use error code from second call to ::MoveFileEx() + + //cleanup: (try to) restore file attributes: assume oldName is still existing + ::SetFileAttributes(oldNameFmt.c_str(), oldAttr); + } + } + } + } + + std::wstring errorMessage = _("Error moving file:") + "\n\"" + oldName + "\" ->\n\"" + newName + "\"" + "\n\n" + getLastErrorFormatted(lastError); + + if (lastError == ERROR_NOT_SAME_DEVICE) + throw ErrorDifferentVolume(errorMessage); + else if (lastError == ERROR_FILE_EXISTS) + throw ErrorTargetExisting(errorMessage); + else + throw FileError(errorMessage); + } + +#elif defined FFS_LINUX + if (::rename(oldName.c_str(), newName.c_str()) != 0) + { + const int lastError = errno; + + std::wstring errorMessage = _("Error moving file:") + "\n\"" + oldName + "\" ->\n\"" + newName + "\"" + "\n\n" + getLastErrorFormatted(lastError); + + if (lastError == EXDEV) + throw ErrorDifferentVolume(errorMessage); + else if (lastError == EEXIST) + throw ErrorTargetExisting(errorMessage); + else + throw FileError(errorMessage); + } +#endif +} + + +#ifdef FFS_WIN +/*small wrapper around +::GetShortPathName() +::GetLongPathName() */ +template <typename Function> +Zstring getFilenameFmt(const Zstring& filename, Function fun) //throw(); returns empty string on error +{ + const Zstring filenameFmt = applyLongPathPrefix(filename); + + const DWORD bufferSize = fun(filenameFmt.c_str(), NULL, 0); + if (bufferSize == 0) + return Zstring(); + + std::vector<wchar_t> buffer(bufferSize); + + const DWORD rv = fun(filenameFmt.c_str(), //__in LPCTSTR lpszShortPath, + &buffer[0], //__out LPTSTR lpszLongPath, + static_cast<DWORD>(buffer.size())); //__in DWORD cchBuffer + if (rv == 0 || rv >= buffer.size()) + return Zstring(); + + return &buffer[0]; +} + + +Zstring findUnused8Dot3Name(const Zstring& filename) //find a unique 8.3 short name +{ + const Zstring pathPrefix = filename.find(FILE_NAME_SEPARATOR) != Zstring::npos ? + (beforeLast(filename, FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR) : Zstring(); + + Zstring extension = afterLast(afterLast(filename, FILE_NAME_SEPARATOR), Zchar('.')); //extension needn't contain reasonable data + if (extension.empty()) + extension = Zstr("FFS"); + truncate(extension, 3); + + for (int index = 0; index < 100000000; ++index) //filename must be representable by <= 8 characters + { + const Zstring output = pathPrefix + toString<Zstring>(index) + Zchar('.') + extension; + if (!somethingExists(output)) //ensure uniqueness + return output; + } + + throw std::runtime_error(std::string("100000000 files, one for each number, exist in this directory? You're kidding...\n") + utf8CvrtTo<std::string>(pathPrefix)); +} + + +bool have8dot3NameClash(const Zstring& filename) +{ + if (filename.find(FILE_NAME_SEPARATOR) == Zstring::npos) + return false; + + if (somethingExists(filename)) //name OR directory! + { + const Zstring origName = afterLast(filename, FILE_NAME_SEPARATOR); //returns the whole string if ch not found + const Zstring shortName = afterLast(getFilenameFmt(filename, ::GetShortPathName), FILE_NAME_SEPARATOR); //throw() returns empty string on error + const Zstring longName = afterLast(getFilenameFmt(filename, ::GetLongPathName) , FILE_NAME_SEPARATOR); // + + if (!shortName.empty() && + !longName.empty() && + EqualFilename()(origName, shortName) && + !EqualFilename()(shortName, longName)) + { + //for filename short and long file name are equal and another unrelated file happens to have the same short name + //e.g. filename == "TESTWE~1", but another file is existing named "TestWeb" with short name ""TESTWE~1" + return true; + } + } + return false; +} + +class Fix8Dot3NameClash +{ +public: + Fix8Dot3NameClash(const Zstring& filename) + { + const Zstring longName = afterLast(getFilenameFmt(filename, ::GetLongPathName), FILE_NAME_SEPARATOR); //throw() returns empty string on error + + unrelatedFile = beforeLast(filename, FILE_NAME_SEPARATOR) + FILE_NAME_SEPARATOR + longName; + + //find another name in short format: this ensures the actual short name WILL be renamed as well! + unrelatedFileParked = findUnused8Dot3Name(filename); + + //move already existing short name out of the way for now + renameFile_sub(unrelatedFile, unrelatedFileParked); //throw FileError, ErrorDifferentVolume + //DON'T call renameFile() to avoid reentrance! + } + + ~Fix8Dot3NameClash() + { + //the file system should assign this unrelated file a new (unique) short name + try + { + renameFile_sub(unrelatedFileParked, unrelatedFile); //throw FileError, ErrorDifferentVolume + } + catch (...) {} + } +private: + Zstring unrelatedFile; + Zstring unrelatedFileParked; +}; +#endif +} + + +//rename file: no copying!!! +void zen::renameFile(const Zstring& oldName, const Zstring& newName) //throw FileError, ErrorDifferentVolume, ErrorTargetExisting +{ + try + { + renameFile_sub(oldName, newName); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting + } + catch (const FileError&) + { +#ifdef FFS_WIN + //try to handle issues with already existing short 8.3 file names on Windows + if (have8dot3NameClash(newName)) + { + Fix8Dot3NameClash dummy(newName); //move clashing filename to the side + //now try again... + renameFile_sub(oldName, newName); //throw FileError + return; + } +#endif + throw; + } +} + + +class CopyCallbackImpl : public zen::CallbackCopyFile //callback functionality +{ +public: + CopyCallbackImpl(const Zstring& sourceFile, CallbackMoveFile& callback) : sourceFile_(sourceFile), moveCallback(callback) {} + + virtual void deleteTargetFile(const Zstring& targetFile) { assert(!fileExists(targetFile)); } + + virtual void updateCopyStatus(UInt64 totalBytesTransferred) + { + moveCallback.requestUiRefresh(sourceFile_); + } + +private: + CopyCallbackImpl(const CopyCallbackImpl&); + CopyCallbackImpl& operator=(const CopyCallbackImpl&); + + const Zstring sourceFile_; + CallbackMoveFile& moveCallback; +}; + + +void zen::moveFile(const Zstring& sourceFile, const Zstring& targetFile, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError; +{ + //call back once per file (moveFile() is called by moveDirectory()) + if (callback) + callback->requestUiRefresh(sourceFile); + + const bool targetExisting = fileExists(targetFile); + + if (targetExisting && !ignoreExisting) //test file existence: e.g. Linux might silently overwrite existing symlinks + throw FileError(_("Error moving file:") + "\n\"" + sourceFile + "\" ->\n\"" + targetFile + "\"" + + "\n\n" + _("Target file already existing!")); + + if (!targetExisting) + { + //try to move the file directly without copying + try + { + renameFile(sourceFile, targetFile); //throw FileError, ErrorDifferentVolume + return; //great, we get away cheaply! + } + //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the file) + catch (const ErrorDifferentVolume&) {} + + //file is on a different volume: let's copy it + std::unique_ptr<CopyCallbackImpl> copyCallback(callback != NULL ? new CopyCallbackImpl(sourceFile, *callback) : NULL); + + if (symlinkExists(sourceFile)) + copySymlink(sourceFile, targetFile, false); //throw FileError; don't copy filesystem permissions + else + copyFile(sourceFile, targetFile, false, true, copyCallback.get()); //throw FileError; + + //attention: if copy-operation was cancelled an exception is thrown => sourcefile is not deleted, as we wish! + } + + removeFile(sourceFile); //throw FileError + //note: copying file is NOT undone in case of exception: currently this function is called in context of user-defined deletion dir, where this behavior is fine +} + +namespace +{ +class TraverseOneLevel : public zen::TraverseCallback +{ +public: + typedef std::pair<Zstring, Zstring> NamePair; + typedef std::vector<NamePair> NameList; + + TraverseOneLevel(NameList& files, NameList& dirs) : + files_(files), + dirs_(dirs) {} + + virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details) + { + files_.push_back(NamePair(shortName, fullName)); + } + + virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) + { + if (details.dirLink) + dirs_.push_back(NamePair(shortName, fullName)); + else + files_.push_back(NamePair(shortName, fullName)); + } + + virtual ReturnValDir onDir(const Zchar* shortName, const Zstring& fullName) + { + dirs_.push_back(NamePair(shortName, fullName)); + return Int2Type<ReturnValDir::TRAVERSING_DIR_IGNORE>(); //DON'T traverse into subdirs; moveDirectory works recursively! + } + + virtual HandleError onError(const std::wstring& errorText) { throw FileError(errorText); } + +private: + TraverseOneLevel(const TraverseOneLevel&); + TraverseOneLevel& operator=(const TraverseOneLevel&); + + NameList& files_; + NameList& dirs_; +}; + + +struct RemoveCallbackImpl : public CallbackRemoveDir +{ + RemoveCallbackImpl(const Zstring& sourceDir, + CallbackMoveFile& moveCallback) : + sourceDir_(sourceDir), + moveCallback_(moveCallback) {} + + virtual void notifyFileDeletion(const Zstring& filename) { moveCallback_.requestUiRefresh(sourceDir_); } + virtual void notifyDirDeletion(const Zstring& dirname) { moveCallback_.requestUiRefresh(sourceDir_); } + +private: + RemoveCallbackImpl(const RemoveCallbackImpl&); + RemoveCallbackImpl& operator=(const RemoveCallbackImpl&); + + const Zstring sourceDir_; + CallbackMoveFile& moveCallback_; +}; +} + + +void moveDirectoryImpl(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError; +{ + //call back once per folder + if (callback) + callback->requestUiRefresh(sourceDir); + + const bool targetExisting = dirExists(targetDir); + + if (targetExisting && !ignoreExisting) //directory or symlink exists (or even a file... this error will be caught later) + throw FileError(_("Error moving directory:") + "\n\"" + sourceDir + "\" ->\n\"" + targetDir + "\"" + + "\n\n" + _("Target directory already existing!")); + + const bool isSymlink = symlinkExists(sourceDir); + + if (!targetExisting) + { + //first try to move the directory directly without copying + try + { + renameFile(sourceDir, targetDir); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting + return; //great, we get away cheaply! + } + //if moving failed treat as error (except when it tried to move to a different volume: in this case we will copy the directory) + catch (const ErrorDifferentVolume&) {} + + //create target + if (isSymlink) + copySymlink(sourceDir, targetDir, false); //throw FileError -> don't copy permissions + else + createDirectory(targetDir, sourceDir, false); //throw FileError + } + + if (!isSymlink) //handle symbolic links + { + //move files/folders recursively + TraverseOneLevel::NameList fileList; //list of names: 1. short 2.long + TraverseOneLevel::NameList dirList; // + + //traverse source directory one level + TraverseOneLevel traverseCallback(fileList, dirList); + traverseFolder(sourceDir, false, traverseCallback); //traverse one level, don't follow symlinks + + const Zstring targetDirFormatted = endsWith(targetDir, FILE_NAME_SEPARATOR) ? //ends with path separator + targetDir : + targetDir + FILE_NAME_SEPARATOR; + + //move files + for (TraverseOneLevel::NameList::const_iterator i = fileList.begin(); i != fileList.end(); ++i) + moveFile(i->second, targetDirFormatted + i->first, ignoreExisting, callback); //throw FileError, ErrorTargetExisting + + //move directories + for (TraverseOneLevel::NameList::const_iterator i = dirList.begin(); i != dirList.end(); ++i) + ::moveDirectoryImpl(i->second, targetDirFormatted + i->first, ignoreExisting, callback); + + //attention: if move-operation was cancelled an exception is thrown => sourceDir is not deleted, as we wish! + } + + //delete source + std::unique_ptr<RemoveCallbackImpl> removeCallback(callback != NULL ? new RemoveCallbackImpl(sourceDir, *callback) : NULL); + removeDirectory(sourceDir, removeCallback.get()); //throw FileError; +} + + +void zen::moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback) //throw FileError; +{ +#ifdef FFS_WIN + const Zstring& sourceDirFormatted = sourceDir; + const Zstring& targetDirFormatted = targetDir; + +#elif defined FFS_LINUX + const Zstring sourceDirFormatted = //remove trailing slash + sourceDir.size() > 1 && endsWith(sourceDir, FILE_NAME_SEPARATOR) ? //exception: allow '/' + beforeLast(sourceDir, FILE_NAME_SEPARATOR) : + sourceDir; + const Zstring targetDirFormatted = //remove trailing slash + targetDir.size() > 1 && endsWith(targetDir, FILE_NAME_SEPARATOR) ? //exception: allow '/' + beforeLast(targetDir, FILE_NAME_SEPARATOR) : + targetDir; +#endif + + ::moveDirectoryImpl(sourceDirFormatted, targetDirFormatted, ignoreExisting, callback); +} + + +class FilesDirsOnlyTraverser : public zen::TraverseCallback +{ +public: + FilesDirsOnlyTraverser(std::vector<Zstring>& files, std::vector<Zstring>& dirs) : + m_files(files), + m_dirs(dirs) {} + + virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details) + { + m_files.push_back(fullName); + } + virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) + { + if (details.dirLink) + m_dirs.push_back(fullName); + else + m_files.push_back(fullName); + } + virtual ReturnValDir onDir(const Zchar* shortName, const Zstring& fullName) + { + m_dirs.push_back(fullName); + return Int2Type<ReturnValDir::TRAVERSING_DIR_IGNORE>(); //DON'T traverse into subdirs; removeDirectory works recursively! + } + virtual HandleError onError(const std::wstring& errorText) { throw FileError(errorText); } + +private: + FilesDirsOnlyTraverser(const FilesDirsOnlyTraverser&); + FilesDirsOnlyTraverser& operator=(const FilesDirsOnlyTraverser&); + + std::vector<Zstring>& m_files; + std::vector<Zstring>& m_dirs; +}; + + +void zen::removeDirectory(const Zstring& directory, CallbackRemoveDir* callback) +{ + //no error situation if directory is not existing! manual deletion relies on it! + if (!somethingExists(directory)) + return; //neither directory nor any other object (e.g. broken symlink) with that name existing + +#ifdef FFS_WIN + const Zstring directoryFmt = applyLongPathPrefix(directory); //support for \\?\-prefix + + //(try to) normalize file attributes: actually NEEDED for symbolic links also! + ::SetFileAttributes(directoryFmt.c_str(), FILE_ATTRIBUTE_NORMAL); +#endif + + //attention: check if directory is a symlink! Do NOT traverse into it deleting contained files!!! + if (symlinkExists(directory)) //remove symlink directly + { +#ifdef FFS_WIN + if (!::RemoveDirectory(directoryFmt.c_str())) +#elif defined FFS_LINUX + if (::unlink(directory.c_str()) != 0) +#endif + throw FileError(_("Error deleting directory:") + "\n\"" + directory + "\"" + "\n\n" + getLastErrorFormatted()); + + if (callback) + callback->notifyDirDeletion(directory); //once per symlink + return; + } + + std::vector<Zstring> fileList; + std::vector<Zstring> dirList; + + //get all files and directories from current directory (WITHOUT subdirectories!) + FilesDirsOnlyTraverser traverser(fileList, dirList); + traverseFolder(directory, false, traverser); //don't follow symlinks + + //delete files + for (std::vector<Zstring>::const_iterator i = fileList.begin(); i != fileList.end(); ++i) + { + const bool workDone = removeFile(*i); + if (callback && workDone) + callback->notifyFileDeletion(*i); //call once per file + } + + //delete directories recursively + for (std::vector<Zstring>::const_iterator i = dirList.begin(); i != dirList.end(); ++i) + removeDirectory(*i, callback); //call recursively to correctly handle symbolic links + + //parent directory is deleted last +#ifdef FFS_WIN + if (!::RemoveDirectory(directoryFmt.c_str())) //remove directory, support for \\?\-prefix +#else + if (::rmdir(directory.c_str()) != 0) +#endif + { + throw FileError(_("Error deleting directory:") + "\n\"" + directory + "\"" + "\n\n" + getLastErrorFormatted()); + } + if (callback) + callback->notifyDirDeletion(directory); //and once per folder +} + + +void zen::setFileTime(const Zstring& filename, const Int64& modificationTime, ProcSymlink procSl) //throw FileError +{ +#ifdef FFS_WIN + FILETIME creationTime = {}; + FILETIME lastWriteTime = tofiletime(modificationTime); + + //####################################### DST hack ########################################### + if (dst::isFatDrive(filename)) //throw() + { + const dst::RawTime encodedTime = dst::fatEncodeUtcTime(lastWriteTime); //throw (std::runtime_error) + creationTime = encodedTime.createTimeRaw; + lastWriteTime = encodedTime.writeTimeRaw; + } + //####################################### DST hack ########################################### + + //privilege SE_BACKUP_NAME doesn't seem to be required here for symbolic links + //note: setting privileges requires admin rights! + + //opening newly created target file may fail due to some AV-software scanning it: no error, we will wait! + //http://support.microsoft.com/?scid=kb%3Ben-us%3B316609&x=17&y=20 + //-> enable as soon it turns out it is required! + + /*const int retryInterval = 50; + const int maxRetries = 2000 / retryInterval; + for (int i = 0; i < maxRetries; ++i) + { + */ + + //may need to remove the readonly-attribute (e.g. FAT usb drives) + FileUpdateHandle targetHandle(filename, [ = ]() + { + return ::CreateFile(applyLongPathPrefix(filename).c_str(), + GENERIC_READ | GENERIC_WRITE, //use both when writing over network, see comment in file_io.cpp + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | //needed to open a directory + (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //process symlinks + NULL); + }); + + if (targetHandle.get() == INVALID_HANDLE_VALUE) + throw FileError(_("Error changing modification time:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted()); + + /* + if (hTarget == INVALID_HANDLE_VALUE && ::GetLastError() == ERROR_SHARING_VIOLATION) + ::Sleep(retryInterval); //wait then retry + else //success or unknown failure + break; + } + */ + + auto isNullTime = [](const FILETIME & ft) { return ft.dwLowDateTime == 0 && ft.dwHighDateTime == 0; }; + + if (!::SetFileTime(targetHandle.get(), + isNullTime(creationTime) ? NULL : &creationTime, + NULL, + &lastWriteTime)) + throw FileError(_("Error changing modification time:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted()); + +#ifndef NDEBUG //dst hack: verify data written + if (dst::isFatDrive(filename) && !dirExists(filename)) //throw() + { + WIN32_FILE_ATTRIBUTE_DATA debugeAttr = {}; + assert(::GetFileAttributesEx(applyLongPathPrefix(filename).c_str(), //__in LPCTSTR lpFileName, + GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, + &debugeAttr)); //__out LPVOID lpFileInformation + + assert(::CompareFileTime(&debugeAttr.ftCreationTime, &creationTime) == 0); + assert(::CompareFileTime(&debugeAttr.ftLastWriteTime, &lastWriteTime) == 0); + } +#endif + +#elif defined FFS_LINUX + if (procSl == SYMLINK_FOLLOW) + { + struct utimbuf newTimes = {}; + newTimes.actime = ::time(NULL); + newTimes.modtime = to<time_t>(modificationTime); + + // set new "last write time" + if (::utime(filename.c_str(), &newTimes) != 0) + throw FileError(_("Error changing modification time:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted()); + } + else + { + struct timeval newTimes[2] = {}; + newTimes[0].tv_sec = ::time(NULL); /* seconds */ + newTimes[0].tv_usec = 0; /* microseconds */ + + newTimes[1].tv_sec = to<time_t>(modificationTime); + newTimes[1].tv_usec = 0; + + if (::lutimes(filename.c_str(), newTimes) != 0) + throw FileError(_("Error changing modification time:") + "\n\"" + filename + "\"" + "\n\n" + getLastErrorFormatted()); + } +#endif +} + + +namespace +{ +#ifdef FFS_WIN +Zstring resolveDirectorySymlink(const Zstring& dirLinkName) //get full target path of symbolic link to a directory; throw (FileError) +{ + //open handle to target of symbolic link + const HANDLE hDir = ::CreateFile(applyLongPathPrefix(dirLinkName).c_str(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, //needed to open a directory + NULL); + if (hDir == INVALID_HANDLE_VALUE) + throw FileError(_("Error resolving symbolic link:") + "\n\"" + dirLinkName + "\"" + "\n\n" + getLastErrorFormatted()); + ZEN_ON_BLOCK_EXIT(::CloseHandle(hDir)); + + const DWORD BUFFER_SIZE = 10000; + std::vector<wchar_t> targetPath(BUFFER_SIZE); + + //dynamically load windows API function + typedef DWORD (WINAPI *GetFinalPathNameByHandleWFunc)(HANDLE hFile, + LPTSTR lpszFilePath, + DWORD cchFilePath, + DWORD dwFlags); + const SysDllFun<GetFinalPathNameByHandleWFunc> getFinalPathNameByHandle(L"kernel32.dll", "GetFinalPathNameByHandleW"); + if (!getFinalPathNameByHandle) + throw FileError(_("Error loading library function:") + "\n\"" + "GetFinalPathNameByHandleW" + "\""); + + const DWORD charsWritten = getFinalPathNameByHandle(hDir, //__in HANDLE hFile, + &targetPath[0], //__out LPTSTR lpszFilePath, + BUFFER_SIZE, //__in DWORD cchFilePath, + FILE_NAME_NORMALIZED); //__in DWORD dwFlags + if (charsWritten >= BUFFER_SIZE || charsWritten == 0) + { + std::wstring errorMessage = _("Error resolving symbolic link:") + "\n\"" + dirLinkName + "\""; + if (charsWritten == 0) + errorMessage += L"\n\n" + getLastErrorFormatted(); + throw FileError(errorMessage); + } + + return Zstring(&targetPath[0], charsWritten); +} +#endif + + +#ifdef HAVE_SELINUX +//copy SELinux security context +void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError +{ + security_context_t contextSource = NULL; + const int rv = procSl == SYMLINK_FOLLOW ? + ::getfilecon(source.c_str(), &contextSource) : + ::lgetfilecon(source.c_str(), &contextSource); + if (rv < 0) + { + if (errno == ENODATA || //no security context (allegedly) is not an error condition on SELinux + errno == EOPNOTSUPP) //extended attributes are not supported by the filesystem + return; + + throw FileError(_("Error reading security context:") + "\n\"" + source + "\"" + "\n\n" + getLastErrorFormatted()); + } + ZEN_ON_BLOCK_EXIT(::freecon(contextSource)); + + { + security_context_t contextTarget = NULL; + const int rv2 = procSl == SYMLINK_FOLLOW ? + ::getfilecon(target.c_str(), &contextTarget) : + ::lgetfilecon(target.c_str(), &contextTarget); + if (rv2 < 0) + { + if (errno == EOPNOTSUPP) + return; + //else: still try to set security context + } + else + { + ZEN_ON_BLOCK_EXIT(::freecon(contextTarget)); + + if (::strcmp(contextSource, contextTarget) == 0) //nothing to do + return; + } + } + + const int rv3 = procSl == SYMLINK_FOLLOW ? + ::setfilecon(target.c_str(), contextSource) : + ::lsetfilecon(target.c_str(), contextSource); + if (rv3 < 0) + throw FileError(_("Error writing security context:") + "\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted()); +} +#endif //HAVE_SELINUX + + +//copy permissions for files, directories or symbolic links: requires admin rights +void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError; +{ +#ifdef FFS_WIN + //setting privileges requires admin rights! + try + { + //enable privilege: required to read/write SACL information + Privileges::getInstance().ensureActive(SE_SECURITY_NAME); //polling allowed... + + //enable privilege: required to copy owner information + Privileges::getInstance().ensureActive(SE_RESTORE_NAME); + + //the following privilege may be required according to http://msdn.microsoft.com/en-us/library/aa364399(VS.85).aspx (although not needed nor active in my tests) + Privileges::getInstance().ensureActive(SE_BACKUP_NAME); + } + catch (const FileError& e) + { + throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + e.msg()); + } + + PSECURITY_DESCRIPTOR buffer = NULL; + PSID owner = NULL; + PSID group = NULL; + PACL dacl = NULL; + PACL sacl = NULL; + + //http://msdn.microsoft.com/en-us/library/aa364399(v=VS.85).aspx + const HANDLE hSource = ::CreateFile(applyLongPathPrefix(source).c_str(), + READ_CONTROL | ACCESS_SYSTEM_SECURITY, //ACCESS_SYSTEM_SECURITY required for SACL access + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), //FILE_FLAG_BACKUP_SEMANTICS needed to open a directory + NULL); + if (hSource == INVALID_HANDLE_VALUE) + throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted() + " (OR)"); + ZEN_ON_BLOCK_EXIT(::CloseHandle(hSource)); + + // DWORD rc = ::GetNamedSecurityInfo(const_cast<WCHAR*>(applyLongPathPrefix(source).c_str()), -> does NOT dereference symlinks! + DWORD rc = ::GetSecurityInfo(hSource, //__in LPTSTR pObjectName, + SE_FILE_OBJECT, //__in SE_OBJECT_TYPE ObjectType, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, //__in SECURITY_INFORMATION SecurityInfo, + &owner, //__out_opt PSID *ppsidOwner, + &group, //__out_opt PSID *ppsidGroup, + &dacl, //__out_opt PACL *ppDacl, + &sacl, //__out_opt PACL *ppSacl, + &buffer); //__out_opt PSECURITY_DESCRIPTOR *ppSecurityDescriptor + if (rc != ERROR_SUCCESS) + throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted(rc) + " (R)"); + ZEN_ON_BLOCK_EXIT(::LocalFree(buffer)); + + //may need to remove the readonly-attribute (e.g. FAT usb drives) + FileUpdateHandle targetHandle(target, [ = ]() + { + return ::CreateFile(applyLongPathPrefix(target).c_str(), // lpFileName + GENERIC_WRITE | WRITE_OWNER | WRITE_DAC | ACCESS_SYSTEM_SECURITY, // dwDesiredAccess: all four seem to be required!!! + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, // dwShareMode + 0, // lpSecurityAttributes + OPEN_EXISTING, // dwCreationDisposition + FILE_FLAG_BACKUP_SEMANTICS | (procSl == SYMLINK_DIRECT ? FILE_FLAG_OPEN_REPARSE_POINT : 0), // dwFlagsAndAttributes + NULL); // hTemplateFile + }); + + if (targetHandle.get() == INVALID_HANDLE_VALUE) + throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted() + " (OW)"); + + // rc = ::SetNamedSecurityInfo(const_cast<WCHAR*>(applyLongPathPrefix(target).c_str()), //__in LPTSTR pObjectName, -> does NOT dereference symlinks! + rc = ::SetSecurityInfo(targetHandle.get(), //__in LPTSTR pObjectName, + SE_FILE_OBJECT, //__in SE_OBJECT_TYPE ObjectType, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION, //__in SECURITY_INFORMATION SecurityInfo, + owner, //__in_opt PSID psidOwner, + group, //__in_opt PSID psidGroup, + dacl, //__in_opt PACL pDacl, + sacl); //__in_opt PACL pSacl + + if (rc != ERROR_SUCCESS) + throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted(rc) + " (W)"); + +#elif defined FFS_LINUX + +#ifdef HAVE_SELINUX //copy SELinux security context + copySecurityContext(source, target, procSl); //throw FileError +#endif + + struct stat fileInfo = {}; + if (procSl == SYMLINK_FOLLOW) + { + if (::stat(source.c_str(), &fileInfo) != 0 || + ::chown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0 || // may require admin rights! + ::chmod(target.c_str(), fileInfo.st_mode) != 0) + throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted() + " (R)"); + } + else + { + if (::lstat(source.c_str(), &fileInfo) != 0 || + ::lchown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0 || // may require admin rights! + (!symlinkExists(target) && ::chmod(target.c_str(), fileInfo.st_mode) != 0)) //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod() + throw FileError(_("Error copying file permissions:") + "\n\"" + source + "\" ->\n\"" + target + "\"" + "\n\n" + getLastErrorFormatted() + " (W)"); + } +#endif +} + + +void createDirectory_straight(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions, int level) +{ + //default directory creation +#ifdef FFS_WIN + //don't use ::CreateDirectoryEx: + //- it may fail with "wrong parameter (error code 87)" when source is on mapped online storage + //- automatically copies symbolic links if encountered: unfortunately it doesn't copy symlinks over network shares but silently creates empty folders instead (on XP)! + //- it isn't able to copy most junctions because of missing permissions (although target path can be retrieved alternatively!) + if (!::CreateDirectory(applyLongPathPrefixCreateDir(directory).c_str(), NULL)) +#elif defined FFS_LINUX + if (::mkdir(directory.c_str(), 0755) != 0) +#endif + { + if (level != 0) return; + throw FileError(_("Error creating directory:") + "\n\"" + directory + "\"" + "\n\n" + getLastErrorFormatted()); + } + + if (!templateDir.empty()) + { +#ifdef FFS_WIN + //try to copy file attributes + Zstring sourcePath; + + if (symlinkExists(templateDir)) //dereference symlink! + { + try + { + //get target directory of symbolic link + sourcePath = resolveDirectorySymlink(templateDir); //throw FileError + } + catch (FileError&) {} //dereferencing a symbolic link usually fails if it is located on network drive or client is XP: NOT really an error... + } + else //no symbolic link + sourcePath = templateDir; + + //try to copy file attributes + if (!sourcePath.empty()) + { + const DWORD sourceAttr = ::GetFileAttributes(applyLongPathPrefix(sourcePath).c_str()); + if (sourceAttr != INVALID_FILE_ATTRIBUTES) + { + const bool isCompressed = (sourceAttr & FILE_ATTRIBUTE_COMPRESSED) != 0; + const bool isEncrypted = (sourceAttr & FILE_ATTRIBUTE_ENCRYPTED) != 0; + + ::SetFileAttributes(applyLongPathPrefix(directory).c_str(), sourceAttr); + + if (isEncrypted) + ::EncryptFile(directory.c_str()); //seems no long path is required (check passed!) + + if (isCompressed) + { + HANDLE hDir = ::CreateFile(applyLongPathPrefix(directory).c_str(), + GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (hDir != INVALID_HANDLE_VALUE) + { + ZEN_ON_BLOCK_EXIT(::CloseHandle(hDir)); + + USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; + + DWORD bytesReturned = 0; + ::DeviceIoControl(hDir, //handle to file or directory + FSCTL_SET_COMPRESSION, //dwIoControlCode + &cmpState, //input buffer + sizeof(cmpState), //size of input buffer + NULL, //lpOutBuffer + 0, //OutBufferSize + &bytesReturned, //number of bytes returned + NULL); //OVERLAPPED structure + } + } + } + } +#endif + zen::ScopeGuard guardNewDir = zen::makeGuard([&]() { removeDirectory(directory); }); //ensure cleanup: + + //enforce copying file permissions: it's advertized on GUI... + if (copyFilePermissions) + copyObjectPermissions(templateDir, directory, SYMLINK_FOLLOW); //throw FileError + + guardNewDir.dismiss(); //target has been created successfully! + } +} + + +void createDirectoryRecursively(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions, int level) +{ + if (level == 100) //catch endless recursion + return; + +#ifdef FFS_WIN + std::unique_ptr<Fix8Dot3NameClash> fnc; + if (somethingExists(directory)) + { + //handle issues with already existing short 8.3 file names on Windows + if (have8dot3NameClash(directory)) + fnc.reset(new Fix8Dot3NameClash(directory)); //move clashing object to the side + else if (dirExists(directory)) + return; + } +#elif defined FFS_LINUX + if (dirExists(directory)) + return; +#endif + else //if "somethingExists" we needn't create the parent directory + { + //try to create parent folders first + const Zstring dirParent = beforeLast(directory, FILE_NAME_SEPARATOR); + if (!dirParent.empty() && !dirExists(dirParent)) + { + //call function recursively + const Zstring templateParent = beforeLast(templateDir, FILE_NAME_SEPARATOR); //returns empty string if ch not found + createDirectoryRecursively(dirParent, templateParent, copyFilePermissions, level + 1); + } + } + + //now creation should be possible + createDirectory_straight(directory, templateDir, copyFilePermissions, level); //throw FileError +} +} + + +void zen::createDirectory(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions) +{ + //remove trailing separator + const Zstring dirFormatted = endsWith(directory, FILE_NAME_SEPARATOR) ? + beforeLast(directory, FILE_NAME_SEPARATOR) : + directory; + + const Zstring templateFormatted = endsWith(templateDir, FILE_NAME_SEPARATOR) ? + beforeLast(templateDir, FILE_NAME_SEPARATOR) : + templateDir; + + createDirectoryRecursively(dirFormatted, templateFormatted, copyFilePermissions, 0); +} + + +void zen::createDirectory(const Zstring& directory) +{ + zen::createDirectory(directory, Zstring(), false); +} + + +void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions) //throw FileError +{ + const Zstring linkPath = getSymlinkRawTargetString(sourceLink); //accept broken symlinks; throw FileError + +#ifdef FFS_WIN + const bool isDirLink = [&]() -> bool + { + const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(sourceLink).c_str()); + return ret != INVALID_FILE_ATTRIBUTES && (ret& FILE_ATTRIBUTE_DIRECTORY); + }(); + + //dynamically load windows API function + typedef BOOLEAN (WINAPI *CreateSymbolicLinkFunc)(LPCTSTR lpSymlinkFileName, LPCTSTR lpTargetFileName, DWORD dwFlags); + + const SysDllFun<CreateSymbolicLinkFunc> createSymbolicLink(L"kernel32.dll", "CreateSymbolicLinkW"); + if (!createSymbolicLink) + throw FileError(_("Error loading library function:") + "\n\"" + "CreateSymbolicLinkW" + "\""); + + if (!createSymbolicLink(targetLink.c_str(), //__in LPTSTR lpSymlinkFileName, - seems no long path prefix is required... + linkPath.c_str(), //__in LPTSTR lpTargetFileName, + (isDirLink ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0))) //__in DWORD dwFlags +#elif defined FFS_LINUX + if (::symlink(linkPath.c_str(), targetLink.c_str()) != 0) +#endif + throw FileError(_("Error copying symbolic link:") + "\n\"" + sourceLink + "\" ->\n\"" + targetLink + "\"" + "\n\n" + getLastErrorFormatted()); + + //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist + zen::ScopeGuard guardNewDir = zen::makeGuard([&]() + { +#ifdef FFS_WIN + if (isDirLink) + removeDirectory(targetLink); + else +#endif + removeFile(targetLink); + }); + + //file times: essential for a symlink: enforce this! (don't just try!) + { + const Int64 modTime = getFileTime(sourceLink, SYMLINK_DIRECT); //throw FileError + setFileTime(targetLink, modTime, SYMLINK_DIRECT); //throw FileError + } + + if (copyFilePermissions) + copyObjectPermissions(sourceLink, targetLink, SYMLINK_DIRECT); //throw FileError + + guardNewDir.dismiss(); //target has been created successfully! +} + + +namespace +{ +Zstring createTempName(const Zstring& filename) +{ + Zstring output = filename + zen::TEMP_FILE_ENDING; + + //ensure uniqueness + for (int i = 1; somethingExists(output); ++i) + output = filename + Zchar('_') + toString<Zstring>(i) + zen::TEMP_FILE_ENDING; + + return output; +} + +#ifdef FFS_WIN +class CallbackData +{ +public: + CallbackData(CallbackCopyFile* cb, //may be NULL + const Zstring& sourceFile, + const Zstring& targetFile, + bool osIsvistaOrLater) : + userCallback(cb), + sourceFile_(sourceFile), + targetFile_(targetFile), + osIsvistaOrLater_(osIsvistaOrLater), + exceptionInUserCallback(false) {} + + CallbackCopyFile* userCallback; //optional! + const Zstring& sourceFile_; + const Zstring& targetFile_; + const bool osIsvistaOrLater_; + + //there is some mixed responsibility in this class, pure read-only data and abstraction for error reporting + //however we need to keep it at one place as ::CopyFileEx() requires! + + void reportUserException(const UInt64& bytesTransferred) + { + exceptionInUserCallback = true; + bytesTransferredOnException = bytesTransferred; + } + + void reportError(const std::wstring& message) { errorMsg = message; } + + void evaluateErrors() //throw + { + if (exceptionInUserCallback) + { + assert(userCallback); + if (userCallback) + userCallback->updateCopyStatus(bytesTransferredOnException); //rethrow (hopefully!) + } + if (!errorMsg.empty()) + throw FileError(errorMsg); + } + + void setSrcAttr(const FileAttrib& attr) { sourceAttr = attr; } + FileAttrib getSrcAttr() const { assert(sourceAttr.modificationTime != 0); return sourceAttr; } + +private: + CallbackData(const CallbackData&); + CallbackData& operator=(const CallbackData&); + + FileAttrib sourceAttr; + std::wstring errorMsg; // + bool exceptionInUserCallback; //these two are exclusive! + UInt64 bytesTransferredOnException; +}; + + +DWORD CALLBACK copyCallbackInternal( + LARGE_INTEGER totalFileSize, + LARGE_INTEGER totalBytesTransferred, + LARGE_INTEGER streamSize, + LARGE_INTEGER streamBytesTransferred, + DWORD dwStreamNumber, + DWORD dwCallbackReason, + HANDLE hSourceFile, + HANDLE hDestinationFile, + LPVOID lpData) +{ + //this callback is invoked for block sizes managed by Windows, these may vary from e.g. 64 kB up to 1MB. It seems this is dependent from file size amongst others. + + //symlink handling: + //if source is a symlink and COPY_FILE_COPY_SYMLINK is specified, this callback is NOT invoked! + //if source is a symlink and COPY_FILE_COPY_SYMLINK is NOT specified, this callback is called and hSourceFile is a handle to the *target* of the link! + + //file time handling: + //::CopyFileEx() will copy file modification time (only) over from source file AFTER the last inocation of this callback + //=> it is possible to adapt file creation time of target in here, but NOT file modification time! + + CallbackData& cbd = *static_cast<CallbackData*>(lpData); + + //#################### return source file attributes ################################ + if (dwCallbackReason == CALLBACK_STREAM_SWITCH) //called up front for every file (even if 0-sized) + { + BY_HANDLE_FILE_INFORMATION fileInfo = {}; + if (!::GetFileInformationByHandle(hSourceFile, &fileInfo)) + { + cbd.reportError(_("Error reading file attributes:") + "\n\"" + cbd.sourceFile_ + "\"" + "\n\n" + getLastErrorFormatted()); + return PROGRESS_CANCEL; + } + + const FileAttrib attr = { UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh), + toTimeT(fileInfo.ftLastWriteTime) + }; + //extractFileID(fileInfo)); + + cbd.setSrcAttr(attr); + + //#################### copy file creation time ################################ + FILETIME creationTime = {}; + + if (!::GetFileTime(hSourceFile, //__in HANDLE hFile, + &creationTime, //__out_opt LPFILETIME lpCreationTime, + NULL, //__out_opt LPFILETIME lpLastAccessTime, + NULL)) //__out_opt LPFILETIME lpLastWriteTime + { + cbd.reportError(_("Error reading file attributes:") + "\n\"" + cbd.sourceFile_ + "\"" + "\n\n" + getLastErrorFormatted()); + return PROGRESS_CANCEL; + } + + if (!::SetFileTime(hDestinationFile, + &creationTime, + NULL, + NULL)) + { + cbd.reportError(_("Error changing modification time:") + "\n\"" + cbd.targetFile_ + "\"" + "\n\n" + getLastErrorFormatted()); + return PROGRESS_CANCEL; + } + //############################################################################## + } + + //if (totalFileSize.QuadPart == totalBytesTransferred.QuadPart) {} //called after copy operation is finished - note: for 0-sized files this callback is invoked just ONCE! + + if (cbd.userCallback) + { + //some odd check for some possible(?) error condition + if (totalBytesTransferred.QuadPart < 0) //let's see if someone answers the call... + ::MessageBox(NULL, L"You've just discovered a bug in WIN32 API function \"CopyFileEx\"! \n\n\ + Please write a mail to the author of FreeFileSync at zhnmju123@gmx.de and simply state that\n\ + \"totalBytesTransferred.HighPart can be below zero\"!\n\n\ + This will then be handled in future versions of FreeFileSync.\n\nThanks -ZenJu", + NULL, 0); + try + { + cbd.userCallback->updateCopyStatus(UInt64(totalBytesTransferred.QuadPart)); + } + catch (...) + { + //#warning migrate to std::exception_ptr when available + + cbd.reportUserException(UInt64(totalBytesTransferred.QuadPart)); + return PROGRESS_CANCEL; + } + } + return PROGRESS_CONTINUE; +} + + +#ifndef COPY_FILE_ALLOW_DECRYPTED_DESTINATION +#define COPY_FILE_ALLOW_DECRYPTED_DESTINATION 0x00000008 +#endif + +void rawCopyWinApi_sub(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackCopyFile* callback, + FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked +{ + zen::ScopeGuard guardTarget = zen::makeGuard([&]() { removeFile(targetFile); }); //transactional behavior: guard just before starting copy, we don't trust ::CopyFileEx(), do we ;) + + DWORD copyFlags = COPY_FILE_FAIL_IF_EXISTS; + + //allow copying from encrypted to non-encrytped location + static bool nonEncSupported = false; + { + static boost::once_flag initNonEncOnce = BOOST_ONCE_INIT; //caveat: function scope static initialization is not thread-safe in VS 2010! + boost::call_once(initNonEncOnce, []() { nonEncSupported = winXpOrLater(); }); //encrypted destination is not supported with Windows 2000 + } + if (nonEncSupported) + copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; + + static bool osIsvistaOrLater = false; + { + static boost::once_flag initVistaLaterOnce = BOOST_ONCE_INIT; //caveat: function scope static initialization is not thread-safe in VS 2010! + boost::call_once(initVistaLaterOnce, []() { osIsvistaOrLater = vistaOrLater(); }); + } + + CallbackData cbd(callback, sourceFile, targetFile, osIsvistaOrLater); + + const bool success = ::CopyFileEx( //same performance like CopyFile() + applyLongPathPrefix(sourceFile).c_str(), + applyLongPathPrefix(targetFile).c_str(), + copyCallbackInternal, + &cbd, + NULL, + copyFlags) == TRUE; //silence x64 perf warning + + cbd.evaluateErrors(); //throw ?, process errors in callback first! + if (!success) + { + const DWORD lastError = ::GetLastError(); + + //don't suppress "lastError == ERROR_REQUEST_ABORTED": a user aborted operation IS an error condition! + + //assemble error message... + std::wstring errorMessage = _("Error copying file:") + "\n\"" + sourceFile + "\" ->\n\"" + targetFile + "\"" + + "\n\n" + getLastErrorFormatted(lastError); + + //if file is locked (try to) use Windows Volume Shadow Copy Service + if (lastError == ERROR_SHARING_VIOLATION || + lastError == ERROR_LOCK_VIOLATION) + throw ErrorFileLocked(errorMessage); + + if (lastError == ERROR_FILE_EXISTS) //if target is existing this functions is expected to throw ErrorTargetExisting!!! + { + guardTarget.dismiss(); //don't delete file that existed previously! + throw ErrorTargetExisting(errorMessage); + } + + if (lastError == ERROR_PATH_NOT_FOUND) + { + guardTarget.dismiss(); //not relevant + throw ErrorTargetPathMissing(errorMessage); + } + + try //add more meaningful message + { + //trying to copy > 4GB file to FAT/FAT32 volume gives obscure ERROR_INVALID_PARAMETER (FAT can indeed handle files up to 4 Gig, tested!) + if (lastError == ERROR_INVALID_PARAMETER && + dst::isFatDrive(targetFile) && + getFilesize(sourceFile) >= 4U * UInt64(1024U * 1024 * 1024)) //throw FileError + errorMessage += L"\nFAT volume cannot store files larger than 4 gigabyte!"; + + //note: ERROR_INVALID_PARAMETER can also occur when copying to a SharePoint server or MS SkyDrive and the target filename is of a restricted type. + } + catch (...) {} + + throw FileError(errorMessage); + } + + if (sourceAttr) + *sourceAttr = cbd.getSrcAttr(); + + { + const Int64 modTime = getFileTime(sourceFile, SYMLINK_FOLLOW); //throw FileError + setFileTime(targetFile, modTime, SYMLINK_FOLLOW); //throw FileError + //note: this sequence leads to a loss of precision of up to 1 sec! + //perf-loss on USB sticks with many small files of about 30%! damn! + } + + guardTarget.dismiss(); //target has been created successfully! +} + + +inline +void rawCopyWinApi(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackCopyFile* callback, + FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked +{ + try + { + rawCopyWinApi_sub(sourceFile, targetFile, callback, sourceAttr); // throw ... + } + catch (ErrorTargetExisting&) + { + //try to handle issues with already existing short 8.3 file names on Windows + if (have8dot3NameClash(targetFile)) + { + Fix8Dot3NameClash dummy(targetFile); //move clashing filename to the side + rawCopyWinApi_sub(sourceFile, targetFile, callback, sourceAttr); //throw FileError; the short filename name clash is solved, this should work now + return; + } + throw; + } +} + +//void rawCopyWinOptimized(const Zstring& sourceFile, +// const Zstring& targetFile, +// CallbackCopyFile* callback) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked +//{ +// /* +// BackupRead() FileRead() CopyFileEx() +// -------------------------------------------- +// Attributes NO NO YES +// create time NO NO NO +// ADS YES NO YES +// Encrypted NO(silent fail) NO YES +// Compressed NO NO NO +// Sparse YES NO NO +// PERF 6% faster - +// +// Mark stream as compressed: FSCTL_SET_COMPRESSION +// compatible with: BackupRead() FileRead() +// */ +// +// //open sourceFile for reading +// HANDLE hFileIn = ::CreateFile(applyLongPathPrefix(sourceFile).c_str(), +// GENERIC_READ, +// FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //all shared modes are required to read files that are open in other applications +// 0, +// OPEN_EXISTING, +// FILE_FLAG_SEQUENTIAL_SCAN, +// NULL); +// if (hFileIn == INVALID_HANDLE_VALUE) +// { +// const DWORD lastError = ::GetLastError(); +// const std::wstring& errorMessage = _("Error opening file:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted(lastError); +// +// //if file is locked (try to) use Windows Volume Shadow Copy Service +// if (lastError == ERROR_SHARING_VIOLATION || +// lastError == ERROR_LOCK_VIOLATION) +// throw ErrorFileLocked(errorMessage); +// +// throw FileError(errorMessage); +// } +// ZEN_ON_BLOCK_EXIT(::CloseHandle, hFileIn); +// +// +// BY_HANDLE_FILE_INFORMATION infoFileIn = {}; +// if (!::GetFileInformationByHandle(hFileIn, &infoFileIn)) +// throw FileError(_("Error reading file attributes:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted()); +// +// //####################################### DST hack ########################################### +// if (dst::isFatDrive(sourceFile)) //throw() +// { +// const dst::RawTime rawTime(infoFileIn.ftCreationTime, infoFileIn.ftLastWriteTime); +// if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error) +// { +// infoFileIn.ftLastWriteTime = dst::fatDecodeUtcTime(rawTime); //return last write time in real UTC, throw (std::runtime_error) +// ::GetSystemTimeAsFileTime(&infoFileIn.ftCreationTime); //real creation time information is not available... +// } +// } +// +// if (dst::isFatDrive(targetFile)) //throw() +// { +// const dst::RawTime encodedTime = dst::fatEncodeUtcTime(infoFileIn.ftLastWriteTime); //throw (std::runtime_error) +// infoFileIn.ftCreationTime = encodedTime.createTimeRaw; +// infoFileIn.ftLastWriteTime = encodedTime.writeTimeRaw; +// } +// //####################################### DST hack ########################################### +// +// const DWORD validAttribs = FILE_ATTRIBUTE_READONLY | +// FILE_ATTRIBUTE_HIDDEN | +// FILE_ATTRIBUTE_SYSTEM | +// FILE_ATTRIBUTE_ARCHIVE | //those two are not set properly (not worse than ::CopyFileEx()) +// FILE_ATTRIBUTE_NOT_CONTENT_INDEXED | // +// FILE_ATTRIBUTE_ENCRYPTED; +// +// //create targetFile and open it for writing +// HANDLE hFileOut = ::CreateFile(applyLongPathPrefix(targetFile).c_str(), +// GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION +// FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, +// 0, +// CREATE_NEW, +// (infoFileIn.dwFileAttributes & validAttribs) | FILE_FLAG_SEQUENTIAL_SCAN, +// NULL); +// if (hFileOut == INVALID_HANDLE_VALUE) +// { +// const DWORD lastError = ::GetLastError(); +// const std::wstring& errorMessage = _("Error writing file:") + "\n\"" + targetFile + "\"" + +// "\n\n" + getLastErrorFormatted(lastError); +// +// if (lastError == ERROR_FILE_EXISTS) +// throw ErrorTargetExisting(errorMessage); +// +// if (lastError == ERROR_PATH_NOT_FOUND) +// throw ErrorTargetPathMissing(errorMessage); +// +// throw FileError(errorMessage); +// } +// Loki::ScopeGuard guardTarget = Loki::MakeGuard(&removeFile, targetFile); //transactional behavior: guard just after opening target and before managing hFileOut +// +// ZEN_ON_BLOCK_EXIT(::CloseHandle, hFileOut); +// +// +//#ifndef _MSC_VER +//#warning teste perf von GetVolumeInformationByHandleW +//#endif +// DWORD fsFlags = 0; +// if (!GetVolumeInformationByHandleW(hFileOut, //__in HANDLE hFile, +// NULL, //__out_opt LPTSTR lpVolumeNameBuffer, +// 0, //__in DWORD nVolumeNameSize, +// NULL, //__out_opt LPDWORD lpVolumeSerialNumber, +// NULL, //__out_opt LPDWORD lpMaximumComponentLength, +// &fsFlags, //__out_opt LPDWORD lpFileSystemFlags, +// NULL, //__out LPTSTR lpFileSystemNameBuffer, +// 0)) //__in DWORD nFileSystemNameSize +// throw FileError(_("Error reading file attributes:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted()); +// +// const bool sourceIsEncrypted = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; +// const bool sourceIsCompressed = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; +// const bool sourceIsSparse = (infoFileIn.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) != 0; +// +// bool targetSupportSparse = (fsFlags & FILE_SUPPORTS_SPARSE_FILES) != 0; +// bool targetSupportCompressed = (fsFlags & FILE_FILE_COMPRESSION ) != 0; +// bool targetSupportStreams = (fsFlags & FILE_NAMED_STREAMS ) != 0; +// +// +// const bool useBackupFun = !sourceIsEncrypted; //http://msdn.microsoft.com/en-us/library/aa362509(v=VS.85).aspx +// +// if (sourceIsCompressed && targetSupportCompressed) +// { +// USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; +// +// DWORD bytesReturned = 0; +// if (!DeviceIoControl(hFileOut, //handle to file or directory +// FSCTL_SET_COMPRESSION, //dwIoControlCode +// &cmpState, //input buffer +// sizeof(cmpState), //size of input buffer +// NULL, //lpOutBuffer +// 0, //OutBufferSize +// &bytesReturned, //number of bytes returned +// NULL)) //OVERLAPPED structure +// throw FileError(_("Error writing file:") + "\n\"" + targetFile + "\"" + +// "\n\n" + getLastErrorFormatted() + +// "\nFailed to write NTFS compressed attribute!"); +// } +// +// //although it seems the sparse attribute is set automatically by BackupWrite, we are required to do this manually: http://support.microsoft.com/kb/271398/en-us +// if (sourceIsSparse && targetSupportSparse) +// { +// if (useBackupFun) +// { +// DWORD bytesReturned = 0; +// if (!DeviceIoControl(hFileOut, //handle to file +// FSCTL_SET_SPARSE, //dwIoControlCode +// NULL, //input buffer +// 0, //size of input buffer +// NULL, //lpOutBuffer +// 0, //OutBufferSize +// &bytesReturned, //number of bytes returned +// NULL)) //OVERLAPPED structure +// throw FileError(_("Error writing file:") + "\n\"" + targetFile + "\"" + +// "\n\n" + getLastErrorFormatted() + +// "\nFailed to write NTFS sparse attribute!"); +// } +// } +// +// +// const DWORD BUFFER_SIZE = 512 * 1024; //512 kb seems to be a reasonable buffer size +// static boost::thread_specific_ptr<std::vector<char>> cpyBuf; +// if (!cpyBuf.get()) +// cpyBuf.reset(new std::vector<char>(BUFFER_SIZE)); //512 kb seems to be a reasonable buffer size +// std::vector<char>& buffer = *cpyBuf; +// +// struct ManageCtxt //manage context for BackupRead()/BackupWrite() +// { +// ManageCtxt() : read(NULL), write(NULL) {} +// ~ManageCtxt() +// { +// if (read != NULL) +// ::BackupRead (0, NULL, 0, NULL, true, false, &read); +// if (write != NULL) +// ::BackupWrite(0, NULL, 0, NULL, true, false, &write); +// } +// +// LPVOID read; +// LPVOID write; +// } context; +// +// //copy contents of sourceFile to targetFile +// UInt64 totalBytesTransferred; +// +// bool eof = false; +// do +// { +// DWORD bytesRead = 0; +// +// if (useBackupFun) +// { +// if (!::BackupRead(hFileIn, //__in HANDLE hFile, +// &buffer[0], //__out LPBYTE lpBuffer, +// BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead, +// &bytesRead, //__out LPDWORD lpNumberOfBytesRead, +// false, //__in BOOL bAbort, +// false, //__in BOOL bProcessSecurity, +// &context.read)) //__out LPVOID *lpContext +// throw FileError(_("Error reading file:") + "\n\"" + sourceFile + "\"" + +// "\n\n" + getLastErrorFormatted()); +// } +// else if (!::ReadFile(hFileIn, //__in HANDLE hFile, +// &buffer[0], //__out LPVOID lpBuffer, +// BUFFER_SIZE, //__in DWORD nNumberOfBytesToRead, +// &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead, +// NULL)) //__inout_opt LPOVERLAPPED lpOverlapped +// throw FileError(_("Error reading file:") + "\n\"" + sourceFile + "\"" + +// "\n\n" + getLastErrorFormatted()); +// +// if (bytesRead > BUFFER_SIZE) +// throw FileError(_("Error reading file:") + "\n\"" + sourceFile + "\"" + +// "\n\n" + "buffer overflow"); +// +// if (bytesRead < BUFFER_SIZE) +// eof = true; +// +// DWORD bytesWritten = 0; +// +// if (useBackupFun) +// { +// if (!::BackupWrite(hFileOut, //__in HANDLE hFile, +// &buffer[0], //__in LPBYTE lpBuffer, +// bytesRead, //__in DWORD nNumberOfBytesToWrite, +// &bytesWritten, //__out LPDWORD lpNumberOfBytesWritten, +// false, //__in BOOL bAbort, +// false, //__in BOOL bProcessSecurity, +// &context.write)) //__out LPVOID *lpContext +// throw FileError(_("Error writing file:") + "\n\"" + targetFile + "\"" + +// "\n\n" + getLastErrorFormatted() + " (w)"); //w -> distinguish from fopen error message! +// } +// else if (!::WriteFile(hFileOut, //__in HANDLE hFile, +// &buffer[0], //__out LPVOID lpBuffer, +// bytesRead, //__in DWORD nNumberOfBytesToWrite, +// &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten, +// NULL)) //__inout_opt LPOVERLAPPED lpOverlapped +// throw FileError(_("Error writing file:") + "\n\"" + targetFile + "\"" + +// "\n\n" + getLastErrorFormatted() + " (w)"); //w -> distinguish from fopen error message! +// +// if (bytesWritten != bytesRead) +// throw FileError(_("Error writing file:") + "\n\"" + targetFile + "\"" + "\n\n" + "incomplete write"); +// +// totalBytesTransferred += bytesRead; +// +//#ifndef _MSC_VER +//#warning totalBytesTransferred kann größer als filesize sein!! +//#endif +// +// //invoke callback method to update progress indicators +// if (callback != NULL) +// switch (callback->updateCopyStatus(totalBytesTransferred)) +// { +// case CallbackCopyFile::CONTINUE: +// break; +// +// case CallbackCopyFile::CANCEL: //a user aborted operation IS an error condition! +// throw FileError(_("Error copying file:") + "\n\"" + sourceFile + "\" ->\n\"" + +// targetFile + "\"\n\n" + _("Operation aborted!")); +// } +// } +// while (!eof); +// +// +// if (totalBytesTransferred == 0) //BackupRead silently fails reading encrypted files -> double check! +// { +// LARGE_INTEGER inputSize = {}; +// if (!::GetFileSizeEx(hFileIn, &inputSize)) +// throw FileError(_("Error reading file attributes:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted()); +// +// if (inputSize.QuadPart != 0) +// throw FileError(_("Error reading file:") + "\n\"" + sourceFile + "\"" + "\n\n" + "unknown error"); +// } +// +// //time needs to be set at the end: BackupWrite() changes file time +// if (!::SetFileTime(hFileOut, +// &infoFileIn.ftCreationTime, +// NULL, +// &infoFileIn.ftLastWriteTime)) +// throw FileError(_("Error changing modification time:") + "\n\"" + targetFile + "\"" + "\n\n" + getLastErrorFormatted()); +// +// +//#ifndef NDEBUG //dst hack: verify data written +// if (dst::isFatDrive(targetFile)) //throw() +// { +// WIN32_FILE_ATTRIBUTE_DATA debugeAttr = {}; +// assert(::GetFileAttributesEx(applyLongPathPrefix(targetFile).c_str(), //__in LPCTSTR lpFileName, +// GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, +// &debugeAttr)); //__out LPVOID lpFileInformation +// +// assert(::CompareFileTime(&debugeAttr.ftCreationTime, &infoFileIn.ftCreationTime) == 0); +// assert(::CompareFileTime(&debugeAttr.ftLastWriteTime, &infoFileIn.ftLastWriteTime) == 0); +// } +//#endif +// +// guardTarget.Dismiss(); +// +// /* +// //create test sparse file +// HANDLE hSparse = ::CreateFile(L"C:\\sparse.file", +// GENERIC_READ | GENERIC_WRITE, //read access required for FSCTL_SET_COMPRESSION +// FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, +// 0, +// CREATE_NEW, +// FILE_FLAG_SEQUENTIAL_SCAN, +// NULL); +// DWORD br = 0; +// if (!::DeviceIoControl(hSparse, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &br,NULL)) +// throw 1; +// +// LARGE_INTEGER liDistanceToMove = {}; +// liDistanceToMove.QuadPart = 1024 * 1024 * 1024; //create 5 TB sparse file +// liDistanceToMove.QuadPart *= 5 * 1024; // +// if (!::SetFilePointerEx(hSparse, liDistanceToMove, NULL, FILE_BEGIN)) +// throw 1; +// +// if (!SetEndOfFile(hSparse)) +// throw 1; +// +// FILE_ZERO_DATA_INFORMATION zeroInfo = {}; +// zeroInfo.BeyondFinalZero.QuadPart = liDistanceToMove.QuadPart; +// if (!::DeviceIoControl(hSparse, FSCTL_SET_ZERO_DATA, &zeroInfo, sizeof(zeroInfo), NULL, 0, &br, NULL)) +// throw 1; +// +// ::CloseHandle(hSparse); +// */ +//} +#endif + +#ifdef FFS_LINUX +void rawCopyStream(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackCopyFile* callback, + FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting +{ + zen::ScopeGuard guardTarget = zen::makeGuard([&]() { removeFile(targetFile); }); //transactional behavior: place guard before lifetime of FileOutput + 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 + + 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 + std::vector<char>& buffer = *cpyBuf; + + //copy contents of sourceFile to targetFile + UInt64 totalBytesTransferred; + do + { + const size_t bytesRead = fileIn.read(&buffer[0], buffer.size()); //throw FileError + + fileOut.write(&buffer[0], bytesRead); //throw FileError + + totalBytesTransferred += bytesRead; + + //invoke callback method to update progress indicators + if (callback) + callback->updateCopyStatus(totalBytesTransferred); + } + 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(_("Error reading file attributes:") + "\n\"" + sourceFile + "\"" + "\n\n" + getLastErrorFormatted()); + + if (sourceAttr) + { + sourceAttr->fileSize = UInt64(srcInfo.st_size); + sourceAttr->modificationTime = srcInfo.st_mtime; + } + + 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(_("Error changing modification time:") + "\n\"" + targetFile + "\"" + "\n\n" + getLastErrorFormatted()); + } + + guardTarget.dismiss(); //target has been created successfully! +} +#endif + + +inline +void copyFileImpl(const Zstring& sourceFile, + const Zstring& targetFile, + CallbackCopyFile* callback, + FileAttrib* sourceAttr) //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked +{ +#ifdef FFS_WIN + /* + rawCopyWinApi() rawCopyWinOptimized() + ------------------------------------- + Attributes YES YES + Filetimes YES YES + ADS YES YES + Encrypted YES YES + Compressed NO YES + Sparse NO YES + PERF - 6% faster + SAMBA, ect. YES UNKNOWN! -> issues writing ADS to Samba, issues reading from NAS, error copying files having "blocked" state... ect. damn! + */ + + rawCopyWinApi(sourceFile, targetFile, callback, sourceAttr); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked + //rawCopyWinOptimized(sourceFile, targetFile, callback); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked ->about 8% faster + +#elif defined FFS_LINUX + rawCopyStream(sourceFile, targetFile, callback, sourceAttr); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting +#endif +} +} + + +void zen::copyFile(const Zstring& sourceFile, //throw FileError, ErrorTargetPathMissing, ErrorFileLocked + const Zstring& targetFile, + bool copyFilePermissions, + bool transactionalCopy, + CallbackCopyFile* callback, + FileAttrib* sourceAttr) +{ + if (transactionalCopy) + { + Zstring temporary = targetFile + zen::TEMP_FILE_ENDING; //use temporary file until a correct date has been set + zen::ScopeGuard guardTempFile = zen::makeGuard([&]() { removeFile(temporary); }); //transactional behavior: ensure cleanup (e.g. network drop) -> ref to temporary[!] + + //raw file copy + try + { + copyFileImpl(sourceFile, temporary, callback, sourceAttr); //throw FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked + } + catch (ErrorTargetExisting&) + { + //determine non-used temp file name "first": + //using optimistic strategy: assume everything goes well, but recover on error -> minimize file accesses + temporary = createTempName(targetFile); + + //retry + copyFileImpl(sourceFile, temporary, callback, sourceAttr); //throw FileError + } + + //have target file deleted (after read access on source and target has been confirmed) => allow for almost transactional overwrite + if (callback) + callback->deleteTargetFile(targetFile); + + //rename temporary file: + //perf: this call is REALLY expensive on unbuffered volumes! ~40% performance decrease on FAT USB stick! + renameFile(temporary, targetFile); //throw FileError + + guardTempFile.dismiss(); + } + else + { + //have target file deleted + if (callback) callback->deleteTargetFile(targetFile); + + copyFileImpl(sourceFile, targetFile, callback, sourceAttr); //throw FileError: ErrorTargetPathMissing, ErrorTargetExisting, ErrorFileLocked + } + /* + Note: non-transactional file copy solves at least four problems: + -> skydrive - doesn't allow for .ffs_tmp extension and returns ERROR_INVALID_PARAMETER + -> network renaming issues + -> allow for true delete before copy to handle low disk space problems + -> higher performance on non-buffered drives (e.g. usb sticks) + */ + + //set file permissions + if (copyFilePermissions) + { + zen::ScopeGuard guardTargetFile = zen::makeGuard([&]() { removeFile(targetFile);}); + copyObjectPermissions(sourceFile, targetFile, SYMLINK_FOLLOW); //throw FileError + guardTargetFile.dismiss(); //target has been created successfully! + } +} diff --git a/zen/file_handling.h b/zen/file_handling.h new file mode 100644 index 00000000..baa51516 --- /dev/null +++ b/zen/file_handling.h @@ -0,0 +1,120 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef FILE_HANDLING_H_INCLUDED +#define FILE_HANDLING_H_INCLUDED + +#include "string.h" +#include "file_error.h" +#include "int64.h" + +namespace zen +{ +struct CallbackRemoveDir; +struct CallbackMoveFile; +struct CallbackCopyFile; + + +bool fileExists (const Zstring& filename); //throw() check whether file or symlink exists +bool dirExists (const Zstring& dirname); //throw() check whether directory or symlink exists +bool symlinkExists (const Zstring& objname); //throw() check whether a symbolic link exists +bool somethingExists(const Zstring& objname); //throw() check whether any object with this name exists + +//check whether two folders are located on the same (logical) volume +//left and right directories NEED NOT yet exist! volume prefix is sufficient! path may end with PATH_SEPARATOR +enum ResponseSame +{ + IS_SAME_YES, + IS_SAME_NO, + IS_SAME_CANT_SAY +}; +ResponseSame onSameVolume(const Zstring& folderLeft, const Zstring& folderRight); //throw() + +enum ProcSymlink +{ + SYMLINK_DIRECT, + SYMLINK_FOLLOW +}; + +Int64 getFileTime(const Zstring& filename, ProcSymlink procSl); //throw FileError +void setFileTime(const Zstring& filename, const Int64& modificationTime, ProcSymlink procSl); //throw FileError + +//symlink handling: always evaluate target +UInt64 getFilesize(const Zstring& filename); //throw FileError + +//file handling +bool removeFile(const Zstring& filename); //return "true" if file was actually deleted; throw (FileError) +void removeDirectory(const Zstring& directory, CallbackRemoveDir* callback = NULL); //throw FileError + + +//rename file or directory: no copying!!! +void renameFile(const Zstring& oldName, const Zstring& newName); //throw FileError; + +//move source to target; expectations: all super-directories of target exist +//"ignoreExisting": if target already exists, source is deleted +void moveFile(const Zstring& sourceFile, const Zstring& targetFile, bool ignoreExisting, CallbackMoveFile* callback); //throw FileError; + +//move source to target including subdirectories +//"ignoreExisting": existing directories and files will be enriched +void moveDirectory(const Zstring& sourceDir, const Zstring& targetDir, bool ignoreExisting, CallbackMoveFile* callback); //throw FileError; + +//creates superdirectories automatically: +void createDirectory(const Zstring& directory, const Zstring& templateDir, bool copyFilePermissions); //throw FileError; +void createDirectory(const Zstring& directory); //throw FileError; -> function overload avoids default parameter ambiguity issues! + +struct FileAttrib +{ + UInt64 fileSize; + Int64 modificationTime; //size_t UTC compatible +}; + +void copyFile(const Zstring& sourceFile, //throw FileError, ErrorTargetPathMissing, ErrorFileLocked (Windows-only) + const Zstring& targetFile, //symlink handling: dereference source + bool copyFilePermissions, + bool transactionalCopy, + CallbackCopyFile* callback, //may be NULL + FileAttrib* sourceAttr = NULL); //return current attributes at the time of copy +//Note: it MAY happen that copyFile() leaves temp files behind, e.g. temporary network drop. +// => clean them up at an appropriate time (automatically set sync directions to delete them). They have the following ending: +const Zstring TEMP_FILE_ENDING = Zstr(".ffs_tmp"); + +void copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions); //throw FileError + + + + +//----------- callbacks --------------- +struct CallbackRemoveDir +{ + virtual ~CallbackRemoveDir() {} + virtual void notifyFileDeletion(const Zstring& filename) = 0; + virtual void notifyDirDeletion(const Zstring& dirname) = 0; +}; + + +struct CallbackCopyFile //callback functionality +{ + virtual ~CallbackCopyFile() {} + + //if target is existing user needs to implement deletion: copyFile() NEVER deletes target if already existing! + //if transactionalCopy == true, full read access on source had been proven at this point, so it's safe to delete it. + virtual void deleteTargetFile(const Zstring& targetFile) = 0; //may throw exceptions + + //may throw: + //Linux: unconditionally + //Windows: first exception is swallowed, requestUiRefresh() is then called again where it should throw again and exception will propagate as expected + virtual void updateCopyStatus(UInt64 totalBytesTransferred) = 0; +}; + + +struct CallbackMoveFile //callback functionality +{ + virtual ~CallbackMoveFile() {} + virtual void requestUiRefresh(const Zstring& currentObject) = 0; //see CallbackCopyFile! +}; +} + +#endif //FILE_HANDLING_H_INCLUDED diff --git a/zen/file_id.cpp b/zen/file_id.cpp new file mode 100644 index 00000000..c9f422ac --- /dev/null +++ b/zen/file_id.cpp @@ -0,0 +1,67 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#include "file_id.h" +#include "file_id_internal.h" + +#ifdef FFS_WIN +#include "win.h" //includes "windows.h" +#include "long_path_prefix.h" +#include "scope_guard.h" + +#elif defined FFS_LINUX +#include <sys/stat.h> +#endif + + +std::string zen::getFileID(const Zstring& filename) +{ +#ifdef FFS_WIN + //WARNING: CreateFile() is SLOW, while GetFileInformationByHandle() is cheap! + //http://msdn.microsoft.com/en-us/library/aa363788(VS.85).aspx + + //privilege SE_BACKUP_NAME doesn't seem to be required here at all + + const HANDLE hFile = ::CreateFile(zen::applyLongPathPrefix(filename).c_str(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, //FILE_FLAG_BACKUP_SEMANTICS needed to open a directory + NULL); + if (hFile != INVALID_HANDLE_VALUE) + { + ZEN_ON_BLOCK_EXIT(::CloseHandle(hFile)); + + BY_HANDLE_FILE_INFORMATION fileInfo = {}; + if (::GetFileInformationByHandle(hFile, &fileInfo)) + return extractFileID(fileInfo); + } + +#elif defined FFS_LINUX + struct ::stat fileInfo = {}; + if (::lstat(filename.c_str(), &fileInfo) == 0) //lstat() does not follow symlinks + return extractFileID(fileInfo); +#endif + + assert(false); + return std::string(); +} + + +bool zen::samePhysicalFile(const Zstring& file1, const Zstring& file2) +{ + if (EqualFilename()(file1, file2)) //quick check + return true; + + const std::string id1 = getFileID(file1); + const std::string id2 = getFileID(file2); + + if (id1.empty() || id2.empty()) + return false; + + return id1 == id2; +} diff --git a/zen/file_id.h b/zen/file_id.h new file mode 100644 index 00000000..f015569d --- /dev/null +++ b/zen/file_id.h @@ -0,0 +1,27 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef FILEID_H_INCLUDED +#define FILEID_H_INCLUDED + +#include "zstring.h" +#include <string> + +//unique file identifier + +namespace zen +{ +//get unique file id (symbolic link handling: opens the link!!!) +//returns empty string on error! +std::string getFileID(const Zstring& filename); + +//test whether two distinct paths point to the same file or directory: +// true: paths point to same files/dirs +// false: error occurred OR point to different files/dirs +bool samePhysicalFile(const Zstring& file1, const Zstring& file2); +} + +#endif // FILEID_H_INCLUDED diff --git a/zen/file_id_internal.h b/zen/file_id_internal.h new file mode 100644 index 00000000..492d8432 --- /dev/null +++ b/zen/file_id_internal.h @@ -0,0 +1,48 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef FILE_ID_INTERNAL_HEADER_013287632486321493 +#define FILE_ID_INTERNAL_HEADER_013287632486321493 + +#include <string> + +#ifdef FFS_WIN +#include "win.h" //includes "windows.h" + +#elif defined FFS_LINUX +#include <sys/stat.h> +#endif + +namespace +{ +template <class T> inline +std::string numberToBytes(T number) +{ + const char* rawBegin = reinterpret_cast<const char*>(&number); + return std::string(rawBegin, rawBegin + sizeof(number)); +} + +#ifdef FFS_WIN +inline +std::string extractFileID(const BY_HANDLE_FILE_INFORMATION& fileInfo) +{ + return numberToBytes(fileInfo.dwVolumeSerialNumber) + + numberToBytes(fileInfo.nFileIndexHigh) + + numberToBytes(fileInfo.nFileIndexLow); +} +#elif defined FFS_LINUX +inline +std::string extractFileID(const struct stat& fileInfo) +{ + return numberToBytes(fileInfo.st_dev) + + numberToBytes(fileInfo.st_ino); +} +#endif + +} + + +#endif //FILE_ID_INTERNAL_HEADER_013287632486321493 diff --git a/zen/file_io.cpp b/zen/file_io.cpp new file mode 100644 index 00000000..6b3a5214 --- /dev/null +++ b/zen/file_io.cpp @@ -0,0 +1,217 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#include "file_io.h" + +#ifdef FFS_WIN +#include "long_path_prefix.h" +#elif defined FFS_LINUX +#include <cerrno> +#endif + +using namespace zen; + + +FileInput::FileInput(FileHandle handle, const Zstring& filename) : + eofReached(false), + fileHandle(handle), + filename_(filename) {} + + +FileInput::FileInput(const Zstring& filename) : //throw FileError, ErrorNotExisting + eofReached(false), + filename_(filename) +{ +#ifdef FFS_WIN + fileHandle = ::CreateFile(zen::applyLongPathPrefix(filename).c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //all shared modes are required to read open files that are shared by other applications + 0, + OPEN_EXISTING, + FILE_FLAG_SEQUENTIAL_SCAN, + /* possible values: (Reference http://msdn.microsoft.com/en-us/library/aa363858(VS.85).aspx#caching_behavior) + FILE_FLAG_NO_BUFFERING + FILE_FLAG_RANDOM_ACCESS + FILE_FLAG_SEQUENTIAL_SCAN + + tests on Win7 x64 show that FILE_FLAG_SEQUENTIAL_SCAN provides best performance for binary comparison in all cases: + - comparing different physical disks (DVD <-> HDD and HDD <-> HDD) + - even on same physical disk! (HDD <-> HDD) + - independent from client buffer size! + + tests on XP show that FILE_FLAG_SEQUENTIAL_SCAN provides best performance for binary comparison when + - comparing different physical disks (DVD <-> HDD) + + while FILE_FLAG_RANDOM_ACCESS offers best performance for + - same physical disk (HDD <-> HDD) + + Problem: bad XP implementation of prefetch makes flag FILE_FLAG_SEQUENTIAL_SCAN effectively load two files at once from one drive + swapping every 64 kB (or similar). File access times explode! + => For XP it is critical to use FILE_FLAG_RANDOM_ACCESS (to disable prefetch) if reading two files on same disk and + FILE_FLAG_SEQUENTIAL_SCAN when reading from different disk (e.g. massive performance improvement compared to random access for DVD <-> HDD!) + => there is no compromise that satisfies all cases! (on XP) + + for FFS most comparisons are probably between different disks => let's use FILE_FLAG_SEQUENTIAL_SCAN + */ + NULL); + if (fileHandle == INVALID_HANDLE_VALUE) + { + const DWORD lastError = ::GetLastError(); + + std::wstring errorMessage = _("Error opening file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted(lastError); + + if (lastError == ERROR_FILE_NOT_FOUND || + lastError == ERROR_PATH_NOT_FOUND || + lastError == ERROR_BAD_NETPATH || + lastError == ERROR_NETNAME_DELETED) + throw ErrorNotExisting(errorMessage); + + throw FileError(errorMessage); + } + +#elif defined FFS_LINUX + fileHandle = ::fopen(filename.c_str(), "r,type=record,noseek"); //utilize UTF-8 filename + if (fileHandle == NULL) + { + const int lastError = errno; + + std::wstring errorMessage = _("Error opening file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted(lastError); + + if (lastError == ENOENT) + throw ErrorNotExisting(errorMessage); + + throw FileError(errorMessage); + } +#endif +} + + +FileInput::~FileInput() +{ +#ifdef FFS_WIN + ::CloseHandle(fileHandle); +#elif defined FFS_LINUX + ::fclose(fileHandle); //NEVER allow passing NULL to fclose! -> crash!; fileHandle != NULL in this context! +#endif +} + + +size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number of bytes read; throw (FileError) +{ +#ifdef FFS_WIN + DWORD bytesRead = 0; + if (!::ReadFile(fileHandle, //__in HANDLE hFile, + buffer, //__out LPVOID lpBuffer, + static_cast<DWORD>(bytesToRead), //__in DWORD nNumberOfBytesToRead, + &bytesRead, //__out_opt LPDWORD lpNumberOfBytesRead, + NULL)) //__inout_opt LPOVERLAPPED lpOverlapped +#elif defined FFS_LINUX + const size_t bytesRead = ::fread(buffer, 1, bytesToRead, fileHandle); + if (::ferror(fileHandle) != 0) +#endif + throw FileError(_("Error reading file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted() + " (r)"); + +#ifdef FFS_WIN + if (bytesRead < bytesToRead) //falsify only! +#elif defined FFS_LINUX + if (::feof(fileHandle) != 0) +#endif + eofReached = true; + + if (bytesRead > bytesToRead) + throw FileError(_("Error reading file:") + "\n\"" + filename_ + "\"" + "\n\n" + "buffer overflow"); + + return bytesRead; +} + + +bool FileInput::eof() //end of file reached +{ + return eofReached; +} + + +FileOutput::FileOutput(FileHandle handle, const Zstring& filename) : fileHandle(handle), filename_(filename) {} + + +FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting + 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_READ | FILE_SHARE_DELETE, //note: FILE_SHARE_DELETE is required to rename file while handle is open! + 0, + access == ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, + NULL); + if (fileHandle == INVALID_HANDLE_VALUE) + { + const DWORD lastError = ::GetLastError(); + std::wstring errorMessage = _("Error writing file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted(lastError); + + if (lastError == ERROR_FILE_EXISTS) + throw ErrorTargetExisting(errorMessage); + + if (lastError == ERROR_PATH_NOT_FOUND) + throw ErrorTargetPathMissing(errorMessage); + + throw FileError(errorMessage); + } + +#elif defined FFS_LINUX + 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 == NULL) + { + const int lastError = errno; + std::wstring errorMessage = _("Error writing file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted(lastError); + if (lastError == EEXIST) + throw ErrorTargetExisting(errorMessage); + + if (lastError == ENOENT) + throw ErrorTargetPathMissing(errorMessage); + + throw FileError(errorMessage); + } +#endif +} + + +FileOutput::~FileOutput() +{ +#ifdef FFS_WIN + ::CloseHandle(fileHandle); +#elif defined FFS_LINUX + ::fclose(fileHandle); //NEVER allow passing NULL to fclose! -> crash! +#endif +} + + +void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileError +{ +#ifdef FFS_WIN + DWORD bytesWritten = 0; + if (!::WriteFile(fileHandle, //__in HANDLE hFile, + buffer, //__out LPVOID lpBuffer, + static_cast<DWORD>(bytesToWrite), //__in DWORD nNumberOfBytesToWrite, + &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten, + NULL)) //__inout_opt LPOVERLAPPED lpOverlapped +#elif defined FFS_LINUX + const size_t bytesWritten = ::fwrite(buffer, 1, bytesToWrite, fileHandle); + if (::ferror(fileHandle) != 0) +#endif + throw FileError(_("Error writing file:") + "\n\"" + filename_ + "\"" + "\n\n" + zen::getLastErrorFormatted() + " (w)"); //w -> distinguish from fopen error message! + + if (bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes! + throw FileError(_("Error writing file:") + "\n\"" + filename_ + "\"" + "\n\n" + "incomplete write"); +} diff --git a/zen/file_io.h b/zen/file_io.h new file mode 100644 index 00000000..26964ae8 --- /dev/null +++ b/zen/file_io.h @@ -0,0 +1,74 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef FILEIO_H_INCLUDED +#define FILEIO_H_INCLUDED + +#ifdef FFS_WIN +#include "win.h" //includes "windows.h" + +#elif defined FFS_LINUX +#include <cstdio> +#endif + +#include "string.h" +#include "file_error.h" + +namespace zen +{ +//file IO optimized for sequential read/write accesses + better error reporting + long path support (following symlinks) + +#ifdef FFS_WIN +typedef HANDLE FileHandle; +#elif defined FFS_LINUX +typedef FILE* FileHandle; +#endif + +class FileInput +{ +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(); //end of file reached + +private: + FileInput(const FileInput&); + FileInput& operator=(const FileInput&); + + bool eofReached; + FileHandle fileHandle; + const Zstring filename_; +}; + + +class FileOutput +{ +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 + +private: + FileOutput(const FileOutput&); + FileOutput& operator=(const FileOutput&); + + FileHandle fileHandle; + const Zstring filename_; +}; + +} + +#endif // FILEIO_H_INCLUDED diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp new file mode 100644 index 00000000..44b9d184 --- /dev/null +++ b/zen/file_traverser.cpp @@ -0,0 +1,611 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#include "file_traverser.h" +#include "last_error.h" +#include "assert_static.h" +#include "symlink_target.h" + +#ifdef FFS_WIN +#include "win.h" //includes "windows.h" +#include "long_path_prefix.h" +#include "dst_hack.h" +#include "file_update_handle.h" +//#include "dll_loader.h" +//#include "FindFilePlus/find_file_plus.h" + +#elif defined FFS_LINUX +#include <sys/stat.h> +#include <dirent.h> +#include <cerrno> +#endif + +using namespace zen; + + +namespace +{ +//implement "retry" in a generic way: + +//returns "true" if "cmd" was invoked successfully, "false" if error occured and was ignored +template <class Command> inline //function object with bool operator()(std::wstring& errorMsg), returns "true" on success, "false" on error and writes "errorMsg" in this case +bool tryReportingError(Command cmd, zen::TraverseCallback& callback) +{ + for (;;) + { + std::wstring errorMsg; + if (cmd(errorMsg)) + return true; + + switch (callback.onError(errorMsg)) + { + case TraverseCallback::TRAV_ERROR_RETRY: + break; + case TraverseCallback::TRAV_ERROR_IGNORE: + return false; + default: + assert(false); + break; + } + } +} + + +#ifdef FFS_WIN +inline +bool setWin32FileInformationFromSymlink(const Zstring& linkName, zen::TraverseCallback::FileInfo& output) +{ + //open handle to target of symbolic link + HANDLE hFile = ::CreateFile(zen::applyLongPathPrefix(linkName).c_str(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (hFile == INVALID_HANDLE_VALUE) + return false; + ZEN_ON_BLOCK_EXIT(::CloseHandle(hFile)); + + BY_HANDLE_FILE_INFORMATION fileInfoByHandle = {}; + if (!::GetFileInformationByHandle(hFile, &fileInfoByHandle)) + return false; + + //write output + output.lastWriteTimeRaw = toTimeT(fileInfoByHandle.ftLastWriteTime); + output.fileSize = zen::UInt64(fileInfoByHandle.nFileSizeLow, fileInfoByHandle.nFileSizeHigh); + return true; +} + +/* +warn_static("finish") + DllFun<findplus::OpenDirFunc> openDir (findplus::getDllName(), findplus::openDirFuncName); + DllFun<findplus::ReadDirFunc> readDir (findplus::getDllName(), findplus::readDirFuncName); + DllFun<findplus::CloseDirFunc> closeDir(findplus::getDllName(), findplus::closeDirFuncName); + -> thread safety! +*/ +#endif +} + + +class DirTraverser +{ +public: + DirTraverser(const Zstring& baseDirectory, bool followSymlinks, zen::TraverseCallback& sink, zen::DstHackCallback* dstCallback) : +#ifdef FFS_WIN + isFatFileSystem(dst::isFatDrive(baseDirectory)), +#endif + followSymlinks_(followSymlinks) + { +#ifdef FFS_WIN + //format base directory name + const Zstring& directoryFormatted = baseDirectory; + +#elif defined FFS_LINUX + const Zstring directoryFormatted = //remove trailing slash + baseDirectory.size() > 1 && endsWith(baseDirectory, FILE_NAME_SEPARATOR) ? //exception: allow '/' + beforeLast(baseDirectory, FILE_NAME_SEPARATOR) : + baseDirectory; +#endif + + //traverse directories + traverse(directoryFormatted, sink, 0); + + //apply daylight saving time hack AFTER file traversing, to give separate feedback to user +#ifdef FFS_WIN + if (isFatFileSystem && dstCallback) + applyDstHack(*dstCallback); +#endif + } + +private: + DirTraverser(const DirTraverser&); + DirTraverser& operator=(const DirTraverser&); + + void traverse(const Zstring& directory, zen::TraverseCallback& sink, int level) + { + tryReportingError([&](std::wstring& errorMsg) -> bool + { + if (level == 100) //notify endless recursion + { + errorMsg = _("Endless loop when traversing directory:") + "\n\"" + directory + "\""; + return false; + } + return true; + }, sink); + +#ifdef FFS_WIN + /* + //ensure directoryPf ends with backslash + const Zstring& directoryPf = directory.EndsWith(FILE_NAME_SEPARATOR) ? + directory : + directory + FILE_NAME_SEPARATOR; + + FindHandle searchHandle = NULL; + tryReportingError([&](std::wstring& errorMsg) -> bool + { + searchHandle = this->openDir(applyLongPathPrefix(directoryPf).c_str()); + //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH + + if (searchHandle == NULL) + { + //const DWORD lastError = ::GetLastError(); + //if (lastError == ERROR_FILE_NOT_FOUND) -> actually NOT okay, even for an empty directory this should not occur (., ..) + //return true; //fine: empty directory + + //else: we have a problem... report it: + errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted(); + return false; + } + return true; + }, sink); + + if (searchHandle == NULL) + return; //empty dir or ignore error + ZEN_ON_BLOCK_EXIT(this->closeDir(searchHandle)); + + FileInformation fileInfo = {}; + for (;;) + { + bool moreData = false; + tryReportingError([&](std::wstring& errorMsg) -> bool + { + if (!this->readDir(searchHandle, fileInfo)) + { + if (::GetLastError() == ERROR_NO_MORE_FILES) //this is fine + return true; + + //else we have a problem... report it: + errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted(); + return false; + } + + moreData = true; + return true; + }, sink); + if (!moreData) //no more items or ignore error + return; + + + //don't return "." and ".." + const Zchar* const shortName = fileInfo.shortName; + if (shortName[0] == L'.' && + (shortName[1] == L'\0' || (shortName[1] == L'.' && shortName[2] == L'\0'))) + continue; + + const Zstring& fullName = directoryPf + shortName; + + const bool isSymbolicLink = (fileInfo.fileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; + + if (isSymbolicLink && !followSymlinks_) //evaluate symlink directly + { + TraverseCallback::SymlinkInfo details; + try + { + details.targetPath = getSymlinkRawTargetString(fullName); //throw FileError + } + catch (FileError& e) + { + (void)e; + #ifndef NDEBUG //show broken symlink / access errors in debug build! + sink.onError(e.msg()); + #endif + } + + details.lastWriteTimeRaw = toTimeT(fileInfo.lastWriteTime); + details.dirLink = (fileInfo.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; //directory symlinks have this flag on Windows + sink.onSymlink(shortName, fullName, details); + } + else if (fileInfo.fileAttributes & FILE_ATTRIBUTE_DIRECTORY) //a directory... or symlink that needs to be followed (for directory symlinks this flag is set too!) + { + const TraverseCallback::ReturnValDir rv = sink.onDir(shortName, fullName); + switch (rv.returnCode) + { + case TraverseCallback::ReturnValDir::TRAVERSING_DIR_IGNORE: + break; + + case TraverseCallback::ReturnValDir::TRAVERSING_DIR_CONTINUE: + traverse<followSymlinks_>(fullName, *rv.subDirCb, level + 1); + break; + } + } + else //a file or symlink that is followed... + { + TraverseCallback::FileInfo details; + + if (isSymbolicLink) //dereference symlinks! + { + if (!setWin32FileInformationFromSymlink(fullName, details)) + { + //broken symlink... + details.lastWriteTimeRaw = 0; //we are not interested in the modification time of the link + details.fileSize = 0U; + } + } + else + { + //####################################### DST hack ########################################### + if (isFatFileSystem) + { + const dst::RawTime rawTime(fileInfo.creationTime, fileInfo.lastWriteTime); + + if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error) + fileInfo.lastWriteTime = dst::fatDecodeUtcTime(rawTime); //return real UTC time; throw (std::runtime_error) + else + markForDstHack.push_back(std::make_pair(fullName, fileInfo.lastWriteTime)); + } + //####################################### DST hack ########################################### + + details.lastWriteTimeRaw = toTimeT(fileInfo.lastWriteTime); + details.fileSize = fileInfo.fileSize.QuadPart; + } + + sink.onFile(shortName, fullName, details); + } + } + */ + + + + //ensure directoryPf ends with backslash + const Zstring& directoryPf = endsWith(directory, FILE_NAME_SEPARATOR) ? + directory : + directory + FILE_NAME_SEPARATOR; + WIN32_FIND_DATA fileInfo = {}; + + HANDLE searchHandle = INVALID_HANDLE_VALUE; + tryReportingError([&](std::wstring& errorMsg) -> bool + { + searchHandle = ::FindFirstFile(applyLongPathPrefix(directoryPf + L'*').c_str(), &fileInfo); + //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH + + if (searchHandle == INVALID_HANDLE_VALUE) + { + //const DWORD lastError = ::GetLastError(); + //if (lastError == ERROR_FILE_NOT_FOUND) -> actually NOT okay, even for an empty directory this should not occur (., ..) + //return true; //fine: empty directory + + //else: we have a problem... report it: + errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted(); + return false; + } + return true; + }, sink); + + if (searchHandle == INVALID_HANDLE_VALUE) + return; //empty dir or ignore error + ZEN_ON_BLOCK_EXIT(::FindClose(searchHandle)); + + do + { + //don't return "." and ".." + const Zchar* const shortName = fileInfo.cFileName; + if (shortName[0] == L'.' && + (shortName[1] == L'\0' || (shortName[1] == L'.' && shortName[2] == L'\0'))) + continue; + + const Zstring& fullName = directoryPf + shortName; + + const bool isSymbolicLink = (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0; + + if (isSymbolicLink && !followSymlinks_) //evaluate symlink directly + { + TraverseCallback::SymlinkInfo details; + try + { + details.targetPath = getSymlinkRawTargetString(fullName); //throw FileError + } + catch (FileError& e) + { + (void)e; +#ifndef NDEBUG //show broken symlink / access errors in debug build! + sink.onError(e.msg()); +#endif + } + + details.lastWriteTimeRaw = toTimeT(fileInfo.ftLastWriteTime); + details.dirLink = (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; //directory symlinks have this flag on Windows + sink.onSymlink(shortName, fullName, details); + } + else if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //a directory... or symlink that needs to be followed (for directory symlinks this flag is set too!) + { + const TraverseCallback::ReturnValDir rv = sink.onDir(shortName, fullName); + switch (rv.returnCode) + { + case TraverseCallback::ReturnValDir::TRAVERSING_DIR_IGNORE: + break; + + case TraverseCallback::ReturnValDir::TRAVERSING_DIR_CONTINUE: + traverse(fullName, *rv.subDirCb, level + 1); + break; + } + } + else //a file or symlink that is followed... + { + TraverseCallback::FileInfo details; + + if (isSymbolicLink) //dereference symlinks! + { + if (!setWin32FileInformationFromSymlink(fullName, details)) + { + //broken symlink... + details.lastWriteTimeRaw = 0; //we are not interested in the modification time of the link + details.fileSize = 0U; + } + } + else + { + //####################################### DST hack ########################################### + if (isFatFileSystem) + { + const dst::RawTime rawTime(fileInfo.ftCreationTime, fileInfo.ftLastWriteTime); + + if (dst::fatHasUtcEncoded(rawTime)) //throw (std::runtime_error) + fileInfo.ftLastWriteTime = dst::fatDecodeUtcTime(rawTime); //return real UTC time; throw (std::runtime_error) + else + markForDstHack.push_back(std::make_pair(fullName, fileInfo.ftLastWriteTime)); + } + //####################################### DST hack ########################################### + details.lastWriteTimeRaw = toTimeT(fileInfo.ftLastWriteTime); + details.fileSize = zen::UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); + } + + sink.onFile(shortName, fullName, details); + } + } + while ([&]() -> bool + { + bool moreData = false; + + tryReportingError([&](std::wstring& errorMsg) -> bool + { + if (!::FindNextFile(searchHandle, // handle to search + &fileInfo)) // pointer to structure for data on found file + { + if (::GetLastError() == ERROR_NO_MORE_FILES) //this is fine + return true; + + //else we have a problem... report it: + errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted(); + return false; + } + + moreData = true; + return true; + }, sink); + + return moreData; + }()); + +#elif defined FFS_LINUX + DIR* dirObj = NULL; + if (!tryReportingError([&](std::wstring& errorMsg) -> bool + { + dirObj = ::opendir(directory.c_str()); //directory must NOT end with path separator, except "/" + if (dirObj == NULL) + { + errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted(); + return false; + } + return true; + }, sink)) + return; + ZEN_ON_BLOCK_EXIT(::closedir(dirObj)); //never close NULL handles! -> crash + + while (true) + { + struct dirent* dirEntry = NULL; + tryReportingError([&](std::wstring& errorMsg) -> bool + { + errno = 0; //set errno to 0 as unfortunately this isn't done when readdir() returns NULL because it can't find any files + dirEntry = ::readdir(dirObj); + if (dirEntry == NULL) + { + if (errno == 0) + return true; //everything okay, not more items + + //else: we have a problem... report it: + errorMsg = _("Error traversing directory:") + "\n\"" + directory + "\"" + "\n\n" + zen::getLastErrorFormatted(); + return false; + } + return true; + }, sink); + if (dirEntry == NULL) //no more items or ignore error + return; + + + //don't return "." and ".." + const char* const shortName = dirEntry->d_name; + if (shortName[0] == '.' && + (shortName[1] == '\0' || (shortName[1] == '.' && shortName[2] == '\0'))) + continue; + + const Zstring& fullName = endsWith(directory, FILE_NAME_SEPARATOR) ? //e.g. "/" + directory + shortName : + directory + FILE_NAME_SEPARATOR + shortName; + + struct stat fileInfo = {}; + + if (!tryReportingError([&](std::wstring& errorMsg) -> bool + { + if (::lstat(fullName.c_str(), &fileInfo) != 0) //lstat() does not resolve symlinks + { + errorMsg = _("Error reading file attributes:") + "\n\"" + fullName + "\"" + "\n\n" + zen::getLastErrorFormatted(); + return false; + } + return true; + }, sink)) + continue; //ignore error: skip file + + const bool isSymbolicLink = S_ISLNK(fileInfo.st_mode); + + if (isSymbolicLink) + { + if (followSymlinks_) //on Linux Symlinks need to be followed to evaluate whether they point to a file or directory + { + if (::stat(fullName.c_str(), &fileInfo) != 0) //stat() resolves symlinks + { + sink.onFile(shortName, fullName, TraverseCallback::FileInfo()); //report broken symlink as file! + continue; + } + } + else //evaluate symlink directly + { + TraverseCallback::SymlinkInfo details; + try + { + details.targetPath = getSymlinkRawTargetString(fullName); //throw FileError + } + catch (FileError& e) + { +#ifndef NDEBUG //show broken symlink / access errors in debug build! + sink.onError(e.msg()); +#endif + } + + details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second + details.dirLink = ::stat(fullName.c_str(), &fileInfo) == 0 && S_ISDIR(fileInfo.st_mode); //S_ISDIR and S_ISLNK are mutually exclusive on Linux => need to follow link + sink.onSymlink(shortName, fullName, details); + continue; + } + } + + //fileInfo contains dereferenced data in any case from here on + + if (S_ISDIR(fileInfo.st_mode)) //a directory... cannot be a symlink on Linux in this case + { + const TraverseCallback::ReturnValDir rv = sink.onDir(shortName, fullName); + switch (rv.returnCode) + { + case TraverseCallback::ReturnValDir::TRAVERSING_DIR_IGNORE: + break; + + case TraverseCallback::ReturnValDir::TRAVERSING_DIR_CONTINUE: + traverse(fullName, *rv.subDirCb, level + 1); + break; + } + } + else //a file... (or symlink; pathological!) + { + TraverseCallback::FileInfo details; + details.lastWriteTimeRaw = fileInfo.st_mtime; //UTC time(ANSI C format); unit: 1 second + details.fileSize = zen::UInt64(fileInfo.st_size); + + sink.onFile(shortName, fullName, details); + } + } +#endif + } + + +#ifdef FFS_WIN + //####################################### DST hack ########################################### + void applyDstHack(zen::DstHackCallback& dstCallback) + { + int failedAttempts = 0; + int filesToValidate = 50; //don't let data verification become a performance issue + + for (FilenameTimeList::const_iterator i = markForDstHack.begin(); i != markForDstHack.end(); ++i) + { + if (failedAttempts >= 10) //some cloud storages don't support changing creation/modification times => don't waste (a lot of) time trying to + return; + + dstCallback.requestUiRefresh(i->first); + + const dst::RawTime encodedTime = dst::fatEncodeUtcTime(i->second); //throw (std::runtime_error) + { + //may need to remove the readonly-attribute (e.g. FAT usb drives) + FileUpdateHandle updateHandle(i->first, [ = ]() + { + return ::CreateFile(zen::applyLongPathPrefix(i->first).c_str(), + GENERIC_READ | GENERIC_WRITE, //use both when writing over network, see comment in file_io.cpp + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + }); + if (updateHandle.get() == INVALID_HANDLE_VALUE) + { + ++failedAttempts; + assert(false); //don't throw exceptions due to dst hack here + continue; + } + + if (!::SetFileTime(updateHandle.get(), + &encodedTime.createTimeRaw, + NULL, + &encodedTime.writeTimeRaw)) + { + ++failedAttempts; + assert(false); //don't throw exceptions due to dst hack here + continue; + } + } + + //even at this point it's not sure whether data was written correctly, again cloud storages tend to lie about success status + if (filesToValidate > 0) + { + --filesToValidate; //don't change during check! + + //dst hack: verify data written; attention: this check may fail for "sync.ffs_lock" + WIN32_FILE_ATTRIBUTE_DATA debugeAttr = {}; + ::GetFileAttributesEx(zen::applyLongPathPrefix(i->first).c_str(), //__in LPCTSTR lpFileName, + GetFileExInfoStandard, //__in GET_FILEEX_INFO_LEVELS fInfoLevelId, + &debugeAttr); //__out LPVOID lpFileInformation + + if (::CompareFileTime(&debugeAttr.ftCreationTime, &encodedTime.createTimeRaw) != 0 || + ::CompareFileTime(&debugeAttr.ftLastWriteTime, &encodedTime.writeTimeRaw) != 0) + { + ++failedAttempts; + assert(false); //don't throw exceptions due to dst hack here + continue; + } + } + } + } + + const bool isFatFileSystem; + typedef std::vector<std::pair<Zstring, FILETIME> > FilenameTimeList; + FilenameTimeList markForDstHack; + //####################################### DST hack ########################################### +#endif + const bool followSymlinks_; +}; + + +void zen::traverseFolder(const Zstring& directory, bool followSymlinks, TraverseCallback& sink, DstHackCallback* dstCallback) +{ +#ifdef FFS_WIN + try //traversing certain folders with restricted permissions requires this privilege! (but copying these files may still fail) + { + zen::Privileges::getInstance().ensureActive(SE_BACKUP_NAME); //throw FileError + } + catch (...) {} //don't cause issues in user mode +#endif + + DirTraverser(directory, followSymlinks, sink, dstCallback); +} diff --git a/zen/file_traverser.h b/zen/file_traverser.h new file mode 100644 index 00000000..4dbed9f9 --- /dev/null +++ b/zen/file_traverser.h @@ -0,0 +1,86 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef FILETRAVERSER_H_INCLUDED +#define FILETRAVERSER_H_INCLUDED + +#include "zstring.h" +#include "type_tools.h" +#include "int64.h" + +//advanced file traverser returning metadata and hierarchical information on files and directories + +namespace zen +{ +class TraverseCallback +{ +public: + virtual ~TraverseCallback() {} + + struct FileInfo + { + UInt64 fileSize; //unit: bytes! + Int64 lastWriteTimeRaw; //number of seconds since Jan. 1st 1970 UTC + }; + + struct SymlinkInfo + { + Int64 lastWriteTimeRaw; //number of seconds since Jan. 1st 1970 UTC + Zstring targetPath; //may be empty if something goes wrong + bool dirLink; //"true": point to dir; "false": point to file (or broken Link on Linux) + }; + + struct ReturnValDir + { + enum ReturnValueEnh + { + TRAVERSING_DIR_IGNORE, + TRAVERSING_DIR_CONTINUE + }; + + ReturnValDir(Int2Type<TRAVERSING_DIR_IGNORE>) : returnCode(TRAVERSING_DIR_IGNORE), subDirCb(NULL) {} + ReturnValDir(Int2Type<TRAVERSING_DIR_CONTINUE>, TraverseCallback& subDirCallback) : returnCode(TRAVERSING_DIR_CONTINUE), subDirCb(&subDirCallback) {} + + ReturnValueEnh returnCode; + TraverseCallback* subDirCb; + }; + + enum HandleError + { + TRAV_ERROR_RETRY, + TRAV_ERROR_IGNORE + }; + + //overwrite these virtual methods + virtual void onFile (const Zchar* shortName, const Zstring& fullName, const FileInfo& details) = 0; + virtual void onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) = 0; + virtual ReturnValDir onDir (const Zchar* shortName, const Zstring& fullName) = 0; + virtual HandleError onError (const std::wstring& errorText) = 0; +}; + + +#ifdef FFS_WIN +struct DstHackCallback +{ + virtual ~DstHackCallback() {} + virtual void requestUiRefresh(const Zstring& filename) = 0; //applying DST hack imposes significant one-time performance drawback => callback to inform user +}; +#else +struct DstHackCallback; //DST hack not required on Linux +#endif + +//custom traverser with detail information about files +//directory may end with PATH_SEPARATOR +void traverseFolder(const Zstring& directory, //throw(); + bool followSymlinks, + TraverseCallback& sink, + DstHackCallback* dstCallback = NULL); //apply DST hack if callback is supplied +//followSymlinks: +//"true": Symlinks dereferenced and reported via onFile() and onDir() => onSymlink not used! +//"false": Symlinks directly reported via onSymlink(), directory symlinks are not followed +} + +#endif // FILETRAVERSER_H_INCLUDED diff --git a/zen/file_update_handle.h b/zen/file_update_handle.h new file mode 100644 index 00000000..aa9edebd --- /dev/null +++ b/zen/file_update_handle.h @@ -0,0 +1,67 @@ +#ifndef FILE_UPDATE_HANDLE_H_INCLUDED +#define FILE_UPDATE_HANDLE_H_INCLUDED + +#include "win.h" //includes "windows.h" +#include "long_path_prefix.h" + +namespace +{ +//manage file handle to update existing files (temporarily resetting read-only if necessary) +//CreateFileCmd: lambda directly returning non-owned file handle from ::CreateFile() +class FileUpdateHandle +{ +public: + template <class CreateFileCmd> + FileUpdateHandle(const Zstring& filename, CreateFileCmd cmd) : + filenameFmt(zen::applyLongPathPrefix(filename)), + hFile(INVALID_HANDLE_VALUE), + attr(INVALID_FILE_ATTRIBUTES) + { + hFile = cmd(); + if (hFile != INVALID_HANDLE_VALUE) + return; + + const DWORD lastError = ::GetLastError(); + if (lastError == ERROR_ACCESS_DENIED) //function fails if file is read-only + { + zen::ScopeGuard guardErrorCode = zen::makeGuard([&]() { ::SetLastError(lastError); }); //transactional behavior: ensure cleanup (e.g. network drop) -> cref [!] + + //read-only file attribute may cause trouble: temporarily reset it + const DWORD tmpAttr = ::GetFileAttributes(filenameFmt.c_str()); + if (tmpAttr != INVALID_FILE_ATTRIBUTES && (tmpAttr & FILE_ATTRIBUTE_READONLY)) + { + if (::SetFileAttributes(filenameFmt.c_str(), FILE_ATTRIBUTE_NORMAL)) + { + guardErrorCode.dismiss(); + attr = tmpAttr; //"create" guard on read-only attribute + + //now try again + hFile = cmd(); + } + } + } + } + + ~FileUpdateHandle() + { + if (hFile != INVALID_HANDLE_VALUE) + ::CloseHandle(hFile); + + if (attr != INVALID_FILE_ATTRIBUTES) + ::SetFileAttributes(filenameFmt.c_str(), attr); + } + + //may return INVALID_FILE_ATTRIBUTES, in which case ::GetLastError() may be called directly after FileUpdateHandle() + HANDLE get() const { return hFile; } + +private: + FileUpdateHandle(const FileUpdateHandle&); + FileUpdateHandle& operator=(const FileUpdateHandle&); + + Zstring filenameFmt; + HANDLE hFile; + DWORD attr; +}; +} + +#endif // FILE_UPDATE_HANDLE_H_INCLUDED diff --git a/zen/fixed_list.h b/zen/fixed_list.h new file mode 100644 index 00000000..c17ce0da --- /dev/null +++ b/zen/fixed_list.h @@ -0,0 +1,142 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef PTR_WRAP_012384670856841394535 +#define PTR_WRAP_012384670856841394535 + +#include <iterator> + +namespace zen +{ +//std::list(C++11) compatible class supporting inplace element construction for non-copyable/movable types +//may be replaced by C++11 std::list when available +template <class T> +class FixedList +{ + struct Node + { + Node() : next(NULL), val() {} + template <class A> Node(A&& a) : next(NULL), val(a) {} + template <class A, class B> Node(A&& a, B&& b) : next(NULL), val(a, b) {} + template <class A, class B, class C> Node(A&& a, B&& b, C&& c) : next(NULL), val(a, b, c) {} + template <class A, class B, class C, class D> Node(A&& a, B&& b, C&& c, D&& d) : next(NULL), val(a, b, c, d) {} + template <class A, class B, class C, class D, class E> Node(A&& a, B&& b, C&& c, D&& d, E&& e) : next(NULL), val(a, b, c, d, e) {} + template <class A, class B, class C, class D, class E, class F> Node(A&& a, B&& b, C&& c, D&& d, E&& e, F&& f) : next(NULL), val(a, b, c, d, e, f) {} + + Node* next; //singly linked list is sufficient + T val; + }; + +public: + FixedList() : + first(NULL), + lastInsert(NULL), + sz(0) {} + + ~FixedList() { clear(); } + + template <class NodeT, class U> + class ListIterator : public std::iterator<std::forward_iterator_tag, U> + { + public: + ListIterator(NodeT* it = NULL) : iter(it) {} + ListIterator& operator++() { iter = iter->next; return *this; } + inline friend bool operator==(const ListIterator& lhs, const ListIterator& rhs) { return lhs.iter == rhs.iter; } + inline friend bool operator!=(const ListIterator& lhs, const ListIterator& rhs) { return !(lhs == rhs); } + U& operator* () { return iter->val; } + U* operator->() { return &iter->val; } + private: + NodeT* iter; + }; + + typedef T value_type; + typedef ListIterator<Node, T> iterator; + typedef ListIterator<const Node, const T> const_iterator; + typedef T& reference; + typedef const T& const_reference; + + iterator begin() { return first; } + iterator end() { return iterator(); } + + const_iterator begin() const { return first; } + const_iterator end () const { return const_iterator(); } + + reference front() { return first->val; } + const_reference front() const { return first->val; } + + reference& back() { return lastInsert->val; } + const_reference& back() const { return lastInsert->val; } + + void emplace_back() { pushNode(new Node); } + template <class A> void emplace_back(A&& a) { pushNode(new Node(std::forward<A>(a))); } + template <class A, class B> void emplace_back(A&& a, B&& b) { pushNode(new Node(std::forward<A>(a), std::forward<B>(b))); } + template <class A, class B, class C> void emplace_back(A&& a, B&& b, C&& c) { pushNode(new Node(std::forward<A>(a), std::forward<B>(b), std::forward<C>(c))); } + template <class A, class B, class C, class D> void emplace_back(A&& a, B&& b, C&& c, D&& d) { pushNode(new Node(std::forward<A>(a), std::forward<B>(b), std::forward<C>(c), std::forward<D>(d))); } + template <class A, class B, class C, class D, class E> void emplace_back(A&& a, B&& b, C&& c, D&& d, E&& e) { pushNode(new Node(std::forward<A>(a), std::forward<B>(b), std::forward<C>(c), std::forward<D>(d), std::forward<E>(e))); } + template <class A, class B, class C, class D, class E, class F> void emplace_back(A&& a, B&& b, C&& c, D&& d, E&& e, F&& f) { pushNode(new Node(std::forward<A>(a), std::forward<B>(b), std::forward<C>(c), std::forward<D>(d), std::forward<E>(e), std::forward<F>(f))); } + + template <class Predicate> + void remove_if(Predicate pred) + { + Node* prev = NULL; + for (auto ptr = first; ptr;) + if (pred(ptr->val)) + { + Node* tmp = ptr->next; + deleteNode(ptr); + if (prev) + prev->next = ptr = tmp; + else + first = ptr = tmp; + if (tmp == NULL) + lastInsert = prev; + } + else + { + prev = ptr; + ptr = ptr->next; + } + } + + void clear() { remove_if([](T&) { return true; }); } + bool empty() const { return first == NULL; } + size_t size() const { return sz; } + +private: + FixedList(const FixedList&); + FixedList& operator=(const FixedList&); + + void pushNode(Node* newNode) + { + ++sz; + if (lastInsert == NULL) + { + assert(first == NULL); + first = lastInsert = newNode; + } + else + { + assert(lastInsert->next == NULL); + lastInsert->next = newNode; + lastInsert = newNode; + } + } + + void deleteNode(Node* oldNode) + { + assert(sz > 0); + --sz; + delete oldNode; + } + + Node* first; + Node* lastInsert; //point to last insertion; required by emplace_back() + size_t sz; +}; +} + + +#endif //PTR_WRAP_012384670856841394535 diff --git a/zen/guid.h b/zen/guid.h new file mode 100644 index 00000000..218c80a5 --- /dev/null +++ b/zen/guid.h @@ -0,0 +1,36 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef GUID_H_INCLUDED +#define GUID_H_INCLUDED + +#include <string> +#include <boost/uuid/uuid.hpp> + +#ifdef __MINGW32__ //boost should start and clean up! +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wuninitialized" +#endif + +#include <boost/uuid/uuid_generators.hpp> + +#ifdef __MINGW32__ +#pragma GCC diagnostic pop +#endif + + +namespace zen +{ +inline +std::string generateGUID() //creates a 16 byte GUID +{ + boost::uuids::uuid nativeRep = boost::uuids::random_generator()(); + return std::string(nativeRep.begin(), nativeRep.end()); +} +} + +#endif // GUID_H_INCLUDED diff --git a/zen/i18n.h b/zen/i18n.h new file mode 100644 index 00000000..de615cdb --- /dev/null +++ b/zen/i18n.h @@ -0,0 +1,96 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef I18_N_HEADER_3843489325045 +#define I18_N_HEADER_3843489325045 + +#include <string> +#include <memory> + +//thin layer to enable localization - without platform/library dependencies! +#ifndef WXINTL_NO_GETTEXT_MACRO +#error WXINTL_NO_GETTEXT_MACRO must be defined to deactivate wxWidgets underscore macro +#endif + +#define ZEN_CONCAT_SUB(X, Y) X ## Y +#define _(s) zen::implementation::translate(ZEN_CONCAT_SUB(L, s)) +#define _P(s, p, n) zen::implementation::translate(ZEN_CONCAT_SUB(L, s), ZEN_CONCAT_SUB(L, p), n) + + +namespace zen +{ +//implement handler to enable program wide localizations +struct TranslationHandler +{ + virtual ~TranslationHandler() {} + + virtual std::wstring thousandsSeparator() = 0; + virtual std::wstring translate(const std::wstring& text) = 0; //simple translation + virtual std::wstring translate(const std::wstring& singular, const std::wstring& plural, int n) = 0; +}; + +void setTranslator(TranslationHandler* newHandler = NULL); //takes ownership +TranslationHandler* getTranslator(); + +inline +std::wstring getThousandsSeparator() { return getTranslator() ? getTranslator()->thousandsSeparator() : L","; }; + + + + + + + + + + + + + + + + + + +//######################## implementation ############################## +namespace implementation +{ +inline +std::wstring translate(const std::wstring& text) +{ + return getTranslator() ? getTranslator()->translate(text) : text; +} + +//translate plural forms: "%x day" "%x days" +//returns "%x day" if n == 1; "%x days" else for english language +inline +std::wstring translate(const std::wstring& singular, const std::wstring& plural, int n) +{ + return getTranslator() ? getTranslator()->translate(singular, plural, n) : n == 1 ? singular : plural; +} + +template <class T> inline +std::wstring translate(const std::wstring& singular, const std::wstring& plural, T n) +{ + return translate(singular, plural, static_cast<int>(n % 1000000)); +} + +inline +std::unique_ptr<TranslationHandler>& globalHandler() +{ + static std::unique_ptr<TranslationHandler> inst; //external linkage even in header! + return inst; +} +} + +inline +void setTranslator(TranslationHandler* newHandler) { implementation::globalHandler().reset(newHandler); } //takes ownership + +inline +TranslationHandler* getTranslator() { return implementation::globalHandler().get(); } +} + +#endif //I18_N_HEADER_3843489325045 diff --git a/zen/int64.h b/zen/int64.h new file mode 100644 index 00000000..f7a7e800 --- /dev/null +++ b/zen/int64.h @@ -0,0 +1,258 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef FFS_LARGE_64_BIT_INTEGER_H_INCLUDED +#define FFS_LARGE_64_BIT_INTEGER_H_INCLUDED + +#include <cassert> +#include <limits> +#include <cstdint> +#include <cstdint> +#include <ostream> +#include "assert_static.h" +#include "type_tools.h" + +#ifdef FFS_WIN +#include "win.h" +#endif + + +/* +zen::Int64/zen::UInt64: wrapper classes around std::int64_t/std::uint64_t + + - default initialization with 0 + - debug runtime overflow/underflow checks + - safe and explicit semantics: no unsafe type conversions + - safe conversion to and from Windows 64-bit integers + - specializes std::numeric_limits + - support stream operator<< and operator>> +*/ + +namespace zen +{ +template <class T, class U> inline void checkRange(U value) +{ + //caveat: std::numeric_limits<T>::min returns minimum positive(!) number for T = double, while behaving correctly for integer types... sigh + assert(double(std::numeric_limits<T>::lowest()) <= double(value) && //new with C++11! + double(std::numeric_limits<T>::max() ) >= double(value)); + + // assert(double(boost::numeric::bounds<T>::lowest ()) <= double(value) && + // double(boost::numeric::bounds<T>::highest()) >= double(value)); +} + +class Int64 +{ + struct DummyClass { operator int() { return 0; } }; +public: + //safe implicit conversions + Int64() : value(0) {} + Int64(const Int64& rhs) : value(rhs.value) {} + Int64(int rhs) : value(rhs) {} //ambiguity intentional for types other than these + Int64(long rhs) : value(rhs) {} + Int64(Select<IsSameType<std::int64_t, long>::result, DummyClass, std::int64_t>::Result rhs) : + value(rhs) {} //-> std::int64_t equals long int on x64 Linux! Still we want implicit behavior for all other systems! + + //unsafe explicit but checked conversion from arbitrary integer type + template <class T> explicit Int64(T rhs) : value(rhs) { checkRange<std::int64_t>(rhs); } + + Int64& operator=(const Int64& rhs) { value = rhs.value; return *this; } + +#ifdef FFS_WIN + Int64(DWORD low, LONG high) + { + assert_static(sizeof(low) + sizeof(high) == sizeof(value)); + + LARGE_INTEGER cvt = {}; + cvt.LowPart = low; + cvt.HighPart = high; + value = cvt.QuadPart; + } + LONG getHi() const + { + LARGE_INTEGER cvt = {}; + cvt.QuadPart = value; + return cvt.HighPart; + } + DWORD getLo() const + { + LARGE_INTEGER cvt = {}; + cvt.QuadPart = value; + return cvt.LowPart; + } +#endif + + Int64& operator+=(const Int64& rhs) { checkRange<std::int64_t>(double(value) + rhs.value); value += rhs.value; return *this; } + Int64& operator-=(const Int64& rhs) { checkRange<std::int64_t>(double(value) - rhs.value); value -= rhs.value; return *this; } + Int64& operator*=(const Int64& rhs) { checkRange<std::int64_t>(double(value) * rhs.value); value *= rhs.value; return *this; } + Int64& operator/=(const Int64& rhs) { assert(rhs.value != 0); value /= rhs.value; return *this; } + Int64& operator%=(const Int64& rhs) { assert(rhs.value != 0); value %= rhs.value; return *this; } + Int64& operator&=(const Int64& rhs) { value &= rhs.value; return *this;} + Int64& operator|=(const Int64& rhs) { value |= rhs.value; return *this;} + Int64& operator<<=(int rhs) { assert(rhs < 0 || (value << rhs) >> rhs == value); value <<= rhs; return *this; } + Int64& operator>>=(int rhs) { assert(rhs > 0 || (value >> rhs) << rhs == value); value >>= rhs; return *this; } + + inline friend bool operator==(const Int64& lhs, const Int64& rhs) { return lhs.value == rhs.value; } + inline friend bool operator!=(const Int64& lhs, const Int64& rhs) { return lhs.value != rhs.value; } + inline friend bool operator< (const Int64& lhs, const Int64& rhs) { return lhs.value < rhs.value; } + inline friend bool operator> (const Int64& lhs, const Int64& rhs) { return lhs.value > rhs.value; } + inline friend bool operator<=(const Int64& lhs, const Int64& rhs) { return lhs.value <= rhs.value; } + inline friend bool operator>=(const Int64& lhs, const Int64& rhs) { return lhs.value >= rhs.value; } + + //checked conversion to arbitrary target integer type + template <class T> inline friend T to(Int64 number) { checkRange<T>(number.value); return static_cast<T>(number.value); } + + template <class T> inline friend std::basic_istream<T>& operator>>(std::basic_istream<T>& lhs, Int64& rhs) { lhs >> rhs.value; return lhs; } + template <class T> inline friend std::basic_ostream<T>& operator<<(std::basic_ostream<T>& lhs, const Int64& rhs) { lhs << rhs.value; return lhs; } + +private: + std::int64_t value; +}; + +inline Int64 operator+(const Int64& lhs, const Int64& rhs) { return Int64(lhs) += rhs; } +inline Int64 operator-(const Int64& lhs, const Int64& rhs) { return Int64(lhs) -= rhs; } +inline Int64 operator*(const Int64& lhs, const Int64& rhs) { return Int64(lhs) *= rhs; } +inline Int64 operator/(const Int64& lhs, const Int64& rhs) { return Int64(lhs) /= rhs; } +inline Int64 operator%(const Int64& lhs, const Int64& rhs) { return Int64(lhs) %= rhs; } +inline Int64 operator&(const Int64& lhs, const Int64& rhs) { return Int64(lhs) &= rhs; } +inline Int64 operator|(const Int64& lhs, const Int64& rhs) { return Int64(lhs) |= rhs; } +inline Int64 operator<<(const Int64& lhs, int rhs) { return Int64(lhs) <<= rhs; } +inline Int64 operator>>(const Int64& lhs, int rhs) { return Int64(lhs) >>= rhs; } + + +class UInt64 +{ + struct DummyClass { operator size_t() { return 0U; } }; +public: + //safe implicit conversions + UInt64() : value(0) {} + UInt64(const UInt64& rhs) : value(rhs.value) {} + UInt64(unsigned int rhs) : value(rhs) {} //ambiguity intentional for types other than these + UInt64(unsigned long rhs) : value(rhs) {} + UInt64(Select<IsSameType<std::uint64_t, unsigned long>::result, DummyClass, std::uint64_t>::Result rhs) : + value(rhs) {} //-> std::uint64_t equals unsigned long int on x64 Linux! Still we want implicit behavior for all other systems! + + //unsafe explicit but checked conversion from arbitrary integer type + template <class T> explicit UInt64(T rhs) : value(rhs) { checkRange<std::uint64_t>(rhs); } + + UInt64& operator=(const UInt64& rhs) { value = rhs.value; return *this; } + +#ifdef FFS_WIN + UInt64(DWORD low, DWORD high) + { + assert_static(sizeof(low) + sizeof(high) == sizeof(value)); + + ULARGE_INTEGER cvt = {}; + cvt.LowPart = low; + cvt.HighPart = high; + value = cvt.QuadPart; + } + DWORD getHi() const + { + ULARGE_INTEGER cvt = {}; + cvt.QuadPart = value; + return cvt.HighPart; + } + DWORD getLo() const + { + ULARGE_INTEGER cvt = {}; + cvt.QuadPart = value; + return cvt.LowPart; + } +#endif + + UInt64& operator+=(const UInt64& rhs) { checkRange<std::uint64_t>(double(value) + rhs.value); value += rhs.value; return *this; } + UInt64& operator-=(const UInt64& rhs) { checkRange<std::uint64_t>(double(value) - rhs.value); value -= rhs.value; return *this; } + UInt64& operator*=(const UInt64& rhs) { checkRange<std::uint64_t>(double(value) * rhs.value); value *= rhs.value; return *this; } + UInt64& operator/=(const UInt64& rhs) { assert(rhs.value != 0); value /= rhs.value; return *this; } + UInt64& operator%=(const UInt64& rhs) { assert(rhs.value != 0); value %= rhs.value; return *this; } + UInt64& operator&=(const UInt64& rhs) { value &= rhs.value; return *this;} + UInt64& operator|=(const UInt64& rhs) { value |= rhs.value; return *this;} + UInt64& operator<<=(int rhs) { assert(rhs < 0 || (value << rhs) >> rhs == value); value <<= rhs; return *this; } + UInt64& operator>>=(int rhs) { assert(rhs > 0 || (value >> rhs) << rhs == value); value >>= rhs; return *this; } + + inline friend bool operator==(const UInt64& lhs, const UInt64& rhs) { return lhs.value == rhs.value; } + inline friend bool operator!=(const UInt64& lhs, const UInt64& rhs) { return lhs.value != rhs.value; } + inline friend bool operator< (const UInt64& lhs, const UInt64& rhs) { return lhs.value < rhs.value; } + inline friend bool operator> (const UInt64& lhs, const UInt64& rhs) { return lhs.value > rhs.value; } + inline friend bool operator<=(const UInt64& lhs, const UInt64& rhs) { return lhs.value <= rhs.value; } + inline friend bool operator>=(const UInt64& lhs, const UInt64& rhs) { return lhs.value >= rhs.value; } + + //checked conversion to arbitrary target integer type + template <class T> inline friend T to(UInt64 number) { checkRange<T>(number.value); return static_cast<T>(number.value); } + + template <class T> inline friend std::basic_istream<T>& operator>>(std::basic_istream<T>& lhs, UInt64& rhs) { lhs >> rhs.value; return lhs; } + template <class T> inline friend std::basic_ostream<T>& operator<<(std::basic_ostream<T>& lhs, const UInt64& rhs) { lhs << rhs.value; return lhs; } + +private: + std::uint64_t value; +}; + +inline UInt64 operator+(const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) += rhs; } +inline UInt64 operator-(const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) -= rhs; } +inline UInt64 operator*(const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) *= rhs; } +inline UInt64 operator/(const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) /= rhs; } +inline UInt64 operator%(const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) %= rhs; } +inline UInt64 operator&(const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) &= rhs; } +inline UInt64 operator|(const UInt64& lhs, const UInt64& rhs) { return UInt64(lhs) |= rhs; } +inline UInt64 operator<<(const UInt64& lhs, int rhs) { return UInt64(lhs) <<= rhs; } +inline UInt64 operator>>(const UInt64& lhs, int rhs) { return UInt64(lhs) >>= rhs; } + +template <> inline UInt64 to(Int64 number) { checkRange<std::uint64_t>(number.value); return UInt64(number.value); } +template <> inline Int64 to(UInt64 number) { checkRange<std:: int64_t>(number.value); return Int64(number.value); } + + +#ifdef FFS_WIN +//convert FILETIME (number of 100-nanosecond intervals since January 1, 1601 UTC) +// to time_t (number of seconds since Jan. 1st 1970 UTC) +// +//FAT32 time is preserved exactly: FAT32 -> toTimeT -> tofiletime -> FAT32 +inline +Int64 toTimeT(const FILETIME& ft) +{ + return to<Int64>(UInt64(ft.dwLowDateTime, ft.dwHighDateTime) / 10000000U) - Int64(3054539008UL, 2); + //timeshift between ansi C time and FILETIME in seconds == 11644473600s +} + +inline +FILETIME tofiletime(const Int64& utcTime) +{ + const UInt64 fileTimeLong = to<UInt64>(utcTime + Int64(3054539008UL, 2)) * 10000000U; + const FILETIME output = { fileTimeLong.getLo(), fileTimeLong.getHi() }; + return output; +} +#endif +} + +//specialize numeric limits +namespace std +{ +assert_static(std::numeric_limits<std:: int64_t>::is_specialized); +assert_static(std::numeric_limits<std::uint64_t>::is_specialized); + +template <> class numeric_limits<zen::Int64> : public numeric_limits<std::int64_t> +{ +public: + static zen::Int64 min() throw() { return numeric_limits<std::int64_t>::min(); } + static zen::Int64 max() throw() { return numeric_limits<std::int64_t>::max(); } +}; + +template <> class numeric_limits<zen::UInt64> : public numeric_limits<std::uint64_t> +{ +public: + static zen::UInt64 min() throw() { return numeric_limits<std::uint64_t>::min(); } + static zen::UInt64 max() throw() { return numeric_limits<std::uint64_t>::max(); } +}; +} + +/* +//specialize zen type trait +namespace zen -> we cannot mix signed/unsigned in general arithmetic operations -> we'll use the ostream-approach +{ +template <> struct IsUnsignedInt<UInt64> { enum { result = true }; }; +template <> struct IsSignedInt <Int64> { enum { result = true }; }; +} +*/ +#endif //FFS_LARGE_64_BIT_INTEGER_H_INCLUDED diff --git a/zen/last_error.h b/zen/last_error.h new file mode 100644 index 00000000..d2eaebfc --- /dev/null +++ b/zen/last_error.h @@ -0,0 +1,110 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef SYSTEMFUNCTIONS_H_INCLUDED +#define SYSTEMFUNCTIONS_H_INCLUDED + +#include <string> +#include "utf8.h" +#include "i18n.h" + +#ifdef FFS_WIN +#include "win.h" //includes "windows.h" + +#elif defined FFS_LINUX +#include <cstring> +#include <cerrno> +#endif + + + +namespace zen +{ +//evaluate GetLastError()/errno and assemble specific error message +#ifdef FFS_WIN +std::wstring getLastErrorFormatted(DWORD lastError = 0); +#elif defined FFS_LINUX +std::wstring getLastErrorFormatted(int lastError = 0); +#endif + + + + + + + + + + + + + + + + + + + + + + + + + + + +//######################## Implementation ######################## + +#ifdef FFS_WIN +inline +std::wstring getLastErrorFormatted(DWORD lastError) //try to get additional Windows error information +{ + //determine error code if none was specified + if (lastError == 0) + lastError = ::GetLastError(); + + std::wstring output = _("Windows Error Code %x:"); + replace(output, L"%x", toString<std::wstring>(lastError)); + + LPWSTR buffer = NULL; + if (::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_MAX_WIDTH_MASK | + FORMAT_MESSAGE_IGNORE_INSERTS | //important: without this flag ::FormatMessage() will fail if message contains placeholders + FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, lastError, 0, reinterpret_cast<LPWSTR>(&buffer), 0, NULL) != 0) + { + if (buffer) //just to be sure + { + output += L" "; + output += buffer; + ::LocalFree(buffer); + } + } + ::SetLastError(lastError); //restore last error + return output; +} + +#elif defined FFS_LINUX +inline +std::wstring getLastErrorFormatted(int lastError) //try to get additional Linux error information +{ + //determine error code if none was specified + if (lastError == 0) + lastError = errno; //don't use "::", errno is a macro! + + std::wstring output = _("Linux Error Code %x:"); + replace(output, L"%x", toString<std::wstring>(lastError)); + + output += L" "; + output += utf8CvrtTo<std::wstring>(::strerror(lastError)); + + errno = lastError; //restore errno + return output; +} +#endif +} + + +#endif // SYSTEMFUNCTIONS_H_INCLUDED diff --git a/zen/long_path_prefix.h b/zen/long_path_prefix.h new file mode 100644 index 00000000..1d9213d1 --- /dev/null +++ b/zen/long_path_prefix.h @@ -0,0 +1,98 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef LONGPATHPREFIX_H_INCLUDED +#define LONGPATHPREFIX_H_INCLUDED + +#include "zstring.h" + +namespace zen +{ +//handle filenames longer-equal 260 (== MAX_PATH) characters by applying \\?\-prefix (Reference: http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath) +/* +1. path must be absolute +2. if path is smaller than MAX_PATH nothing is changed! +3. path may already contain \\?\-prefix +*/ +Zstring applyLongPathPrefix(const Zstring& path); //throw() +Zstring applyLongPathPrefixCreateDir(const Zstring& path); //throw() -> special rule for ::CreateDirectoryEx(): MAX_PATH - 12(=^ 8.3 filename) is threshold + +Zstring removeLongPathPrefix(const Zstring& path); //throw() +} + + + + + + + + + + + + + + + + + + + + + + +//################## implementation ################## + +//there are two flavors of long path prefix: one for UNC paths, one for regular paths +const Zstring LONG_PATH_PREFIX = L"\\\\?\\"; +const Zstring LONG_PATH_PREFIX_UNC = L"\\\\?\\UNC"; + +template <size_t max_path> inline +Zstring applyLongPathPrefixImpl(const Zstring& path) +{ + assert(!path.empty()); //nicely check almost all WinAPI accesses! + + if (path.length() >= max_path && //maximum allowed path length without prefix is (MAX_PATH - 1) + !zen::startsWith(path, LONG_PATH_PREFIX)) + { + if (zen::startsWith(path, L"\\\\")) //UNC-name, e.g. \\zenju-pc\Users + return LONG_PATH_PREFIX_UNC + zen::afterFirst(path, L'\\'); //convert to \\?\UNC\zenju-pc\Users + else + return LONG_PATH_PREFIX + path; //prepend \\?\ prefix + } + return path; //fallback +} + + +inline +Zstring zen::applyLongPathPrefix(const Zstring& path) +{ + return applyLongPathPrefixImpl<MAX_PATH>(path); +} + + +inline +Zstring zen::applyLongPathPrefixCreateDir(const Zstring& path) //throw() +{ + //special rule for ::CreateDirectoryEx(): MAX_PATH - 12(=^ 8.3 filename) is threshold + return applyLongPathPrefixImpl<MAX_PATH - 12> (path); +} + + +inline +Zstring zen::removeLongPathPrefix(const Zstring& path) //throw() +{ + if (zen::startsWith(path, LONG_PATH_PREFIX)) + { + if (zen::startsWith(path, LONG_PATH_PREFIX_UNC)) //UNC-name + return replaceCpy(path, LONG_PATH_PREFIX_UNC, Zstr("\\"), false); + else + return replaceCpy(path, LONG_PATH_PREFIX, Zstr(""), false); + } + return path; //fallback +} + +#endif //LONGPATHPREFIX_H_INCLUDED diff --git a/zen/notify_removal.cpp b/zen/notify_removal.cpp new file mode 100644 index 00000000..805c1c09 --- /dev/null +++ b/zen/notify_removal.cpp @@ -0,0 +1,236 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#include "notify_removal.h" +#include <set> +#include <algorithm> +#include <dbt.h> +#include "scope_guard.h" + +using namespace zen; + +/* +//convert bitmask into "real" drive-letter +Zstring getDriveFromMask(ULONG unitmask) +{ + for (int i = 0; i < 26; ++i) + { + if (unitmask & 0x1) + return Zstring() + static_cast<DefaultChar>(DefaultChar('A') + i) + DefaultStr(":\\"); + unitmask >>= 1; + } + return Zstring(); +} +*/ + +namespace +{ +bool messageProviderConstructed = false; +} + + +class MessageProvider //administrates a single dummy window to receive messages +{ +public: + static MessageProvider& instance() //throw (FileError) + { + static MessageProvider inst; + messageProviderConstructed = true; + return inst; + } + + class Listener + { + public: + virtual ~Listener() {} + virtual void onMessage(UINT message, WPARAM wParam, LPARAM lParam) = 0; //throw()! + }; + void registerListener(Listener& l) { listener.insert(&l); } + void unregisterListener(Listener& l) { listener.erase(&l); } //don't unregister objects with static lifetime + + HWND getWnd() const { return windowHandle; } //get handle in order to register additional notifications + +private: + MessageProvider(); + ~MessageProvider(); + MessageProvider(const MessageProvider&); + MessageProvider& operator=(const MessageProvider&); + + static const wchar_t WINDOW_NAME[]; + + friend LRESULT CALLBACK topWndProc(HWND, UINT, WPARAM, LPARAM); + void processMessage(UINT message, WPARAM wParam, LPARAM lParam); + + const HINSTANCE process; + HWND windowHandle; + + std::set<Listener*> listener; +}; + + +const wchar_t MessageProvider::WINDOW_NAME[] = L"E6AD5EB1-527B-4EEF-AC75-27883B233380"; //random name + + +LRESULT CALLBACK topWndProc( + HWND hwnd, //handle to window + UINT uMsg, //message identifier + WPARAM wParam, //first message parameter + LPARAM lParam) //second message parameter +{ + if (messageProviderConstructed) //attention: this callback is triggered in the middle of singleton construction! It is a bad idea to to call back at this time! + try + { + MessageProvider::instance().processMessage(uMsg, wParam, lParam); //not supposed to throw + } + catch (...) {} + + return ::DefWindowProc(hwnd, uMsg, wParam, lParam); +} + + +MessageProvider::MessageProvider() : + process(::GetModuleHandle(NULL)), //get program's module handle + windowHandle(NULL) +{ + if (process == NULL) + throw zen::FileError(std::wstring(L"Could not start monitoring window notifications:") + "\n\n" + getLastErrorFormatted() + " (GetModuleHandle)"); + + //register the main window class + WNDCLASS wc = {}; + wc.lpfnWndProc = topWndProc; + wc.hInstance = process; + wc.lpszClassName = WINDOW_NAME; + + if (::RegisterClass(&wc) == 0) + throw zen::FileError(std::wstring(L"Could not start monitoring window notifications:") + "\n\n" + getLastErrorFormatted() + " (RegisterClass)"); + + zen::ScopeGuard guardClass = zen::makeGuard([&]() { ::UnregisterClass(WINDOW_NAME, process); }); + + //create dummy-window + windowHandle = ::CreateWindow( + WINDOW_NAME, //LPCTSTR lpClassName OR ATOM in low-order word! + NULL, //LPCTSTR lpWindowName, + 0, //DWORD dwStyle, + 0, //int x, + 0, //int y, + 0, //int nWidth, + 0, //int nHeight, + 0, //note: we need a toplevel window to receive device arrival events, not a message-window (HWND_MESSAGE)! + NULL, //HMENU hMenu, + process, //HINSTANCE hInstance, + NULL); //LPVOID lpParam + if (windowHandle == NULL) + throw zen::FileError(std::wstring(L"Could not start monitoring window notifications:") + "\n\n" + getLastErrorFormatted() + " (CreateWindow)"); + + guardClass.dismiss(); +} + + +MessageProvider::~MessageProvider() +{ + //clean-up in reverse order + ::DestroyWindow(windowHandle); + ::UnregisterClass(WINDOW_NAME, //LPCTSTR lpClassName OR ATOM in low-order word! + process); //HINSTANCE hInstance +} + + +void MessageProvider::processMessage(UINT message, WPARAM wParam, LPARAM lParam) +{ + std::for_each(listener.begin(), listener.end(), + [&](Listener* ls) { ls->onMessage(message, wParam, lParam); }); +} +//#################################################################################################### + + +class NotifyRequestDeviceRemoval::Pimpl : private MessageProvider::Listener +{ +public: + Pimpl(NotifyRequestDeviceRemoval& parent, HANDLE hDir) : //throw (FileError) + parent_(parent) + { + MessageProvider::instance().registerListener(*this); //throw (FileError) + + //register handles to receive notifications + DEV_BROADCAST_HANDLE filter = {}; + filter.dbch_size = sizeof(filter); + filter.dbch_devicetype = DBT_DEVTYP_HANDLE; + filter.dbch_handle = hDir; + + hNotification = ::RegisterDeviceNotification( + MessageProvider::instance().getWnd(), //__in HANDLE hRecipient, + &filter, //__in LPVOID NotificationFilter, + DEVICE_NOTIFY_WINDOW_HANDLE); //__in DWORD Flags + if (hNotification == NULL) + { + const DWORD lastError = ::GetLastError(); + if (lastError != ERROR_CALL_NOT_IMPLEMENTED && //fail on SAMBA share: this shouldn't be a showstopper! + lastError != ERROR_SERVICE_SPECIFIC_ERROR && //neither should be fail for "Pogoplug" mapped network drives + lastError != ERROR_INVALID_DATA) //this seems to happen for a NetDrive-mapped FTP server + throw zen::FileError(std::wstring(L"Could not register device removal notifications:") + "\n\n" + getLastErrorFormatted(lastError)); + } + } + + ~Pimpl() + { + ::UnregisterDeviceNotification(hNotification); + MessageProvider::instance().unregisterListener(*this); + } + +private: + Pimpl(Pimpl&); + Pimpl& operator=(Pimpl&); + + virtual void onMessage(UINT message, WPARAM wParam, LPARAM lParam) //throw()! + { + //DBT_DEVICEQUERYREMOVE example: http://msdn.microsoft.com/en-us/library/aa363427(v=VS.85).aspx + if (message == WM_DEVICECHANGE) + { + if ( wParam == DBT_DEVICEQUERYREMOVE || + wParam == DBT_DEVICEQUERYREMOVEFAILED || + wParam == DBT_DEVICEREMOVECOMPLETE) + { + PDEV_BROADCAST_HDR header = reinterpret_cast<PDEV_BROADCAST_HDR>(lParam); + if (header->dbch_devicetype == DBT_DEVTYP_HANDLE) + { + PDEV_BROADCAST_HANDLE body = reinterpret_cast<PDEV_BROADCAST_HANDLE>(lParam); + +#ifdef __MINGW32__ + const HDEVNOTIFY requestNotification = reinterpret_cast<HDEVNOTIFY>(body->dbch_hdevnotify); +#else + const HDEVNOTIFY requestNotification = body->dbch_hdevnotify; +#endif + if (requestNotification == hNotification) //is it for the notification we registered? + switch (wParam) + { + case DBT_DEVICEQUERYREMOVE: + parent_.onRequestRemoval(body->dbch_handle); + break; + case DBT_DEVICEQUERYREMOVEFAILED: + parent_.onRemovalFinished(body->dbch_handle, false); + break; + case DBT_DEVICEREMOVECOMPLETE: + parent_.onRemovalFinished(body->dbch_handle, true); + break; + } + } + } + } + } + + NotifyRequestDeviceRemoval& parent_; + HDEVNOTIFY hNotification; +}; +//#################################################################################################### + + +NotifyRequestDeviceRemoval::NotifyRequestDeviceRemoval(HANDLE hDir) +{ + pimpl.reset(new Pimpl(*this, hDir)); +} + + +NotifyRequestDeviceRemoval::~NotifyRequestDeviceRemoval() {} //make sure ~auto_ptr() works with complete type diff --git a/zen/notify_removal.h b/zen/notify_removal.h new file mode 100644 index 00000000..bd47684e --- /dev/null +++ b/zen/notify_removal.h @@ -0,0 +1,36 @@ +// ************************************************************************** +// * This file is part of the FreeFileSync project. It is distributed under * +// * GNU General Public License: http://www.gnu.org/licenses/gpl.html * +// * Copyright (C) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef NOTIFY_H_INCLUDED +#define NOTIFY_H_INCLUDED + +#include <vector> +#include <memory> +#include "win.h" //includes "windows.h" +#include "file_error.h" + +//handle (user-) request for device removal via template method pattern +//evaluate directly after processing window messages +class NotifyRequestDeviceRemoval +{ +public: + NotifyRequestDeviceRemoval(HANDLE hDir); //throw FileError + virtual ~NotifyRequestDeviceRemoval(); + +private: + virtual void onRequestRemoval(HANDLE hnd) = 0; //throw()! + //NOTE: onRemovalFinished is NOT guaranteed to execute after onRequestRemoval()! but most likely will + virtual void onRemovalFinished(HANDLE hnd, bool successful) = 0; //throw()! + + NotifyRequestDeviceRemoval(NotifyRequestDeviceRemoval&); //no copying + void operator=(NotifyRequestDeviceRemoval&); // + + class Pimpl; + std::unique_ptr<Pimpl> pimpl; +}; + + +#endif // NOTIFY_H_INCLUDED diff --git a/zen/perf.h b/zen/perf.h new file mode 100644 index 00000000..f9970d0a --- /dev/null +++ b/zen/perf.h @@ -0,0 +1,73 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef DEBUG_PERF_HEADER +#define DEBUG_PERF_HEADER + +#include <sstream> +#include "win.h" //includes "windows.h" + +#ifdef __MINGW32__ +#define DEPRECATED(x) x __attribute__ ((deprecated)) +#elif defined _MSC_VER +#define DEPRECATED(x) __declspec(deprecated) x +#endif + + +//two macros for quick performance measurements +#define PERF_START CpuTimer perfTest; +#define PERF_STOP perfTest.showResult(); + +class CpuTimer +{ +public: + class TimerError {}; + + DEPRECATED(CpuTimer()) : frequency(), startTime(), resultShown(false) + { + SetThreadAffinity dummy; + if (!::QueryPerformanceFrequency(&frequency)) throw TimerError(); + if (!::QueryPerformanceCounter (&startTime)) throw TimerError(); + } + + ~CpuTimer() + { + if (!resultShown) + showResult(); + } + + void showResult() + { + SetThreadAffinity dummy; + LARGE_INTEGER currentTime = {}; + if (!::QueryPerformanceCounter(¤tTime)) throw TimerError(); + + const long delta = static_cast<long>(1000.0 * (currentTime.QuadPart - startTime.QuadPart) / frequency.QuadPart); + std::ostringstream ss; + ss << delta << " ms"; + + ::MessageBoxA(NULL, ss.str().c_str(), "Timer", 0); + resultShown = true; + + if (!::QueryPerformanceCounter(&startTime)) throw TimerError(); //don't include call to MessageBox()! + } + +private: + class SetThreadAffinity + { + public: + SetThreadAffinity() : oldmask(::SetThreadAffinityMask(::GetCurrentThread(), 1)) { if (oldmask == 0) throw TimerError(); } + ~SetThreadAffinity() { ::SetThreadAffinityMask(::GetCurrentThread(), oldmask); } + private: + const DWORD_PTR oldmask; + }; + + LARGE_INTEGER frequency; + LARGE_INTEGER startTime; + bool resultShown; +}; + +#endif //DEBUG_PERF_HEADER diff --git a/zen/privilege.cpp b/zen/privilege.cpp new file mode 100644 index 00000000..495b1254 --- /dev/null +++ b/zen/privilege.cpp @@ -0,0 +1,79 @@ +#include "privilege.h" +#include "scope_guard.h" + +using namespace zen; + + +Privileges& Privileges::getInstance() +{ + static Privileges instance; + return instance; +} + + +bool Privileges::privilegeIsActive(LPCTSTR privilege) //throw FileError +{ + HANDLE hToken = NULL; + if (!::OpenProcessToken(::GetCurrentProcess(), //__in HANDLE ProcessHandle, + TOKEN_QUERY, //__in DWORD DesiredAccess, + &hToken)) //__out PHANDLE TokenHandle + throw FileError(_("Error setting privilege:") + " \"" + privilege + "\"" + "\n\n" + getLastErrorFormatted()); + ZEN_ON_BLOCK_EXIT(::CloseHandle(hToken)); + + LUID luid = {}; + if (!::LookupPrivilegeValue( + NULL, //__in_opt LPCTSTR lpSystemName, + privilege, //__in LPCTSTR lpName, + &luid )) //__out PLUID lpLuid + throw FileError(_("Error setting privilege:") + " \"" + privilege + "\"" + "\n\n" + getLastErrorFormatted()); + + PRIVILEGE_SET priv = {}; + priv.PrivilegeCount = 1; + priv.Control = PRIVILEGE_SET_ALL_NECESSARY; + priv.Privilege[0].Luid = luid; + priv.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED; + + BOOL alreadyGranted = FALSE; + if (!::PrivilegeCheck( + hToken, //__in HANDLE ClientToken, + &priv, //__inout PPRIVILEGE_SET RequiredPrivileges, + &alreadyGranted)) //__out LPBOOL pfResult + throw FileError(_("Error setting privilege:") + " \"" + privilege + "\"" + "\n\n" + getLastErrorFormatted()); + + return alreadyGranted == TRUE; +} + + +void Privileges::setPrivilege(LPCTSTR privilege, bool enable) //throw FileError +{ + HANDLE hToken = NULL; + if (!::OpenProcessToken(::GetCurrentProcess(), //__in HANDLE ProcessHandle, + TOKEN_ADJUST_PRIVILEGES, //__in DWORD DesiredAccess, + &hToken)) //__out PHANDLE TokenHandle + throw FileError(_("Error setting privilege:") + " \"" + privilege + "\"" + "\n\n" + getLastErrorFormatted()); + ZEN_ON_BLOCK_EXIT(::CloseHandle(hToken)); + + LUID luid = {}; + if (!::LookupPrivilegeValue( + NULL, //__in_opt LPCTSTR lpSystemName, + privilege, //__in LPCTSTR lpName, + &luid )) //__out PLUID lpLuid + throw FileError(_("Error setting privilege:") + " \"" + privilege + "\"" + "\n\n" + getLastErrorFormatted()); + + TOKEN_PRIVILEGES tp = {}; + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + tp.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0; + + if (!::AdjustTokenPrivileges( + hToken, //__in HANDLE TokenHandle, + false, //__in BOOL DisableAllPrivileges, + &tp, //__in_opt PTOKEN_PRIVILEGES NewState, + 0, //__in DWORD BufferLength, + NULL, //__out_opt PTOKEN_PRIVILEGES PreviousState, + NULL)) //__out_opt PDWORD ReturnLength + throw FileError(_("Error setting privilege:") + " \"" + privilege + "\"" + "\n\n" + getLastErrorFormatted()); + + if (::GetLastError() == ERROR_NOT_ALL_ASSIGNED) //check although previous function returned with success! + throw FileError(_("Error setting privilege:") + " \"" + privilege + "\"" + "\n\n" + getLastErrorFormatted()); +} diff --git a/zen/privilege.h b/zen/privilege.h new file mode 100644 index 00000000..4545aac7 --- /dev/null +++ b/zen/privilege.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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef PRIVILEGE_H_INCLUDED +#define PRIVILEGE_H_INCLUDED + +#include <map> +#include "zstring.h" +#include "file_error.h" +#include "win.h" //includes "windows.h" + +namespace zen +{ +#ifdef FFS_WIN +class Privileges +{ +public: + static Privileges& getInstance(); + + void ensureActive(LPCTSTR privilege) //throw FileError + { + if (activePrivileges.find(privilege) != activePrivileges.end()) + return; //privilege already active + + if (privilegeIsActive(privilege)) //privilege was already active before starting this tool + activePrivileges.insert(std::make_pair(privilege, false)); + else + { + setPrivilege(privilege, true); + activePrivileges.insert(std::make_pair(privilege, true)); + } + } + +private: + Privileges() {} + Privileges(Privileges&); + void operator=(Privileges&); + + ~Privileges() //clean up: deactivate all privileges that have been activated by this application + { + for (PrivBuffType::const_iterator i = activePrivileges.begin(); i != activePrivileges.end(); ++i) + try + { + if (i->second) + Privileges::setPrivilege(i->first.c_str(), false); + } + catch (...) {} + } + + static bool privilegeIsActive(LPCTSTR privilege); //throw FileError + static void setPrivilege(LPCTSTR privilege, bool enable); //throw FileError + + typedef std::map<Zstring, bool> PrivBuffType; //bool: enabled by this application + + PrivBuffType activePrivileges; +}; +#endif +} + + +#endif // PRIVILEGE_H_INCLUDED diff --git a/zen/read_txt.cpp b/zen/read_txt.cpp new file mode 100644 index 00000000..fd92a10c --- /dev/null +++ b/zen/read_txt.cpp @@ -0,0 +1,91 @@ +#include "read_txt.h" + +using namespace zen; + + +namespace +{ +std::string detectLineBreak(const Zstring& filename) //throw (FileError) +{ + //read a (hopefully) significant portion of data + zen::FileInput input(filename); + + std::vector<char> buffer(64 * 1024); + size_t bytesRead = input.read(&buffer[0], buffer.size()); //throw (FileError); + buffer.resize(bytesRead); + + //detect line break + std::string linebreakChars = "\r\n"; + std::vector<char>::iterator iter = std::find_first_of(buffer.begin(), buffer.end(), + linebreakChars.begin(), linebreakChars.end()); + if (iter != buffer.end()) + { + if (*iter == '\r') + { + ++iter; + if (iter != buffer.end()) + { + + if (*iter == '\n') + return "\r\n"; //Windows + else + return "\r"; //Mac + } + } + else if (*iter == '\n') + return "\n"; //Linux + } + //fallback + return "\n"; +} +} + + +ExtractLines::ExtractLines(const Zstring& filename, const std::string& lineBreak) : //throw (FileError) + inputStream(filename), bufferLogBegin(buffer.begin()), lineBreak_(lineBreak) +{ + if (lineBreak.empty()) + lineBreak_ = detectLineBreak(filename); //throw (FileError) +} + + +bool ExtractLines::getLine(std::string& output) //throw (FileError) +{ + for (;;) + { + //check if full line is in buffer + std::vector<char>::iterator iter = std::search(bufferLogBegin, buffer.end(), lineBreak_.begin(), lineBreak_.end()); + if (iter != buffer.end()) + { + output.assign(bufferLogBegin, iter); + bufferLogBegin = iter + lineBreak_.size(); + return true; + } + + buffer.erase(buffer.begin(), bufferLogBegin); + bufferLogBegin = buffer.begin(); + + //if done: cleanup + if (inputStream.eof()) + { + if (buffer.empty()) + return false; + + output.assign(buffer.begin(), buffer.end()); + buffer.clear(); + return true; + } + + //read next block + const size_t BLOCK_SIZE = 512 * 1024; + buffer.resize(buffer.size() + BLOCK_SIZE); + + size_t bytesRead = inputStream.read(&buffer[0] + buffer.size() - BLOCK_SIZE, BLOCK_SIZE); //throw (FileError); + assert(bytesRead <= BLOCK_SIZE); //promised by FileInput() + + if (bytesRead < BLOCK_SIZE) + buffer.resize(buffer.size() - (BLOCK_SIZE - bytesRead)); + + bufferLogBegin = buffer.begin(); + } +} diff --git a/zen/read_txt.h b/zen/read_txt.h new file mode 100644 index 00000000..479f950e --- /dev/null +++ b/zen/read_txt.h @@ -0,0 +1,32 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef PARSE_TXT_H_INCLUDED +#define PARSE_TXT_H_INCLUDED + +#include "file_io.h" +#include <vector> +#include <string> + +namespace zen +{ +class ExtractLines +{ +public: + ExtractLines(const Zstring& filename, const std::string& lineBreak = std::string()); //throw FileError + bool getLine(std::string& output); //throw FileError + +private: + zen::FileInput inputStream; + std::vector<char> buffer; + std::vector<char>::iterator bufferLogBegin; + std::string lineBreak_; +}; + +} + + +#endif // PARSE_TXT_H_INCLUDED diff --git a/zen/scope_guard.h b/zen/scope_guard.h new file mode 100644 index 00000000..d3633284 --- /dev/null +++ b/zen/scope_guard.h @@ -0,0 +1,77 @@ +// ************************************************************************** +// * This file is part of the zenXML project. It is distributed under the * +// * Boost Software License, Version 1.0. See accompanying file * +// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. * +// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef ZEN_SCOPEGUARD_8971632487321434 +#define ZEN_SCOPEGUARD_8971632487321434 + +//best of Zen, Loki and C++11 + +namespace zen +{ +//Scope Guard +/* + zen::ScopeGuard lockAio = zen::makeGuard([&]() { ::CancelIo(hDir); }); + ... + lockAio.dismiss(); +*/ + +//Scope Exit +/* + ZEN_ON_BLOCK_EXIT(::CloseHandle(hDir)); +*/ + +class ScopeGuardBase +{ +public: + void dismiss() const { dismissed_ = true; } + +protected: + ScopeGuardBase() : dismissed_(false) {} + ScopeGuardBase(const ScopeGuardBase& other) : dismissed_(other.dismissed_) { other.dismissed_ = true; } //take over responsibility + ~ScopeGuardBase() {} + + bool isDismissed() const { return dismissed_; } + +private: + ScopeGuardBase& operator=(const ScopeGuardBase&); // = delete; + + mutable bool dismissed_; +}; + + +template <typename F> +class ScopeGuardImpl : public ScopeGuardBase +{ +public: + ScopeGuardImpl(F fun) : fun_(fun) {} + + ~ScopeGuardImpl() + { + if (!this->isDismissed()) + try + { + fun_(); + } + catch (...) {} + } + +private: + F fun_; +}; + +typedef const ScopeGuardBase& ScopeGuard; + +template <class F> inline +ScopeGuardImpl<F> makeGuard(F fun) { return ScopeGuardImpl<F>(fun); } +} + +#define ZEN_CONCAT_SUB(X, Y) X ## Y +#define ZEN_CONCAT(X, Y) ZEN_CONCAT_SUB(X, Y) + +#define ZEN_ON_BLOCK_EXIT(X) zen::ScopeGuard ZEN_CONCAT(dummy, __LINE__) = zen::makeGuard([&](){X;}); (void)ZEN_CONCAT(dummy, __LINE__); + +#endif //ZEN_SCOPEGUARD_8971632487321434 diff --git a/zen/stl_tools.h b/zen/stl_tools.h new file mode 100644 index 00000000..6cfe35f8 --- /dev/null +++ b/zen/stl_tools.h @@ -0,0 +1,49 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef STL_TOOLS_HEADER_84567184321434 +#define STL_TOOLS_HEADER_84567184321434 + +//no need to drag in any STL includes :) + +namespace zen +{ +template <class V, class Predicate> inline +void vector_remove_if(V& vec, Predicate p) +{ + vec.erase(std::remove_if(vec.begin(), vec.end(), p), vec.end()); +} + + +template <class S, class Predicate> inline +void set_remove_if(S& set, Predicate p) +{ + for (auto iter = set.begin(); iter != set.end();) + if (p(*iter)) + set.erase(iter++); + else + ++iter; +} + + +template <class M, class Predicate> inline +void map_remove_if(M& map, Predicate p) { set_remove_if(map, p); } + + +// binary search returning an iterator +template <class ForwardIterator, class T, typename Compare> inline +ForwardIterator custom_binary_search(ForwardIterator first, ForwardIterator last, const T& value, Compare comp) +{ + first = std::lower_bound(first, last, value, comp); + if (first != last && !comp(value, *first)) + return first; + else + return last; +} +} + + +#endif //STL_TOOLS_HEADER_84567184321434
\ No newline at end of file diff --git a/zen/string_base.h b/zen/string_base.h new file mode 100644 index 00000000..914e0434 --- /dev/null +++ b/zen/string_base.h @@ -0,0 +1,824 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef Z_BASE_H_INCLUDED +#define Z_BASE_H_INCLUDED + +#include <algorithm> +#include <cassert> +#include "string_tools.h" +#include <boost/detail/atomic_count.hpp> + +//Zbase - a policy based string class + + +/* +Allocator Policy: +----------------- + void* allocate(size_t size) //throw (std::bad_alloc) + void deallocate(void* ptr) + size_t calcCapacity(size_t length) +*/ +class AllocatorOptimalSpeed //exponential growth + min size +{ +public: + //::operator new/ ::operator delete show same performance characterisics like malloc()/free()! + static void* allocate(size_t size) { return ::operator new(size); } //throw (std::bad_alloc) + static void deallocate(void* ptr) { ::operator delete(ptr); } + static size_t calcCapacity(size_t length) { return std::max<size_t>(16, length + length / 2); } +}; + + +class AllocatorOptimalMemory //no wasted memory, but more reallocations required when manipulating string +{ +public: + static void* allocate(size_t size) { return ::operator new(size); } //throw (std::bad_alloc) + static void deallocate(void* ptr) { ::operator delete(ptr); } + static size_t calcCapacity(size_t length) { return length; } +}; + +/* +Storage Policy: +--------------- +template <typename T, //Character Type + class AP> //Allocator Policy + + T* create(size_t size) + T* create(size_t size, size_t minCapacity) + T* clone(T* ptr) + void destroy(T* ptr) + bool canWrite(const T* ptr, size_t minCapacity) //needs to be checked before writing to "ptr" + size_t length(const T* ptr) + void setLength(T* ptr, size_t newLength) +*/ + +template <typename T, //Character Type + class AP> //Allocator Policy +class StorageDeepCopy : public AP +{ +protected: + ~StorageDeepCopy() {} + + static T* create(size_t size) { return create(size, size); } + static T* create(size_t size, size_t minCapacity) + { + const size_t newCapacity = AP::calcCapacity(minCapacity); + assert(newCapacity >= minCapacity); + assert(minCapacity >= size); + + Descriptor* const newDescr = static_cast<Descriptor*>(AP::allocate(sizeof(Descriptor) + (newCapacity + 1) * sizeof(T))); + + newDescr->length = size; + newDescr->capacity = newCapacity; + + return reinterpret_cast<T*>(newDescr + 1); + } + + static T* clone(T* ptr) + { + T* newData = create(length(ptr)); + std::copy(ptr, ptr + length(ptr) + 1, newData); + return newData; + } + + static void destroy(T* ptr) { AP::deallocate(descr(ptr)); } + + //this needs to be checked before writing to "ptr" + static bool canWrite(const T* ptr, size_t minCapacity) { return minCapacity <= descr(ptr)->capacity; } + static size_t length(const T* ptr) { return descr(ptr)->length; } + + static void setLength(T* ptr, size_t newLength) + { + assert(canWrite(ptr, newLength)); + descr(ptr)->length = newLength; + } + +private: + struct Descriptor + { + size_t length; + size_t capacity; //allocated size without null-termination + }; + + static Descriptor* descr( T* ptr) { return reinterpret_cast< Descriptor*>(ptr) - 1; } + static const Descriptor* descr(const T* ptr) { return reinterpret_cast<const Descriptor*>(ptr) - 1; } +}; + + +template <typename T, //Character Type + class AP> //Allocator Policy +class StorageRefCountThreadSafe : public AP +{ +protected: + ~StorageRefCountThreadSafe() {} + + static T* create(size_t size) + { + return create(size, size); + } + + static T* create(size_t size, size_t minCapacity) + { + const size_t newCapacity = AP::calcCapacity(minCapacity); + assert(newCapacity >= minCapacity); + assert(minCapacity >= size); + + Descriptor* const newDescr = static_cast<Descriptor*>(AP::allocate(sizeof(Descriptor) + (newCapacity + 1) * sizeof(T))); + new (newDescr) Descriptor(1, size, newCapacity); + + return reinterpret_cast<T*>(newDescr + 1); + } + + static T* clone(T* ptr) + { + assert(descr(ptr)->refCount > 0); + ++descr(ptr)->refCount; + return ptr; + } + + static void destroy(T* ptr) + { + if (--descr(ptr)->refCount == 0) + { + descr(ptr)->~Descriptor(); + AP::deallocate(descr(ptr)); + } + } + + static bool canWrite(const T* ptr, size_t minCapacity) //needs to be checked before writing to "ptr" + { + assert(descr(ptr)->refCount > 0); + return descr(ptr)->refCount == 1 && minCapacity <= descr(ptr)->capacity; + } + + static size_t length(const T* ptr) + { + return descr(ptr)->length; + } + + static void setLength(T* ptr, size_t newLength) + { + assert(canWrite(ptr, newLength)); + descr(ptr)->length = newLength; + } + +private: + struct Descriptor + { + Descriptor(long rc, size_t len, size_t cap) : refCount(rc), length(len), capacity(cap) {} + + boost::detail::atomic_count refCount; //practically no perf loss: ~0.2%! (FFS comparison) + size_t length; + size_t capacity; //allocated size without null-termination + }; + + static Descriptor* descr( T* ptr) { return reinterpret_cast< Descriptor*>(ptr) - 1; } + static const Descriptor* descr(const T* ptr) { return reinterpret_cast<const Descriptor*>(ptr) - 1; } +}; +//################################################################################################################################################################ + + +//perf note: interstingly StorageDeepCopy and StorageRefCountThreadSafe show same performance in FFS comparison + +template <class T, //Character Type + template <class, class> class SP = StorageRefCountThreadSafe, //Storage Policy + class AP = AllocatorOptimalSpeed> //Allocator Policy +class Zbase : public SP<T, AP> +{ +public: + Zbase(); + Zbase(const T* source); //implicit conversion from a C-string + Zbase(const T* source, size_t length); + Zbase(const Zbase& source); + Zbase(Zbase&& tmp); + explicit Zbase(T source); //dangerous if implicit: T buffer[]; Zbase name = buffer; ups... + //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(); + + //operator const T* () const; //NO implicit conversion to a C-string!! Many problems... one of them: if we forget to provide operator overloads, it'll just work with a T*... + + //STL accessors + typedef T* iterator; + typedef const T* const_iterator; + typedef T& reference; + typedef const T& const_reference; + typedef T value_type; + const T* begin() const; + const T* end() const; + T* begin(); + T* end(); + + //std::string functions + size_t length() const; + size_t size() const; + const T* c_str() const; //C-string format with NULL-termination + const T* data() const; //internal representation, NULL-termination not guaranteed + const T operator[](size_t pos) const; + bool empty() const; + void clear(); + size_t find(const Zbase& str, size_t pos = 0) const; // + size_t find(const T* str, size_t pos = 0) const; //returns "npos" if not found + size_t find(T ch, size_t pos = 0) const; // + size_t rfind(T ch, size_t pos = npos) const; // + size_t rfind(const T* str, size_t pos = npos) const; // + Zbase& replace(size_t pos1, size_t n1, const Zbase& str); + void reserve(size_t minCapacity); + Zbase& assign(const T* source, size_t len); + void resize(size_t newSize, T fillChar = 0); + void swap(Zbase& other); + void push_back(T val); //STL access + + Zbase& operator=(const Zbase& source); + Zbase& operator=(Zbase&& tmp); + Zbase& operator=(const T* source); + Zbase& operator=(T source); + Zbase& operator+=(const Zbase& other); + Zbase& operator+=(const T* other); + Zbase& operator+=(T ch); + + static const size_t npos = static_cast<size_t>(-1); + +private: + Zbase(int); //detect usage errors + Zbase& operator=(int); // + + T* rawStr; +}; + +template <class T, template <class, class> class SP, class AP> bool operator==(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs); +template <class T, template <class, class> class SP, class AP> bool operator==(const Zbase<T, SP, AP>& lhs, const T* rhs); +template <class T, template <class, class> class SP, class AP> bool operator==(const T* lhs, const Zbase<T, SP, AP>& rhs); + +template <class T, template <class, class> class SP, class AP> bool operator!=(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs); +template <class T, template <class, class> class SP, class AP> bool operator!=(const Zbase<T, SP, AP>& lhs, const T* rhs); +template <class T, template <class, class> class SP, class AP> bool operator!=(const T* lhs, const Zbase<T, SP, AP>& rhs); + +template <class T, template <class, class> class SP, class AP> bool operator< (const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs); +template <class T, template <class, class> class SP, class AP> bool operator< (const Zbase<T, SP, AP>& lhs, const T* rhs); +template <class T, template <class, class> class SP, class AP> bool operator< (const T* lhs, const Zbase<T, SP, AP>& rhs); + +template <class T, template <class, class> class SP, class AP> const Zbase<T, SP, AP> operator+(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs); +template <class T, template <class, class> class SP, class AP> const Zbase<T, SP, AP> operator+(const Zbase<T, SP, AP>& lhs, const T* rhs); +template <class T, template <class, class> class SP, class AP> const Zbase<T, SP, AP> operator+(const T* lhs, const Zbase<T, SP, AP>& rhs); +template <class T, template <class, class> class SP, class AP> const Zbase<T, SP, AP> operator+( T lhs, const Zbase<T, SP, AP>& rhs); +template <class T, template <class, class> class SP, class AP> const Zbase<T, SP, AP> operator+(const Zbase<T, SP, AP>& lhs, T rhs); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//################################# inline implementation ######################################## +template <class T, template <class, class> class SP, class AP> inline +Zbase<T, SP, AP>::Zbase() +{ + //resist the temptation to avoid this allocation by referening a static global: NO performance advantage, MT issues! + rawStr = this->create(0); + rawStr[0] = 0; +} + + +template <class T, template <class, class> class SP, class AP> inline +Zbase<T, SP, AP>::Zbase(T source) +{ + rawStr = this->create(1); + rawStr[0] = source; + rawStr[1] = 0; +} + + +template <class T, template <class, class> class SP, class AP> inline +Zbase<T, SP, AP>::Zbase(const T* source) +{ + const size_t sourceLen = zen::strLength(source); + rawStr = this->create(sourceLen); + std::copy(source, source + sourceLen + 1, rawStr); //include null-termination +} + + +template <class T, template <class, class> class SP, class AP> inline +Zbase<T, SP, AP>::Zbase(const T* source, size_t sourceLen) +{ + rawStr = this->create(sourceLen); + std::copy(source, source + sourceLen, rawStr); + rawStr[sourceLen] = 0; +} + + +template <class T, template <class, class> class SP, class AP> inline +Zbase<T, SP, AP>::Zbase(const Zbase<T, SP, AP>& source) +{ + rawStr = this->clone(source.rawStr); +} + + +template <class T, template <class, class> class SP, class AP> inline +Zbase<T, SP, AP>::Zbase(Zbase<T, SP, AP>&& tmp) +{ + rawStr = this->clone(tmp.rawStr); //for a ref-counting string there probably isn't a faster way, even with r-value references +} + + +template <class T, template <class, class> class SP, class AP> +template <class S> inline +Zbase<T, SP, AP>::Zbase(const S& other, typename S::value_type) +{ + const size_t sourceLen = other.size(); + rawStr = this->create(sourceLen); + std::copy(other.c_str(), other.c_str() + sourceLen, rawStr); + rawStr[sourceLen] = 0; +} + + +template <class T, template <class, class> class SP, class AP> inline +Zbase<T, SP, AP>::~Zbase() +{ + this->destroy(rawStr); +} + + +template <class T, template <class, class> class SP, class AP> inline +size_t Zbase<T, SP, AP>::find(const Zbase& str, size_t pos) const +{ + assert(pos <= length()); + const T* thisEnd = end(); //respect embedded 0 + const T* iter = std::search(begin() + pos, thisEnd, + str.begin(), str.end()); + return iter == thisEnd ? npos : iter - begin(); +} + + +template <class T, template <class, class> class SP, class AP> inline +size_t Zbase<T, SP, AP>::find(const T* str, size_t pos) const +{ + assert(pos <= length()); + const T* thisEnd = end(); //respect embedded 0 + const T* iter = std::search(begin() + pos, thisEnd, + str, str + zen::strLength(str)); + return iter == thisEnd ? npos : iter - begin(); +} + + +template <class T, template <class, class> class SP, class AP> inline +size_t Zbase<T, SP, AP>::find(T ch, size_t pos) const +{ + assert(pos <= length()); + const T* thisEnd = end(); + const T* iter = std::find(begin() + pos, thisEnd, ch); //respect embedded 0 + return iter == thisEnd ? npos : iter - begin(); +} + + +template <class T, template <class, class> class SP, class AP> inline +size_t Zbase<T, SP, AP>::rfind(T ch, size_t pos) const +{ + assert(pos == npos || pos <= length()); + + const size_t thisLen = length(); + if (thisLen == 0) return npos; + pos = std::min(thisLen - 1, pos); //handle "npos" and "pos == length()" implicitly + + while (rawStr[pos] != ch) //pos points to last char of the string + { + if (pos == 0) + return npos; + --pos; + } + return pos; +} + + +template <class T, template <class, class> class SP, class AP> inline +size_t Zbase<T, SP, AP>::rfind(const T* str, size_t pos) const +{ + assert(pos == npos || pos <= length()); + + const size_t strLen = zen::strLength(str); + const T* currEnd = pos == npos ? end() : begin() + std::min(pos + strLen, length()); + + const T* iter = std::find_end(begin(), currEnd, + str, str + strLen); + return iter == currEnd ? npos : iter - begin(); +} + + +template <class T, template <class, class> class SP, class AP> +Zbase<T, SP, AP>& Zbase<T, SP, AP>::replace(size_t pos1, size_t n1, const Zbase& str) +{ + assert(str.data() < rawStr || rawStr + length() < str.data()); //str mustn't point to data in this string + assert(pos1 + n1 <= length()); + + const size_t n2 = str.length(); + + const size_t oldLen = length(); + if (oldLen == 0) + return *this = str; + + const size_t newLen = oldLen - n1 + n2; + + if (canWrite(rawStr, newLen)) + { + if (n1 < n2) //move remainder right -> std::copy_backward + { + std::copy_backward(rawStr + pos1 + n1, rawStr + oldLen + 1, rawStr + newLen + 1); //include null-termination + setLength(rawStr, newLen); + } + else if (n1 > n2) //shift left -> std::copy + { + std::copy(rawStr + pos1 + n1, rawStr + oldLen + 1, rawStr + pos1 + n2); //include null-termination + setLength(rawStr, newLen); + } + + std::copy(str.data(), str.data() + n2, rawStr + pos1); + } + else + { + //copy directly into new string + T* const newStr = this->create(newLen); + + std::copy(rawStr, rawStr + pos1, newStr); + std::copy(str.data(), str.data() + n2, newStr + pos1); + std::copy(rawStr + pos1 + n1, rawStr + oldLen + 1, newStr + pos1 + n2); //include null-termination + + destroy(rawStr); + rawStr = newStr; + } + return *this; +} + + +template <class T, template <class, class> class SP, class AP> inline +void Zbase<T, SP, AP>::resize(size_t newSize, T fillChar) +{ + if (canWrite(rawStr, newSize)) + { + if (length() < newSize) + std::fill(rawStr + length(), rawStr + newSize, fillChar); + rawStr[newSize] = 0; + setLength(rawStr, newSize); //keep after call to length() + } + else + { + T* newStr = this->create(newSize); + newStr[newSize] = 0; + + if (length() < newSize) + { + std::copy(rawStr, rawStr + length(), newStr); + std::fill(newStr + length(), newStr + newSize, fillChar); + } + else + std::copy(rawStr, rawStr + newSize, newStr); + + destroy(rawStr); + rawStr = newStr; + } +} + + +template <class T, template <class, class> class SP, class AP> inline +bool operator==(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs) +{ + return lhs.length() == rhs.length() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); //respect embedded 0 +} + + +template <class T, template <class, class> class SP, class AP> inline +bool operator==(const Zbase<T, SP, AP>& lhs, const T* rhs) +{ + return lhs.length() == zen::strLength(rhs) && std::equal(lhs.begin(), lhs.end(), rhs); //respect embedded 0 +} + + +template <class T, template <class, class> class SP, class AP> inline +bool operator==(const T* lhs, const Zbase<T, SP, AP>& rhs) +{ + return operator==(rhs, lhs); +} + + +template <class T, template <class, class> class SP, class AP> inline +bool operator!=(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs) +{ + return !operator==(lhs, rhs); +} + + +template <class T, template <class, class> class SP, class AP> inline +bool operator!=(const Zbase<T, SP, AP>& lhs, const T* rhs) +{ + return !operator==(lhs, rhs); +} + + +template <class T, template <class, class> class SP, class AP> inline +bool operator!=(const T* lhs, const Zbase<T, SP, AP>& rhs) +{ + return !operator==(lhs, rhs); +} + + +template <class T, template <class, class> class SP, class AP> inline +bool operator<(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs) +{ + return std::lexicographical_compare(lhs.begin(), lhs.end(), //respect embedded 0 + rhs.begin(), rhs.end()); +} + + +template <class T, template <class, class> class SP, class AP> inline +bool operator<(const Zbase<T, SP, AP>& lhs, const T* rhs) +{ + return std::lexicographical_compare(lhs.begin(), lhs.end(), //respect embedded 0 + rhs, rhs + zen::strLength(rhs)); +} + + +template <class T, template <class, class> class SP, class AP> inline +bool operator<(const T* lhs, const Zbase<T, SP, AP>& rhs) +{ + return std::lexicographical_compare(lhs, lhs + zen::strLength(lhs), //respect embedded 0 + rhs.begin(), rhs.end()); +} + + +template <class T, template <class, class> class SP, class AP> inline +size_t Zbase<T, SP, AP>::length() const +{ + return SP<T, AP>::length(rawStr); +} + + +template <class T, template <class, class> class SP, class AP> inline +size_t Zbase<T, SP, AP>::size() const +{ + return length(); +} + + +template <class T, template <class, class> class SP, class AP> inline +const T* Zbase<T, SP, AP>::c_str() const +{ + return rawStr; +} + + +template <class T, template <class, class> class SP, class AP> inline +const T* Zbase<T, SP, AP>::data() const +{ + return rawStr; +} + + +template <class T, template <class, class> class SP, class AP> inline +const T Zbase<T, SP, AP>::operator[](size_t pos) const +{ + assert(pos < length()); + return rawStr[pos]; +} + + +template <class T, template <class, class> class SP, class AP> inline +const T* Zbase<T, SP, AP>::begin() const +{ + return rawStr; +} + + +template <class T, template <class, class> class SP, class AP> inline +const T* Zbase<T, SP, AP>::end() const +{ + return rawStr + length(); +} + + +template <class T, template <class, class> class SP, class AP> inline +T* Zbase<T, SP, AP>::begin() +{ + reserve(length()); + return rawStr; +} + + +template <class T, template <class, class> class SP, class AP> inline +T* Zbase<T, SP, AP>::end() +{ + return begin() + length(); +} + + +template <class T, template <class, class> class SP, class AP> inline +void Zbase<T, SP, AP>::push_back(T val) +{ + operator+=(val); +} + + +template <class T, template <class, class> class SP, class AP> inline +bool Zbase<T, SP, AP>::empty() const +{ + return length() == 0; +} + + +template <class T, template <class, class> class SP, class AP> inline +void Zbase<T, SP, AP>::clear() +{ + if (!empty()) + { + if (canWrite(rawStr, 0)) + { + rawStr[0] = 0; //keep allocated memory + setLength(rawStr, 0); // + } + else + *this = Zbase(); + } +} + + +template <class T, template <class, class> class SP, class AP> inline +const Zbase<T, SP, AP> operator+(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs) +{ + return Zbase<T, SP, AP>(lhs) += rhs; +} + + +template <class T, template <class, class> class SP, class AP> inline +const Zbase<T, SP, AP> operator+(const Zbase<T, SP, AP>& lhs, const T* rhs) +{ + return Zbase<T, SP, AP>(lhs) += rhs; +} + + +template <class T, template <class, class> class SP, class AP> inline +const Zbase<T, SP, AP> operator+(const T* lhs, const Zbase<T, SP, AP>& rhs) +{ + return Zbase<T, SP, AP>(lhs) += rhs; +} + + +template <class T, template <class, class> class SP, class AP> inline +const Zbase<T, SP, AP> operator+(T lhs, const Zbase<T, SP, AP>& rhs) +{ + return (Zbase<T, SP, AP>() += lhs) += rhs; +} + + +template <class T, template <class, class> class SP, class AP> inline +const Zbase<T, SP, AP> operator+(const Zbase<T, SP, AP>& lhs, T rhs) +{ + return Zbase<T, SP, AP>(lhs) += rhs; +} + + +template <class T, template <class, class> class SP, class AP> inline +void Zbase<T, SP, AP>::swap(Zbase<T, SP, AP>& other) +{ + std::swap(rawStr, other.rawStr); +} + + +template <class T, template <class, class> class SP, class AP> inline +void Zbase<T, SP, AP>::reserve(size_t minCapacity) //make unshared and check capacity +{ + if (!canWrite(rawStr, minCapacity)) + { + //allocate a new string + T* newStr = create(length(), std::max(minCapacity, length())); //reserve() must NEVER shrink the string: logical const! + std::copy(rawStr, rawStr + length() + 1, newStr); //include NULL-termination + + destroy(rawStr); + rawStr = newStr; + } +} + + +template <class T, template <class, class> class SP, class AP> inline +Zbase<T, SP, AP>& Zbase<T, SP, AP>::assign(const T* source, size_t len) +{ + if (canWrite(rawStr, len)) + { + std::copy(source, source + len, rawStr); + rawStr[len] = 0; //include null-termination + setLength(rawStr, len); + } + else + *this = Zbase(source, len); + + return *this; +} + + +template <class T, template <class, class> class SP, class AP> inline +Zbase<T, SP, AP>& Zbase<T, SP, AP>::operator=(const Zbase<T, SP, AP>& source) +{ + Zbase(source).swap(*this); + return *this; +} + + +template <class T, template <class, class> class SP, class AP> inline +Zbase<T, SP, AP>& Zbase<T, SP, AP>::operator=(Zbase<T, SP, AP>&& tmp) +{ + swap(tmp); + return *this; +} + + +template <class T, template <class, class> class SP, class AP> inline +Zbase<T, SP, AP>& Zbase<T, SP, AP>::operator=(const T* source) +{ + return assign(source, zen::strLength(source)); +} + + +template <class T, template <class, class> class SP, class AP> inline +Zbase<T, SP, AP>& Zbase<T, SP, AP>::operator=(T source) +{ + if (canWrite(rawStr, 1)) + { + rawStr[0] = source; + rawStr[1] = 0; //include null-termination + setLength(rawStr, 1); + } + else + *this = Zbase(source); + + return *this; +} + + +template <class T, template <class, class> class SP, class AP> inline +Zbase<T, SP, AP>& Zbase<T, SP, AP>::operator+=(const Zbase<T, SP, AP>& other) +{ + const size_t thisLen = length(); + const size_t otherLen = other.length(); + reserve(thisLen + otherLen); //make unshared and check capacity + + std::copy(other.rawStr, other.rawStr + otherLen + 1, rawStr + thisLen); //include null-termination + setLength(rawStr, thisLen + otherLen); + return *this; +} + + +template <class T, template <class, class> class SP, class AP> inline +Zbase<T, SP, AP>& Zbase<T, SP, AP>::operator+=(const T* other) +{ + const size_t thisLen = length(); + const size_t otherLen = zen::strLength(other); + reserve(thisLen + otherLen); //make unshared and check capacity + + std::copy(other, other + otherLen + 1, rawStr + thisLen); //include null-termination + setLength(rawStr, thisLen + otherLen); + return *this; +} + + +template <class T, template <class, class> class SP, class AP> inline +Zbase<T, SP, AP>& Zbase<T, SP, AP>::operator+=(T ch) +{ + const size_t thisLen = length(); + reserve(thisLen + 1); //make unshared and check capacity + rawStr[thisLen] = ch; + rawStr[thisLen + 1] = 0; + setLength(rawStr, thisLen + 1); + return *this; +} + +#endif //Z_BASE_H_INCLUDED diff --git a/zen/string_tools.h b/zen/string_tools.h new file mode 100644 index 00000000..8cafad07 --- /dev/null +++ b/zen/string_tools.h @@ -0,0 +1,569 @@ +// ************************************************************************** +// * This file is part of the zenXML project. It is distributed under the * +// * Boost Software License, Version 1.0. See accompanying file * +// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. * +// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef STRING_TOOLS_HEADER_213458973046 +#define STRING_TOOLS_HEADER_213458973046 + +#include <cctype> //isspace +#include <cwctype> //iswspace +#include <cstdio> //sprintf +#include <cwchar> //swprintf +#include <algorithm> +#include <cassert> +#include <sstream> +#include <vector> +#include "string_traits.h" +#include "type_traits.h" + + +//enhance arbitray string class with useful non-member functions: +namespace zen +{ +template <class C> bool cStringIsWhiteSpace(C ch); +template <class C> bool cStringIsDigit(C ch); + +template <class S, class T> bool startsWith(const S& str, const T& prefix); //both S and T can be strings or char/wchar_t arrays or simple char/wchar_t +template <class S, class T> bool endsWith (const S& str, const T& postfix); // + +template <class S, class T> S afterLast (const S& str, const T& ch); //returns the whole string if ch not found +template <class S, class T> S beforeLast (const S& str, const T& ch); //returns empty string if ch not found +template <class S, class T> S afterFirst (const S& str, const T& ch); //returns empty string if ch not found +template <class S, class T> S beforeFirst(const S& str, const T& ch); //returns the whole string if ch not found + +template <class S, class T> std::vector<S> split(const S& str, const T& delimiter); +template <class S> void truncate(S& str, size_t newLen); +template <class S> void trim(S& str, bool fromLeft = true, bool fromRight = true); +template <class S, class T, class U> void replace ( S& str, const T& oldOne, const U& newOne, bool replaceAll = true); +template <class S, class T, class U> S replaceCpy(const S& str, const T& oldOne, const U& newOne, bool replaceAll = true); + +//high-performance conversion from numbers to strings +template <class S, class T, class Num> S printNumber(const T& format, const Num& number); //format a single number using ::sprintf + +template <class S, class Num> S toString(const Num& number); +template <class Num, class S > Num toNumber(const S& str); + +//string to string conversion: converst string-like type into char-compatible target string class +template <class T, class S> T cvrtString(const S& str); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//---------------------- implementation ---------------------- +template <> inline +bool cStringIsWhiteSpace(char ch) +{ + //caveat 1: std::isspace() takes an int, but expects an unsigned char + //caveat 2: some parts of UTF-8 chars are erroneously seen as whitespace, e.g. the a0 from "\xec\x8b\xa0" (MSVC) + return static_cast<unsigned char>(ch) < 128 && + std::isspace(static_cast<unsigned char>(ch)) != 0; +} + +//template <> inline bool cStringIsWhiteSpace(unsigned char ch) { return cStringIsWhiteSpace<char>(ch); } -> not character types! +//template <> inline bool cStringIsWhiteSpace(signed char ch) { return cStringIsWhiteSpace<char>(ch); } +template <> inline bool cStringIsWhiteSpace(wchar_t ch) { return std::iswspace(ch) != 0; } + +template <> inline +bool cStringIsDigit(char ch) +{ + return std::isdigit(static_cast<unsigned char>(ch)) != 0; //caveat: takes an int, but expects an unsigned char +} + + +template <> inline +bool cStringIsDigit(wchar_t ch) +{ + return std::iswdigit(ch) != 0; +} + + +template <class S, class T> inline +bool startsWith(const S& str, const T& prefix) +{ + assert_static(StringTraits<S>::isStringLike); + assert_static(StringTraits<T>::isStringLike); + + const size_t pfLength = strLength(prefix); + if (strLength(str) < pfLength) + return false; + + return std::equal(strBegin(str), strBegin(str) + pfLength, + strBegin(prefix)); +} + + +template <class S, class T> inline +bool endsWith(const S& str, const T& postfix) +{ + assert_static(StringTraits<S>::isStringLike); + assert_static(StringTraits<T>::isStringLike); + + size_t strLen = strLength(str); + size_t pfLen = strLength(postfix); + if (strLen < pfLen) + return false; + + typedef typename StringTraits<S>::CharType CharType; + + const CharType* cmpBegin = strBegin(str) + strLen - pfLen; + return std::equal(cmpBegin, cmpBegin + pfLen, + strBegin(postfix)); +} + + +//returns the whole string if ch not found +template <class S, class T> inline +S afterLast(const S& str, const T& ch) +{ + assert_static(StringTraits<T>::isStringLike); + + const size_t pos = str.rfind(ch); + if (pos != S::npos) + { + size_t chLen = strLength(ch); + return S(str.c_str() + pos + chLen, str.length() - pos - chLen); + } + else + return str; +} + + +//returns empty string if ch not found +template <class S, class T> inline +S beforeLast(const S& str, const T& ch) +{ + assert_static(StringTraits<T>::isStringLike); + + const size_t pos = str.rfind(ch); + if (pos != S::npos) + return S(str.c_str(), pos); //data is non-empty string in this context: else ch would not have been found! + else + return S(); +} + + +//returns empty string if ch not found +template <class S, class T> inline +S afterFirst(const S& str, const T& ch) +{ + assert_static(StringTraits<T>::isStringLike); + + const size_t pos = str.find(ch); + if (pos != S::npos) + { + size_t chLen = strLength(ch); + return S(str.c_str() + pos + chLen, str.length() - pos - chLen); + } + else + return S(); + +} + + +//returns the whole string if ch not found +template <class S, class T> inline +S beforeFirst(const S& str, const T& ch) +{ + assert_static(StringTraits<T>::isStringLike); + + const size_t pos = str.find(ch); + if (pos != S::npos) + return S(str.c_str(), pos); //data is non-empty string in this context: else ch would not have been found! + else + return str; +} + + +template <class S, class T> inline +std::vector<S> split(const S& str, const T& delimiter) +{ + assert_static(StringTraits<T>::isStringLike); + + std::vector<S> output; + size_t bockStart = 0; + size_t delimLen = strLength(delimiter); + if (delimLen != 0) + { + for (size_t blockEnd = str.find(delimiter, bockStart); + blockEnd != S::npos; + bockStart = blockEnd + delimLen, blockEnd = str.find(delimiter, bockStart)) + { + output.push_back(S(str.c_str() + bockStart, blockEnd - bockStart)); + } + } + output.push_back(S(str.c_str() + bockStart, str.length() - bockStart)); + return output; +} + + +template <class S> inline +void truncate(S& str, size_t newLen) +{ + if (newLen < str.length()) + str.resize(newLen); +} + + +template <class S, class T, class U> inline +S replaceCpy(const S& str, const T& oldOne, const U& newOne, bool replaceAll) +{ + assert_static(StringTraits<T>::isStringLike); + assert_static(StringTraits<U>::isStringLike); + + typedef typename StringTraits<S>::CharType CharType; + + const size_t oldLen = strLength(oldOne); + const size_t newLen = strLength(newOne); + + S output; + + const CharType* strPos = strBegin(str); + const CharType* strEnd = strPos + strLength(str); + + for (;;) + { + const CharType* ptr = std::search(strPos, strEnd, + strBegin(oldOne), strBegin(oldOne) + oldLen); + if (ptr == strEnd) + break; + + output += S(strPos, ptr - strPos); + output += S(strBegin(newOne), newLen); + + strPos = ptr + oldLen; + + if (!replaceAll) + break; + } + output += S(strPos, strEnd - strPos); + + return output; +} + + +template <class S, class T, class U> inline +void replace(S& str, const T& oldOne, const U& newOne, bool replaceAll) +{ + str = replaceCpy(str, oldOne, newOne, replaceAll); +} + + +template <class S> inline +void trim(S& str, bool fromLeft, bool fromRight) +{ + assert(fromLeft || fromRight); + + typedef typename S::value_type CharType; + + const CharType* newBegin = str.c_str(); + const CharType* newEnd = str.c_str() + str.length(); + + if (fromRight) + while (newBegin != newEnd && cStringIsWhiteSpace(newEnd[-1])) + --newEnd; + + if (fromLeft) + while (newBegin != newEnd && cStringIsWhiteSpace(*newBegin)) + ++newBegin; + + const size_t newLength = newEnd - newBegin; + if (newLength != str.length()) + { + if (newBegin != str.c_str()) + str = S(newBegin, newLength); //minor inefficiency: in case "str" is not shared, we could save an allocation and do a memory move only + else + str.resize(newLength); + } +} + + +namespace implementation +{ +template <class S, class T> +struct CnvtStringToString +{ + T convert(const S& src) const { return T(strBegin(src), strLength(src)); } +}; + +template <class S> +struct CnvtStringToString<S, S> //perf: we don't need a deep copy if string types match +{ + const S& convert(const S& src) const { return src; } +}; +} + +template <class T, class S> inline +T cvrtString(const S& str) { return implementation::CnvtStringToString<S, T>().convert(str); } + + +namespace implementation +{ +template <class Num> inline +int saferPrintf(char* buffer, size_t bufferSize, const char* format, const Num& number) //there is no such thing as a "safe" printf ;) +{ +#ifdef _MSC_VER + return ::_snprintf(buffer, bufferSize, format, number); //VS2010 doesn't respect ISO C +#else + return std::snprintf(buffer, bufferSize, format, number); //C99 +#endif +} + +template <class Num> inline +int saferPrintf(wchar_t* buffer, size_t bufferSize, const wchar_t* format, const Num& number) +{ +#ifdef __MINGW32__ //MinGW doesn't respect ISO C + return ::snwprintf(buffer, bufferSize, format, number); +#else + return std::swprintf(buffer, bufferSize, format, number); //C99 +#endif +} +} + +template <class S, class T, class Num> inline +S printNumber(const T& format, const Num& number) //format a single number using ::sprintf +{ + assert_static(StringTraits<T>::isStringLike); + assert_static((IsSameType< + typename StringTraits<S>::CharType, + typename StringTraits<T>::CharType>::result)); + + typedef typename StringTraits<S>::CharType CharType; + + const int BUFFER_SIZE = 128; + CharType buffer[BUFFER_SIZE]; + const int charsWritten = implementation::saferPrintf(buffer, BUFFER_SIZE, format, number); + + return charsWritten > 0 ? S(buffer, charsWritten) : S(); +} + + +namespace implementation +{ +enum NumberType +{ + NUM_TYPE_SIGNED_INT, + NUM_TYPE_UNSIGNED_INT, + NUM_TYPE_FLOATING_POINT, + NUM_TYPE_OTHER, +}; + + +template <class S, class Num, NumberType> +struct CvrtNumberToString +{ + S convert(const Num& number) const //default number to string conversion using streams: convenient, but SLOW, SLOW, SLOW!!!! (~ factor of 20) + { + typedef typename StringTraits<S>::CharType CharType; + + std::basic_ostringstream<CharType> ss; + ss << number; + return cvrtString<S>(ss.str()); + } +}; + + +template <class S, class Num> +struct CvrtNumberToString<S, Num, NUM_TYPE_FLOATING_POINT> +{ + S convert(const Num& number) const { return convertFloat(number, typename StringTraits<S>::CharType()); } + +private: + S convertFloat(const Num& number, char ) const { return printNumber<S>( "%g", static_cast<double>(number)); } + S convertFloat(const Num& number, wchar_t) const { return printNumber<S>(L"%g", static_cast<double>(number)); } +}; + +/* +perf: integer to string: (executed 10 mio. times) + std::stringstream - 14796 ms + std::sprintf - 3086 ms + formatInteger - 778 ms +*/ + +template <class S, class Num> inline +S formatInteger(Num n, bool hasMinus) +{ + assert(n >= 0); + S output; + do + { + output += '0' + n % 10; + n /= 10; + } + while (n != 0); + if (hasMinus) + output += '-'; + + std::reverse(output.begin(), output.end()); + return output; +} + +template <class S, class Num> +struct CvrtNumberToString<S, Num, NUM_TYPE_SIGNED_INT> +{ + S convert(const Num& number) const { return formatInteger<S>(number < 0 ? -number : number, number < 0); } +}; + +template <class S, class Num> +struct CvrtNumberToString<S, Num, NUM_TYPE_UNSIGNED_INT> +{ + S convert(const Num& number) const { return formatInteger<S>(number, false); } +}; + +//-------------------------------------------------------------------------------- + +template <class S, class Num, NumberType> +struct CvrtStringToNumber +{ + Num convert(const S& str) const //default string to number conversion using streams: convenient, but SLOW + { + typedef typename StringTraits<S>::CharType CharType; + Num number = 0; + std::basic_istringstream<CharType>(cvrtString<std::basic_string<CharType> >(str)) >> number; + return number; + } +}; + + +template <class S, class Num> +struct CvrtStringToNumber<S, Num, NUM_TYPE_FLOATING_POINT> +{ + Num convert(const S& str) const { return convertFloat(strBegin(str)); } + +private: + Num convertFloat(const char* str) const { return std::strtod(str, NULL); } + Num convertFloat(const wchar_t* str) const { return std::wcstod(str, NULL); } +}; + +template <class Num, class S> +Num extractInteger(const S& str, bool& hasMinusSign) //very fast conversion to integers: slightly faster than std::atoi, but more importantly: generic +{ + typedef typename StringTraits<S>::CharType CharType; + + const CharType* first = strBegin(str); + const CharType* last = first + strLength(str); + + while (first != last && cStringIsWhiteSpace(*first)) //skip leading whitespace + ++first; + + hasMinusSign = false; //handle minus sign + if (first != last) + { + if (*first == '-') + { + hasMinusSign = true; + ++first; + } + else if (*first == '+') + ++first; + } + + Num number = 0; + for (const CharType* iter = first; iter != last; ++iter) + { + const CharType c = *iter; + if ('0' <= c && c <= '9') + { + number *= 10; + number += c - '0'; + } + else + { + assert(std::find_if(iter, last, std::not1(std::ptr_fun(&cStringIsWhiteSpace<CharType>))) == last); //rest of string should contain whitespace only + break; + } + } + return number; +} + + +template <class S, class Num> +struct CvrtStringToNumber<S, Num, NUM_TYPE_SIGNED_INT> +{ + Num convert(const S& str) const + { + bool hasMinusSign = false; //handle minus sign + const Num number = extractInteger<Num>(str, hasMinusSign); + return hasMinusSign ? -number : number; + } +}; + + +template <class S, class Num> +struct CvrtStringToNumber<S, Num, NUM_TYPE_UNSIGNED_INT> +{ + Num convert(const S& str) const //very fast conversion to integers: slightly faster than std::atoi, but more importantly: generic + { + bool hasMinusSign = false; //handle minus sign + const Num number = extractInteger<Num>(str, hasMinusSign); + if (hasMinusSign) + { + assert(false); + return 0U; + } + return number; + } +}; +} + + +template <class S, class Num> +inline +S toString(const Num& number) //convert number to string the C++ way +{ + using namespace implementation; + return CvrtNumberToString<S, Num, + IsSignedInt <Num>::result ? NUM_TYPE_SIGNED_INT : + IsUnsignedInt<Num>::result ? NUM_TYPE_UNSIGNED_INT : + IsFloat <Num>::result ? NUM_TYPE_FLOATING_POINT : + NUM_TYPE_OTHER + >().convert(number); +} + + +template <class Num, class S> +inline +Num toNumber(const S& str) //convert string to number the C++ way +{ + using namespace implementation; + return CvrtStringToNumber<S, Num, + IsSignedInt <Num>::result ? NUM_TYPE_SIGNED_INT : + IsUnsignedInt<Num>::result ? NUM_TYPE_UNSIGNED_INT : + IsFloat <Num>::result ? NUM_TYPE_FLOATING_POINT : + NUM_TYPE_OTHER + >().convert(str); +} + +} + +#endif //STRING_TOOLS_HEADER_213458973046 diff --git a/zen/string_traits.h b/zen/string_traits.h new file mode 100644 index 00000000..59da2f79 --- /dev/null +++ b/zen/string_traits.h @@ -0,0 +1,176 @@ +// ************************************************************************** +// * This file is part of the zenXML project. It is distributed under the * +// * Boost Software License, Version 1.0. See accompanying file * +// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. * +// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef STRING_TRAITS_HEADER_813274321443234 +#define STRING_TRAITS_HEADER_813274321443234 + +#include "type_tools.h" +#include "assert_static.h" + + +//uniform access to string-like types: classes and character arrays +namespace zen +{ +/* +strBegin(): + std::wstring str(L"dummy"); + char array[] = "dummy"; + const wchar_t* iter = strBegin(str); //returns str.c_str() + const char* iter2 = strBegin(array); //returns array + +strLength(): + strLength(str); //equals str.size() + strLength(array); //equals cStringLength(array) + +StringTraits<>::CharType: + StringTraits<std::wstring>::CharType //equals wchar_t + StringTraits<wchar_t[5]> ::CharType //equals wchar_t + +StringTraits<>::isStringLike: + StringTraits<const wchar_t*>::isStringLike; //equals "true" + StringTraits<const int*> ::isStringLike; //equals "false" + +StringTraits<>::isStringClass: + StringTraits<std::wstring>::isStringClass //equals "true" + StringTraits<wchar_t[5]> ::isStringClass //equals "false" +*/ + + + + + + + + + + + + + +//---------------------- implementation ---------------------- +namespace implementation +{ +template<typename T> +class HasValueTypedef +{ + typedef char Yes[1]; + typedef char No [2]; + + template <typename U> class HelperTp {}; + + //detect presence of a member type called value_type + template <class U> static Yes& hasMemberValueType(HelperTp<typename U::value_type>*); + template <class U> static No& hasMemberValueType(...); + +public: + enum { result = sizeof(hasMemberValueType<T>(NULL)) == sizeof(Yes) + }; +}; + + +template<typename T, bool isClassType> +class HasStringMembers +{ +public: + enum { result = false }; +}; + +template<typename T> +class HasStringMembers<T, true> +{ + typedef char Yes[1]; + typedef char No [2]; + + //detect presence of member functions (without specific restriction on return type, within T or one of it's base classes) + template <typename U, U t> class HelperFn {}; + + struct Fallback + { + int c_str; + int length; + }; + + template <class U> + struct Helper2 : public U, public Fallback {}; //U must be a class-type! + + //we don't know the exact declaration of the member attribute (may be in base class), but we know what NOT to expect: + template <class U> static No& hasMemberCstr(HelperFn<int Fallback::*, &Helper2<U>::c_str>*); + template <class U> static Yes& hasMemberCstr(...); + + template <class U> static No& hasMemberLength(HelperFn<int Fallback::*, &Helper2<U>::length>*); + template <class U> static Yes& hasMemberLength(...); +public: + enum { result = sizeof(hasMemberCstr <T>(NULL)) == sizeof(Yes) && + sizeof(hasMemberLength<T>(NULL)) == sizeof(Yes) + }; +}; + +template <class S, bool isStringClass> struct StringTraits2 { typedef EmptyType Result; }; //"StringTraits2": fix some VS bug with namespace and partial template specialization + +template <class S> struct StringTraits2<S, true > { typedef typename S::value_type Result; }; +template <> struct StringTraits2<char, false> { typedef char Result; }; +template <> struct StringTraits2<wchar_t, false> { typedef wchar_t Result; }; +} + + +template <class S> +struct StringTraits +{ +private: + typedef typename RemoveRef <S >::Result NonRefType; + typedef typename RemoveConst <NonRefType >::Result NonConstType; + typedef typename RemoveArray <NonConstType>::Result NonArrayType; + typedef typename RemovePointer<NonArrayType>::Result NonPtrType; + typedef typename RemoveConst <NonPtrType >::Result UndecoratedType; //handle "const char* const" +public: + enum + { + isStringClass = implementation::HasStringMembers<NonConstType, implementation::HasValueTypedef<NonConstType>::result>::result + }; + + typedef typename implementation::StringTraits2<UndecoratedType, isStringClass>::Result CharType; + + enum + { + isStringLike = IsSameType<CharType, char>::result || IsSameType<CharType, wchar_t>::result + }; +}; + + +namespace implementation +{ +template <class C> inline +size_t cStringLength(const C* str) //strlen() +{ + assert_static((IsSameType<C, char>::result || IsSameType<C, wchar_t>::result)); + size_t len = 0; + while (*str++ != 0) + ++len; + return len; +} +} + + +template <class S> inline +const typename StringTraits<S>::CharType* strBegin(const S& str, typename S::value_type dummy = 0) { return str.c_str(); } //SFINAE: T must be a "string" + +template <class Char> +inline const typename StringTraits<Char>::CharType* strBegin(const Char* str) { return str; } +inline const char* strBegin(const char& ch) { return &ch; } +inline const wchar_t* strBegin(const wchar_t& ch) { return &ch; } + + +template <class S> inline +size_t strLength(const S& str, typename S::value_type dummy = 0) { return str.length(); } //SFINAE: T must be a "string" + +template <class Char> +inline size_t strLength(const Char* str) { return implementation::cStringLength(str); } +inline size_t strLength(char) { return 1; } +inline size_t strLength(wchar_t) { return 1; } +} + +#endif //STRING_TRAITS_HEADER_813274321443234 diff --git a/zen/symlink_target.h b/zen/symlink_target.h new file mode 100644 index 00000000..3704eebe --- /dev/null +++ b/zen/symlink_target.h @@ -0,0 +1,140 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef SYMLINK_WIN_H_INCLUDED +#define SYMLINK_WIN_H_INCLUDED + +#include "scope_guard.h" +#include "file_error.h" + +#ifdef FFS_WIN +#include "win.h" //includes "windows.h" +#include "WinIoCtl.h" +#include "privilege.h" +#include "long_path_prefix.h" + +#elif defined FFS_LINUX +#include <unistd.h> +#endif + + +#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 +{ + ULONG ReparseTag; + USHORT ReparseDataLength; + USHORT Reserved; + union + { + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + ULONG Flags; + WCHAR PathBuffer[1]; + } SymbolicLinkReparseBuffer; + struct + { + USHORT SubstituteNameOffset; + USHORT SubstituteNameLength; + USHORT PrintNameOffset; + USHORT PrintNameLength; + WCHAR PathBuffer[1]; + } MountPointReparseBuffer; + struct + { + UCHAR DataBuffer[1]; + } GenericReparseBuffer; + }; +} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; +#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 +{ + using namespace zen; +#ifdef FFS_WIN + //FSCTL_GET_REPARSE_POINT: http://msdn.microsoft.com/en-us/library/aa364571(VS.85).aspx + + try //reading certain symlinks/junctions requires admin rights! This shall not cause an error in user mode! + { + Privileges::getInstance().ensureActive(SE_BACKUP_NAME); //throw FileError + } + catch (...) {} + + const HANDLE hLink = ::CreateFile(applyLongPathPrefix(linkPath).c_str(), + GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + 0, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, + NULL); + if (hLink == INVALID_HANDLE_VALUE) + throw FileError(_("Error resolving symbolic link:") + "\n\"" + linkPath + "\"" + "\n\n" + getLastErrorFormatted()); + ZEN_ON_BLOCK_EXIT(::CloseHandle(hLink)); + + //respect alignment issues... + const DWORD bufferSize = REPARSE_DATA_BUFFER_HEADER_SIZE + MAXIMUM_REPARSE_DATA_BUFFER_SIZE; + std::vector<char> buffer(bufferSize); + + DWORD bytesReturned; //dummy value required by FSCTL_GET_REPARSE_POINT! + if (!::DeviceIoControl(hLink, //__in HANDLE hDevice, + FSCTL_GET_REPARSE_POINT, //__in DWORD dwIoControlCode, + NULL, //__in_opt LPVOID lpInBuffer, + 0, //__in DWORD nInBufferSize, + &buffer[0], //__out_opt LPVOID lpOutBuffer, + bufferSize, //__in DWORD nOutBufferSize, + &bytesReturned, //__out_opt LPDWORD lpBytesReturned, + NULL)) //__inout_opt LPOVERLAPPED lpOverlapped + throw FileError(_("Error resolving symbolic link:") + "\n\"" + linkPath + "\"" + "\n\n" + getLastErrorFormatted()); + + REPARSE_DATA_BUFFER& reparseData = *reinterpret_cast<REPARSE_DATA_BUFFER*>(&buffer[0]); //REPARSE_DATA_BUFFER needs to be artificially enlarged! + + Zstring output; + if (reparseData.ReparseTag == IO_REPARSE_TAG_SYMLINK) + { + output = Zstring(reparseData.SymbolicLinkReparseBuffer.PathBuffer + reparseData.SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR), + reparseData.SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); + } + else if (reparseData.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) + { + output = Zstring(reparseData.MountPointReparseBuffer.PathBuffer + reparseData.MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR), + reparseData.MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR)); + } + else + throw FileError(_("Error resolving symbolic link:") + "\n\"" + linkPath + "\"" + "\n\n" + "Not a symbolic link or junction!"); + + //absolute symlinks and junctions technically start with \??\ while relative ones do not + if (startsWith(output, Zstr("\\??\\"))) + output = Zstring(output.c_str() + 4, output.length() - 4); + + return output; + +#elif defined FFS_LINUX + const int BUFFER_SIZE = 10000; + std::vector<char> buffer(BUFFER_SIZE); + + const int bytesWritten = ::readlink(linkPath.c_str(), &buffer[0], BUFFER_SIZE); + if (bytesWritten < 0 || bytesWritten >= BUFFER_SIZE) + { + std::wstring errorMessage = _("Error resolving symbolic link:") + "\n\"" + linkPath + "\""; + if (bytesWritten < 0) + errorMessage += L"\n\n" + getLastErrorFormatted(); + throw FileError(errorMessage); + } + buffer[bytesWritten] = 0; //set null-terminating char + + return Zstring(&buffer[0], bytesWritten); +#endif +} +} + +#endif // SYMLINK_WIN_H_INCLUDED diff --git a/zen/thread.h b/zen/thread.h new file mode 100644 index 00000000..4db1e613 --- /dev/null +++ b/zen/thread.h @@ -0,0 +1,92 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef BOOST_THREAD_WRAP_H +#define BOOST_THREAD_WRAP_H + +//temporary solution until C++11 thread becomes fully available + +#ifdef __MINGW32__ +#pragma GCC diagnostic push + +#pragma GCC diagnostic ignored "-Wswitch-enum" +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +//#pragma GCC diagnostic ignored "-Wno-attributes" +//#pragma GCC diagnostic ignored "-Wredundant-decls" +//#pragma GCC diagnostic ignored "-Wcast-align" +//#pragma GCC diagnostic ignored "-Wunused-value" +#endif + +#include <boost/thread.hpp> + +#ifdef __MINGW32__ +#pragma GCC diagnostic pop +#endif + +namespace zen +{ +//until std::async is available: +/* +Example: + Zstring dirname = ... + auto ft = zen::async([=](){ return zen::dirExists(dirname); }); + if (ft.timed_wait(boost::posix_time::milliseconds(200)) && ft.get()) + //dir exising +*/ +template <class Function> +auto async(Function fun) -> boost::unique_future<decltype(fun())>; + +template<class InputIterator, class Duration> +void wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration); + + + + + + + + + + + + + + + + + + + + +//###################### implementation ###################### +template <class T, class Function> inline +auto async2(Function fun) -> boost::unique_future<T> //workaround VS2010 bug: bool (*fun)(); decltype(fun()) == int! +{ + boost::packaged_task<T> pt([=] { return fun(); }); + auto fut = pt.get_future(); + boost::thread(std::move(pt)); + return std::move(fut); +} + + +template <class Function> inline auto async(Function fun) -> boost::unique_future<decltype(fun())> { return async2<decltype(fun())>(fun); } + + +template<class InputIterator, class Duration> inline +void wait_for_all_timed(InputIterator first, InputIterator last, const Duration& wait_duration) +{ + const boost::system_time endTime = boost::get_system_time() + wait_duration; + while (first != last) + { + first->timed_wait_until(endTime); + if (boost::get_system_time() >= endTime) + return; + ++first; + } +} +} + +#endif //BOOST_THREAD_WRAP_H diff --git a/zen/type_tools.h b/zen/type_tools.h new file mode 100644 index 00000000..06ef76e1 --- /dev/null +++ b/zen/type_tools.h @@ -0,0 +1,74 @@ +// ************************************************************************** +// * This file is part of the zenXML project. It is distributed under the * +// * Boost Software License, Version 1.0. See accompanying file * +// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. * +// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef TYPE_TOOLS_HEADER_45237590734254545 +#define TYPE_TOOLS_HEADER_45237590734254545 + +namespace zen +{ +//########## Strawman Classes ########################## +struct EmptyType {}; +struct NullType {}; + +//########## Type Mapping ############################## +template <int n> +struct Int2Type {}; +//------------------------------------------------------ +template <class T> +struct Type2Type {}; + +//########## Control Structures ######################## +template <bool flag, class T, class U> +struct Select +{ + typedef T Result; +}; +template <class T, class U> +struct Select<false, T, U> +{ + typedef U Result; +}; +//------------------------------------------------------ +template <class T, class U> +struct IsSameType +{ + enum { result = false }; +}; + +template <class T> +struct IsSameType<T, T> +{ + enum { result = true }; +}; + +//########## Type Cleanup ############################## +template <class T> +struct RemoveRef { typedef T Result; }; + +template <class T> +struct RemoveRef<T&> { typedef T Result; }; +//------------------------------------------------------ +template <class T> +struct RemoveConst { typedef T Result; }; + +template <class T> +struct RemoveConst<const T> { typedef T Result; }; +//------------------------------------------------------ +template <class T> +struct RemovePointer { typedef T Result; }; + +template <class T> +struct RemovePointer<T*> { typedef T Result; }; +//------------------------------------------------------ +template <class T> +struct RemoveArray { typedef T Result; }; + +template <class T, int N> +struct RemoveArray<T[N]> { typedef T Result; }; +} + +#endif //TYPE_TOOLS_HEADER_45237590734254545 diff --git a/zen/type_traits.h b/zen/type_traits.h new file mode 100644 index 00000000..0a705def --- /dev/null +++ b/zen/type_traits.h @@ -0,0 +1,88 @@ +// ************************************************************************** +// * This file is part of the zenXML project. It is distributed under the * +// * Boost Software License, Version 1.0. See accompanying file * +// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. * +// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef TYPE_TRAITS_HEADER_3425628658765467 +#define TYPE_TRAITS_HEADER_3425628658765467 + +namespace zen +{ +//Example: "IsSignedInt<int>::result" evaluates to "true" + +template <class T> struct IsUnsignedInt; +template <class T> struct IsSignedInt; +template <class T> struct IsFloat; + +template <class T> struct IsInteger; //IsSignedInt or IsUnsignedInt +template <class T> struct IsArithmetic; //IsInteger or IsFloat +//remaining non-arithmetic types: bool, char, wchar_t + +//optional: specialize new types like: +//template <> struct IsUnsignedInt<UInt64> { enum { result = true }; }; + + + + + + + + + + + + + + + + + + + + + + + +//################ implementation ###################### +#define ZEN_SPECIALIZE_TRAIT(X, Y) template <> struct X<Y> { enum { result = true }; }; + +template <class T> +struct IsUnsignedInt { enum { result = false }; }; + +ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned char); +ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned short int); +ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned int); +ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned long int); +ZEN_SPECIALIZE_TRAIT(IsUnsignedInt, unsigned long long int); //new with C++11 - same type as unsigned __int64 in VS2010 +//------------------------------------------------------ + +template <class T> +struct IsSignedInt { enum { result = false }; }; + +ZEN_SPECIALIZE_TRAIT(IsSignedInt, signed char); +ZEN_SPECIALIZE_TRAIT(IsSignedInt, short int); +ZEN_SPECIALIZE_TRAIT(IsSignedInt, int); +ZEN_SPECIALIZE_TRAIT(IsSignedInt, long int); +ZEN_SPECIALIZE_TRAIT(IsSignedInt, long long int); //new with C++11 - same type as __int64 in VS2010 +//------------------------------------------------------ + +template <class T> +struct IsFloat { enum { result = false }; }; + +ZEN_SPECIALIZE_TRAIT(IsFloat, float); +ZEN_SPECIALIZE_TRAIT(IsFloat, double); +ZEN_SPECIALIZE_TRAIT(IsFloat, long double); +//------------------------------------------------------ + +#undef ZEN_SPECIALIZE_TRAIT + +template <class T> +struct IsInteger { enum { result = IsUnsignedInt<T>::result || IsSignedInt<T>::result }; }; + +template <class T> +struct IsArithmetic { enum { result = IsInteger<T>::result || IsFloat<T>::result }; }; +} + +#endif //TYPE_TRAITS_HEADER_3425628658765467 diff --git a/zen/utf8.h b/zen/utf8.h new file mode 100644 index 00000000..e72a8e3c --- /dev/null +++ b/zen/utf8.h @@ -0,0 +1,336 @@ +// ************************************************************************** +// * This file is part of the zenXML project. It is distributed under the * +// * Boost Software License, Version 1.0. See accompanying file * +// * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt. * +// * Copyright (C) 2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef STRING_UTF8_HEADER_01832479146991573473545 +#define STRING_UTF8_HEADER_01832479146991573473545 + +#include <iterator> +#include "string_tools.h" +//#include "type_tools.h" +//#include "string_traits.h" + +namespace zen +{ +//convert any(!) "string-like" object into target string by applying a UTF8 conversion (but only if necessary!) +template <class TargetString, class SourceString> TargetString utf8CvrtTo(const SourceString& str); + +//convert wide to utf8 string; example: std::string tmp = toUtf8<std::string>(L"abc"); +template <class CharString, class WideString> +CharString wideToUtf8(const WideString& str); + +//convert utf8 string to wide; example: std::wstring tmp = utf8To<std::wstring>("abc"); +template <class WideString, class CharString> +WideString utf8ToWide(const CharString& str); + +const char BYTE_ORDER_MARK_UTF8[] = "\xEF\xBB\xBF"; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//----------------------- implementation ---------------------------------- +namespace implementation +{ +typedef unsigned int CodePoint; + +const CodePoint CODE_POINT_MAX = 0x10ffff; + +const CodePoint HIGH_SURROGATE = 0xd800; +const CodePoint HIGH_SURROGATE_MAX = 0xdbff; + +const CodePoint LOW_SURROGATE = 0xdc00; +const CodePoint LOW_SURROGATE_MAX = 0xdfff; + + +template <class OutputIterator> inline +OutputIterator codePointToUtf16(CodePoint cp, OutputIterator result) //http://en.wikipedia.org/wiki/UTF-16 +{ + typedef unsigned short Char16; //this isn't necessarily 16 bit, but all we need is an unsigned type + + assert(cp < HIGH_SURROGATE || LOW_SURROGATE_MAX < cp); //code points [0xd800, 0xdfff] are not allowed for UTF-16 + assert(cp <= CODE_POINT_MAX); + + if (cp < 0x10000) + *result++ = static_cast<Char16>(cp); + else + { + cp -= 0x10000; + *result++ = static_cast<Char16>((cp >> 10) + HIGH_SURROGATE); + *result++ = static_cast<Char16>((cp & 0x3ff) + LOW_SURROGATE); + } + return result; +} + + +template <class CharIterator, class Function> inline +Function utf16ToCodePoint(CharIterator first, CharIterator last, Function f) //f is a unary function taking a CodePoint as single parameter +{ + assert_static(sizeof(typename std::iterator_traits<CharIterator>::value_type) == 2); + typedef unsigned short Char16; //this isn't necessarily 16 bit, but all we need is an unsigned type + + for ( ; first != last; ++first) + { + CodePoint cp = static_cast<Char16>(*first); + if (HIGH_SURROGATE <= cp && cp <= HIGH_SURROGATE_MAX) + { + if (++first == last) + { + assert(false); //low surrogate expected + break; + } + assert(LOW_SURROGATE <= static_cast<Char16>(*first) && static_cast<Char16>(*first) <= LOW_SURROGATE_MAX); //low surrogate expected + cp = ((cp - HIGH_SURROGATE) << 10) + static_cast<Char16>(*first) - LOW_SURROGATE + 0x10000; + } + else + assert(cp < LOW_SURROGATE || LOW_SURROGATE_MAX < cp); //NO low surrogate expected + + f(cp); + } + return f; +} + + +template <class OutputIterator> inline +OutputIterator codePointToUtf8(CodePoint cp, OutputIterator result) //http://en.wikipedia.org/wiki/UTF-8 +{ + typedef unsigned char Char8; + + assert(cp <= CODE_POINT_MAX); + + if (cp < 0x80) + *result++ = static_cast<Char8>(cp); + else if (cp < 0x800) + { + *result++ = static_cast<Char8>((cp >> 6 ) | 0xc0); + *result++ = static_cast<Char8>((cp & 0x3f) | 0x80); + } + else if (cp < 0x10000) + { + *result++ = static_cast<Char8>((cp >> 12 ) | 0xe0); + *result++ = static_cast<Char8>(((cp >> 6) & 0x3f) | 0x80); + *result++ = static_cast<Char8>((cp & 0x3f ) | 0x80); + } + else + { + *result++ = static_cast<Char8>((cp >> 18 ) | 0xf0); + *result++ = static_cast<Char8>(((cp >> 12) & 0x3f) | 0x80); + *result++ = static_cast<Char8>(((cp >> 6) & 0x3f) | 0x80); + *result++ = static_cast<Char8>((cp & 0x3f ) | 0x80); + } + return result; +} + + +inline +size_t getUtf8Len(unsigned char ch) +{ + if (ch < 0x80) + return 1; + if (ch >> 5 == 0x6) + return 2; + if (ch >> 4 == 0xe) + return 3; + if (ch >> 3 == 0x1e) + return 4; + + assert(false); //no valid begin of UTF8 encoding + return 1; +} + + +template <class CharIterator, class Function> inline +Function utf8ToCodePoint(CharIterator first, CharIterator last, Function f) //f is a unary function taking a CodePoint as single parameter +{ + assert_static(sizeof(typename std::iterator_traits<CharIterator>::value_type) == 1); + typedef unsigned char Char8; + + for ( ; first != last; ++first) + { + auto getChar = [&](Char8 & ch) -> bool + { + if (++first == last) + { + assert(false); //low surrogate expected + return false; + } + ch = static_cast<Char8>(*first); + assert(ch >> 6 == 0x2); + return true; + }; + + CodePoint cp = static_cast<Char8>(*first); + switch (getUtf8Len(static_cast<Char8>(cp))) + { + case 1: + break; + case 2: + { + cp = (cp & 0x1f) << 6; + Char8 ch; + if (!getChar(ch)) continue; + cp += ch & 0x3f; + } + break; + case 3: + { + cp = (cp & 0xf) << 12; + Char8 ch; + if (!getChar(ch)) continue; + cp += (ch & 0x3f) << 6; + if (!getChar(ch)) continue; + cp += ch & 0x3f; + + } + break; + case 4: + { + cp = (cp & 0x7) << 18; + Char8 ch; + if (!getChar(ch)) continue; + cp += (ch & 0x3f) << 12; + if (!getChar(ch)) continue; + cp += (ch & 0x3f) << 6; + if (!getChar(ch)) continue; + cp += ch & 0x3f; + } + break; + default: + assert(false); + } + f(cp); + } + return f; +} + + +template <class String> +class AppendStringIterator: public std::iterator<std::output_iterator_tag, void, void, void, void> +{ +public: + explicit AppendStringIterator (String& x) : str(&x) {} + AppendStringIterator& operator= (typename String::value_type value) { *str += value; return *this; } + AppendStringIterator& operator* () { return *this; } + AppendStringIterator& operator++ () { return *this; } + AppendStringIterator operator++ (int) { return *this; } +private: + String* str; +}; + + +template <class WideString, class CharString> inline +WideString utf8ToWide(const CharString& str, Int2Type<2>) //windows: convert utf8 to utf16 wchar_t +{ + WideString output; + utf8ToCodePoint(strBegin(str), strBegin(str) + strLength(str), + [&](CodePoint cp) { codePointToUtf16(cp, AppendStringIterator<WideString>(output)); }); + return output; +} + + +template <class WideString, class CharString> inline +WideString utf8ToWide(const CharString& str, Int2Type<4>) //other OS: convert utf8 to utf32 wchar_t +{ + WideString output; + utf8ToCodePoint(strBegin(str), strBegin(str) + strLength(str), + [&](CodePoint cp) { output += cp; }); + return output; +} + + +template <class CharString, class WideString> inline +CharString wideToUtf8(const WideString& str, Int2Type<2>) //windows: convert utf16-wchar_t to utf8 +{ + CharString output; + utf16ToCodePoint(strBegin(str), strBegin(str) + strLength(str), + [&](CodePoint cp) { codePointToUtf8(cp, AppendStringIterator<CharString>(output)); }); + return output; +} + + +template <class CharString, class WideString> inline +CharString wideToUtf8(const WideString& str, Int2Type<4>) //other OS: convert utf32-wchar_t to utf8 +{ + CharString output; + std::for_each(strBegin(str), strBegin(str) + strLength(str), + [&](CodePoint cp) { codePointToUtf8(cp, AppendStringIterator<CharString>(output)); }); + return output; +} +} + + +template <class WideString, class CharString> inline +WideString utf8ToWide(const CharString& str) +{ + assert_static((IsSameType<typename StringTraits<CharString>::CharType, char >::result)); + assert_static((IsSameType<typename StringTraits<WideString>::CharType, wchar_t>::result)); + + return implementation::utf8ToWide<WideString>(str, Int2Type<sizeof(wchar_t)>()); +} + + +template <class CharString, class WideString> inline +CharString wideToUtf8(const WideString& str) +{ + assert_static((IsSameType<typename StringTraits<CharString>::CharType, char >::result)); + assert_static((IsSameType<typename StringTraits<WideString>::CharType, wchar_t>::result)); + + return implementation::wideToUtf8<CharString>(str, Int2Type<sizeof(wchar_t)>()); +} + + +//------------------------------------------------------------------------------------------- +template <class TargetString, class SourceString> inline +TargetString utf8CvrtTo(const SourceString& str, char, wchar_t) { return utf8ToWide<TargetString>(str); } + +template <class TargetString, class SourceString> inline +TargetString utf8CvrtTo(const SourceString& str, wchar_t, char) { return wideToUtf8<TargetString>(str); } + +template <class TargetString, class SourceString> inline +TargetString utf8CvrtTo(const SourceString& str, char, char) { return cvrtString<TargetString>(str); } + +template <class TargetString, class SourceString> inline +TargetString utf8CvrtTo(const SourceString& str, wchar_t, wchar_t) { return cvrtString<TargetString>(str); } + +template <class TargetString, class SourceString> inline +TargetString utf8CvrtTo(const SourceString& str) +{ + return utf8CvrtTo<TargetString>(str, + typename StringTraits<SourceString>::CharType(), + typename StringTraits<TargetString>::CharType()); +} +} + +#endif //STRING_UTF8_HEADER_01832479146991573473545 diff --git a/zen/warn_static.h b/zen/warn_static.h new file mode 100644 index 00000000..db472ccd --- /dev/null +++ b/zen/warn_static.h @@ -0,0 +1,34 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef WARN_STATIC_HEADER_08724567834560832745 +#define WARN_STATIC_HEADER_08724567834560832745 + +/* +Portable Compile-Time Warning +----------------------------- +Usage: + warn_static("my message") +*/ + +#ifdef _MSC_VER +#define MAKE_STRING_SUB(NUM) #NUM +#define MAKE_STRING(NUM) MAKE_STRING_SUB(NUM) + +#define warn_static(TXT) \ + __pragma(message (__FILE__ "(" MAKE_STRING(__LINE__) "): Warning: " ## TXT)) + +#elif defined __GNUC__ +#define ZEN_CONCAT_SUB(X, Y) X ## Y +#define ZEN_CONCAT(X, Y) ZEN_CONCAT_SUB(X, Y) + +#define warn_static(TXT) \ + typedef int STATIC_WARNING __attribute__ ((deprecated)); \ + enum { ZEN_CONCAT(warn_static_dummy_value, __LINE__) = sizeof(STATIC_WARNING) }; +#endif + + +#endif //WARN_STATIC_HEADER_08724567834560832745
\ No newline at end of file diff --git a/zen/win.h b/zen/win.h new file mode 100644 index 00000000..618c92fa --- /dev/null +++ b/zen/win.h @@ -0,0 +1,30 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef YAWFWH_YET_ANOTHER_WRAPPER_FOR_WINDOWS_H +#define YAWFWH_YET_ANOTHER_WRAPPER_FOR_WINDOWS_H + +//------------------------------------------------------ +#ifdef __WXMSW__ //we have wxWidgets +#include <wx/msw/wrapwin.h> //includes "windows.h" +//------------------------------------------------------ +#else +//#define WIN32_LEAN_AND_MEAN + +#ifndef NOMINMAX +#define NOMINMAX +#endif + +#ifndef STRICT +#define STRICT //improve type checking +#endif + +#include <windows.h> + +#endif +//------------------------------------------------------ + +#endif //YAWFWH_YET_ANOTHER_WRAPPER_FOR_WINDOWS_H diff --git a/zen/win_ver.h b/zen/win_ver.h new file mode 100644 index 00000000..ca075bbe --- /dev/null +++ b/zen/win_ver.h @@ -0,0 +1,72 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef WINDOWS_VERSION_HEADER_238470348254325 +#define WINDOWS_VERSION_HEADER_238470348254325 + +#include "win.h" + +namespace zen +{ +bool winXpOrLater(); +bool winServer2003orLater(); +bool vistaOrLater(); +bool win7OrLater(); + + + + + + + + + + + + + + + + + + +//######################### implementation ######################### +namespace impl +{ +inline +bool winXyOrLater(DWORD major, DWORD minor) +{ + OSVERSIONINFO osvi = {}; + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + if (::GetVersionEx(&osvi)) + return osvi.dwMajorVersion > major || + (osvi.dwMajorVersion == major && osvi.dwMinorVersion >= minor); + return false; +} +} + +//version overview: http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx + +//2000 is version 5.0 +//XP is version 5.1 +//Server 2003 is version 5.2 +//Vista is version 6.0 +//Seven is version 6.1 + +inline +bool winXpOrLater() { return impl::winXyOrLater(5, 1); } + +inline +bool winServer2003orLater() { return impl::winXyOrLater(5, 2); } + +inline +bool vistaOrLater() { return impl::winXyOrLater(6, 0); } + +inline +bool win7OrLater() { return impl::winXyOrLater(6, 1); } +} + +#endif //WINDOWS_VERSION_HEADER_238470348254325 diff --git a/zen/zstring.cpp b/zen/zstring.cpp new file mode 100644 index 00000000..5331499f --- /dev/null +++ b/zen/zstring.cpp @@ -0,0 +1,234 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#include "zstring.h" +#include <stdexcept> +#include <boost/thread/once.hpp> + +#ifdef FFS_WIN +#include "dll.h" +#include "win_ver.h" +#endif + +#ifndef NDEBUG +#include <iostream> +#include <cstdlib> +#endif + +using namespace zen; + + +#ifndef NDEBUG +LeakChecker::~LeakChecker() +{ + if (activeStrings.size() > 0) + { + int rowCount = 0; + std::string leakingStrings; + + for (VoidPtrSizeMap::const_iterator i = activeStrings.begin(); + i != activeStrings.end() && ++rowCount <= 20; + ++i) + leakingStrings += "\"" + rawMemToString(i->first, i->second) + "\"\n"; + + const std::string message = std::string("Memory leak detected!") + "\n\n" + + "Candidates:\n" + leakingStrings; +#ifdef FFS_WIN + MessageBoxA(NULL, message.c_str(), "Error", 0); +#else + std::cerr << message; + std::abort(); +#endif + } +} + + +LeakChecker& LeakChecker::instance() +{ + static LeakChecker inst; + return inst; +} + +//caveat: function scope static initialization is not thread-safe in VS 2010! => make sure to call at app start! +namespace +{ +struct Dummy { Dummy() { LeakChecker::instance(); }} blah; +} + + +std::string LeakChecker::rawMemToString(const void* ptr, size_t size) +{ + std::string output = std::string(reinterpret_cast<const char*>(ptr), size); + output.erase(std::remove(output.begin(), output.end(), 0), output.end()); //remove intermediate 0-termination + if (output.size() > 100) + output.resize(100); + return output; +} + + +void LeakChecker::reportProblem(const std::string& message) //throw (std::logic_error) +{ +#ifdef FFS_WIN + ::MessageBoxA(NULL, message.c_str(), "Error", 0); +#else + std::cerr << message; +#endif + throw std::logic_error("Memory leak! " + message); +} +#endif //NDEBUG + + +#ifdef FFS_WIN +namespace +{ +#ifndef LOCALE_INVARIANT //invariant locale has been introduced with XP +#define LOCALE_INVARIANT 0x007f +#endif + + +//warning: LOCALE_INVARIANT is NOT available with Windows 2000, so we have to make yet another distinction... +const LCID ZSTRING_INVARIANT_LOCALE = zen::winXpOrLater() ? + LOCALE_INVARIANT : + MAKELCID(MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), SORT_DEFAULT); //see: http://msdn.microsoft.com/en-us/goglobal/bb688122.aspx + + +//try to call "CompareStringOrdinal" for low-level string comparison: unfortunately available not before Windows Vista! +//by a factor ~3 faster than old string comparison using "LCMapString" +typedef int (WINAPI* CompareStringOrdinalFunc)(LPCWSTR lpString1, + int cchCount1, + LPCWSTR lpString2, + int cchCount2, + BOOL bIgnoreCase); +SysDllFun<CompareStringOrdinalFunc> ordinalCompare; //caveat: function scope static initialization is not thread-safe in VS 2010! +boost::once_flag initCmpStrOrdOnce = BOOST_ONCE_INIT; +} + + +int z_impl::compareFilenamesWin(const wchar_t* a, const wchar_t* b, size_t sizeA, size_t sizeB) +{ + boost::call_once(initCmpStrOrdOnce, []() { ordinalCompare = SysDllFun<CompareStringOrdinalFunc>(L"kernel32.dll", "CompareStringOrdinal"); }); + + if (ordinalCompare) //this additional test has no noticeable performance impact + { + const int rv = ordinalCompare(a, //pointer to first string + static_cast<int>(sizeA), //size, in bytes or characters, of first string + b, //pointer to second string + static_cast<int>(sizeB), //size, in bytes or characters, of second string + true); //ignore case + if (rv == 0) + throw std::runtime_error("Error comparing strings (ordinal)!"); + else + return rv - 2; //convert to C-style string compare result + } + else //fallback + { + //do NOT use "CompareString"; this function is NOT accurate (even with LOCALE_INVARIANT and SORT_STRINGSORT): for example "weiß" == "weiss"!!! + //the only reliable way to compare filenames (with XP) is to call "CharUpper" or "LCMapString": + + const int minSize = std::min<int>(sizeA, sizeB); + + if (minSize == 0) //LCMapString does not allow input sizes of 0! + return static_cast<int>(sizeA) - static_cast<int>(sizeB); + + int rv = 0; //always initialize... + if (minSize <= 5000) //performance optimization: stack + { + wchar_t bufferA[5000]; + wchar_t bufferB[5000]; + + //faster than CharUpperBuff + wmemcpy or CharUpper + wmemcpy and same speed like ::CompareString() + if (::LCMapString(ZSTRING_INVARIANT_LOCALE, //__in LCID Locale, + LCMAP_UPPERCASE, //__in DWORD dwMapFlags, + a, //__in LPCTSTR lpSrcStr, + minSize, //__in int cchSrc, + bufferA, //__out LPTSTR lpDestStr, + 5000) == 0) //__in int cchDest + throw std::runtime_error("Error comparing strings! (LCMapString)"); + + if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, b, minSize, bufferB, 5000) == 0) + throw std::runtime_error("Error comparing strings! (LCMapString)"); + + rv = ::wmemcmp(bufferA, bufferB, minSize); + } + else //use freestore + { + std::vector<wchar_t> bufferA(minSize); + std::vector<wchar_t> bufferB(minSize); + + if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, a, minSize, &bufferA[0], minSize) == 0) + throw std::runtime_error("Error comparing strings! (LCMapString: FS)"); + + if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, b, minSize, &bufferB[0], minSize) == 0) + throw std::runtime_error("Error comparing strings! (LCMapString: FS)"); + + rv = ::wmemcmp(&bufferA[0], &bufferB[0], minSize); + } + + return rv == 0 ? + static_cast<int>(sizeA) - static_cast<int>(sizeB) : + rv; + } + + // const int rv = CompareString( + // invariantLocale, //locale independent + // NORM_IGNORECASE | SORT_STRINGSORT, //comparison-style options + // a, //pointer to first string + // aCount, //size, in bytes or characters, of first string + // b, //pointer to second string + // bCount); //size, in bytes or characters, of second string + // + // if (rv == 0) + // throw std::runtime_error("Error comparing strings!"); + // else + // return rv - 2; //convert to C-style string compare result +} + + +void z_impl::makeUpperCaseWin(wchar_t* str, size_t size) +{ + if (size == 0) //LCMapString does not allow input sizes of 0! + return; + + //use Windows' upper case conversion: faster than ::CharUpper() + if (::LCMapString(ZSTRING_INVARIANT_LOCALE, LCMAP_UPPERCASE, str, static_cast<int>(size), str, static_cast<int>(size)) == 0) + throw std::runtime_error("Error converting to upper case! (LCMapString)"); +} + + +/* +#include <fstream> + extern std::wofstream statShared; +extern std::wofstream statLowCapacity; +extern std::wofstream statOverwrite; + +std::wstring p(ptr); +p.erase(std::remove(p.begin(), p.end(), L'\n'), p.end()); +p.erase(std::remove(p.begin(), p.end(), L','), p.end()); + + if (descr(ptr)->refCount > 1) + statShared << + minCapacity << L"," << + descr(ptr)->refCount << L"," << + descr(ptr)->length << L"," << + descr(ptr)->capacity << L"," << + p << L"\n"; +else if (minCapacity > descr(ptr)->capacity) + statLowCapacity << + minCapacity << L"," << + descr(ptr)->refCount << L"," << + descr(ptr)->length << L"," << + descr(ptr)->capacity << L"," << + p << L"\n"; +else + statOverwrite << + minCapacity << L"," << + descr(ptr)->refCount << L"," << + descr(ptr)->length << L"," << + descr(ptr)->capacity << L"," << + p << L"\n"; +*/ + +#endif //FFS_WIN diff --git a/zen/zstring.h b/zen/zstring.h new file mode 100644 index 00000000..0ba9f108 --- /dev/null +++ b/zen/zstring.h @@ -0,0 +1,216 @@ +// ************************************************************************** +// * 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) 2008-2011 ZenJu (zhnmju123 AT gmx.de) * +// ************************************************************************** + +#ifndef ZSTRING_H_INCLUDED +#define ZSTRING_H_INCLUDED + +#include "string_base.h" +#include <cstring> //strcmp() + +#ifndef NDEBUG +#include "thread.h" //includes <boost/thread.hpp> +#include <map> +#endif + + +#ifndef NDEBUG +class LeakChecker //small test for memory leaks +{ +public: + void insert(const void* ptr, size_t size) + { + boost::lock_guard<boost::mutex> dummy(lockActStrings); + if (activeStrings.find(ptr) != activeStrings.end()) + reportProblem(std::string("Fatal Error: New memory points into occupied space: ") + rawMemToString(ptr, size)); + + activeStrings[ptr] = size; + } + + void remove(const void* ptr) + { + boost::lock_guard<boost::mutex> dummy(lockActStrings); + if (activeStrings.find(ptr) == activeStrings.end()) + reportProblem(std::string("Fatal Error: No memory available for deallocation at this location!")); + + activeStrings.erase(ptr); + } + + static LeakChecker& instance(); + +private: + LeakChecker() {} + LeakChecker(const LeakChecker&); + LeakChecker& operator=(const LeakChecker&); + ~LeakChecker(); + + static std::string rawMemToString(const void* ptr, size_t size); + void reportProblem(const std::string& message); //throw (std::logic_error) + + boost::mutex lockActStrings; + typedef std::map<const void*, size_t> VoidPtrSizeMap; + VoidPtrSizeMap activeStrings; +}; +#endif //NDEBUG + + +class AllocatorFreeStoreChecked +{ +public: + static void* allocate(size_t size) //throw (std::bad_alloc) + { +#ifndef NDEBUG + void* newMem = ::operator new(size); + LeakChecker::instance().insert(newMem, size); //test Zbase for memory leaks + return newMem; +#else + return ::operator new(size); +#endif + } + + static void deallocate(void* ptr) + { +#ifndef NDEBUG + LeakChecker::instance().remove(ptr); //check for memory leaks +#endif + ::operator delete(ptr); + } + + static size_t calcCapacity(size_t length) { return std::max<size_t>(16, length + length / 2); } //exponential growth + min size +}; + + +//############################## helper functions ############################################# +#if defined(FFS_WIN) || defined(FFS_LINUX) +//Compare filenames: Windows does NOT distinguish between upper/lower-case, while Linux DOES +template <class T, template <class, class> class SP, class AP> int cmpFileName(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs); + +struct LessFilename //case-insensitive on Windows, case-sensitive on Linux +{ + template <class T, template <class, class> class SP, class AP> + bool operator()(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs) const; +}; + +struct EqualFilename //case-insensitive on Windows, case-sensitive on Linux +{ + template <class T, template <class, class> class SP, class AP> + bool operator()(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs) const; +}; +#endif + +#ifdef FFS_WIN +template <template <class, class> class SP, class AP> +void makeUpper(Zbase<wchar_t, SP, AP>& str); +#endif + +#ifdef FFS_WIN //Windows stores filenames in wide character format +typedef wchar_t Zchar; +#define Zstr(x) L ## x +const Zchar FILE_NAME_SEPARATOR = L'\\'; + +#elif defined FFS_LINUX //Linux uses UTF-8 +typedef char Zchar; +#define Zstr(x) x +const Zchar FILE_NAME_SEPARATOR = '/'; + +#else +#error define platform you are in: FFS_WIN or FFS_LINUX +#endif + +//"The reason for all the fuss above" - Loki/SmartPtr +//a high-performant string for use as file name in multithreaded contexts +typedef Zbase<Zchar, StorageRefCountThreadSafe, AllocatorFreeStoreChecked> Zstring; + + + + + + + + + + + + + + + + + + + + + + + + + + +//################################# inline implementation ######################################## +#if defined(FFS_WIN) || defined(FFS_LINUX) +namespace z_impl +{ +int compareFilenamesWin(const wchar_t* a, const wchar_t* b, size_t sizeA, size_t sizeB); +void makeUpperCaseWin(wchar_t* str, size_t size); +} + + +template <class T, template <class, class> class SP, class AP> +inline +int cmpFileName(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs) +{ +#ifdef FFS_WIN + return z_impl::compareFilenamesWin(lhs.data(), rhs.data(), lhs.length(), rhs.length()); +#elif defined FFS_LINUX + return ::strcmp(lhs.c_str(), rhs.c_str()); //POSIX filenames don't have embedded 0 +#endif +} + + +template <class T, template <class, class> class SP, class AP> +inline +bool LessFilename::operator()(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs) const +{ +#ifdef FFS_WIN + return z_impl::compareFilenamesWin(lhs.data(), rhs.data(), lhs.length(), rhs.length()) < 0; +#elif defined FFS_LINUX + return ::strcmp(lhs.c_str(), rhs.c_str()) < 0; //POSIX filenames don't have embedded 0 +#endif +} + + +template <class T, template <class, class> class SP, class AP> +inline +bool EqualFilename::operator()(const Zbase<T, SP, AP>& lhs, const Zbase<T, SP, AP>& rhs) const +{ +#ifdef FFS_WIN + return z_impl::compareFilenamesWin(lhs.data(), rhs.data(), lhs.length(), rhs.length()) == 0; +#elif defined FFS_LINUX + return ::strcmp(lhs.c_str(), rhs.c_str()) == 0; //POSIX filenames don't have embedded 0 +#endif +} +#endif //defined(FFS_WIN) || defined(FFS_LINUX) + + +#ifdef FFS_WIN +template <template <class, class> class SP, class AP> +inline +void makeUpper(Zbase<wchar_t, SP, AP>& str) +{ + z_impl::makeUpperCaseWin(str.begin(), str.length()); +} +#endif + + +namespace std +{ +template<> inline +void swap(Zstring& rhs, Zstring& lhs) +{ + rhs.swap(lhs); +} +} + +#endif //ZSTRING_H_INCLUDED |