diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:24:35 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 17:24:35 +0200 |
commit | 460091fb0b2ff114cc741372f15bb43b702ea3b1 (patch) | |
tree | 0562c2eda4c66969c6e6d0910080db9f5b0def3e /zen | |
parent | 5.15 (diff) | |
download | FreeFileSync-460091fb0b2ff114cc741372f15bb43b702ea3b1.tar.gz FreeFileSync-460091fb0b2ff114cc741372f15bb43b702ea3b1.tar.bz2 FreeFileSync-460091fb0b2ff114cc741372f15bb43b702ea3b1.zip |
5.16
Diffstat (limited to 'zen')
-rw-r--r-- | zen/async_task.h | 4 | ||||
-rw-r--r-- | zen/dir_watcher.cpp | 3 | ||||
-rw-r--r-- | zen/file_handling.cpp | 186 | ||||
-rw-r--r-- | zen/file_handling.h | 8 | ||||
-rw-r--r-- | zen/file_io.cpp | 13 | ||||
-rw-r--r-- | zen/file_io.h | 9 | ||||
-rw-r--r-- | zen/file_io_base.h | 4 | ||||
-rw-r--r-- | zen/file_traverser.cpp | 257 | ||||
-rw-r--r-- | zen/file_traverser.h | 15 | ||||
-rw-r--r-- | zen/format_unit.cpp | 3 | ||||
-rw-r--r-- | zen/int64.h | 1 | ||||
-rw-r--r-- | zen/long_path_prefix.h | 4 | ||||
-rw-r--r-- | zen/serialize.h | 6 | ||||
-rw-r--r-- | zen/stl_tools.h | 13 | ||||
-rw-r--r-- | zen/string_base.h | 17 | ||||
-rw-r--r-- | zen/string_traits.h | 49 | ||||
-rw-r--r-- | zen/symlink_target.h | 73 | ||||
-rw-r--r-- | zen/thread.h | 3 | ||||
-rw-r--r-- | zen/tick_count.h | 2 | ||||
-rw-r--r-- | zen/time.h | 2 | ||||
-rw-r--r-- | zen/utf.h | 199 | ||||
-rw-r--r-- | zen/zstring.cpp | 6 |
22 files changed, 458 insertions, 419 deletions
diff --git a/zen/async_task.h b/zen/async_task.h index b4123b5d..c5e5857a 100644 --- a/zen/async_task.h +++ b/zen/async_task.h @@ -11,6 +11,7 @@ #include <functional> #include <zen/thread.h> #include <zen/scope_guard.h> +//#include "type_tools.h" namespace zen { @@ -34,7 +35,7 @@ public: } template <class Fun, class Fun2> - void add2(Fun doAsync, Fun2 evalOnGui) //for doAsync returning void + void add2(Fun doAsync, Fun2 evalOnGui) //for evalOnGui taking no parameters { tasks.push_back(zen::async([=]() -> std::function<void()> { doAsync(); return [=]{ evalOnGui(); }; })); } @@ -64,7 +65,6 @@ private: bool inRecursion; std::list<boost::unique_future<std::function<void()>>> tasks; }; - } #endif //ASYNC_JOB_839147839170432143214321 diff --git a/zen/dir_watcher.cpp b/zen/dir_watcher.cpp index 6c6c0929..6402dce7 100644 --- a/zen/dir_watcher.cpp +++ b/zen/dir_watcher.cpp @@ -386,7 +386,8 @@ public: dirs_.push_back(fullName); return otherMe_; } - virtual HandleError onError(const std::wstring& msg) { throw FileError(msg); } + virtual HandleError reportDirError (const std::wstring& msg) { throw FileError(msg); } + virtual HandleError reportItemError(const std::wstring& msg, const Zchar* shortName) { throw FileError(msg); } private: const std::shared_ptr<TraverseCallback>& otherMe_; //lifetime management, two options: 1. use std::weak_ptr 2. ref to shared_ptr diff --git a/zen/file_handling.cpp b/zen/file_handling.cpp index b529720f..8dc3e72d 100644 --- a/zen/file_handling.cpp +++ b/zen/file_handling.cpp @@ -50,11 +50,11 @@ 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 + const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(filename).c_str()); + return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) == 0; //returns true for (file-)symlinks also #elif defined FFS_LINUX || defined FFS_MAC - struct stat fileInfo = {}; + 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 @@ -65,11 +65,11 @@ 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 + const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(dirname).c_str()); + return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_DIRECTORY) != 0; //returns true for (dir-)symlinks also #elif defined FFS_LINUX || defined FFS_MAC - struct stat dirInfo = {}; + 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 @@ -89,7 +89,7 @@ bool zen::symlinkExists(const Zstring& linkname) return isSymlink(fileInfo); #elif defined FFS_LINUX || defined FFS_MAC - struct stat fileInfo = {}; + struct ::stat fileInfo = {}; return ::lstat(linkname.c_str(), &fileInfo) == 0 && S_ISLNK(fileInfo.st_mode); //symbolic link #endif @@ -99,16 +99,35 @@ bool zen::symlinkExists(const Zstring& linkname) bool zen::somethingExists(const Zstring& objname) { #ifdef FFS_WIN - const DWORD rv = ::GetFileAttributes(applyLongPathPrefix(objname).c_str()); - return rv != INVALID_FILE_ATTRIBUTES || ::GetLastError() == ERROR_SHARING_VIOLATION; //"C:\pagefile.sys" + const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(objname).c_str()); + return attr != INVALID_FILE_ATTRIBUTES || ::GetLastError() == ERROR_SHARING_VIOLATION; //"C:\pagefile.sys" #elif defined FFS_LINUX || defined FFS_MAC - struct stat fileInfo = {}; + struct ::stat fileInfo = {}; return ::lstat(objname.c_str(), &fileInfo) == 0; #endif } +SymLinkType zen::getSymlinkType(const Zstring& linkname) //throw() +{ + assert(symlinkExists(linkname)); +#ifdef FFS_WIN + const DWORD attr = ::GetFileAttributes(applyLongPathPrefix(linkname).c_str()); + if (attr == INVALID_FILE_ATTRIBUTES) + return SYMLINK_TYPE_UNKNOWN; + return (attr & FILE_ATTRIBUTE_DIRECTORY) ? SYMLINK_TYPE_DIR : SYMLINK_TYPE_FILE; + +#elif defined FFS_LINUX || defined FFS_MAC + //S_ISDIR and S_ISLNK are mutually exclusive on Linux => explicitly need to follow link + struct ::stat fileInfo = {}; + if (::stat(linkname.c_str(), &fileInfo) != 0) + return SYMLINK_TYPE_UNKNOWN; + return S_ISDIR(fileInfo.st_mode) ? SYMLINK_TYPE_DIR : SYMLINK_TYPE_FILE; +#endif +} + + namespace { void getFileAttrib(const Zstring& filename, FileAttrib& attr, ProcSymlink procSl) //throw FileError @@ -155,7 +174,6 @@ void getFileAttrib(const Zstring& filename, FileAttrib& attr, ProcSymlink procSl nullptr); if (hFile == INVALID_HANDLE_VALUE) throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); - ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); BY_HANDLE_FILE_INFORMATION fileInfoHnd = {}; @@ -167,7 +185,7 @@ void getFileAttrib(const Zstring& filename, FileAttrib& attr, ProcSymlink procSl } #elif defined FFS_LINUX || defined FFS_MAC - struct stat fileInfo = {}; + struct ::stat fileInfo = {}; const int rv = procSl == SYMLINK_FOLLOW ? :: stat(filename.c_str(), &fileInfo) : @@ -575,34 +593,42 @@ class CollectFilesFlat : public zen::TraverseCallback { public: CollectFilesFlat(std::vector<Zstring>& files, std::vector<Zstring>& dirs) : - m_files(files), - m_dirs(dirs) {} + files_(files), + dirs_(dirs) {} virtual void onFile(const Zchar* shortName, const Zstring& fullName, const FileInfo& details) { - m_files.push_back(fullName); + files_.push_back(fullName); } virtual HandleLink onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) { - if (details.dirLink) - m_dirs.push_back(fullName); - else - m_files.push_back(fullName); + switch (getSymlinkType(fullName)) + { + case SYMLINK_TYPE_DIR: + dirs_.push_back(shortName); + break; + + case SYMLINK_TYPE_FILE: + case SYMLINK_TYPE_UNKNOWN: + files_.push_back(shortName); + break; + } return LINK_SKIP; } virtual std::shared_ptr<TraverseCallback> onDir(const Zchar* shortName, const Zstring& fullName) { - m_dirs.push_back(fullName); + dirs_.push_back(fullName); return nullptr; //DON'T traverse into subdirs; removeDirectory works recursively! } - virtual HandleError onError(const std::wstring& msg) { throw FileError(msg); } + virtual HandleError reportDirError (const std::wstring& msg) { throw FileError(msg); } + virtual HandleError reportItemError(const std::wstring& msg, const Zchar* shortName) { throw FileError(msg); } private: CollectFilesFlat(const CollectFilesFlat&); CollectFilesFlat& operator=(const CollectFilesFlat&); - std::vector<Zstring>& m_files; - std::vector<Zstring>& m_dirs; + std::vector<Zstring>& files_; + std::vector<Zstring>& dirs_; }; @@ -989,49 +1015,6 @@ bool zen::supportsPermissions(const Zstring& dirname) //throw FileError namespace { -#ifdef FFS_WIN -Zstring getSymlinkTargetPath(const Zstring& symlink) //throw FileError -{ - //open handle to target of symbolic link - const HANDLE hDir = ::CreateFile(applyLongPathPrefix(symlink).c_str(), - 0, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - nullptr, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, //needed to open a directory - nullptr); - if (hDir == INVALID_HANDLE_VALUE) - throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(symlink)) + L"\n\n" + getLastErrorFormatted()); - ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir)); - - //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(replaceCpy(_("Cannot find system function %x."), L"%x", L"\"GetFinalPathNameByHandleW\"")); - - const DWORD BUFFER_SIZE = 10000; - std::vector<wchar_t> targetPath(BUFFER_SIZE); - 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 = replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(symlink)); - 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 @@ -1086,9 +1069,9 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym //in contrast to ::SetSecurityInfo(), ::SetFileSecurity() seems to honor the "inherit DACL/SACL" flags //CAVEAT: if a file system does not support ACLs, GetFileSecurity() will return successfully with a *valid* security descriptor containing *no* ACL entries! - //NOTE: ::GetFileSecurity()/::SetFileSecurity() do NOT follow Symlinks! - const Zstring sourceResolved = procSl == SYMLINK_FOLLOW && symlinkExists(source) ? getSymlinkTargetPath(source) : source; - const Zstring targetResolved = procSl == SYMLINK_FOLLOW && symlinkExists(target) ? getSymlinkTargetPath(target) : target; + //NOTE: ::GetFileSecurity()/::SetFileSecurity() do NOT follow Symlinks! getResolvedFilePath() requires Vista or later! + const Zstring sourceResolved = procSl == SYMLINK_FOLLOW && symlinkExists(source) ? getResolvedFilePath(source) : source; //throw FileError + const Zstring targetResolved = procSl == SYMLINK_FOLLOW && symlinkExists(target) ? getResolvedFilePath(target) : target; // //setting privileges requires admin rights! try @@ -1400,50 +1383,46 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT if (!templateDir.empty()) { #ifdef FFS_WIN - //try to copy file attributes - Zstring sourcePath; - - if (symlinkExists(templateDir)) - try - { - //get target directory of symbolic link - sourcePath = getSymlinkTargetPath(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 - sourcePath = templateDir; - - //*try* to copy file attributes - if (!sourcePath.empty()) + //try to copy file attributes (dereference symlinks and junctions) + const HANDLE hDirSrc = ::CreateFile(zen::applyLongPathPrefix(templateDir).c_str(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS /*needed to open a directory*/ /*| FILE_FLAG_OPEN_REPARSE_POINT -> no, we follow symlinks!*/ , + nullptr); + if (hDirSrc != INVALID_HANDLE_VALUE) //dereferencing a symbolic link usually fails if it is located on network drive or client is XP: NOT really an error... { - const DWORD sourceAttr = ::GetFileAttributes(applyLongPathPrefix(sourcePath).c_str()); - if (sourceAttr != INVALID_FILE_ATTRIBUTES) + ZEN_ON_SCOPE_EXIT(::CloseHandle(hDirSrc)); + + BY_HANDLE_FILE_INFORMATION dirInfo = {}; + if (::GetFileInformationByHandle(hDirSrc, &dirInfo)) { - ::SetFileAttributes(applyLongPathPrefix(directory).c_str(), sourceAttr); + ::SetFileAttributes(applyLongPathPrefix(directory).c_str(), dirInfo.dwFileAttributes); //copy "read-only and system attributes": http://blogs.msdn.com/b/oldnewthing/archive/2003/09/30/55100.aspx - const bool isCompressed = (sourceAttr & FILE_ATTRIBUTE_COMPRESSED) != 0; - const bool isEncrypted = (sourceAttr & FILE_ATTRIBUTE_ENCRYPTED) != 0; + const bool isCompressed = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; + const bool isEncrypted = (dirInfo.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) != 0; 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, - nullptr, - OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, - nullptr); - if (hDir != INVALID_HANDLE_VALUE) + HANDLE hDirTrg = ::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, + nullptr, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + nullptr); + if (hDirTrg != INVALID_HANDLE_VALUE) { - ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir)); + ZEN_ON_SCOPE_EXIT(::CloseHandle(hDirTrg)); USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; DWORD bytesReturned = 0; - ::DeviceIoControl(hDir, //handle to file or directory + ::DeviceIoControl(hDirTrg, //handle to file or directory FSCTL_SET_COMPRESSION, //dwIoControlCode &cmpState, //input buffer sizeof(cmpState), //size of input buffer @@ -1470,19 +1449,19 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool copyFilePermissions) //throw FileError { - const Zstring linkPath = getSymlinkRawTargetString(sourceLink); //accept broken symlinks; throw FileError + const Zstring linkPath = getSymlinkTargetRaw(sourceLink); //throw FileError; accept broken symlinks #ifdef FFS_WIN const bool isDirLink = [&]() -> bool { const DWORD ret = ::GetFileAttributes(applyLongPathPrefix(sourceLink).c_str()); - return ret != INVALID_FILE_ATTRIBUTES && (ret& FILE_ATTRIBUTE_DIRECTORY); + 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(replaceCpy(_("Cannot find system function %x."), L"%x", L"\"CreateSymbolicLinkW\"")); @@ -1492,8 +1471,7 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool #elif defined FFS_LINUX || defined FFS_MAC if (::symlink(linkPath.c_str(), targetLink.c_str()) != 0) #endif - throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."), L"%x", L"\n" + fmtFileName(sourceLink)), L"%y", L"\n" + fmtFileName(targetLink)) + - L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot create symbolic link %x."), L"%x", fmtFileName(targetLink)) + L"\n\n" + getLastErrorFormatted()); //allow only consistent objects to be created -> don't place before ::symlink, targetLink may already exist zen::ScopeGuard guardNewDir = zen::makeGuard([&] diff --git a/zen/file_handling.h b/zen/file_handling.h index a0bd9b5b..c18a68ac 100644 --- a/zen/file_handling.h +++ b/zen/file_handling.h @@ -33,6 +33,14 @@ enum ResponseSame }; ResponseSame onSameVolume(const Zstring& folderLeft, const Zstring& folderRight); //throw() +enum SymLinkType +{ + SYMLINK_TYPE_DIR, //Windows: may be broken + SYMLINK_TYPE_FILE, //Windows: may be broken + SYMLINK_TYPE_UNKNOWN, //Windows: unable to determine type; Linux: broken Symlink +}; +SymLinkType getSymlinkType(const Zstring& linkname); //throw() + enum ProcSymlink { SYMLINK_DIRECT, diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 4961cac9..cda48e36 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -168,7 +168,7 @@ size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number const size_t bytesRead = ::fread(buffer, 1, bytesToRead, fileHandle); if (::ferror(fileHandle) != 0) //checks status of stream, not fread()! #endif - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted() + L" (read)"); + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted() + L" (ReadFile)"); #ifdef FFS_WIN if (bytesRead < bytesToRead) //verify only! @@ -227,7 +227,7 @@ FileOutput::FileOutput(const Zstring& filename, AccessFlag access) : //throw Fil const DWORD attrib = ::GetFileAttributes(applyLongPathPrefix(filename).c_str()); if (attrib != INVALID_FILE_ATTRIBUTES) { - fileHandle = getHandle(attrib); //retry + fileHandle = getHandle(attrib); //retry: alas this may still fail for hidden file, e.g. accessing shared folder in XP as Virtual Box guest! lastError = ::GetLastError(); } } @@ -301,7 +301,7 @@ void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileErro const size_t bytesWritten = ::fwrite(buffer, 1, bytesToWrite, fileHandle); if (::ferror(fileHandle) != 0) //checks status of stream, not fwrite()! #endif - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted() + L" (w)"); //w -> distinguish from fopen error message! + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted() + L" (WriteFile)"); if (bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes! throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + L"Incomplete write!"); @@ -316,7 +316,7 @@ FileInputUnbuffered::FileInputUnbuffered(const Zstring& filename) : FileInputBas checkForUnsupportedType(filename); //throw FileError; reading a named pipe would block forever! fdFile = ::open(filename.c_str(), O_RDONLY); - if (fdFile < 0) + if (fdFile == -1) { const ErrorCode lastError = getLastError(); @@ -361,7 +361,7 @@ FileOutputUnbuffered::FileOutputUnbuffered(const Zstring& filename, mode_t mode) //overwrite is: O_CREAT | O_WRONLY | O_TRUNC fdFile = ::open(filename.c_str(), O_CREAT | O_WRONLY | O_EXCL, mode & (S_IRWXU | S_IRWXG | S_IRWXO)); - if (fdFile < 0) + if (fdFile == -1) { const int lastError = errno; const std::wstring errorMessage = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filename)) + L"\n\n" + zen::getLastErrorFormatted(lastError); @@ -375,6 +375,7 @@ FileOutputUnbuffered::FileOutputUnbuffered(const Zstring& filename, mode_t mode) } } +FileOutputUnbuffered::FileOutputUnbuffered(int fd, const Zstring& filename) : FileOutputBase(filename), fdFile(fd) {} FileOutputUnbuffered::~FileOutputUnbuffered() { ::close(fdFile); } @@ -395,7 +396,7 @@ void FileOutputUnbuffered::write(const void* buffer, size_t bytesToWrite) //thro if (bytesWritten == 0) //comment in safe-read.c suggests to treat this as an error due to buggy drivers errno = ENOSPC; - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted() + L" (w)"); + throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + getLastErrorFormatted() + L" (write)"); } if (bytesWritten > static_cast<ssize_t>(bytesToWrite)) //better safe than sorry throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())) + L"\n\n" + L"buffer overflow"); diff --git a/zen/file_io.h b/zen/file_io.h index 8e501172..407109f7 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -4,8 +4,8 @@ // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** -#ifndef FILEIO_H_INCLUDED -#define FILEIO_H_INCLUDED +#ifndef FILEIO_89578342758342572345 +#define FILEIO_89578342758342572345 #include "file_io_base.h" #include "file_error.h" @@ -73,7 +73,7 @@ public: //considering safe-read.c it seems buffer size should be a multiple of 8192 virtual size_t read(void* buffer, size_t bytesToRead); //throw FileError; returns actual number of bytes read - //we should not rely on buffer being filled completely! + //do NOT rely on partially filled buffer meaning EOF! int getDescriptor() { return fdFile;} @@ -86,6 +86,7 @@ class FileOutputUnbuffered : public FileOutputBase public: //creates a new file (no overwrite allowed!) FileOutputUnbuffered(const Zstring& filename, mode_t mode); //throw FileError, ErrorTargetPathMissing, ErrorTargetExisting + FileOutputUnbuffered(int fd, const Zstring& filename); //takes ownership! ~FileOutputUnbuffered(); virtual void write(const void* buffer, size_t bytesToWrite); //throw FileError @@ -97,4 +98,4 @@ private: #endif } -#endif // FILEIO_H_INCLUDED +#endif //FILEIO_89578342758342572345 diff --git a/zen/file_io_base.h b/zen/file_io_base.h index f26cd8c2..8e9bf12e 100644 --- a/zen/file_io_base.h +++ b/zen/file_io_base.h @@ -21,8 +21,8 @@ protected: ~FileBase() {} private: - FileBase(const FileBase&); - FileBase& operator=(const FileBase&); + FileBase(const FileBase&); //=delete + FileBase& operator=(const FileBase&); // const Zstring filename_; }; diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index 84bd6c64..53049c21 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -34,7 +34,7 @@ namespace //implement "retry" in a generic way: template <class Command> inline //function object expecting to throw FileError if operation fails -bool tryReportingError(Command cmd, zen::TraverseCallback& callback) //return "true" on success, "false" if error was ignored +bool tryReportingDirError(Command cmd, zen::TraverseCallback& callback) //return "true" on success, "false" if error was ignored { for (;;) try @@ -44,23 +44,40 @@ bool tryReportingError(Command cmd, zen::TraverseCallback& callback) //return "t } catch (const FileError& e) { - switch (callback.onError(e.toString())) + switch (callback.reportDirError(e.toString())) + { + case TraverseCallback::ON_ERROR_RETRY: + break; + case TraverseCallback::ON_ERROR_IGNORE: + return false; + } + } +} + +template <class Command> inline //function object expecting to throw FileError if operation fails +bool tryReportingItemError(Command cmd, zen::TraverseCallback& callback, const Zchar* shortName) //return "true" on success, "false" if error was ignored +{ + for (;;) + try + { + cmd(); //throw FileError + return true; + } + catch (const FileError& e) + { + switch (callback.reportItemError(e.toString(), shortName)) { case TraverseCallback::ON_ERROR_RETRY: break; case TraverseCallback::ON_ERROR_IGNORE: return false; - //default: - // assert(false); - //break; } } } #ifdef FFS_WIN -inline -bool getTargetInfoFromSymlink(const Zstring& linkName, zen::TraverseCallback::FileInfo& output) +void getInfoFromFileSymlink(const Zstring& linkName, zen::TraverseCallback::FileInfo& output) //throw FileError { //open handle to target of symbolic link HANDLE hFile = ::CreateFile(zen::applyLongPathPrefix(linkName).c_str(), @@ -68,21 +85,27 @@ bool getTargetInfoFromSymlink(const Zstring& linkName, zen::TraverseCallback::Fi FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, //needed to open a directory + FILE_FLAG_BACKUP_SEMANTICS, //needed to open a directory -> keep it even if we expect to open a file! See comment below nullptr); if (hFile == INVALID_HANDLE_VALUE) - return false; + throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkName)) + L"\n\n" + getLastErrorFormatted() + L" (CreateFile)"); ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); - BY_HANDLE_FILE_INFORMATION fileInfoByHandle = {}; - if (!::GetFileInformationByHandle(hFile, &fileInfoByHandle)) - return false; + BY_HANDLE_FILE_INFORMATION fileInfo = {}; + if (!::GetFileInformationByHandle(hFile, &fileInfo)) + throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkName)) + L"\n\n" + getLastErrorFormatted() + L" (GetFileInformationByHandle)"); + + //a file symlink may incorrectly point to a directory, but both CreateFile() and GetFileInformationByHandle() will succeed and return garbage! + //- if we did not use FILE_FLAG_BACKUP_SEMANTICS above, CreateFile() would error out with an even less helpful ERROR_ACCESS_DENIED! + //- reinterpreting the link as a directory symlink would still fail during traversal, so just show an error here + //- OTOH a directory symlink that points to a file fails immediately in ::FindFirstFile() with ERROR_DIRECTORY! -> nothing to do in this case + if (fileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkName)) + L"\n\n" + getLastErrorFormatted(ERROR_FILE_INVALID) + L" (GetFileInformationByHandle)"); //write output - output.fileSize = UInt64(fileInfoByHandle.nFileSizeLow, fileInfoByHandle.nFileSizeHigh); - output.lastWriteTimeRaw = toTimeT(fileInfoByHandle.ftLastWriteTime); - output.id = FileId(); //= extractFileID(fileInfoByHandle); -> id from dereferenced symlink is problematic, since renaming will consider the link, not the target! - return true; + output.fileSize = UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); + output.lastWriteTime = toTimeT(fileInfo.ftLastWriteTime); + output.id = extractFileID(fileInfo); //consider detection of moved files: allow for duplicate file ids, renaming affects symlink, not target, ... } @@ -92,8 +115,7 @@ DWORD retrieveVolumeSerial(const Zstring& pathName) //returns 0 on error or if s //- root paths "C:\", "D:\" //- network shares: \\share\dirname //- indirection: subst S: %USERPROFILE% - // -> GetVolumePathName() on the other hand resolves "S:\Desktop\somedir" to "S:\Desktop\" - nice try... - + // -> GetVolumePathName()+GetVolumeInformation() OTOH incorrectly resolves "S:\Desktop\somedir" to "S:\Desktop\" - nice try... const HANDLE hDir = ::CreateFile(zen::applyLongPathPrefix(pathName).c_str(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, @@ -113,43 +135,12 @@ DWORD retrieveVolumeSerial(const Zstring& pathName) //returns 0 on error or if s } -/* -DWORD retrieveVolumeSerial(const Zstring& pathName) //returns 0 on error! -{ - //note: this works for network shares: \\share\dirname, but not "subst"! - - const DWORD BUFFER_SIZE = 10000; - std::vector<wchar_t> buffer(BUFFER_SIZE); - - //full pathName need not yet exist! - if (!::GetVolumePathName(pathName.c_str(), //__in LPCTSTR lpszFileName, - &buffer[0], //__out LPTSTR lpszVolumePathName, - BUFFER_SIZE)) //__in DWORD cchBufferLength - return 0; - - Zstring volumePath = appendSeparator(&buffer[0]); - - DWORD volumeSerial = 0; - if (!::GetVolumeInformation(volumePath.c_str(), //__in_opt LPCTSTR lpRootPathName, - nullptr, //__out LPTSTR lpVolumeNameBuffer, - 0, //__in DWORD nVolumeNameSize, - &volumeSerial, //__out_opt LPDWORD lpVolumeSerialNumber, - nullptr, //__out_opt LPDWORD lpMaximumComponentLength, - nullptr, //__out_opt LPDWORD lpFileSystemFlags, - nullptr, //__out LPTSTR lpFileSystemNameBuffer, - 0)) //__in DWORD nFileSystemNameSize - return 0; - - return volumeSerial; -} -*/ - - const bool isXpOrLater = winXpOrLater(); //VS2010 compiled DLLs are not supported on Win 2000: Popup dialog "DecodePointer not found" -const auto openDir = isXpOrLater ? DllFun<findplus::FunType_openDir >(findplus::getDllName(), findplus::funName_openDir ) : DllFun<findplus::FunType_openDir >(); // -const auto readDir = isXpOrLater ? DllFun<findplus::FunType_readDir >(findplus::getDllName(), findplus::funName_readDir ) : DllFun<findplus::FunType_readDir >(); //load at startup: avoid pre C++11 static initialization MT issues -const auto closeDir= isXpOrLater ? DllFun<findplus::FunType_closeDir>(findplus::getDllName(), findplus::funName_closeDir) : DllFun<findplus::FunType_closeDir>(); // +#define DEF_DLL_FUN(name) const auto name = isXpOrLater ? DllFun<findplus::FunType_##name>(findplus::getDllName(), findplus::funName_##name) : DllFun<findplus::FunType_##name>(); +DEF_DLL_FUN(openDir); // +DEF_DLL_FUN(readDir); //load at startup: avoid pre C++11 static initialization MT issues +DEF_DLL_FUN(closeDir); // /* Common C-style interface for Win32 FindFirstFile(), FindNextFile() and FileFilePlus openDir(), closeDir(): @@ -192,11 +183,11 @@ struct Win32Traverser typedef WIN32_FIND_DATA FindData; - static void create(const Zstring& directory, DirHandle& hnd) //throw FileError + static void create(const Zstring& dirname, DirHandle& hnd) //throw FileError { - const Zstring& directoryPf = appendSeparator(directory); + const Zstring& dirnamePf = appendSeparator(dirname); - hnd.searchHandle = ::FindFirstFile(applyLongPathPrefix(directoryPf + L'*').c_str(), &hnd.data); + hnd.searchHandle = ::FindFirstFile(applyLongPathPrefix(dirnamePf + L'*').c_str(), &hnd.data); //no noticable performance difference compared to FindFirstFileEx with FindExInfoBasic, FIND_FIRST_EX_CASE_SENSITIVE and/or FIND_FIRST_EX_LARGE_FETCH if (hnd.searchHandle == INVALID_HANDLE_VALUE) { @@ -205,17 +196,17 @@ struct Win32Traverser { //1. directory may not exist *or* 2. it is completely empty: not all directories contain "., .." entries, e.g. a drive's root directory; NetDrive // -> FindFirstFile() is a nice example of violation of API design principle of single responsibility - if (dirExists(directory)) //yes, a race-condition, still the best we can do + if (dirExists(dirname)) //yes, a race-condition, still the best we can do return; } - throw FileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirname)) + L"\n\n" + getLastErrorFormatted()); } } static void destroy(const DirHandle& hnd) { ::FindClose(hnd.searchHandle); } //throw() template <class FallbackFun> - static bool getEntry(DirHandle& hnd, const Zstring& directory, FindData& fileInfo, FallbackFun) //throw FileError + static bool getEntry(DirHandle& hnd, const Zstring& dirname, FindData& fileInfo, FallbackFun) //throw FileError { if (hnd.searchHandle == INVALID_HANDLE_VALUE) //handle special case of "truly empty directories" return false; @@ -232,15 +223,15 @@ struct Win32Traverser if (::GetLastError() == ERROR_NO_MORE_FILES) //not an error situation return false; //else we have a problem... report it: - throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirname)) + L"\n\n" + getLastErrorFormatted()); } return true; } static void extractFileInfo(const FindData& fileInfo, DWORD volumeSerial, TraverseCallback::FileInfo& output) { - output.fileSize = UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); - output.lastWriteTimeRaw = getModTime(fileInfo); + output.fileSize = UInt64(fileInfo.nFileSizeLow, fileInfo.nFileSizeHigh); + output.lastWriteTime = getModTime(fileInfo); output.id = FileId(); } @@ -264,17 +255,17 @@ struct FilePlusTraverser typedef findplus::FileInformation FindData; - static void create(const Zstring& directory, DirHandle& hnd) //throw FileError + static void create(const Zstring& dirname, DirHandle& hnd) //throw FileError { - hnd.searchHandle = ::openDir(applyLongPathPrefix(directory).c_str()); + hnd.searchHandle = ::openDir(applyLongPathPrefix(dirname).c_str()); if (hnd.searchHandle == nullptr) - throw FileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted() + L" (+)"); + throw FileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirname)) + L"\n\n" + getLastErrorFormatted() + L" (+)"); } static void destroy(DirHandle hnd) { ::closeDir(hnd.searchHandle); } //throw() template <class FallbackFun> - static bool getEntry(DirHandle hnd, const Zstring& directory, FindData& fileInfo, FallbackFun fb) //throw FileError + static bool getEntry(DirHandle hnd, const Zstring& dirname, FindData& fileInfo, FallbackFun fb) //throw FileError { if (!::readDir(hnd.searchHandle, fileInfo)) { @@ -293,7 +284,7 @@ struct FilePlusTraverser } //else we have a problem... report it: - throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted(lastError) + L" (+)"); + throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirname)) + L"\n\n" + getLastErrorFormatted(lastError) + L" (+)"); } return true; @@ -301,9 +292,9 @@ struct FilePlusTraverser static void extractFileInfo(const FindData& fileInfo, DWORD volumeSerial, TraverseCallback::FileInfo& output) { - output.fileSize = fileInfo.fileSize.QuadPart; - output.lastWriteTimeRaw = getModTime(fileInfo); - output.id = extractFileID(volumeSerial, fileInfo.fileId); + output.fileSize = fileInfo.fileSize.QuadPart; + output.lastWriteTime = getModTime(fileInfo); + output.id = extractFileID(volumeSerial, fileInfo.fileId); } static Int64 getModTime (const FindData& fileInfo) { return toTimeT(fileInfo.lastWriteTime); } @@ -319,9 +310,10 @@ class DirTraverser { public: DirTraverser(const Zstring& baseDirectory, TraverseCallback& sink, DstHackCallback* dstCallback) : - isFatFileSystem(dst::isFatDrive(baseDirectory)), - volumeSerial(retrieveVolumeSerial(baseDirectory)) //return 0 on error + isFatFileSystem(dst::isFatDrive(baseDirectory)) { + warn_static("ineffizient, wenn kein dst hack/file ids gebraucht werden???") + try //traversing certain folders with restricted permissions requires this privilege! (but copying these files may still fail) { activatePrivilege(SE_BACKUP_NAME); //throw FileError @@ -329,7 +321,7 @@ public: catch (FileError&) {} //don't cause issues in user mode if (::openDir && ::readDir && ::closeDir) - traverse<FilePlusTraverser>(baseDirectory, sink, 0); + traverse<FilePlusTraverser>(baseDirectory, sink, retrieveVolumeSerial(baseDirectory)); //retrieveVolumeSerial returns 0 on error else //fallback traverse<Win32Traverser>(baseDirectory, sink, 0); @@ -343,32 +335,28 @@ private: DirTraverser& operator=(const DirTraverser&); template <class Trav> - void traverse(const Zstring& directory, zen::TraverseCallback& sink, int level) + void traverse(const Zstring& dirname, zen::TraverseCallback& sink, DWORD volumeSerial /*may be 0!*/) { - tryReportingError([&] - { - if (level == 100) //notify endless recursion - throw FileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + _("Detected endless directory recursion.")); - }, sink); + //no need to check for endless recursion: Windows seems to have an internal path limit of about 700 chars typename Trav::DirHandle searchHandle; - if (!tryReportingError([&] + if (!tryReportingDirError([&] { typedef Trav Trav; //f u VS! - Trav::create(directory, searchHandle); //throw FileError + Trav::create(dirname, searchHandle); //throw FileError }, sink)) return; //ignored error ZEN_ON_SCOPE_EXIT(typedef Trav Trav; Trav::destroy(searchHandle)); typename Trav::FindData findData = {}; - auto fallback = [&] { this->traverse<Win32Traverser>(directory, sink, level); }; //help VS2010 a little by avoiding too deeply nested lambdas + auto fallback = [&] { this->traverse<Win32Traverser>(dirname, sink, volumeSerial); }; //help VS2010 a little by avoiding too deeply nested lambdas for (;;) { bool gotEntry = false; - tryReportingError([&] { typedef Trav Trav; /*VS 2010 bug*/ gotEntry = Trav::getEntry(searchHandle, directory, findData, fallback); }, sink); //throw FileError + tryReportingDirError([&] { typedef Trav Trav; /*VS 2010 bug*/ gotEntry = Trav::getEntry(searchHandle, dirname, findData, fallback); }, sink); //throw FileError if (!gotEntry) //no more items or ignored error return; @@ -378,18 +366,12 @@ private: (shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0))) continue; - const Zstring& fullName = appendSeparator(directory) + shortName; + const Zstring& fullName = appendSeparator(dirname) + shortName; if (Trav::isSymlink(findData)) //check first! { TraverseCallback::SymlinkInfo linkInfo; - try - { - linkInfo.targetPath = getSymlinkRawTargetString(fullName); //throw FileError - } - catch (FileError&) { assert(false); } - linkInfo.lastWriteTimeRaw = Trav::getModTime (findData); - linkInfo.dirLink = Trav::isDirectory(findData); //directory symlinks have this flag on Windows + linkInfo.lastWriteTime = Trav::getModTime (findData); switch (sink.onSymlink(shortName, fullName, linkInfo)) { @@ -397,21 +379,21 @@ private: if (Trav::isDirectory(findData)) { if (const std::shared_ptr<TraverseCallback>& rv = sink.onDir(shortName, fullName)) - traverse<Trav>(fullName, *rv, level + 1); + traverse<Trav>(fullName, *rv, retrieveVolumeSerial(fullName)); //symlink may link to different volume => redetermine volume serial! } else //a file { TraverseCallback::FileInfo targetInfo; - const bool validLink = tryReportingError([&] //try to resolve symlink (and report error on failure!!!) + const bool validLink = tryReportingItemError([&] //try to resolve symlink (and report error on failure!!!) { - if (!getTargetInfoFromSymlink(fullName, targetInfo)) - throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(fullName)) + L"\n\n" + getLastErrorFormatted()); - }, sink); + getInfoFromFileSymlink(fullName, targetInfo); //throw FileError + }, sink, shortName); if (validLink) sink.onFile(shortName, fullName, targetInfo); - else //broken symlink - sink.onFile(shortName, fullName, TraverseCallback::FileInfo()); + // else //broken symlink + // sink.onFile(shortName, fullName, TraverseCallback::FileInfo()); + warn_static("impact of ignored broken file/incomplete dir read on two-way variant!?") } break; @@ -422,7 +404,7 @@ private: else if (Trav::isDirectory(findData)) { if (const std::shared_ptr<TraverseCallback>& rv = sink.onDir(shortName, fullName)) - traverse<Trav>(fullName, *rv, level + 1); + traverse<Trav>(fullName, *rv, volumeSerial); } else //a file { @@ -435,7 +417,7 @@ private: const dst::RawTime rawTime(Trav::getCreateTimeRaw(findData), Trav::getModTimeRaw(findData)); if (dst::fatHasUtcEncoded(rawTime)) //throw std::runtime_error - fileInfo.lastWriteTimeRaw = toTimeT(dst::fatDecodeUtcTime(rawTime)); //return real UTC time; throw (std::runtime_error) + fileInfo.lastWriteTime = toTimeT(dst::fatDecodeUtcTime(rawTime)); //return real UTC time; throw (std::runtime_error) else markForDstHack.push_back(std::make_pair(fullName, toTimeT(rawTime.writeTimeRaw))); } @@ -498,8 +480,6 @@ private: typedef std::vector<std::pair<Zstring, Int64> > FilenameTimeList; FilenameTimeList markForDstHack; //####################################### DST hack ########################################### - - const DWORD volumeSerial; //may be 0! }; #elif defined FFS_LINUX || defined FFS_MAC @@ -521,28 +501,23 @@ public: const size_t maxPath = std::max<long>(::pathconf(directoryFormatted.c_str(), _PC_NAME_MAX), 10000); //::pathconf may return long(-1) buffer.resize(offsetof(struct ::dirent, d_name) + maxPath + 1); - traverse(directoryFormatted, sink, 0); + traverse(directoryFormatted, sink); } private: DirTraverser(const DirTraverser&); DirTraverser& operator=(const DirTraverser&); - void traverse(const Zstring& directory, zen::TraverseCallback& sink, int level) + void traverse(const Zstring& dirname, zen::TraverseCallback& sink) { - tryReportingError([&] - { - if (level == 100) //notify endless recursion - throw FileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + _("Detected endless directory recursion.")); - }, sink); - + //no need to check for endless recursion: Linux has a fixed limit on the number of symbolic links in a path DIR* dirObj = nullptr; - if (!tryReportingError([&] + if (!tryReportingDirError([&] { - dirObj = ::opendir(directory.c_str()); //directory must NOT end with path separator, except "/" + dirObj = ::opendir(dirname.c_str()); //directory must NOT end with path separator, except "/" if (!dirObj) - throw FileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot open directory %x."), L"%x", fmtFileName(dirname)) + L"\n\n" + getLastErrorFormatted()); }, sink)) return; //ignored error ZEN_ON_SCOPE_EXIT(::closedir(dirObj)); //never close nullptr handles! -> crash @@ -550,10 +525,10 @@ private: for (;;) { struct ::dirent* dirEntry = nullptr; - tryReportingError([&] + tryReportingDirError([&] { if (::readdir_r(dirObj, reinterpret_cast< ::dirent*>(&buffer[0]), &dirEntry) != 0) - throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(directory)) + L"\n\n" + getLastErrorFormatted()); + throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirname)) + L"\n\n" + getLastErrorFormatted()); }, sink); if (!dirEntry) //no more items or ignored error return; @@ -583,61 +558,53 @@ private: //const char* sampleDecomposed = "\x6f\xcc\x81.txt"; //const char* samplePrecomposed = "\xc3\xb3.txt"; #endif - const Zstring& fullName = appendSeparator(directory) + shortName; + const Zstring& fullName = appendSeparator(dirname) + shortName; struct ::stat statData = {}; - if (!tryReportingError([&] + if (!tryReportingItemError([&] { if (::lstat(fullName.c_str(), &statData) != 0) //lstat() does not resolve symlinks throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(fullName)) + L"\n\n" + getLastErrorFormatted()); - }, sink)) + }, sink, shortName)) continue; //ignore error: skip file if (S_ISLNK(statData.st_mode)) //on Linux there is no distinction between file and directory symlinks! { - struct ::stat statDataTrg = {}; - bool validLink = ::stat(fullName.c_str(), &statDataTrg) == 0; //if "LINK_SKIP", a broken link is no error! - TraverseCallback::SymlinkInfo linkInfo; - try - { - linkInfo.targetPath = getSymlinkRawTargetString(fullName); //throw FileError - } - catch (FileError&) { assert(false); } - linkInfo.lastWriteTimeRaw = statData.st_mtime; //UTC time (ANSI C format); unit: 1 second - linkInfo.dirLink = validLink && S_ISDIR(statDataTrg.st_mode); - //S_ISDIR and S_ISLNK are mutually exclusive on Linux => explicitly need to follow link + linkInfo.lastWriteTime = statData.st_mtime; //UTC time (ANSI C format); unit: 1 second switch (sink.onSymlink(shortName, fullName, linkInfo)) { case TraverseCallback::LINK_FOLLOW: + { //try to resolve symlink (and report error on failure!!!) - validLink = tryReportingError([&] + struct ::stat statDataTrg = {}; + bool validLink = tryReportingItemError([&] { - if (validLink) return; //no need to check twice if (::stat(fullName.c_str(), &statDataTrg) != 0) throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(fullName)) + L"\n\n" + getLastErrorFormatted()); - }, sink); + }, sink, shortName); if (validLink) { if (S_ISDIR(statDataTrg.st_mode)) //a directory { if (const std::shared_ptr<TraverseCallback>& rv = sink.onDir(shortName, fullName)) - traverse(fullName, *rv, level + 1); + traverse(fullName, *rv); } else //a file or named pipe, ect. { TraverseCallback::FileInfo fileInfo; - fileInfo.fileSize = zen::UInt64(statDataTrg.st_size); - fileInfo.lastWriteTimeRaw = statDataTrg.st_mtime; //UTC time (time_t format); unit: 1 second - //fileInfo.id = extractFileID(statDataTrg); -> id from dereferenced symlink is problematic, since renaming would consider the link, not the target! + fileInfo.fileSize = zen::UInt64(statDataTrg.st_size); + fileInfo.lastWriteTime = statDataTrg.st_mtime; //UTC time (time_t format); unit: 1 second + fileInfo.id = extractFileID(statDataTrg); sink.onFile(shortName, fullName, fileInfo); } } - else //report broken symlink as file! - sink.onFile(shortName, fullName, TraverseCallback::FileInfo()); - break; + // else //report broken symlink as file! + // sink.onFile(shortName, fullName, TraverseCallback::FileInfo()); + } + break; case TraverseCallback::LINK_SKIP: break; @@ -646,14 +613,14 @@ private: else if (S_ISDIR(statData.st_mode)) //a directory { if (const std::shared_ptr<TraverseCallback>& rv = sink.onDir(shortName, fullName)) - traverse(fullName, *rv, level + 1); + traverse(fullName, *rv); } else //a file or named pipe, ect. { TraverseCallback::FileInfo fileInfo; - fileInfo.fileSize = zen::UInt64(statData.st_size); - fileInfo.lastWriteTimeRaw = statData.st_mtime; //UTC time (time_t format); unit: 1 second - fileInfo.id = extractFileID(statData); + fileInfo.fileSize = zen::UInt64(statData.st_size); + fileInfo.lastWriteTime = statData.st_mtime; //UTC time (time_t format); unit: 1 second + fileInfo.id = extractFileID(statData); sink.onFile(shortName, fullName, fileInfo); } @@ -676,7 +643,7 @@ private: } -void zen::traverseFolder(const Zstring& directory, TraverseCallback& sink, DstHackCallback* dstCallback) +void zen::traverseFolder(const Zstring& dirname, TraverseCallback& sink, DstHackCallback* dstCallback) { - DirTraverser(directory, sink, dstCallback); + DirTraverser(dirname, sink, dstCallback); } diff --git a/zen/file_traverser.h b/zen/file_traverser.h index 97fb0e9f..13b76966 100644 --- a/zen/file_traverser.h +++ b/zen/file_traverser.h @@ -22,17 +22,15 @@ struct TraverseCallback struct FileInfo { - UInt64 fileSize; //unit: bytes! - Int64 lastWriteTimeRaw; //number of seconds since Jan. 1st 1970 UTC - FileId id; //optional: initial if not supported! + UInt64 fileSize; //unit: bytes! + Int64 lastWriteTime; //number of seconds since Jan. 1st 1970 UTC + FileId id; //optional: initial if not supported! //std::unique_ptr<SymlinkInfo> symlinkInfo; //only filled if file is dereferenced symlink }; struct SymlinkInfo { - Int64 lastWriteTimeRaw; //number of seconds since Jan. 1st 1970 UTC - Zstring targetPath; //optional: empty if something goes wrong - bool dirLink; //"true": point to dir; "false": point to file (or broken Link on Linux) + Int64 lastWriteTime; //number of seconds since Jan. 1st 1970 UTC }; enum HandleLink @@ -51,7 +49,8 @@ struct TraverseCallback /**/ onDir (const Zchar* shortName, const Zstring& fullName) = 0; virtual void onFile (const Zchar* shortName, const Zstring& fullName, const FileInfo& details) = 0; virtual HandleLink onSymlink(const Zchar* shortName, const Zstring& fullName, const SymlinkInfo& details) = 0; - virtual HandleError onError (const std::wstring& msg) = 0; + virtual HandleError reportDirError (const std::wstring& msg) = 0; //failed directory traversal -> consider directory data as incomplete! + virtual HandleError reportItemError(const std::wstring& msg, const Zchar* shortName) = 0; //failed to get single file/dir/symlink only! }; @@ -68,7 +67,7 @@ struct DstHackCallback; //DST hack not required on Unix //custom traverser with detail information about files //Win: client needs to handle duplicate file notifications! (FilePlusTraverser fallback) //directory may end with PATH_SEPARATOR -void traverseFolder(const Zstring& directory, //throw() +void traverseFolder(const Zstring& dirname, //throw() TraverseCallback& sink, DstHackCallback* dstCallback = nullptr); //apply DST hack if callback is supplied } diff --git a/zen/format_unit.cpp b/zen/format_unit.cpp index 7cc43e6b..60eb6869 100644 --- a/zen/format_unit.cpp +++ b/zen/format_unit.cpp @@ -39,7 +39,7 @@ std::wstring zen::filesizeToShortString(Int64 size) wchar_t buffer[50]; #ifdef __MINGW32__ - int charsWritten = ::_snwprintf(buffer, 50, L"%.*f", precisionDigits, sizeInUnit); //MinGW does not comply to the C standard here + int charsWritten = ::_snwprintf(buffer, 50, L"%.*f", precisionDigits, sizeInUnit); //MinGW does not comply to the C standard here #else int charsWritten = std::swprintf(buffer, 50, L"%.*f", precisionDigits, sizeInUnit); #endif @@ -148,7 +148,6 @@ std::wstring zen::remainingTimeToString(double timeInSec) std::wstring zen::fractionToString(double fraction) { - //return replaceCpy(_("%x%"), L"%x", printNumber<std::wstring>(L"%3.2f", fraction * 100.0), false); return printNumber<std::wstring>(L"%3.2f", fraction * 100.0) + L'%'; //no need to internationalize fraction!? } diff --git a/zen/int64.h b/zen/int64.h index a760d5ab..4c63a24f 100644 --- a/zen/int64.h +++ b/zen/int64.h @@ -14,7 +14,6 @@ #include <ostream> #include "assert_static.h" #include "type_tools.h" -#include "type_traits.h" #ifdef FFS_WIN #include "win.h" diff --git a/zen/long_path_prefix.h b/zen/long_path_prefix.h index c3d705e2..db1fbdca 100644 --- a/zen/long_path_prefix.h +++ b/zen/long_path_prefix.h @@ -58,8 +58,8 @@ Zstring applyLongPathPrefixImpl(const Zstring& path) assert(!zen::isWhiteSpace(path[0])); if (path.length() >= maxPath || //maximum allowed path length without prefix is (MAX_PATH - 1) - endsWith(path, L' ') || //by default all Win32 APIs trim trailing spaces and period, unless long path prefix is supplied! - endsWith(path, L'.')) // + endsWith(path, L' ') || //by default all Win32 APIs trim trailing spaces and period, unless long path prefix is supplied! + endsWith(path, L'.')) //note: adding long path prefix might screw up relative paths "." and ".."! if (!startsWith(path, LONG_PATH_PREFIX)) { if (startsWith(path, L"\\\\")) //UNC-name, e.g. \\zenju-pc\Users diff --git a/zen/serialize.h b/zen/serialize.h index a9238359..982165f9 100644 --- a/zen/serialize.h +++ b/zen/serialize.h @@ -86,6 +86,7 @@ struct BinStreamIn //throw UnexpectedEndOfStreamError const void* requestRead(size_t len) //throw UnexpectedEndOfStreamError { + if (len == 0) return nullptr; //don't allow for possibility to access empty buffer if (pos + len > buffer.size()) throw UnexpectedEndOfStreamError(); size_t oldPos = pos; @@ -102,8 +103,9 @@ struct BinStreamOut { void* requestWrite(size_t len) { - size_t oldSize = buffer.size(); - buffer.resize(buffer.size() + len); + if (len == 0) return nullptr; //don't allow for possibility to access empty buffer + const size_t oldSize = buffer.size(); + buffer.resize(oldSize + len); return &*buffer.begin() + oldSize; } diff --git a/zen/stl_tools.h b/zen/stl_tools.h index 07fc31d7..b394b128 100644 --- a/zen/stl_tools.h +++ b/zen/stl_tools.h @@ -18,7 +18,6 @@ #endif - //enhancements for <algorithm> namespace zen { @@ -47,6 +46,10 @@ template <class BidirectionalIterator1, class BidirectionalIterator2> BidirectionalIterator1 search_last(BidirectionalIterator1 first1, BidirectionalIterator1 last1, BidirectionalIterator2 first2, BidirectionalIterator2 last2); +template <class InputIterator1, class InputIterator2> +bool equal(InputIterator1 first1, InputIterator1 last1, + InputIterator2 first2, InputIterator2 last2); + //hash container: proper name + mitigate MSVC performance bug template <class T> class hash_set; template <class K, class V> class hash_map; @@ -166,6 +169,14 @@ BidirectionalIterator1 search_last(const BidirectionalIterator1 first1, Bidirect } +template <class InputIterator1, class InputIterator2> inline +bool equal(InputIterator1 first1, InputIterator1 last1, + InputIterator2 first2, InputIterator2 last2) +{ + return last1 - first1 == last2 - first2 && std::equal(first1, last1, first2); +} + + #if defined _MSC_VER && _MSC_VER <= 1600 //VS2010 performance bug in std::unordered_set<>: http://drdobbs.com/blogs/cpp/232200410 -> should be fixed in VS11 template <class T> class hash_set : public std::set<T> {}; template <class K, class V> class hash_map : public std::map<K, V> {}; diff --git a/zen/string_base.h b/zen/string_base.h index e38fab94..591ed62b 100644 --- a/zen/string_base.h +++ b/zen/string_base.h @@ -13,7 +13,7 @@ #include "string_tools.h" #include <boost/detail/atomic_count.hpp> -//Zbase - a policy based string class optimizing performance and genericity +//Zbase - a policy based string class optimizing performance and flexibility namespace zen { @@ -183,7 +183,7 @@ private: //################################################################################################################################################################ -//perf note: interstingly StorageDeepCopy and StorageRefCountThreadSafe show same performance in FFS comparison +//perf note: interestingly StorageDeepCopy and StorageRefCountThreadSafe show same performance in FFS comparison template <class Char, //Character Type template <class, class> class SP = StorageRefCountThreadSafe, //Storage Policy @@ -231,7 +231,7 @@ public: size_t find (Char ch, size_t pos = 0) const; //returns "npos" if not found size_t rfind(Char ch, size_t pos = npos) const; // size_t rfind(const Char* str, size_t pos = npos) const; // - Zbase& replace(size_t pos1, size_t n1, const Zbase& str); + //Zbase& replace(size_t pos1, size_t n1, const Zbase& str); void reserve(size_t minCapacity); Zbase& assign(const Char* source, size_t len); Zbase& append(const Char* source, size_t len); @@ -361,11 +361,11 @@ Zbase<Char, SP, AP>::Zbase(const Zbase<Char, SP, AP>& source) template <class Char, template <class, class> class SP, class AP> inline Zbase<Char, SP, AP>::Zbase(Zbase<Char, SP, AP>&& tmp) { - //rawStr = this->clone(tmp.rawStr); NO! do not increment ref-count of a potentially unshared string! We'd lose optimization opportunity of reusing it! - //instead create a dummy string and swap: - if (this->canWrite(tmp.rawStr, 0)) //perf: this check saves about 4% + if (this->canWrite(tmp.rawStr, 0)) //perf: following optimization saves about 4% { - rawStr = this->create(0); //no perf issue! see comment in default constructor + //do not increment ref-count of an unshared string! We'd lose optimization opportunity of reusing its memory! + //instead create a dummy string and swap: + rawStr = this->create(0); //no perf issue! see comment in default constructor rawStr[0] = 0; swap(tmp); } @@ -450,6 +450,7 @@ size_t Zbase<Char, SP, AP>::rfind(const Char* str, size_t pos) const } +/* -> dead code ahead: better use zen::replace template instead! template <class Char, template <class, class> class SP, class AP> Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::replace(size_t pos1, size_t n1, const Zbase& str) { @@ -493,7 +494,7 @@ Zbase<Char, SP, AP>& Zbase<Char, SP, AP>::replace(size_t pos1, size_t n1, const } return *this; } - +*/ template <class Char, template <class, class> class SP, class AP> inline void Zbase<Char, SP, AP>::resize(size_t newSize, Char fillChar) diff --git a/zen/string_traits.h b/zen/string_traits.h index 93e8c510..22aa2ffc 100644 --- a/zen/string_traits.h +++ b/zen/string_traits.h @@ -23,31 +23,31 @@ GetCharType<>::Type: GetCharType<std::wstring>::Type //equals wchar_t GetCharType<wchar_t[5]> ::Type //equals wchar_t -strBegin(): +strLength(): + strLength(str); //equals str.length() + strLength(array); //equals cStringLength(array) + +strBegin(): -> not null-terminated! -> may be nullptr if length is 0! std::wstring str(L"dummy"); char array[] = "dummy"; strBegin(str); //returns str.c_str() strBegin(array); //returns array - -strLength(): - strLength(str); //equals str.length() - strLength(array); //equals cStringLength(array) */ -//reference a sub-string or a char* as an intermediate string class when the length is already known +//reference a sub-string for consumption by zen string_tools template <class Char> -class StringProxy +class StringRef { public: - StringProxy(const Char* cstr, size_t len ) : cstr_(cstr), length_(len) {} - StringProxy(const Char* cstrBegin, const Char* cstrEnd) : cstr_(cstrBegin), length_(cstrEnd - cstrBegin) {} + template <class Iterator> + StringRef(Iterator first, Iterator last) : length_(last - first), data_(first != last ? &*first : nullptr) {} - const Char* c_str() const { return cstr_; } size_t length() const { return length_; } + const Char* data() const { return data_; } //1. no null-termination! 2. may be nullptr! private: - const Char* cstr_; size_t length_; + const Char* data_; }; @@ -61,7 +61,6 @@ private: - //---------------------- implementation ---------------------- namespace implementation { @@ -129,6 +128,28 @@ public: IsSameType<CharType, wchar_t>::value }; }; + + +template <> class StringTraits<StringRef<char>> +{ +public: + enum + { + isStringClass = false, + isStringLike = true + }; + typedef char CharType; +}; +template <> class StringTraits<StringRef<wchar_t>> +{ +public: + enum + { + isStringClass = false, + isStringLike = true + }; + typedef wchar_t CharType; +}; } template <class T> @@ -162,6 +183,8 @@ inline const char* strBegin(const char* str) { return str; } inline const wchar_t* strBegin(const wchar_t* str) { return str; } inline const char* strBegin(const char& ch) { return &ch; } inline const wchar_t* strBegin(const wchar_t& ch) { return &ch; } +inline const char* strBegin(const StringRef<char >& ref) { return ref.data(); } +inline const wchar_t* strBegin(const StringRef<wchar_t>& ref) { return ref.data(); } template <class S> inline @@ -174,6 +197,8 @@ inline size_t strLength(const char* str) { return implementation::cStringLeng inline size_t strLength(const wchar_t* str) { return implementation::cStringLength(str); } inline size_t strLength(char) { return 1; } inline size_t strLength(wchar_t) { return 1; } +inline size_t strLength(const StringRef<char >& ref) { return ref.length(); } +inline size_t strLength(const StringRef<wchar_t>& ref) { return ref.length(); } } #endif //STRING_TRAITS_HEADER_813274321443234 diff --git a/zen/symlink_target.h b/zen/symlink_target.h index 95aa84fb..bbced0fa 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -4,8 +4,8 @@ // * Copyright (C) Zenju (zenju AT gmx DOT de) - All Rights Reserved * // ************************************************************************** -#ifndef SYMLINK_WIN_H_INCLUDED -#define SYMLINK_WIN_H_INCLUDED +#ifndef SYMLINK_80178347019835748321473214 +#define SYMLINK_80178347019835748321473214 #include "scope_guard.h" #include "file_error.h" @@ -15,6 +15,7 @@ #include "WinIoCtl.h" #include "privilege.h" #include "long_path_prefix.h" +#include "dll.h" #elif defined FFS_LINUX || defined FFS_MAC #include <unistd.h> @@ -26,9 +27,11 @@ namespace zen #ifdef FFS_WIN bool isSymlink(const WIN32_FIND_DATA& data); //*not* a simple FILE_ATTRIBUTE_REPARSE_POINT check! bool isSymlink(DWORD fileAttributes, DWORD reparseTag); + +Zstring getResolvedFilePath(const Zstring& filename); //throw FileError; requires Vista or later! #endif -Zstring getSymlinkRawTargetString(const Zstring& linkPath); //throw FileError +Zstring getSymlinkTargetRaw(const Zstring& linkPath); //throw FileError } @@ -90,7 +93,7 @@ Zstring getSymlinkRawTargetString_impl(const Zstring& linkPath) //throw FileErro catch (FileError&) {} //This shall not cause an error in user mode! const HANDLE hLink = ::CreateFile(applyLongPathPrefix(linkPath).c_str(), - GENERIC_READ, + 0, //it seems we do not even need GENERIC_READ! FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, @@ -138,32 +141,78 @@ Zstring getSymlinkRawTargetString_impl(const Zstring& linkPath) //throw FileErro return output; #elif defined FFS_LINUX || defined FFS_MAC - const int BUFFER_SIZE = 10000; + const size_t 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) + const ssize_t bytesWritten = ::readlink(linkPath.c_str(), &buffer[0], BUFFER_SIZE); + if (bytesWritten < 0 || bytesWritten >= static_cast<ssize_t>(BUFFER_SIZE)) //detect truncation! { std::wstring errorMessage = replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtFileName(linkPath)); if (bytesWritten < 0) errorMessage += L"\n\n" + getLastErrorFormatted(); throw FileError(errorMessage); } - buffer[bytesWritten] = 0; //set null-terminating char - - return Zstring(&buffer[0], bytesWritten); + return Zstring(&buffer[0], bytesWritten); //readlink does not append 0-termination! #endif } + + +#ifdef FFS_WIN +Zstring getResolvedFilePath_impl(const Zstring& filename) //throw FileError +{ + using namespace zen; + + const HANDLE hDir = ::CreateFile(applyLongPathPrefix(filename).c_str(), + 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, //needed to open a directory + nullptr); + if (hDir == INVALID_HANDLE_VALUE) + throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted() + L" (CreateFile)"); + ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir)); + + //GetFinalPathNameByHandle() is not available before Vista! + typedef DWORD (WINAPI* GetFinalPathNameByHandleWFunc)(HANDLE hFile, LPTSTR lpszFilePath, DWORD cchFilePath, DWORD dwFlags); + const SysDllFun<GetFinalPathNameByHandleWFunc> getFinalPathNameByHandle(L"kernel32.dll", "GetFinalPathNameByHandleW"); + + if (!getFinalPathNameByHandle) + throw FileError(replaceCpy(_("Cannot find system function %x."), L"%x", L"\"GetFinalPathNameByHandleW\"")); + + const DWORD bufferSize = getFinalPathNameByHandle(hDir, nullptr, 0, 0); + if (bufferSize == 0) + throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(filename)) + L"\n\n" + getLastErrorFormatted()); + + std::vector<wchar_t> targetPath(bufferSize); + const DWORD charsWritten = getFinalPathNameByHandle(hDir, //__in HANDLE hFile, + &targetPath[0], //__out LPTSTR lpszFilePath, + bufferSize, //__in DWORD cchFilePath, + 0); //__in DWORD dwFlags + if (charsWritten == 0 || charsWritten >= bufferSize) + { + std::wstring errorMessage = replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(filename)); + if (charsWritten == 0) + errorMessage += L"\n\n" + getLastErrorFormatted(); + throw FileError(errorMessage); + } + + return Zstring(&targetPath[0], charsWritten); +} +#endif } namespace zen { inline -Zstring getSymlinkRawTargetString(const Zstring& linkPath) { return getSymlinkRawTargetString_impl(linkPath); } +Zstring getSymlinkTargetRaw(const Zstring& linkPath) { return getSymlinkRawTargetString_impl(linkPath); } #ifdef FFS_WIN +inline +Zstring getResolvedFilePath(const Zstring& filename) { return getResolvedFilePath_impl(filename); } + /* Reparse Point Tags http://msdn.microsoft.com/en-us/library/windows/desktop/aa365511(v=vs.85).aspx @@ -190,4 +239,4 @@ bool isSymlink(const WIN32_FIND_DATA& data) #endif } -#endif // SYMLINK_WIN_H_INCLUDED +#endif //SYMLINK_80178347019835748321473214 diff --git a/zen/thread.h b/zen/thread.h index db9cf3a3..638d9474 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -10,11 +10,12 @@ //temporary solution until C++11 thread becomes fully available (considering std::thread's non-interruptibility and std::async craziness, this may be NEVER) #include <memory> -//fix this pathetic boost thread warning mess +//workaround this pathetic boost thread warning mess #ifdef __GNUC__ #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wswitch-enum" #pragma GCC diagnostic ignored "-Wstrict-aliasing" +#pragma GCC diagnostic ignored "-Wredundant-decls" #pragma GCC diagnostic ignored "-Wshadow" #endif #ifdef _MSC_VER diff --git a/zen/tick_count.h b/zen/tick_count.h index be4839ca..d005a828 100644 --- a/zen/tick_count.h +++ b/zen/tick_count.h @@ -129,7 +129,7 @@ std::int64_t ticksPerSec() //return 0 on error inline -TickVal getTicks() //return 0 on error +TickVal getTicks() //return !isValid() on error { #ifdef FFS_WIN LARGE_INTEGER now = {}; @@ -291,7 +291,7 @@ bool parseTime(const String& format, const String& str, TimeComp& comp) //return if (std::any_of(iterStr, iterStr + digitCount, [](CharType c) { return !isDigit(c); })) return false; - result = zen::stringTo<int>(StringProxy<CharType>(iterStr, digitCount)); + result = zen::stringTo<int>(StringRef<CharType>(iterStr, iterStr + digitCount)); iterStr += digitCount; return true; }; @@ -50,18 +50,6 @@ size_t findUnicodePos(const UtfString& str, size_t unicodePos); //return positio - - - - - - - - - - - - //----------------------- implementation ---------------------------------- namespace implementation { @@ -69,45 +57,47 @@ typedef std::uint_fast32_t CodePoint; //must be at least four bytes typedef std::uint_fast16_t Char16; //we need an unsigned type typedef unsigned char Char8; -const CodePoint CODE_POINT_MAX = 0x10ffff; +const CodePoint LEAD_SURROGATE = 0xd800; +const CodePoint TRAIL_SURROGATE = 0xdc00; //== LEAD_SURROGATE_MAX + 1 +const CodePoint TRAIL_SURROGATE_MAX = 0xdfff; -const CodePoint HIGH_SURROGATE = 0xd800; -const CodePoint HIGH_SURROGATE_MAX = 0xdbff; - -const CodePoint LOW_SURROGATE = 0xdc00; -const CodePoint LOW_SURROGATE_MAX = 0xdfff; +const CodePoint REPLACEMENT_CHAR = 0xfffd; +const CodePoint CODE_POINT_MAX = 0x10ffff; template <class Function> inline void codePointToUtf16(CodePoint cp, Function writeOutput) //"writeOutput" is a unary function taking a Char16 { //http://en.wikipedia.org/wiki/UTF-16 - 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) + if (cp < LEAD_SURROGATE) writeOutput(static_cast<Char16>(cp)); - else + else if (cp <= TRAIL_SURROGATE_MAX) //invalid code point + codePointToUtf16(REPLACEMENT_CHAR, writeOutput); //resolves to 1-character utf16 + else if (cp < 0x10000) + writeOutput(static_cast<Char16>(cp)); + else if (cp <= CODE_POINT_MAX) { cp -= 0x10000; - writeOutput(static_cast<Char16>((cp >> 10) + HIGH_SURROGATE)); - writeOutput(static_cast<Char16>((cp & 0x3ff) + LOW_SURROGATE)); + writeOutput(LEAD_SURROGATE + static_cast<Char16>(cp >> 10)); + writeOutput(TRAIL_SURROGATE + static_cast<Char16>(cp & 0x3ff)); } + else //invalid code point + codePointToUtf16(REPLACEMENT_CHAR, writeOutput); //resolves to 1-character utf16 } inline -size_t getUtf16Len(Char16 ch) //ch must be first code unit! +size_t getUtf16Len(Char16 ch) //ch must be first code unit! returns 0 on error! { - const CodePoint cp = ch; - - if (HIGH_SURROGATE <= cp && cp <= HIGH_SURROGATE_MAX) + if (ch < LEAD_SURROGATE) + return 1; + else if (ch < TRAIL_SURROGATE) return 2; + else if (ch <= TRAIL_SURROGATE_MAX) + return 0; //unexpected trail surrogate! else - { - assert(cp < LOW_SURROGATE || LOW_SURROGATE_MAX < cp); //NO low surrogate expected return 1; - } } @@ -119,19 +109,27 @@ void utf16ToCodePoint(CharIterator first, CharIterator last, Function writeOutpu for ( ; first != last; ++first) { CodePoint cp = static_cast<Char16>(*first); - if (HIGH_SURROGATE <= cp && cp <= HIGH_SURROGATE_MAX) + switch (getUtf16Len(static_cast<Char16>(cp))) { - if (++first == last) - { - assert(false); //low surrogate expected - return; - } - 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; + case 0: //invalid utf16 character + cp = REPLACEMENT_CHAR; + break; + case 1: + break; + case 2: + if (++first != last) //trail surrogate expected! + { + const Char16 ch = static_cast<Char16>(*first); + if (TRAIL_SURROGATE <= ch && ch <= TRAIL_SURROGATE_MAX) //trail surrogate expected! + { + cp = ((cp - LEAD_SURROGATE) << 10) + (ch - TRAIL_SURROGATE) + 0x10000; + break; + } + } + --first; + cp = REPLACEMENT_CHAR; + break; } - else - assert(cp < LOW_SURROGATE || LOW_SURROGATE_MAX < cp); //NO low surrogate expected - writeOutput(cp); } } @@ -141,6 +139,7 @@ template <class Function> inline void codePointToUtf8(CodePoint cp, Function writeOutput) //"writeOutput" is a unary function taking a Char8 { //http://en.wikipedia.org/wiki/UTF-8 + //assert(cp < LEAD_SURROGATE || TRAIL_SURROGATE_MAX < cp); //code points [0xd800, 0xdfff] are reserved for UTF-16 and *should* not be encoded in UTF-8 if (cp < 0x80) writeOutput(static_cast<Char8>(cp)); @@ -151,23 +150,24 @@ void codePointToUtf8(CodePoint cp, Function writeOutput) //"writeOutput" is a un } else if (cp < 0x10000) { - writeOutput(static_cast<Char8>((cp >> 12 ) | 0xe0)); + writeOutput(static_cast<Char8>( (cp >> 12 ) | 0xe0)); writeOutput(static_cast<Char8>(((cp >> 6) & 0x3f) | 0x80)); - writeOutput(static_cast<Char8>((cp & 0x3f ) | 0x80)); + writeOutput(static_cast<Char8>( (cp & 0x3f ) | 0x80)); } - else + else if (cp <= CODE_POINT_MAX) { - assert(cp <= CODE_POINT_MAX); - writeOutput(static_cast<Char8>((cp >> 18 ) | 0xf0)); + writeOutput(static_cast<Char8>( (cp >> 18 ) | 0xf0)); writeOutput(static_cast<Char8>(((cp >> 12) & 0x3f) | 0x80)); writeOutput(static_cast<Char8>(((cp >> 6) & 0x3f) | 0x80)); - writeOutput(static_cast<Char8>((cp & 0x3f ) | 0x80)); + writeOutput(static_cast<Char8>( (cp & 0x3f ) | 0x80)); } + else //invalid code point + codePointToUtf8(REPLACEMENT_CHAR, writeOutput); //resolves to 3-byte utf8 } inline -size_t getUtf8Len(unsigned char ch) //ch must be first code unit! +size_t getUtf8Len(unsigned char ch) //ch must be first code unit! returns 0 on error! { if (ch < 0x80) return 1; @@ -177,12 +177,27 @@ size_t getUtf8Len(unsigned char ch) //ch must be first code unit! return 3; if (ch >> 3 == 0x1e) return 4; - - assert(false); //no valid begin of UTF8 encoding - return 1; + return 0; //innvalid begin of UTF8 encoding } +template <class CharIterator> inline +bool decodeTrail(CharIterator& first, CharIterator last, CodePoint& cp) //decode trailing surrogate byte +{ + if (++first != last) //trail surrogate expected! + { + const Char8 ch = static_cast<Char8>(*first); + if (ch >> 6 == 0x2) //trail surrogate expected! + { + cp = (cp << 6) + (ch & 0x3f); + return true; + } + } + --first; + cp = REPLACEMENT_CHAR; + return false; +} + template <class CharIterator, class Function> inline void utf8ToCodePoint(CharIterator first, CharIterator last, Function writeOutput) //"writeOutput" is a unary function taking a CodePoint { @@ -190,57 +205,32 @@ void utf8ToCodePoint(CharIterator first, CharIterator last, Function writeOutput 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; - }; - - Char8 ch = static_cast<Char8>(*first); - switch (getUtf8Len(ch)) + CodePoint cp = static_cast<Char8>(*first); + switch (getUtf8Len(static_cast<Char8>(cp))) { + case 0: //invalid utf8 character + cp = REPLACEMENT_CHAR; + break; case 1: - writeOutput(ch); break; case 2: - { - CodePoint cp = (ch & 0x1f) << 6; - if (!getChar(ch)) return; - cp += ch & 0x3f; - writeOutput(cp); - } - break; + cp &= 0x1f; + decodeTrail(first, last, cp); + break; case 3: - { - CodePoint cp = (ch & 0xf) << 12; - if (!getChar(ch)) return; - cp += (ch & 0x3f) << 6; - if (!getChar(ch)) return; - cp += ch & 0x3f; - writeOutput(cp); - } - break; + cp &= 0xf; + if (decodeTrail(first, last, cp)) + decodeTrail(first, last, cp); + break; case 4: - { - CodePoint cp = (ch & 0x7) << 18; - if (!getChar(ch)) return; - cp += (ch & 0x3f) << 12; - if (!getChar(ch)) return; - cp += (ch & 0x3f) << 6; - if (!getChar(ch)) return; - cp += ch & 0x3f; - writeOutput(cp); - } - break; - default: - assert(false); + cp &= 0x7; + if (decodeTrail(first, last, cp)) + if (decodeTrail(first, last, cp)) + decodeTrail(first, last, cp); + if (cp > CODE_POINT_MAX) cp = REPLACEMENT_CHAR; + break; } + writeOutput(cp); } } @@ -257,7 +247,10 @@ size_t unicodeLength(const CharString& str, char) //utf8 while (strFirst < strLast) //[!] { ++len; - strFirst += getUtf8Len(*strFirst); //[!] + + size_t utf8len = getUtf8Len(*strFirst); + if (utf8len == 0) ++utf8len; //invalid utf8 character + strFirst += utf8len; } return len; } @@ -275,7 +268,9 @@ size_t unicodeLengthWide(const WideString& str, Int2Type<2>) //windows: utf16-wc while (strFirst < strLast) //[!] { ++len; - strFirst += getUtf16Len(*strFirst); //[!] + size_t utf16len = getUtf16Len(*strFirst); + if (utf16len == 0) ++utf16len; //invalid utf16 character + strFirst += utf16len; } return len; } @@ -316,7 +311,9 @@ size_t findUnicodePos(const CharString& str, size_t unicodePos, char) //utf8-cha size_t utfPos = 0; while (unicodePos-- > 0) { - utfPos += getUtf8Len(strFirst[utfPos]); + size_t utf8len = getUtf8Len(strFirst[utfPos]); + if (utf8len == 0) ++utf8len; //invalid utf8 character + utfPos += utf8len; if (utfPos >= strLen) return strLen; @@ -336,7 +333,9 @@ size_t findUnicodePosWide(const WideString& str, size_t unicodePos, Int2Type<2>) size_t utfPos = 0; while (unicodePos-- > 0) { - utfPos += getUtf16Len(strFirst[utfPos]); + size_t utf16len = getUtf16Len(strFirst[utfPos]); + if (utf16len == 0) ++utf16len; //invalid utf16 character + utfPos += utf16len; if (utfPos >= strLen) return strLen; diff --git a/zen/zstring.cpp b/zen/zstring.cpp index 262df49e..085e6f72 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -78,10 +78,8 @@ private: static std::string rawMemToString(const void* ptr, size_t size) { - std::string output(reinterpret_cast<const char*>(ptr), size); - vector_remove_if(output, [](char& c) { return c == 0; }); //remove intermediate 0-termination - if (output.size() > 100) - output.resize(100); + std::string output(reinterpret_cast<const char*>(ptr), std::min<size_t>(size, 100)); + std::replace(output.begin(), output.end(), '\0', ' '); //don't stop at 0-termination return output; } |