diff options
Diffstat (limited to 'zen')
-rw-r--r-- | zen/file_access.cpp | 541 | ||||
-rw-r--r-- | zen/file_access.h | 2 | ||||
-rw-r--r-- | zen/file_io.cpp | 317 | ||||
-rw-r--r-- | zen/file_io.h | 44 | ||||
-rw-r--r-- | zen/file_traverser.cpp | 4 | ||||
-rw-r--r-- | zen/fixed_list.h | 4 | ||||
-rw-r--r-- | zen/perf.h | 6 | ||||
-rw-r--r-- | zen/recycler.h | 2 | ||||
-rw-r--r-- | zen/string_tools.h | 4 | ||||
-rw-r--r-- | zen/symlink_target.h | 10 | ||||
-rw-r--r-- | zen/thread.h | 3 | ||||
-rw-r--r-- | zen/time.h | 22 | ||||
-rw-r--r-- | zen/win_ver.h | 15 | ||||
-rw-r--r-- | zen/zstring.cpp | 8 |
14 files changed, 391 insertions, 591 deletions
diff --git a/zen/file_access.cpp b/zen/file_access.cpp index ca07e76a..ffbdc813 100644 --- a/zen/file_access.cpp +++ b/zen/file_access.cpp @@ -25,7 +25,7 @@ #elif defined ZEN_LINUX #include <sys/vfs.h> //statfs - #include <fcntl.h> //AT_SYMLINK_NOFOLLOW, UTIME_OMIT + #include <sys/time.h> //lutimes #ifdef HAVE_SELINUX #include <selinux/selinux.h> #endif @@ -36,8 +36,8 @@ #endif #if defined ZEN_LINUX || defined ZEN_MAC + #include <fcntl.h> //open, close, AT_SYMLINK_NOFOLLOW, UTIME_OMIT #include <sys/stat.h> - #include <sys/time.h> //lutimes #endif using namespace zen; @@ -250,7 +250,7 @@ std::uint64_t zen::getFilesize(const Zstring& filepath) //throw FileError throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"CreateFile", getLastError()); ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); - //why not use ::GetFileSizeEx() instead??? + //why not use ::GetFileSizeEx() instead??? BY_HANDLE_FILE_INFORMATION fileInfoHnd = {}; if (!::GetFileInformationByHandle(hFile, &fileInfoHnd)) throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(filepath)), L"GetFileInformationByHandle", getLastError()); @@ -462,7 +462,8 @@ Zstring findUnused8Dot3Name(const Zstring& filepath) //find a unique 8.3 short n 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...") + utfCvrtTo<std::string>(pathPrefix)); + throw std::runtime_error(std::string("100000000 files, one for each number, exist in this directory? You're kidding...") + utfCvrtTo<std::string>(pathPrefix) + + "\n" + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); } @@ -581,18 +582,18 @@ void removeDirectoryImpl(const Zstring& directory, //throw FileError { std::vector<Zstring> fileList; std::vector<Zstring> dirList; - //get all files and directories from current directory (WITHOUT subdirectories!) -traverseFolder(directory, - [&](const FileInfo& fi){ fileList.push_back(fi.fullPath); }, - [&](const DirInfo& di){ dirList .push_back(di.fullPath); }, - [&](const SymlinkInfo& si) -{ - if (dirExists(si.fullPath)) //dir symlink - dirList.push_back(si.fullPath); - else //file symlink, broken symlink - fileList.push_back(si.fullPath); -}, -[&](const std::wstring& errorMsg){ throw FileError(errorMsg); }); + //get all files and directories from current directory (WITHOUT subdirectories!) + traverseFolder(directory, + [&](const FileInfo& fi) { fileList.push_back(fi.fullPath); }, + [&](const DirInfo& di) { dirList .push_back(di.fullPath); }, + [&](const SymlinkInfo& si) + { + if (dirExists(si.fullPath)) //dir symlink + dirList.push_back(si.fullPath); + else //file symlink, broken symlink + fileList.push_back(si.fullPath); + }, + [&](const std::wstring& errorMsg) { throw FileError(errorMsg); }); //delete directories recursively for (const Zstring& dirpath : dirList) @@ -955,26 +956,52 @@ void zen::setFileTime(const Zstring& filepath, std::int64_t modTime, ProcSymlink setFileTimeRaw(filepath, nullptr, timetToFileTime(modTime), procSl); //throw FileError #elif defined ZEN_LINUX - //sigh, we can't use utimensat on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit??? - - // struct ::timespec newTimes[2] = {}; - // newTimes[0].tv_nsec = UTIME_OMIT; //omit access time - // newTimes[1].tv_sec = to<time_t>(modTime); //modification time (seconds) - // - // if (::utimensat(AT_FDCWD, filepath.c_str(), newTimes, procSl == SYMLINK_DIRECT ? AT_SYMLINK_NOFOLLOW : 0) != 0) - // throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"utimensat", getLastError()); - + //[2013-05-01] sigh, we can't use utimensat() on NTFS volumes on Ubuntu: silent failure!!! what morons are programming this shit??? //=> fallback to "retarded-idiot version"! -- DarkByte + // + //[2015-03-09] + // - cannot reproduce issues with NTFS and utimensat() on Ubuntu + // - utimensat() is supposed to obsolete utime/utimes and is also used by "touch" + //=> let's give utimensat another chance: + struct ::timespec newTimes[2] = {}; + newTimes[0].tv_nsec = UTIME_OMIT; //omit access time + newTimes[1].tv_sec = modTime; //modification time (seconds) - struct ::timeval newTimes[2] = {}; - newTimes[0].tv_sec = ::time(nullptr); //access time (seconds) - newTimes[1].tv_sec = modTime; //modification time (seconds) + if (procSl == ProcSymlink::FOLLOW) + { + //don't use utimensat() directly, but open file descriptor manually: + //=> solves EINVAL bug for certain CIFS/NTFS drives: https://sourceforge.net/p/freefilesync/discussion/help/thread/1ace042d/ + //=> using utimensat(AT_SYMLINK_NOFOLLOW) for symlinks and open()/futimens() for regular files is consistent with "cp" and "touch"! + const int fdFile = ::open(filepath.c_str(), O_WRONLY, 0); //"if O_CREAT is not specified, then mode is ignored" + if (fdFile == -1) + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"open", getLastError()); + ZEN_ON_SCOPE_EXIT(::close(fdFile)); + + if (::futimens(fdFile, newTimes) != 0) + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"futimens", getLastError()); + } + else + { + if (::utimensat(AT_FDCWD, filepath.c_str(), newTimes, AT_SYMLINK_NOFOLLOW) != 0) + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"utimensat", getLastError()); + } - const int rv = procSl == ProcSymlink::FOLLOW ? - :: utimes(filepath.c_str(), newTimes) : - ::lutimes(filepath.c_str(), newTimes); - if (rv != 0) - throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"utimes", getLastError()); + /* + struct ::timeval newTimes[2] = {}; + newTimes[0].tv_sec = ::time(nullptr); //access time (seconds) + newTimes[1].tv_sec = modTime; //modification time (seconds) + + if (procSl == ProcSymlink::FOLLOW) + { + if (::utimes(filepath.c_str(), newTimes) != 0) + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"utimes", getLastError()); + } + else + { + if (::lutimes(filepath.c_str(), newTimes) != 0) + throwFileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtFileName(filepath)), L"lutimes", getLastError()); + } + */ #elif defined ZEN_MAC struct ::timespec writeTime = {}; @@ -1063,15 +1090,15 @@ void copySecurityContext(const Zstring& source, const Zstring& target, ProcSymli //copy permissions for files, directories or symbolic links: requires admin rights -void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSymlink procSl) //throw FileError +void copyItemPermissions(const Zstring& sourcePath, const Zstring& targetPath, ProcSymlink procSl) //throw FileError { #ifdef ZEN_WIN //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! getResolvedFilePath() requires Vista or later! - const Zstring sourceResolved = procSl == ProcSymlink::FOLLOW && symlinkExists(source) ? getResolvedFilePath(source) : source; //throw FileError - const Zstring targetResolved = procSl == ProcSymlink::FOLLOW && symlinkExists(target) ? getResolvedFilePath(target) : target; // + const Zstring sourceResolved = procSl == ProcSymlink::FOLLOW && symlinkExists(sourcePath) ? getResolvedFilePath(sourcePath) : sourcePath; //throw FileError + const Zstring targetResolved = procSl == ProcSymlink::FOLLOW && symlinkExists(targetPath) ? getResolvedFilePath(targetPath) : targetPath; // //setting privileges requires admin rights! try @@ -1217,32 +1244,32 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym #elif defined ZEN_LINUX #ifdef HAVE_SELINUX //copy SELinux security context - copySecurityContext(source, target, procSl); //throw FileError + copySecurityContext(sourcePath, targetPath, procSl); //throw FileError #endif struct ::stat fileInfo = {}; if (procSl == ProcSymlink::FOLLOW) { - if (::stat(source.c_str(), &fileInfo) != 0) - throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(source)), L"stat", getLastError()); + if (::stat(sourcePath.c_str(), &fileInfo) != 0) + throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(sourcePath)), L"stat", getLastError()); - if (::chown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights! - throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"chown", getLastError()); + if (::chown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights! + throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"chown", getLastError()); - if (::chmod(target.c_str(), fileInfo.st_mode) != 0) - throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"chmod", getLastError()); + if (::chmod(targetPath.c_str(), fileInfo.st_mode) != 0) + throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"chmod", getLastError()); } else { - if (::lstat(source.c_str(), &fileInfo) != 0) - throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(source)), L"lstat", getLastError()); + if (::lstat(sourcePath.c_str(), &fileInfo) != 0) + throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(sourcePath)), L"lstat", getLastError()); - if (::lchown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights! - throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"lchown", getLastError()); + if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights! + throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"lchown", getLastError()); - if (!symlinkExists(target) && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod() - ::chmod(target.c_str(), fileInfo.st_mode) != 0) - throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"chmod", getLastError()); + if (!symlinkExists(targetPath) && //setting access permissions doesn't make sense for symlinks on Linux: there is no lchmod() + ::chmod(targetPath.c_str(), fileInfo.st_mode) != 0) + throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"chmod", getLastError()); } #elif defined ZEN_MAC @@ -1250,27 +1277,27 @@ void copyObjectPermissions(const Zstring& source, const Zstring& target, ProcSym if (procSl == ProcSymlink::DIRECT) flags |= COPYFILE_NOFOLLOW; - if (::copyfile(source.c_str(), target.c_str(), 0, flags) != 0) - throwFileError(replaceCpy(replaceCpy(_("Cannot copy permissions from %x to %y."), L"%x", L"\n" + fmtFileName(source)), L"%y", L"\n" + fmtFileName(target)), L"copyfile", getLastError()); + if (::copyfile(sourcePath.c_str(), targetPath.c_str(), 0, flags) != 0) + throwFileError(replaceCpy(replaceCpy(_("Cannot copy permissions from %x to %y."), L"%x", L"\n" + fmtFileName(sourcePath)), L"%y", L"\n" + fmtFileName(targetPath)), L"copyfile", getLastError()); //owner is *not* copied with ::copyfile(): struct ::stat fileInfo = {}; if (procSl == ProcSymlink::FOLLOW) { - if (::stat(source.c_str(), &fileInfo) != 0) - throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(source)), L"stat", getLastError()); + if (::stat(sourcePath.c_str(), &fileInfo) != 0) + throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(sourcePath)), L"stat", getLastError()); - if (::chown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights! - throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"chown", getLastError()); + if (::chown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights! + throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"chown", getLastError()); } else { - if (::lstat(source.c_str(), &fileInfo) != 0) - throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(source)), L"lstat", getLastError()); + if (::lstat(sourcePath.c_str(), &fileInfo) != 0) + throwFileError(replaceCpy(_("Cannot read permissions of %x."), L"%x", fmtFileName(sourcePath)), L"lstat", getLastError()); - if (::lchown(target.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights! - throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(target)), L"lchown", getLastError()); + if (::lchown(targetPath.c_str(), fileInfo.st_uid, fileInfo.st_gid) != 0) // may require admin rights! + throwFileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtFileName(targetPath)), L"lchown", getLastError()); } #endif } @@ -1412,7 +1439,7 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT mode = dirInfo.st_mode; //analog to "cp" which copies "mode" (considering umask) by default mode |= S_IRWXU; //FFS only: we need full access to copy child items! "cp" seems to apply permissions *after* copying child items } - //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions + //=> need copyItemPermissions() only for "chown" and umask-agnostic permissions if (::mkdir(directory.c_str(), mode) != 0) { @@ -1501,7 +1528,7 @@ void zen::makeDirectoryPlain(const Zstring& directory, //throw FileError, ErrorT //enforce copying file permissions: it's advertized on GUI... if (copyFilePermissions) - copyObjectPermissions(templateDir, directory, ProcSymlink::FOLLOW); //throw FileError + copyItemPermissions(templateDir, directory, ProcSymlink::FOLLOW); //throw FileError guardNewDir.dismiss(); //target has been created successfully! } @@ -1561,23 +1588,26 @@ void zen::copySymlink(const Zstring& sourceLink, const Zstring& targetLink, bool setFileTimeRaw(targetLink, &sourceAttr.ftCreationTime, sourceAttr.ftLastWriteTime, ProcSymlink::DIRECT); //throw FileError -#elif defined ZEN_LINUX || defined ZEN_MAC +#elif defined ZEN_LINUX struct ::stat sourceInfo = {}; if (::lstat(sourceLink.c_str(), &sourceInfo) != 0) throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceLink)), L"lstat", getLastError()); -#ifdef ZEN_LINUX - setFileTime(targetLink, sourceInfo.st_mtime, ProcSymlink::DIRECT); //throw FileError + setFileTime(targetLink, sourceInfo.st_mtime, ProcSymlink::DIRECT); //throw FileError + #elif defined ZEN_MAC - setFileTimeRaw(targetLink, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::DIRECT); //throw FileError + struct ::stat sourceInfo = {}; + if (::lstat(sourceLink.c_str(), &sourceInfo) != 0) + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceLink)), L"lstat", getLastError()); if (::copyfile(sourceLink.c_str(), targetLink.c_str(), 0, COPYFILE_XATTR | COPYFILE_NOFOLLOW) != 0) throwFileError(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtFileName(sourceLink)), L"%y", L"\n" + fmtFileName(targetLink)), L"copyfile", getLastError()); -#endif + + setFileTimeRaw(targetLink, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::DIRECT); //throw FileError #endif if (copyFilePermissions) - copyObjectPermissions(sourceLink, targetLink, ProcSymlink::DIRECT); //throw FileError + copyItemPermissions(sourceLink, targetLink, ProcSymlink::DIRECT); //throw FileError guardNewLink.dismiss(); //target has been created successfully! } @@ -1903,37 +1933,6 @@ void copyFileWindowsSparse(const Zstring& sourceFile, DEFINE_NEW_FILE_ERROR(ErrorShouldCopyAsSparse); -class ErrorHandling -{ -public: - ErrorHandling() : shouldCopyAsSparse(false) {} - - //call context: copyCallbackInternal() - void reportErrorShouldCopyAsSparse() { shouldCopyAsSparse = true; } - - void reportUserException(const std::exception_ptr& e) { exception = e; } - - void reportError(const std::wstring& msg, const std::wstring& description) { errorMsg = std::make_pair(msg, description); } - - //call context: copyFileWindowsDefault() - void evaluateErrors() //throw X - { - if (shouldCopyAsSparse) - throw ErrorShouldCopyAsSparse(L"sparse dummy value"); - - if (exception) - std::rethrow_exception(exception); - - if (!errorMsg.first.empty()) - throw FileError(errorMsg.first, errorMsg.second); - } - -private: - bool shouldCopyAsSparse; // - std::pair<std::wstring, std::wstring> errorMsg; //these are exclusive! - std::exception_ptr exception; -}; - struct CallbackData { @@ -1949,10 +1948,10 @@ struct CallbackData const Zstring& sourceFile_; const Zstring& targetFile_; - const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus_; + const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus_; //optional - ErrorHandling errorHandler; - BY_HANDLE_FILE_INFORMATION fileInfoSrc; //modified by CopyFileEx() at beginning + std::exception_ptr exception; //out + BY_HANDLE_FILE_INFORMATION fileInfoSrc; //out: modified by CopyFileEx() at beginning BY_HANDLE_FILE_INFORMATION fileInfoTrg; // std::int64_t bytesReported; //used internally to calculate bytes transferred delta @@ -1971,6 +1970,7 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, { /* this callback is invoked for block sizes managed by Windows, these may vary from e.g. 64 kB up to 1MB. It seems this depends on file size amongst others. + Note: for 0-sized files this callback is invoked just ONCE! symlink handling: if source is a symlink and COPY_FILE_COPY_SYMLINK is specified, this callback is NOT invoked! @@ -1990,71 +1990,60 @@ DWORD CALLBACK copyCallbackInternal(LARGE_INTEGER totalFileSize, CallbackData& cbd = *static_cast<CallbackData*>(lpData); - if (dwCallbackReason == CALLBACK_STREAM_SWITCH && //called up-front for every file (even if 0-sized) - dwStreamNumber == 1) //consider ADS! + try { - //#################### return source file attributes ################################ - if (!::GetFileInformationByHandle(hSourceFile, &cbd.fileInfoSrc)) + if (dwCallbackReason == CALLBACK_STREAM_SWITCH && //called up-front for every file (even if 0-sized) + dwStreamNumber == 1) //consider ADS! { - const DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls! - cbd.errorHandler.reportError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(cbd.sourceFile_)), formatSystemError(L"GetFileInformationByHandle", lastError)); - return PROGRESS_CANCEL; - } + //#################### return source file attributes ################################ + if (!::GetFileInformationByHandle(hSourceFile, &cbd.fileInfoSrc)) + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(cbd.sourceFile_)), L"GetFileInformationByHandle", ::GetLastError()); - if (!::GetFileInformationByHandle(hDestinationFile, &cbd.fileInfoTrg)) - { - const DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls! - cbd.errorHandler.reportError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(cbd.targetFile_)), formatSystemError(L"GetFileInformationByHandle", lastError)); - return PROGRESS_CANCEL; - } + if (!::GetFileInformationByHandle(hDestinationFile, &cbd.fileInfoTrg)) + throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(cbd.targetFile_)), L"GetFileInformationByHandle", ::GetLastError()); - //#################### switch to sparse file copy if req. ####################### - if (canCopyAsSparse(cbd.fileInfoSrc.dwFileAttributes, cbd.targetFile_)) //throw () - { - cbd.errorHandler.reportErrorShouldCopyAsSparse(); //use a different copy routine! - return PROGRESS_CANCEL; - } + //#################### switch to sparse file copy if req. ####################### + if (canCopyAsSparse(cbd.fileInfoSrc.dwFileAttributes, cbd.targetFile_)) //throw () + throw ErrorShouldCopyAsSparse(L"sparse dummy value"); //use a different copy routine! - //#################### copy file creation time ################################ - ::SetFileTime(hDestinationFile, &cbd.fileInfoSrc.ftCreationTime, nullptr, nullptr); //no error handling! - //=> not really needed here, creation time is set anyway at the end of copyFileWindowsDefault()! + //#################### copy file creation time ################################ + ::SetFileTime(hDestinationFile, &cbd.fileInfoSrc.ftCreationTime, nullptr, nullptr); //no error handling! + //=> not really needed here, creation time is set anyway at the end of copyFileWindowsDefault()! - //#################### copy NTFS compressed attribute ######################### - const bool sourceIsCompressed = (cbd.fileInfoSrc.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; - const bool targetIsCompressed = (cbd.fileInfoTrg.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; //already set by CopyFileEx if target parent folder is compressed! - if (sourceIsCompressed && !targetIsCompressed) - { - USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; - DWORD bytesReturned = 0; - if (!::DeviceIoControl(hDestinationFile, //_In_ HANDLE hDevice, - FSCTL_SET_COMPRESSION, //_In_ DWORD dwIoControlCode, - &cmpState, //_In_opt_ LPVOID lpInBuffer, - sizeof(cmpState), //_In_ DWORD nInBufferSize, - nullptr, //_Out_opt_ LPVOID lpOutBuffer, - 0, //_In_ DWORD nOutBufferSize, - &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned - nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped - {} //may legitimately fail with ERROR_INVALID_FUNCTION if - - // - if target folder is encrypted - // - target volume does not support compressed attribute - //############################################################################# + //#################### copy NTFS compressed attribute ######################### + const bool sourceIsCompressed = (cbd.fileInfoSrc.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; + const bool targetIsCompressed = (cbd.fileInfoTrg.dwFileAttributes & FILE_ATTRIBUTE_COMPRESSED) != 0; //already set by CopyFileEx if target parent folder is compressed! + if (sourceIsCompressed && !targetIsCompressed) + { + USHORT cmpState = COMPRESSION_FORMAT_DEFAULT; + DWORD bytesReturned = 0; + if (!::DeviceIoControl(hDestinationFile, //_In_ HANDLE hDevice, + FSCTL_SET_COMPRESSION, //_In_ DWORD dwIoControlCode, + &cmpState, //_In_opt_ LPVOID lpInBuffer, + sizeof(cmpState), //_In_ DWORD nInBufferSize, + nullptr, //_Out_opt_ LPVOID lpOutBuffer, + 0, //_In_ DWORD nOutBufferSize, + &bytesReturned, //_Out_opt_ LPDWORD lpBytesReturned + nullptr)) //_Inout_opt_ LPOVERLAPPED lpOverlapped + {} //may legitimately fail with ERROR_INVALID_FUNCTION if + + // - if target folder is encrypted + // - target volume does not support compressed attribute + //############################################################################# + } } - } - //called after copy operation is finished - note: for 0-sized files this callback is invoked just ONCE! - //if (totalFileSize.QuadPart == totalBytesTransferred.QuadPart && dwStreamNumber == 1) {} - if (cbd.onUpdateCopyStatus_ && totalBytesTransferred.QuadPart >= 0) //should always be true, but let's still check - try + if (cbd.onUpdateCopyStatus_ && totalBytesTransferred.QuadPart >= 0) //should always be true, but let's still check { cbd.onUpdateCopyStatus_(totalBytesTransferred.QuadPart - cbd.bytesReported); //throw X! cbd.bytesReported = totalBytesTransferred.QuadPart; } - catch (...) - { - cbd.errorHandler.reportUserException(std::current_exception()); - return PROGRESS_CANCEL; - } + } + catch (...) + { + cbd.exception = std::current_exception(); + return PROGRESS_CANCEL; + } return PROGRESS_CONTINUE; } @@ -2083,10 +2072,11 @@ void copyFileWindowsDefault(const Zstring& sourceFile, copyFlags |= COPY_FILE_ALLOW_DECRYPTED_DESTINATION; //allow copying from encrypted to non-encrypted location //if (vistaOrLater()) //see http://blogs.technet.com/b/askperf/archive/2007/05/08/slow-large-file-copy-issues.aspx - // copyFlags |= COPY_FILE_NO_BUFFERING; //no perf difference at worst, huge improvement for large files (20% in test NTFS -> NTFS) - //It's a shame this flag causes file corruption! https://sourceforge.net/projects/freefilesync/forums/forum/847542/topic/5177950 - //documentation on CopyFile2() even states: "It is not recommended to pause copies that are using this flag." How dangerous is this thing, why offer it at all??? - //perf advantage: ~15% faster + // copyFlags |= COPY_FILE_NO_BUFFERING; //no perf difference at worst, improvement for large files (20% in test NTFS -> NTFS) + // - this flag may cause file corruption! https://sourceforge.net/p/freefilesync/discussion/open-discussion/thread/65f48357/ + // - documentation on CopyFile2() even states: "It is not recommended to pause copies that are using this flag." + //=> it's not worth it! instead of skipping buffering at kernel-level (=> also NO prefetching!!!), skip it at user-level: memory mapped files! + // however, perf-measurements for memory mapped files show: it's also not worth it! CallbackData cbd(onUpdateCopyStatus, sourceFile, targetFile); @@ -2096,8 +2086,9 @@ void copyFileWindowsDefault(const Zstring& sourceFile, &cbd, //__in_opt LPVOID lpData, nullptr, //__in_opt LPBOOL pbCancel, copyFlags) != FALSE; //__in DWORD dwCopyFlags + if (cbd.exception) + std::rethrow_exception(cbd.exception); //throw ?, process errors in callback first! - cbd.errorHandler.evaluateErrors(); //throw ?, process errors in callback first! if (!success) { const DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls! @@ -2204,26 +2195,40 @@ void copyFileOsSpecific(const Zstring& sourceFile, } -#elif defined ZEN_LINUX +#elif defined ZEN_LINUX || defined ZEN_MAC void copyFileOsSpecific(const Zstring& sourceFile, const Zstring& targetFile, const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus, InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting { - FileInputUnbuffered fileIn(sourceFile); //throw FileError + FileInput fileIn(sourceFile); //throw FileError struct ::stat sourceInfo = {}; - if (::fstat(fileIn.getDescriptor(), &sourceInfo) != 0) + if (::fstat(fileIn.getHandle(), &sourceInfo) != 0) throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)), L"fstat", getLastError()); - zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); //transactional behavior: place guard before lifetime of FileOutput - try + const int fdTarget = ::open(targetFile.c_str(), O_WRONLY | O_CREAT | O_EXCL, + sourceInfo.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)); //analog to "cp" which copies "mode" (considering umask) by default + //=> need copyItemPermissions() only for "chown" and umask-agnostic permissions + if (fdTarget == -1) + { + const int ec = errno; //copy before making other system calls! + const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(targetFile)); + const std::wstring errorDescr = formatSystemError(L"open", ec); + + if (ec == EEXIST) + throw ErrorTargetExisting(errorMsg, errorDescr); + + throw FileError(errorMsg, errorDescr); + } + + zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); + //transactional behavior: place guard after ::open() and before lifetime of FileOutput: + //=> don't delete file that existed previously!!! { - FileOutputUnbuffered fileOut(targetFile, //throw FileError, ErrorTargetExisting - sourceInfo.st_mode); //analog to "cp" which copies "mode" (considering umask) by default - //=> need copyObjectPermissions() only for "chown" and umask-agnostic permissions + FileOutput fileOut(fdTarget, targetFile); //pass ownership - std::vector<char> buffer(128 * 1024); //see comment in FileInputUnbuffered::read + std::vector<char> buffer(128 * 1024); do { const size_t bytesRead = fileIn.read(&buffer[0], buffer.size()); //throw FileError @@ -2235,186 +2240,58 @@ void copyFileOsSpecific(const Zstring& sourceFile, } while (!fileIn.eof()); - //adapt target file modification time: - { - //read and return file statistics - struct ::stat targetInfo = {}; - if (::fstat(fileOut.getDescriptor(), &targetInfo) != 0) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"fstat", getLastError()); - - if (newAttrib) - { - newAttrib->fileSize = sourceInfo.st_size; - newAttrib->modificationTime = sourceInfo.st_mtime; - newAttrib->sourceFileId = extractFileId(sourceInfo); - newAttrib->targetFileId = extractFileId(targetInfo); - } - } - } - catch (const ErrorTargetExisting&) - { - guardTarget.dismiss(); //don't delete file that existed previously! - throw; - } - - //we cannot set the target file times while the file descriptor is still open after a write operation: - //this triggers bugs on samba shares where the modification time is set to current time instead. - //http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236 - //http://comments.gmane.org/gmane.linux.file-systems.cifs/2854 - //on the other hand we thereby have to reopen https://sourceforge.net/p/freefilesync/bugs/230/ - setFileTime(targetFile, sourceInfo.st_mtime, ProcSymlink::FOLLOW); //throw FileError - - guardTarget.dismiss(); //target has been created successfully! -} - - -#elif defined ZEN_MAC -struct CallbackData -{ - CallbackData(const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus, - const Zstring& sourceFile, - const Zstring& targetFile) : - onUpdateCopyStatus_(onUpdateCopyStatus), - sourceFile_(sourceFile), - targetFile_(targetFile), - bytesReported() {} - - const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus_; //in - const Zstring& sourceFile_; - const Zstring& targetFile_; - - std::pair<std::wstring, std::wstring> errorMsg; //out; these are exclusive! - std::exception_ptr exception; // - - std::int64_t bytesReported; //private to callback -}; - - -int copyFileCallback(int what, int stage, copyfile_state_t state, const char* src, const char* dst, void* ctx) -{ - CallbackData& cbd = *static_cast<CallbackData*>(ctx); - - off_t bytesCopied = 0; - if (::copyfile_state_get(state, COPYFILE_STATE_COPIED, &bytesCopied) != 0) - { - cbd.errorMsg = std::make_pair(replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtFileName(cbd.sourceFile_)), L"%y", L"\n" + fmtFileName(cbd.targetFile_)), - formatSystemError(L"copyfile_state_get, COPYFILE_STATE_COPIED", getLastError())); - return COPYFILE_QUIT; - } - - if (cbd.onUpdateCopyStatus_) - try - { - cbd.onUpdateCopyStatus_(bytesCopied - cbd.bytesReported); //throw X! - cbd.bytesReported = bytesCopied; - } - catch (...) - { - cbd.exception = std::current_exception(); - return COPYFILE_QUIT; - } - return COPYFILE_CONTINUE; -} - - -void copyFileOsSpecific(const Zstring& sourceFile, - const Zstring& targetFile, - const std::function<void(std::int64_t bytesDelta)>& onUpdateCopyStatus, - InSyncAttributes* newAttrib) //throw FileError, ErrorTargetExisting -{ - struct ::stat sourceInfo = {}; - - //http://blog.plasticsfuture.org/2006/03/05/the-state-of-backup-and-cloning-tools-under-mac-os-x/ - { - auto getCopyErrorMessage = [&] { return replaceCpy(replaceCpy(_("Cannot copy file %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)); }; - - copyfile_state_t copyState = ::copyfile_state_alloc(); - ZEN_ON_SCOPE_EXIT(::copyfile_state_free(copyState)); - - CallbackData cbd(onUpdateCopyStatus, sourceFile, targetFile); - - if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CTX, &cbd) != 0) - throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CTX", getLastError()); - - if (::copyfile_state_set(copyState, COPYFILE_STATE_STATUS_CB, reinterpret_cast<const void*>(©FileCallback)) != 0) - throwFileError(getCopyErrorMessage(), L"copyfile_state_set, COPYFILE_STATE_STATUS_CB", getLastError()); - - zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); //transactional behavior: docs seem to indicate that copyfile does not clean up - - //http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/copyfile.3.html - if (::copyfile(sourceFile.c_str(), targetFile.c_str(), - copyState, - COPYFILE_XATTR | COPYFILE_DATA | COPYFILE_EXCL) != 0) - //- even though we don't use COPYFILE_STAT, "mode" (considering umask) is still copied! => harmonized with Linux file copy! - //- COPYFILE_STAT does not copy file creation time - { - //evaluate first! errno is not set for COPYFILE_QUIT! - if (cbd.exception) - std::rethrow_exception(cbd.exception); - - if (!cbd.errorMsg.first.empty()) - throw FileError(cbd.errorMsg.first, cbd.errorMsg.second); - - const int lastError = errno; - std::wstring errorDescr = formatSystemError(L"copyfile", lastError); - - if (lastError == EEXIST) - { - guardTarget.dismiss(); //don't delete file that existed previously! - throw ErrorTargetExisting(getCopyErrorMessage(), errorDescr); - } - - throw FileError(getCopyErrorMessage(), errorDescr); - } - - int fdSource = 0; - if (::copyfile_state_get(copyState, COPYFILE_STATE_SRC_FD, &fdSource) != 0) - throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_SRC_FD", getLastError()); - - int fdTarget = 0; - if (::copyfile_state_get(copyState, COPYFILE_STATE_DST_FD, &fdTarget) != 0) - throwFileError(getCopyErrorMessage(), L"copyfile_state_get, COPYFILE_STATE_DST_FD", getLastError()); - - if (::fstat(fdSource, &sourceInfo) != 0) - throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(sourceFile)), L"fstat", getLastError()); - struct ::stat targetInfo = {}; - if (::fstat(fdTarget, &targetInfo) != 0) + if (::fstat(fileOut.getHandle(), &targetInfo) != 0) throwFileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtFileName(targetFile)), L"fstat", getLastError()); - if (newAttrib) + if (newAttrib) //return file statistics { newAttrib->fileSize = sourceInfo.st_size; - newAttrib->modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable as setFileTimeRaw() for consistency - //newAttrib->modificationTime = sourceInfo.st_mtime; // +#ifdef ZEN_MAC + newAttrib->modificationTime = sourceInfo.st_mtimespec.tv_sec; //use same time variable like setFileTimeRaw() for consistency +#else + newAttrib->modificationTime = sourceInfo.st_mtime; +#endif newAttrib->sourceFileId = extractFileId(sourceInfo); newAttrib->targetFileId = extractFileId(targetInfo); } - guardTarget.dismiss(); - } //make sure target file handle is closed before setting modification time! - +#ifdef ZEN_MAC + //using ::copyfile with COPYFILE_DATA seems to trigger bugs unlike our stream-based copying! + //=> use ::copyfile for extended attributes only: https://sourceforge.net/p/freefilesync/discussion/help/thread/91384c8a/ + //http://blog.plasticsfuture.org/2006/03/05/the-state-of-backup-and-cloning-tools-under-mac-os-x/ + //docs: http://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man3/copyfile.3.html + //source: http://www.opensource.apple.com/source/copyfile/copyfile-103.92.1/copyfile.c + if (::fcopyfile(fileIn.getHandle(), fileOut.getHandle(), 0, COPYFILE_XATTR) != 0) + throwFileError(replaceCpy(replaceCpy(_("Cannot copy attributes from %x to %y."), L"%x", L"\n" + fmtFileName(sourceFile)), L"%y", L"\n" + fmtFileName(targetFile)), L"copyfile", getLastError()); +#endif + } //close output file handle before setting file time - zen::ScopeGuard guardTarget = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {} }); - //same issue like on Linux: we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation: + //we cannot set the target file times (::futimes) while the file descriptor is still open after a write operation: //this triggers bugs on samba shares where the modification time is set to current time instead. - //https://sourceforge.net/p/freefilesync/discussion/help/thread/881357c0/ - //http://comments.gmane.org/gmane.linux.file-systems.cifs/2854 + //Linux: http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=340236 + // http://comments.gmane.org/gmane.linux.file-systems.cifs/2854 + //OS X: https://sourceforge.net/p/freefilesync/discussion/help/thread/881357c0/ +#ifdef ZEN_MAC setFileTimeRaw(targetFile, &sourceInfo.st_birthtimespec, sourceInfo.st_mtimespec, ProcSymlink::FOLLOW); //throw FileError - //sourceInfo.st_birthtime; -> only seconds-preicions - //sourceInfo.st_mtime; -> - guardTarget.dismiss(); + //sourceInfo.st_birthtime; -> only seconds-precision + //sourceInfo.st_mtime; -> +#else + setFileTime(targetFile, sourceInfo.st_mtime, ProcSymlink::FOLLOW); //throw FileError +#endif + + guardTarget.dismiss(); //target has been created successfully! } #endif /* - ------------------ - |File Copy Layers| - ------------------ + ------------------ + |File Copy Layers| + ------------------ copyFile (setup transactional behavior) - | + | copyFileWithPermissions - | + | copyFileOsSpecific (solve 8.3 issue) | copyFileWindowsSelectRoutine @@ -2436,7 +2313,7 @@ void copyFileWithPermissions(const Zstring& sourceFile, //at this point we know we created a new file, so it's fine to delete it for cleanup! zen::ScopeGuard guardTargetFile = zen::makeGuard([&] { try { removeFile(targetFile); } catch (FileError&) {}}); - copyObjectPermissions(sourceFile, targetFile, ProcSymlink::FOLLOW); //throw FileError + copyItemPermissions(sourceFile, targetFile, ProcSymlink::FOLLOW); //throw FileError guardTargetFile.dismiss(); //target has been created successfully! } diff --git a/zen/file_access.h b/zen/file_access.h index 4a36009d..bd1b0168 100644 --- a/zen/file_access.h +++ b/zen/file_access.h @@ -34,7 +34,7 @@ std::uint64_t getFreeDiskSpace(const Zstring& path); //throw FileError bool removeFile(const Zstring& filepath); //throw FileError; return "false" if file is not existing void removeDirectory(const Zstring& directory, //throw FileError const std::function<void (const Zstring& filepath)>& onBeforeFileDeletion = nullptr, //optional; - const std::function<void (const Zstring& dirpath)>& onBeforeDirDeletion = nullptr); //one call for each *existing* object! + const std::function<void (const Zstring& dirpath )>& onBeforeDirDeletion = nullptr); //one call for each *existing* object! //rename file or directory: no copying!!! void renameFile(const Zstring& oldName, const Zstring& newName); //throw FileError, ErrorDifferentVolume, ErrorTargetExisting diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 00e33a60..d4bfdd9b 100644 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -13,6 +13,7 @@ #include "dll.h" #elif defined ZEN_LINUX || defined ZEN_MAC + #include <sys/stat.h> #include <fcntl.h> //open, close #include <unistd.h> //read, write #endif @@ -44,7 +45,9 @@ Zstring getLockingProcessNames(const Zstring& filepath) //throw(), empty string } #elif defined ZEN_LINUX || defined ZEN_MAC -//"filepath" could be a named pipe which *blocks* forever during "open()"! https://sourceforge.net/p/freefilesync/bugs/221/ +//- "filepath" could be a named pipe which *blocks* forever for open()! +//- open() with O_NONBLOCK avoids the block, but opens successfully +//- create sample pipe: "sudo mkfifo named_pipe" void checkForUnsupportedType(const Zstring& filepath) //throw FileError { struct ::stat fileInfo = {}; @@ -78,61 +81,79 @@ FileInput::FileInput(FileHandle handle, const Zstring& filepath) : FileInputBase FileInput::FileInput(const Zstring& filepath) : FileInputBase(filepath) //throw FileError { #ifdef ZEN_WIN - const wchar_t functionName[] = L"CreateFile"; - fileHandle = ::CreateFile(applyLongPathPrefix(filepath).c_str(), //_In_ LPCTSTR lpFileName, - GENERIC_READ, //_In_ DWORD dwDesiredAccess, - FILE_SHARE_READ | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, - FILE_FLAG_SEQUENTIAL_SCAN, //_In_ DWORD dwFlagsAndAttributes, - /* 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 the same time - 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 - */ - nullptr); //_In_opt_ HANDLE hTemplateFile + auto createHandle = [&](DWORD dwShareMode) + { + return ::CreateFile(applyLongPathPrefix(filepath).c_str(), //_In_ LPCTSTR lpFileName, + GENERIC_READ, //_In_ DWORD dwDesiredAccess, + dwShareMode, //_In_ DWORD dwShareMode, + nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, + OPEN_EXISTING, //_In_ DWORD dwCreationDisposition, + FILE_FLAG_SEQUENTIAL_SCAN, //_In_ DWORD dwFlagsAndAttributes, + /* 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 the same time + 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 + */ + nullptr); //_In_opt_ HANDLE hTemplateFile + }; + fileHandle = createHandle(FILE_SHARE_READ | FILE_SHARE_DELETE); if (fileHandle == INVALID_HANDLE_VALUE) -#elif defined ZEN_LINUX || defined ZEN_MAC - checkForUnsupportedType(filepath); //throw FileError; reading a named pipe would block forever! - const wchar_t functionName[] = L"fopen"; - fileHandle = ::fopen(filepath.c_str(), "r,type=record,noseek"); //utilize UTF-8 filepath - if (!fileHandle) -#endif { - const ErrorCode lastError = getLastError(); //copy before making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filepath)); - std::wstring errorDescr = formatSystemError(functionName, lastError); + //=> support reading files which are open for write (e.g. Firefox db files): follow CopyFileEx() by addding FILE_SHARE_WRITE only for second try: + if (::GetLastError() == ERROR_SHARING_VIOLATION) + fileHandle = createHandle(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE); -#ifdef ZEN_WIN - if (lastError == ERROR_SHARING_VIOLATION || //-> enhance error message! - lastError == ERROR_LOCK_VIOLATION) + //begin of "regular" error reporting + if (fileHandle == INVALID_HANDLE_VALUE) { - const Zstring procList = getLockingProcessNames(filepath); //throw() - if (!procList.empty()) - errorDescr = _("The file is locked by another process:") + L"\n" + procList; + const DWORD ec = ::GetLastError(); //copy before directly or indirectly making other system calls! + const std::wstring errorMsg = replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filepath)); + std::wstring errorDescr = formatSystemError(L"CreateFile", ec); + + if (ec == ERROR_SHARING_VIOLATION || //-> enhance error message! + ec == ERROR_LOCK_VIOLATION) + { + const Zstring procList = getLockingProcessNames(filepath); //throw() + if (!procList.empty()) + errorDescr = _("The file is locked by another process:") + L"\n" + procList; + } + throw FileError(errorMsg, errorDescr); } -#endif - throw FileError(errorMsg, errorDescr); } + +#elif defined ZEN_LINUX || defined ZEN_MAC + checkForUnsupportedType(filepath); //throw FileError; opening a named pipe would block forever! + + //don't use O_DIRECT: http://yarchive.net/comp/linux/o_direct.html + fileHandle = ::open(filepath.c_str(), O_RDONLY); + if (fileHandle == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle + throwFileError(replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filepath)), L"open", getLastError()); + +#ifndef ZEN_MAC //posix_fadvise not supported on OS X (and "dtruss" doesn't show alternative use of "fcntl() F_RDAHEAD/F_RDADVISE" for "cp") + //optimize read-ahead on input file: + if (::posix_fadvise(fileHandle, 0, 0, POSIX_FADV_SEQUENTIAL) != 0) + throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(filepath)), L"posix_fadvise", getLastError()); +#endif +#endif } @@ -141,60 +162,74 @@ FileInput::~FileInput() #ifdef ZEN_WIN ::CloseHandle(fileHandle); #elif defined ZEN_LINUX || defined ZEN_MAC - ::fclose(fileHandle); //NEVER allow passing nullptr to fclose! -> crash!; fileHandle != nullptr in this context! + ::close(fileHandle); #endif } -size_t FileInput::read(void* buffer, size_t bytesToRead) //returns actual number of bytes read; throw FileError +size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError; returns actual number of bytes read { - assert(!eof()); - if (bytesToRead == 0) return 0; + assert(!eof() || bytesToRead == 0); #ifdef ZEN_WIN - const wchar_t functionName[] = L"ReadFile"; + if (bytesToRead == 0) return 0; + 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, nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped -#elif defined ZEN_LINUX || defined ZEN_MAC - const wchar_t functionName[] = L"fread"; - const size_t bytesRead = ::fread(buffer, 1, bytesToRead, fileHandle); - if (::ferror(fileHandle) != 0) //checks status of stream, not fread()! -#endif - throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), functionName, getLastError()); + throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"ReadFile", getLastError()); -#ifdef ZEN_WIN if (bytesRead < bytesToRead) //verify only! - setEof(); - -#elif defined ZEN_LINUX || defined ZEN_MAC - if (::feof(fileHandle) != 0) - setEof(); - - if (bytesRead < bytesToRead) - if (!eof()) //pathologic!? - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"Incomplete read."); //user should never see this -#endif + setEof(); // if (bytesRead > bytesToRead) throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"buffer overflow"); //user should never see this return bytesRead; + +#elif defined ZEN_LINUX || defined ZEN_MAC + //Compare copy_reg() in copy.c: ftp://ftp.gnu.org/gnu/coreutils/coreutils-8.23.tar.xz + size_t bytesReadTotal = 0; + + while (bytesToRead > 0 && !eof()) //"read() with a count of 0 returns zero" => indistinguishable from eof! => check! + { + ssize_t bytesRead = 0; + do + { + bytesRead = ::read(fileHandle, buffer, bytesToRead); + } + while (bytesRead < 0 && errno == EINTR); + + if (bytesRead < 0) + throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"read", getLastError()); + else if (bytesRead == 0) //"zero indicates end of file" + setEof(); + else if (bytesRead > static_cast<ssize_t>(bytesToRead)) //better safe than sorry + throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"buffer overflow"); //user should never see this + + //if ::read is interrupted (EINTR) right in the middle, it will return successfully with "bytesRead < bytesToRead" => loop! + buffer = static_cast<char*>(buffer) + bytesRead; //suppress warning about pointer arithmetics on void* + bytesToRead -= bytesRead; + bytesReadTotal += bytesRead; + } + + return bytesReadTotal; +#endif } +//---------------------------------------------------------------------------------------------------- FileOutput::FileOutput(FileHandle handle, const Zstring& filepath) : FileOutputBase(filepath), fileHandle(handle) {} -FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : //throw FileError, ErrorTargetExisting - FileOutputBase(filepath) +FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : FileOutputBase(filepath) //throw FileError, ErrorTargetExisting { #ifdef ZEN_WIN const DWORD dwCreationDisposition = access == FileOutput::ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW; - auto getHandle = [&](DWORD dwFlagsAndAttributes) + auto createHandle = [&](DWORD dwFlagsAndAttributes) { return ::CreateFile(applyLongPathPrefix(filepath).c_str(), //_In_ LPCTSTR lpFileName, GENERIC_READ | GENERIC_WRITE, //_In_ DWORD dwDesiredAccess, @@ -212,20 +247,19 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : //throw Fil nullptr); //_In_opt_ HANDLE hTemplateFile }; - fileHandle = getHandle(FILE_ATTRIBUTE_NORMAL); + fileHandle = createHandle(FILE_ATTRIBUTE_NORMAL); if (fileHandle == INVALID_HANDLE_VALUE) { - DWORD lastError = ::GetLastError(); //copy before directly or indirectly making other system calls! + DWORD ec = ::GetLastError(); //copy before directly or indirectly making other system calls! //CREATE_ALWAYS fails with ERROR_ACCESS_DENIED if the existing file is hidden or "system" http://msdn.microsoft.com/en-us/library/windows/desktop/aa363858(v=vs.85).aspx - if (lastError == ERROR_ACCESS_DENIED && - dwCreationDisposition == CREATE_ALWAYS) + if (ec == ERROR_ACCESS_DENIED && dwCreationDisposition == CREATE_ALWAYS) { const DWORD attrib = ::GetFileAttributes(applyLongPathPrefix(filepath).c_str()); if (attrib != INVALID_FILE_ATTRIBUTES) { - 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(); + fileHandle = createHandle(attrib); //retry: alas this may still fail for hidden file, e.g. accessing shared folder in XP as Virtual Box guest! + ec = ::GetLastError(); } } @@ -233,41 +267,39 @@ FileOutput::FileOutput(const Zstring& filepath, AccessFlag access) : //throw Fil if (fileHandle == INVALID_HANDLE_VALUE) { const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filepath)); - std::wstring errorDescr = formatSystemError(L"CreateFile", lastError); + std::wstring errorDescr = formatSystemError(L"CreateFile", ec); - if (lastError == ERROR_SHARING_VIOLATION || //-> enhance error message! - lastError == ERROR_LOCK_VIOLATION) + if (ec == ERROR_SHARING_VIOLATION || //-> enhance error message! + ec == ERROR_LOCK_VIOLATION) { const Zstring procList = getLockingProcessNames(filepath); //throw() if (!procList.empty()) errorDescr = _("The file is locked by another process:") + L"\n" + procList; } - if (lastError == ERROR_FILE_EXISTS || //confirmed to be used - lastError == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6 + if (ec == ERROR_FILE_EXISTS || //confirmed to be used + ec == ERROR_ALREADY_EXISTS) //comment on msdn claims, this one is used on Windows Mobile 6 throw ErrorTargetExisting(errorMsg, errorDescr); - - //if (lastError == ERROR_PATH_NOT_FOUND) throw ErrorTargetPathMissing(errorMsg, errorDescr); + //if (ec == ERROR_PATH_NOT_FOUND) throw ErrorTargetPathMissing(errorMsg, errorDescr); throw FileError(errorMsg, errorDescr); } } #elif defined ZEN_LINUX || defined ZEN_MAC - checkForUnsupportedType(filepath); //throw FileError; writing a named pipe would block forever! - fileHandle = ::fopen(filepath.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) + //checkForUnsupportedType(filepath); -> not needed, open() + O_WRONLY should fail fast + + fileHandle = ::open(filepath.c_str(), O_WRONLY | O_CREAT | (access == FileOutput::ACC_CREATE_NEW ? O_EXCL : O_TRUNC), + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (fileHandle == -1) { - const int lastError = errno; //copy before directly or indirectly making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())); - const std::wstring errorDescr = formatSystemError(L"fopen", lastError); + const int ec = errno; //copy before making other system calls! + const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filepath)); + const std::wstring errorDescr = formatSystemError(L"open", ec); - if (lastError == EEXIST) + if (ec == EEXIST) throw ErrorTargetExisting(errorMsg, errorDescr); - - //if (lastError == ENOENT) throw ErrorTargetPathMissing(errorMsg, errorDescr); + //if (ec == ENOENT) throw ErrorTargetPathMissing(errorMsg, errorDescr); throw FileError(errorMsg, errorDescr); } @@ -280,7 +312,7 @@ FileOutput::~FileOutput() #ifdef ZEN_WIN ::CloseHandle(fileHandle); #elif defined ZEN_LINUX || defined ZEN_MAC - ::fclose(fileHandle); //NEVER allow passing nullptr to fclose! -> crash! + ::close(fileHandle); #endif } @@ -288,99 +320,24 @@ FileOutput::~FileOutput() void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileError { #ifdef ZEN_WIN - const wchar_t functionName[] = L"WriteFile"; DWORD bytesWritten = 0; //this parameter is NOT optional: http://blogs.msdn.com/b/oldnewthing/archive/2013/04/04/10407417.aspx if (!::WriteFile(fileHandle, //__in HANDLE hFile, buffer, //__out LPVOID lpBuffer, static_cast<DWORD>(bytesToWrite), //__in DWORD nNumberOfBytesToWrite, &bytesWritten, //__out_opt LPDWORD lpNumberOfBytesWritten, nullptr)) //__inout_opt LPOVERLAPPED lpOverlapped -#elif defined ZEN_LINUX || defined ZEN_MAC - const wchar_t functionName[] = L"fwrite"; - const size_t bytesWritten = ::fwrite(buffer, 1, bytesToWrite, fileHandle); - if (::ferror(fileHandle) != 0) //checks status of stream, not fwrite()! -#endif - throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())), functionName, getLastError()); + throwFileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())), L"WriteFile", getLastError()); if (bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes! throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())), L"Incomplete write."); //user should never see this -} - - -#if defined ZEN_LINUX || defined ZEN_MAC -//Compare copy_reg() in copy.c: ftp://ftp.gnu.org/gnu/coreutils/coreutils-5.0.tar.gz - -FileInputUnbuffered::FileInputUnbuffered(const Zstring& filepath) : FileInputBase(filepath) //throw FileError -{ - checkForUnsupportedType(filepath); //throw FileError; reading a named pipe would block forever! - - fdFile = ::open(filepath.c_str(), O_RDONLY); - if (fdFile == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle - throwFileError(replaceCpy(_("Cannot open file %x."), L"%x", fmtFileName(filepath)), L"open", getLastError()); -} - - -FileInputUnbuffered::~FileInputUnbuffered() { ::close(fdFile); } - - -size_t FileInputUnbuffered::read(void* buffer, size_t bytesToRead) //throw FileError; returns actual number of bytes read -{ - assert(!eof()); - if (bytesToRead == 0) return 0; //[!] - - ssize_t bytesRead = 0; - do - { - bytesRead = ::read(fdFile, buffer, bytesToRead); - } - while (bytesRead < 0 && errno == EINTR); - - if (bytesRead < 0) - throwFileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"read", getLastError()); - else if (bytesRead == 0) //"zero indicates end of file" - setEof(); - else if (bytesRead > static_cast<ssize_t>(bytesToRead)) //better safe than sorry - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtFileName(getFilename())), L"buffer overflow"); //user should never see this - //if ::read is interrupted (EINTR) right in the middle, it will return successfully with "bytesRead < bytesToRead"! - return bytesRead; -} - - -FileOutputUnbuffered::FileOutputUnbuffered(const Zstring& filepath, mode_t mode) : FileOutputBase(filepath) //throw FileError, ErrorTargetExisting -{ - //checkForUnsupportedType(filepath); -> not needed, open() + O_EXCL shoul fail fast - - //overwrite is: O_CREAT | O_WRONLY | O_TRUNC - fdFile = ::open(filepath.c_str(), O_CREAT | O_WRONLY | O_EXCL, mode & (S_IRWXU | S_IRWXG | S_IRWXO)); - if (fdFile == -1) - { - const int lastError = errno; //copy before making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(filepath)); - const std::wstring errorDescr = formatSystemError(L"open", lastError); - - if (lastError == EEXIST) - throw ErrorTargetExisting(errorMsg, errorDescr); - - //if (lastError == ENOENT) throw ErrorTargetPathMissing(errorMsg, errorDescr); - - throw FileError(errorMsg, errorDescr); - } -} - -FileOutputUnbuffered::FileOutputUnbuffered(int fd, const Zstring& filepath) : FileOutputBase(filepath), fdFile(fd) {} - -FileOutputUnbuffered::~FileOutputUnbuffered() { ::close(fdFile); } - - -void FileOutputUnbuffered::write(const void* buffer, size_t bytesToWrite) //throw FileError -{ +#elif defined ZEN_LINUX || defined ZEN_MAC while (bytesToWrite > 0) { ssize_t bytesWritten = 0; do { - bytesWritten = ::write(fdFile, buffer, bytesToWrite); + bytesWritten = ::write(fileHandle, buffer, bytesToWrite); } while (bytesWritten < 0 && errno == EINTR); @@ -394,9 +351,9 @@ void FileOutputUnbuffered::write(const void* buffer, size_t bytesToWrite) //thro if (bytesWritten > static_cast<ssize_t>(bytesToWrite)) //better safe than sorry throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtFileName(getFilename())), L"buffer overflow"); //user should never see this - //if ::write is interrupted (EINTR) right in the middle, it will return successfully with "bytesWritten < bytesToWrite"! + //if ::write() is interrupted (EINTR) right in the middle, it will return successfully with "bytesWritten < bytesToWrite"! buffer = static_cast<const char*>(buffer) + bytesWritten; //suppress warning about pointer arithmetics on void* bytesToWrite -= bytesWritten; } -} #endif +} diff --git a/zen/file_io.h b/zen/file_io.h index 111d7a09..ee7841ca 100644 --- a/zen/file_io.h +++ b/zen/file_io.h @@ -12,9 +12,6 @@ #ifdef ZEN_WIN #include "win.h" //includes "windows.h" -#elif defined ZEN_LINUX || defined ZEN_MAC - #include <cstdio> - #include <sys/stat.h> #endif @@ -26,12 +23,12 @@ namespace zen static const char LINE_BREAK[] = "\n"; //since OS X apple uses newline, too #endif -//buffered file IO optimized for sequential read/write accesses + better error reporting + long path support + following symlinks +//OS-buffered file IO optimized for sequential read/write accesses + better error reporting + long path support + following symlinks #ifdef ZEN_WIN typedef HANDLE FileHandle; #elif defined ZEN_LINUX || defined ZEN_MAC - typedef FILE* FileHandle; + typedef int FileHandle; #endif class FileInput : public FileInputBase @@ -42,6 +39,7 @@ public: ~FileInput(); size_t read(void* buffer, size_t bytesToRead) override; //throw FileError; returns actual number of bytes read + FileHandle getHandle() { return fileHandle; } private: FileHandle fileHandle; @@ -56,45 +54,11 @@ public: ~FileOutput(); void write(const void* buffer, size_t bytesToWrite) override; //throw FileError + FileHandle getHandle() { return fileHandle; } private: FileHandle fileHandle; }; - -#if defined ZEN_LINUX || defined ZEN_MAC -warn_static("get rid of FileInputUnbuffered/FileOutputUnbuffered, use fdopen instead") - -class FileInputUnbuffered : public FileInputBase -{ -public: - FileInputUnbuffered(const Zstring& filepath); //throw FileError - ~FileInputUnbuffered(); - - //considering safe-read.c it seems buffer size should be a multiple of 8192 - size_t read(void* buffer, size_t bytesToRead) override; //throw FileError; returns actual number of bytes read - //do NOT rely on partially filled buffer meaning EOF! - - int getDescriptor() { return fdFile;} - -private: - int fdFile; -}; - -class FileOutputUnbuffered : public FileOutputBase -{ -public: - //creates a new file (no overwrite allowed!) - FileOutputUnbuffered(const Zstring& filepath, mode_t mode); //throw FileError, ErrorTargetExisting - FileOutputUnbuffered(int fd, const Zstring& filepath); //takes ownership! - ~FileOutputUnbuffered(); - - void write(const void* buffer, size_t bytesToWrite) override; //throw FileError - int getDescriptor() { return fdFile;} - -private: - int fdFile; -}; -#endif } #endif //FILEIO_89578342758342572345 diff --git a/zen/file_traverser.cpp b/zen/file_traverser.cpp index 2d652d2b..baf40e9e 100644 --- a/zen/file_traverser.cpp +++ b/zen/file_traverser.cpp @@ -68,6 +68,8 @@ void zen::traverseFolder(const Zstring& dirPath, //skip "." and ".." const Zchar* const shortName = findData.cFileName; + + if (shortName[0] == 0) throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), L"Data corruption: Found item without name."); if (shortName[0] == L'.' && (shortName[1] == 0 || (shortName[1] == L'.' && shortName[2] == 0))) continue; @@ -125,6 +127,8 @@ void zen::traverseFolder(const Zstring& dirPath, //don't return "." and ".." const char* shortName = dirEntry->d_name; + + if (shortName[0] == 0) throw FileError(replaceCpy(_("Cannot enumerate directory %x."), L"%x", fmtFileName(dirPath)), L"Data corruption: Found item without name."); if (shortName[0] == '.' && (shortName[1] == 0 || (shortName[1] == '.' && shortName[2] == 0))) continue; diff --git a/zen/fixed_list.h b/zen/fixed_list.h index 2a577f13..a1f83eb4 100644 --- a/zen/fixed_list.h +++ b/zen/fixed_list.h @@ -60,8 +60,8 @@ public: const_iterator begin() const { return firstInsert; } const_iterator end () const { return const_iterator(); } - const_iterator cbegin() const { return firstInsert; } - const_iterator cend () const { return const_iterator(); } + //const_iterator cbegin() const { return firstInsert; } + //const_iterator cend () const { return const_iterator(); } reference front() { return firstInsert->val; } const_reference front() const { return firstInsert->val; } @@ -48,9 +48,9 @@ public: const std::int64_t delta = 1000 * dist(startTime, now) / ticksPerSec_; #ifdef ZEN_WIN - std::ostringstream ss; - ss << delta << " ms"; - ::MessageBoxA(nullptr, ss.str().c_str(), "Timer", 0); + std::wostringstream ss; + ss << delta << L" ms"; + ::MessageBox(nullptr, ss.str().c_str(), L"Timer", MB_OK); #else std::clog << "Perf: duration: " << delta << " ms\n"; #endif diff --git a/zen/recycler.h b/zen/recycler.h index 2319f7b6..5112444d 100644 --- a/zen/recycler.h +++ b/zen/recycler.h @@ -35,7 +35,7 @@ bool recycleOrDelete(const Zstring& itempath); //throw FileError, return "true" #ifdef ZEN_WIN -//can take a long time if recycle bin is full and drive is slow!!! => buffer! +//Win XP: can take a long time if recycle bin is full and drive is slow!!! => buffer result! bool recycleBinExists(const Zstring& dirpath, const std::function<void ()>& onUpdateGui); //throw FileError void recycleOrDelete(const std::vector<Zstring>& filepaths, //throw FileError, return "true" if file/dir was actually deleted diff --git a/zen/string_tools.h b/zen/string_tools.h index 1b8595c7..35a07b64 100644 --- a/zen/string_tools.h +++ b/zen/string_tools.h @@ -560,9 +560,9 @@ Num extractInteger(const S& str, bool& hasMinusSign) //very fast conversion to i } Num number = 0; - for (const CharType* iter = first; iter != last; ++iter) + for (const CharType* it = first; it != last; ++it) { - const CharType c = *iter; + const CharType c = *it; if (static_cast<CharType>('0') <= c && c <= static_cast<CharType>('9')) { number *= 10; diff --git a/zen/symlink_target.h b/zen/symlink_target.h index 1a7f45bd..9239385a 100644 --- a/zen/symlink_target.h +++ b/zen/symlink_target.h @@ -164,7 +164,7 @@ Zstring getResolvedFilePath_impl(const Zstring& linkPath) //throw FileError throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(linkPath)), replaceCpy(_("Cannot find system function %x."), L"%x", L"\"GetFinalPathNameByHandleW\"")); - const HANDLE hDir = ::CreateFile(applyLongPathPrefix(linkPath).c_str(), //_In_ LPCTSTR lpFileName, + const HANDLE hFile = ::CreateFile(applyLongPathPrefix(linkPath).c_str(), //_In_ LPCTSTR lpFileName, 0, //_In_ DWORD dwDesiredAccess, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, @@ -172,16 +172,16 @@ Zstring getResolvedFilePath_impl(const Zstring& linkPath) //throw FileError //needed to open a directory: FILE_FLAG_BACKUP_SEMANTICS, //_In_ DWORD dwFlagsAndAttributes, nullptr); //_In_opt_ HANDLE hTemplateFile - if (hDir == INVALID_HANDLE_VALUE) + if (hFile == INVALID_HANDLE_VALUE) throwFileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(linkPath)), L"CreateFile", getLastError()); - ZEN_ON_SCOPE_EXIT(::CloseHandle(hDir)); + ZEN_ON_SCOPE_EXIT(::CloseHandle(hFile)); - const DWORD bufferSize = getFinalPathNameByHandle(hDir, nullptr, 0, 0); + const DWORD bufferSize = getFinalPathNameByHandle(hFile, nullptr, 0, 0); if (bufferSize == 0) throwFileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtFileName(linkPath)), L"GetFinalPathNameByHandle", getLastError()); std::vector<wchar_t> targetPath(bufferSize); - const DWORD charsWritten = getFinalPathNameByHandle(hDir, //__in HANDLE hFile, + const DWORD charsWritten = getFinalPathNameByHandle(hFile, //__in HANDLE hFile, &targetPath[0], //__out LPTSTR lpszFilePath, bufferSize, //__in DWORD cchFilePath, 0); //__in DWORD dwFlags diff --git a/zen/thread.h b/zen/thread.h index cca2561f..d860abd0 100644 --- a/zen/thread.h +++ b/zen/thread.h @@ -23,7 +23,7 @@ #endif #ifdef _MSC_VER #pragma warning(push) - #pragma warning(disable : 4702 4913) //unreachable code; user defined binary operator ',' exists but no overload could convert all operands, default built-in binary operator ',' used + #pragma warning(disable: 4702 4913) //unreachable code; user defined binary operator ',' exists but no overload could convert all operands, default built-in binary operator ',' used #endif #include <boost/thread.hpp> @@ -83,6 +83,7 @@ private: + //###################### implementation ###################### #ifndef BOOST_HAS_THREADS #error just some paranoia check... @@ -89,12 +89,11 @@ struct std::tm toClibTimeComponents(const TimeComp& comp) ctc.tm_min = comp.minute; //0-59 ctc.tm_sec = comp.second; //0-61 ctc.tm_isdst = -1; //> 0 if DST is active, == 0 if DST is not active, < 0 if the information is not available - return ctc; } inline -TimeComp toZenTimeComponents(const struct std::tm& ctc) +TimeComp toZenTimeComponents(const struct ::tm& ctc) { TimeComp comp; comp.year = ctc.tm_year + 1900; @@ -219,7 +218,7 @@ String formatTime(const String2& format, const TimeComp& comp, UserDefinedFormat std::mktime(&ctc); // unfortunately std::strftime() needs all elements of "struct tm" filled, e.g. tm_wday, tm_yday //note: although std::mktime() explicitly expects "local time", calculating weekday and day of year *should* be time-zone and DST independent - CharType buffer[256]; + CharType buffer[256] = {}; const size_t charsWritten = strftimeWrap(buffer, 256, strBegin(format), &ctc); return String(buffer, charsWritten); } @@ -236,16 +235,17 @@ String formatTime(FormatType, const TimeComp& comp, PredefinedFormatTag) inline TimeComp localTime(time_t utc) { -#ifdef _MSC_VER - struct tm lt = {}; - errno_t rv = ::localtime_s(<, &utc); //more secure? - if (rv != 0) - return TimeComp(); - return implementation::toZenTimeComponents(lt); + struct ::tm lt = {}; + + //use thread-safe variants of localtime()! +#ifdef ZEN_WIN + if (::localtime_s(<, &utc) != 0) #else - struct tm* lt = std::localtime(&utc); //returns nullptr for invalid time_t on Visual 2010!!! (testvalue "-1") - return lt ? implementation::toZenTimeComponents(*lt) : TimeComp(); + if (::localtime_r(&utc, <) == nullptr) #endif + return TimeComp(); + + return implementation::toZenTimeComponents(lt); } diff --git a/zen/win_ver.h b/zen/win_ver.h index 9cf792f2..611a27c0 100644 --- a/zen/win_ver.h +++ b/zen/win_ver.h @@ -28,7 +28,7 @@ inline bool operator==(const OsVersion& lhs, const OsVersion& rhs) { return lhs. //version overview: http://msdn.microsoft.com/en-us/library/ms724834(VS.85).aspx -const OsVersion osVersionWin10 (6, 4); +const OsVersion osVersionWin10 (10, 0); const OsVersion osVersionWin81 (6, 3); const OsVersion osVersionWin8 (6, 2); const OsVersion osVersionWin7 (6, 1); @@ -40,7 +40,8 @@ const OsVersion osVersionWin2000 (5, 0); /* NOTE: there are two basic APIs to check Windows version: (empiric study following) GetVersionEx -> reports version considering compatibility mode (and compatibility setting in app manifest since Windows 8.1) - VerifyVersionInfo -> always reports *real* Windows Version + VerifyVersionInfo -> always reports *real* Windows Version + *) Win10 Technical preview caveat: VerifyVersionInfo returns 6.3 unless manifest entry is added!!! */ //GetVersionEx()-based APIs: @@ -69,13 +70,9 @@ OsVersion getOsVersion() OSVERSIONINFO osvi = {}; osvi.dwOSVersionInfoSize = sizeof(osvi); #ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4996) //"'GetVersionExW': was declared deprecated" +#pragma warning(suppress: 4996) //"'GetVersionExW': was declared deprecated" #endif if (!::GetVersionEx(&osvi)) //38 ns per call! (yes, that's nano!) -> we do NOT miss C++11 thread-safe statics right now... -#ifdef _MSC_VER -#pragma warning(pop) -#endif { assert(false); return OsVersion(); @@ -99,7 +96,7 @@ bool isRealOsVersion(const OsVersion& ver) const bool rv = ::VerifyVersionInfo(&verInfo, VER_MAJORVERSION | VER_MINORVERSION, conditionMask) == TRUE; //silence VC "performance warnings" - assert(rv || GetLastError() == ERROR_OLD_WIN_VERSION); + assert(rv || ::GetLastError() == ERROR_OLD_WIN_VERSION); return rv; } @@ -125,7 +122,7 @@ inline bool running64BitWindows() //http://blogs.msdn.com/b/oldnewthing/archive/2005/02/01/364563.aspx { static_assert(zen::is32BitBuild || zen::is64BitBuild, ""); - return is64BitBuild || runningWOW64(); //should we bother to make this a compile-time check for the first case? + return is64BitBuild || runningWOW64(); //should we bother to give a compile-time result in the first case? } } diff --git a/zen/zstring.cpp b/zen/zstring.cpp index d87e7989..f803f160 100644 --- a/zen/zstring.cpp +++ b/zen/zstring.cpp @@ -92,7 +92,7 @@ private: #else std::cerr << message; #endif - throw std::logic_error("Memory leak! " + message); + throw std::logic_error("Memory leak! " + message + "\n" + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); } std::mutex lockActStrings; @@ -158,7 +158,7 @@ int cmpFileName(const Zstring& lhs, const Zstring& rhs) static_cast<int>(rhs.size()), //__in int cchCount2, true); //__in BOOL bIgnoreCase if (rv <= 0) - throw std::runtime_error("Error comparing strings (CompareStringOrdinal)."); + throw std::runtime_error("Error comparing strings (CompareStringOrdinal). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); else return rv - 2; //convert to C-style string compare result } @@ -184,7 +184,7 @@ int cmpFileName(const Zstring& lhs, const Zstring& rhs) static_cast<int>(minSize), //__in int cchSrc, strOut, //__out LPTSTR lpDestStr, static_cast<int>(minSize)) == 0) //__in int cchDest - throw std::runtime_error("Error comparing strings (LCMapString)."); + throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); }; auto eval = [&](wchar_t* bufL, wchar_t* bufR) @@ -231,7 +231,7 @@ Zstring makeUpperCopy(const Zstring& str) len, //__in int cchSrc, &*output.begin(), //__out LPTSTR lpDestStr, len) == 0) //__in int cchDest - throw std::runtime_error("Error comparing strings (LCMapString)."); + throw std::runtime_error("Error comparing strings (LCMapString). " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); return output; } |