summaryrefslogtreecommitdiff
path: root/zen/dst_hack.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'zen/dst_hack.cpp')
-rw-r--r--zen/dst_hack.cpp369
1 files changed, 369 insertions, 0 deletions
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);
+}
bgstack15