summaryrefslogtreecommitdiff
path: root/zen
diff options
context:
space:
mode:
Diffstat (limited to 'zen')
-rw-r--r--zen/file_access.cpp541
-rw-r--r--zen/file_access.h2
-rw-r--r--zen/file_io.cpp317
-rw-r--r--zen/file_io.h44
-rw-r--r--zen/file_traverser.cpp4
-rw-r--r--zen/fixed_list.h4
-rw-r--r--zen/perf.h6
-rw-r--r--zen/recycler.h2
-rw-r--r--zen/string_tools.h4
-rw-r--r--zen/symlink_target.h10
-rw-r--r--zen/thread.h3
-rw-r--r--zen/time.h22
-rw-r--r--zen/win_ver.h15
-rw-r--r--zen/zstring.cpp8
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*>(&copyFileCallback)) != 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; }
diff --git a/zen/perf.h b/zen/perf.h
index 17aeabd0..53519274 100644
--- a/zen/perf.h
+++ b/zen/perf.h
@@ -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...
diff --git a/zen/time.h b/zen/time.h
index 4593c48b..996593c8 100644
--- a/zen/time.h
+++ b/zen/time.h
@@ -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(&lt, &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(&lt, &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, &lt) == 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;
}
bgstack15