diff options
Diffstat (limited to 'zen/file_io.cpp')
-rwxr-xr-x[-rw-r--r--] | zen/file_io.cpp | 663 |
1 files changed, 273 insertions, 390 deletions
diff --git a/zen/file_io.cpp b/zen/file_io.cpp index 440a5d0d..b4affd37 100644..100755 --- a/zen/file_io.cpp +++ b/zen/file_io.cpp @@ -1,390 +1,273 @@ -// ***************************************************************************** -// * This file is part of the FreeFileSync project. It is distributed under * -// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 * -// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * -// ***************************************************************************** - -#include "file_io.h" -#include "file_access.h" - -#ifdef ZEN_WIN - #include "long_path_prefix.h" - #include "privilege.h" - #ifdef ZEN_WIN_VISTA_AND_LATER - #include "vista_file_op.h" - #endif - -#elif defined ZEN_LINUX || defined ZEN_MAC - #include <sys/stat.h> - #include <fcntl.h> //open, close - #include <unistd.h> //read, write -#endif - -using namespace zen; - - -namespace -{ -#if defined ZEN_LINUX || defined ZEN_MAC -//- "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 = {}; - if (::stat(filePath.c_str(), &fileInfo) != 0) //follows symlinks - return; //let the caller handle errors like "not existing" - - if (!S_ISREG(fileInfo.st_mode) && - !S_ISLNK(fileInfo.st_mode) && - !S_ISDIR(fileInfo.st_mode)) - { - auto getTypeName = [](mode_t m) -> std::wstring - { - const wchar_t* name = - S_ISCHR (m) ? L"character device": - S_ISBLK (m) ? L"block device" : - S_ISFIFO(m) ? L"FIFO, named pipe" : - S_ISSOCK(m) ? L"socket" : nullptr; - const std::wstring numFmt = printNumber<std::wstring>(L"0%06o", m & S_IFMT); - return name ? numFmt + L", " + name : numFmt; - }; - throw FileError(replaceCpy(_("Type of item %x is not supported:"), L"%x", fmtPath(filePath)) + L" " + getTypeName(fileInfo.st_mode)); - } -} -#endif - -inline -FileHandle getInvalidHandle() -{ -#ifdef ZEN_WIN - return INVALID_HANDLE_VALUE; -#elif defined ZEN_LINUX || defined ZEN_MAC - return -1; -#endif -} -} - - -FileInput::FileInput(FileHandle handle, const Zstring& filePath) : FileBase(filePath), fileHandle(handle) {} - - -FileInput::FileInput(const Zstring& filePath) : //throw FileError, ErrorFileLocked - FileBase(filePath), fileHandle(getInvalidHandle()) -{ -#ifdef ZEN_WIN - try { activatePrivilege(PrivilegeName::BACKUP); } - catch (const FileError&) {} - - 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 https://msdn.microsoft.com/en-us/library/aa363858#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 - */ - FILE_FLAG_BACKUP_SEMANTICS, - nullptr); //_In_opt_ HANDLE hTemplateFile - }; - fileHandle = createHandle(FILE_SHARE_READ | FILE_SHARE_DELETE); - if (fileHandle == INVALID_HANDLE_VALUE) - { - //=> support reading files which are open for write (e.g. Firefox .db, .sqlite 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); - - //begin of "regular" error reporting - if (fileHandle == INVALID_HANDLE_VALUE) - { - const DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath)); - std::wstring errorDescr = formatSystemError(L"CreateFile", ec); - - if (ec == ERROR_SHARING_VIOLATION || //-> enhance error message! - ec == ERROR_LOCK_VIOLATION) - { -#ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message - const std::wstring procList = vista::getLockingProcesses(filePath); //noexcept - if (!procList.empty()) - errorDescr = _("The file is locked by another process:") + L"\n" + procList; -#endif - throw ErrorFileLocked(errorMsg, errorDescr); - } - 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 - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath)), L"open"); -#endif - - //------------------------------------------------------------------------------------------------------ - -#ifdef ZEN_WIN //destructor call would lead to member double clean-up!!! - ZEN_ON_SCOPE_FAIL(::CloseHandle(fileHandle)); - -#elif defined ZEN_LINUX || defined ZEN_MAC - ZEN_ON_SCOPE_FAIL(::close(fileHandle)); -#endif - -#ifdef ZEN_LINUX //handle still un-owned => need constructor guard - //optimize read-ahead on input file: - if (::posix_fadvise(fileHandle, 0, 0, POSIX_FADV_SEQUENTIAL) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(filePath)), L"posix_fadvise"); - -#elif defined ZEN_MAC - //"dtruss" doesn't show use of "fcntl() F_RDAHEAD/F_RDADVISE" for "cp") -#endif -} - - -FileInput::~FileInput() -{ - if (fileHandle != getInvalidHandle()) -#ifdef ZEN_WIN - ::CloseHandle(fileHandle); -#elif defined ZEN_LINUX || defined ZEN_MAC - ::close(fileHandle); -#endif -} - - -size_t FileInput::tryRead(void* buffer, size_t bytesToRead) //throw FileError; may return short, only 0 means EOF! -{ - if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check! - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); - -#ifdef ZEN_WIN - //posix ::read() semantics: test for end of file: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365690 - 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 - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"ReadFile"); - -#elif defined ZEN_LINUX || defined ZEN_MAC - ssize_t bytesRead = 0; - do - { - bytesRead = ::read(fileHandle, buffer, bytesToRead); - } - while (bytesRead < 0 && errno == EINTR); //Compare copy_reg() in copy.c: ftp://ftp.gnu.org/gnu/coreutils/coreutils-8.23.tar.xz - - if (bytesRead < 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"read"); -#endif - if (static_cast<size_t>(bytesRead) > bytesToRead) //better safe than sorry - throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"ReadFile: buffer overflow."); //user should never see this - - //if ::read is interrupted (EINTR) right in the middle, it will return successfully with "bytesRead < bytesToRead" => loop! - - return bytesRead; //"zero indicates end of file" -} - -//---------------------------------------------------------------------------------------------------- - -FileOutput::FileOutput(FileHandle handle, const Zstring& filePath) : FileBase(filePath), fileHandle(handle) {} - - -FileOutput::FileOutput(const Zstring& filePath, AccessFlag access) : //throw FileError, ErrorTargetExisting - FileBase(filePath), fileHandle(getInvalidHandle()) -{ -#ifdef ZEN_WIN - try { activatePrivilege(PrivilegeName::BACKUP); } - catch (const FileError&) {} - try { activatePrivilege(PrivilegeName::RESTORE); } - catch (const FileError&) {} - - const DWORD dwCreationDisposition = access == FileOutput::ACC_OVERWRITE ? CREATE_ALWAYS : CREATE_NEW; - - auto createHandle = [&](DWORD dwFlagsAndAttributes) - { - return ::CreateFile(applyLongPathPrefix(filePath).c_str(), //_In_ LPCTSTR lpFileName, - GENERIC_READ | GENERIC_WRITE, //_In_ DWORD dwDesiredAccess, - /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858#files - quote: When an application creates a file across a network, it is better - to use GENERIC_READ | GENERIC_WRITE for dwDesiredAccess than to use GENERIC_WRITE alone. - The resulting code is faster, because the redirector can use the cache manager and send fewer SMBs with more data. - This combination also avoids an issue where writing to a file across a network can occasionally return ERROR_ACCESS_DENIED. */ - FILE_SHARE_DELETE, //_In_ DWORD dwShareMode, - //FILE_SHARE_DELETE is required to rename file while handle is open! - nullptr, //_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, - dwCreationDisposition, //_In_ DWORD dwCreationDisposition, - dwFlagsAndAttributes | - FILE_FLAG_SEQUENTIAL_SCAN | //_In_ DWORD dwFlagsAndAttributes, - FILE_FLAG_BACKUP_SEMANTICS, - nullptr); //_In_opt_ HANDLE hTemplateFile - }; - - fileHandle = createHandle(FILE_ATTRIBUTE_NORMAL); - if (fileHandle == INVALID_HANDLE_VALUE) - { - DWORD ec = ::GetLastError(); //copy before directly/indirectly making other system calls! - - //CREATE_ALWAYS fails with ERROR_ACCESS_DENIED if the existing file is hidden or "system": https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858#files - if (ec == ERROR_ACCESS_DENIED && dwCreationDisposition == CREATE_ALWAYS) - { - const DWORD attrib = ::GetFileAttributes(applyLongPathPrefix(filePath).c_str()); - if (attrib != INVALID_FILE_ATTRIBUTES) - { - 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(); - } - } - - //begin of "regular" error reporting - if (fileHandle == INVALID_HANDLE_VALUE) - { - const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filePath)); - std::wstring errorDescr = formatSystemError(L"CreateFile", ec); - -#ifdef ZEN_WIN_VISTA_AND_LATER //(try to) enhance error message - if (ec == ERROR_SHARING_VIOLATION || //-> enhance error message! - ec == ERROR_LOCK_VIOLATION) - { - const std::wstring procList = vista::getLockingProcesses(filePath); //noexcept - if (!procList.empty()) - errorDescr = _("The file is locked by another process:") + L"\n" + procList; - } -#endif - 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 (ec == ERROR_PATH_NOT_FOUND) throw ErrorTargetPathMissing(errorMsg, errorDescr); - - throw FileError(errorMsg, errorDescr); - } - } - -#elif defined ZEN_LINUX || defined ZEN_MAC - //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); //0666 - if (fileHandle == -1) - { - const int ec = errno; //copy before making other system calls! - const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filePath)); - const std::wstring errorDescr = formatSystemError(L"open", ec); - - if (ec == EEXIST) - throw ErrorTargetExisting(errorMsg, errorDescr); - //if (ec == ENOENT) throw ErrorTargetPathMissing(errorMsg, errorDescr); - - throw FileError(errorMsg, errorDescr); - } -#endif - - //------------------------------------------------------------------------------------------------------ - - //ScopeGuard constructorGuard = zen::makeGuard - - //guard handle when adding code!!! - - //constructorGuard.dismiss(); -} - - -FileOutput::FileOutput(FileOutput&& tmp) : FileBase(tmp.getFilePath()), fileHandle(tmp.fileHandle) { tmp.fileHandle = getInvalidHandle(); } - - -FileOutput::~FileOutput() -{ - if (fileHandle != getInvalidHandle()) - try - { - close(); //throw FileError - } - catch (FileError&) { assert(false); } -} - - -void FileOutput::close() //throw FileError -{ - if (fileHandle == getInvalidHandle()) - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"Contract error: close() called more than once."); - ZEN_ON_SCOPE_EXIT(fileHandle = getInvalidHandle()); - - //no need to clean-up on failure here (just like there is no clean on FileOutput::write failure!) => FileOutput is not transactional! - -#ifdef ZEN_WIN - if (!::CloseHandle(fileHandle)) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"CloseHandle"); -#elif defined ZEN_LINUX || defined ZEN_MAC - if (::close(fileHandle) != 0) - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"close"); -#endif -} - - -size_t FileOutput::tryWrite(const void* buffer, size_t bytesToWrite) //throw FileError; may return short! CONTRACT: bytesToWrite > 0 -{ - if (bytesToWrite == 0) - throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__)); - -#ifdef ZEN_WIN - DWORD bytesWritten = 0; //this parameter is NOT optional: https://blogs.msdn.microsoft.com/oldnewthing/20130404-00/?p=4753/ - 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 - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"WriteFile"); - - if (bytesWritten != bytesToWrite) //must be fulfilled for synchronous writes! - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"WriteFile: incomplete write."); //user should never see this - -#elif defined ZEN_LINUX || defined ZEN_MAC - ssize_t bytesWritten = 0; - do - { - bytesWritten = ::write(fileHandle, buffer, bytesToWrite); - } - while (bytesWritten < 0 && errno == EINTR); - - if (bytesWritten <= 0) - { - if (bytesWritten == 0) //comment in safe-read.c suggests to treat this as an error due to buggy drivers - errno = ENOSPC; - - THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"write"); - } - if (bytesWritten > static_cast<ssize_t>(bytesToWrite)) //better safe than sorry - throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"write: buffer overflow."); //user should never see this - - //if ::write() is interrupted (EINTR) right in the middle, it will return successfully with "bytesWritten < bytesToWrite"! -#endif - return bytesWritten; -} +// *****************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl-3.0 *
+// * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved *
+// *****************************************************************************
+
+#include "file_io.h"
+#include "file_access.h"
+
+ #include <sys/stat.h>
+ #include <fcntl.h> //open, close
+ #include <unistd.h> //read, write
+
+using namespace zen;
+
+
+namespace
+{
+//- "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 = {};
+ if (::stat(filePath.c_str(), &fileInfo) != 0) //follows symlinks
+ return; //let the caller handle errors like "not existing"
+
+ if (!S_ISREG(fileInfo.st_mode) &&
+ !S_ISLNK(fileInfo.st_mode) &&
+ !S_ISDIR(fileInfo.st_mode))
+ {
+ auto getTypeName = [](mode_t m) -> std::wstring
+ {
+ const wchar_t* name =
+ S_ISCHR (m) ? L"character device":
+ S_ISBLK (m) ? L"block device" :
+ S_ISFIFO(m) ? L"FIFO, named pipe" :
+ S_ISSOCK(m) ? L"socket" : nullptr;
+ const std::wstring numFmt = printNumber<std::wstring>(L"0%06o", m & S_IFMT);
+ return name ? numFmt + L", " + name : numFmt;
+ };
+ throw FileError(replaceCpy(_("Type of item %x is not supported:"), L"%x", fmtPath(filePath)) + L" " + getTypeName(fileInfo.st_mode));
+ }
+}
+}
+
+
+ const FileBase::FileHandle FileBase::invalidHandleValue = -1;
+
+
+FileBase::~FileBase()
+{
+ if (fileHandle_ != invalidHandleValue)
+ try
+ {
+ close(); //throw FileError
+ }
+ catch (FileError&) { assert(false); }
+}
+
+
+void FileBase::close() //throw FileError
+{
+ if (fileHandle_ == invalidHandleValue)
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"Contract error: close() called more than once.");
+ ZEN_ON_SCOPE_EXIT(fileHandle_ = invalidHandleValue);
+
+ //no need to clean-up on failure here (just like there is no clean on FileOutput::write failure!) => FileOutput is not transactional!
+
+ if (::close(fileHandle_) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"close");
+}
+
+//----------------------------------------------------------------------------------------------------
+
+namespace
+{
+FileBase::FileHandle openHandleForRead(const Zstring& filePath) //throw FileError, ErrorFileLocked
+{
+ checkForUnsupportedType(filePath); //throw FileError; opening a named pipe would block forever!
+
+ //don't use O_DIRECT: http://yarchive.net/comp/linux/o_direct.html
+ const FileBase::FileHandle 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
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath)), L"open");
+ return fileHandle; //pass ownership
+}
+}
+
+
+FileInput::FileInput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO) :
+ FileBase(handle, filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {}
+
+
+FileInput::FileInput(const Zstring& filePath, const IOCallback& notifyUnbufferedIO) :
+ FileBase(openHandleForRead(filePath), filePath), //throw FileError, ErrorFileLocked
+ notifyUnbufferedIO_(notifyUnbufferedIO)
+{
+ //optimize read-ahead on input file:
+ if (::posix_fadvise(getHandle(), 0, 0, POSIX_FADV_SEQUENTIAL) != 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(filePath)), L"posix_fadvise");
+
+}
+
+
+size_t FileInput::tryRead(void* buffer, size_t bytesToRead) //throw FileError; may return short, only 0 means EOF!
+{
+ if (bytesToRead == 0) //"read() with a count of 0 returns zero" => indistinguishable from end of file! => check!
+ throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
+ assert(bytesToRead == getBlockSize());
+
+ ssize_t bytesRead = 0;
+ do
+ {
+ bytesRead = ::read(getHandle(), buffer, bytesToRead);
+ }
+ while (bytesRead < 0 && errno == EINTR); //Compare copy_reg() in copy.c: ftp://ftp.gnu.org/gnu/coreutils/coreutils-8.23.tar.xz
+
+ if (bytesRead < 0)
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"read");
+ if (static_cast<size_t>(bytesRead) > bytesToRead) //better safe than sorry
+ throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), L"ReadFile: 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; //"zero indicates end of file"
+}
+
+
+size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError, X; return "bytesToRead" bytes unless end of stream!
+{
+ const size_t blockSize = getBlockSize();
+
+ while (memBuf_.size() < bytesToRead)
+ {
+ memBuf_.resize(memBuf_.size() + blockSize);
+ const size_t bytesRead = tryRead(&*(memBuf_.end() - blockSize), blockSize); //throw FileError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0
+ memBuf_.resize(memBuf_.size() - blockSize + bytesRead); //caveat: unsigned arithmetics
+
+ if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesRead); //throw X
+
+ if (bytesRead == 0) //end of file
+ bytesToRead = memBuf_.size();
+ }
+
+ std::copy(memBuf_.begin(), memBuf_.begin() + bytesToRead, static_cast<char*>(buffer));
+ memBuf_.erase(memBuf_.begin(), memBuf_.begin() + bytesToRead);
+ return bytesToRead;
+}
+
+//----------------------------------------------------------------------------------------------------
+
+namespace
+{
+FileBase::FileHandle openHandleForWrite(const Zstring& filePath, FileOutput::AccessFlag access) //throw FileError, ErrorTargetExisting
+{
+ //checkForUnsupportedType(filePath); -> not needed, open() + O_WRONLY should fail fast
+
+ const FileBase::FileHandle 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); //0666
+ if (fileHandle == -1)
+ {
+ const int ec = errno; //copy before making other system calls!
+ const std::wstring errorMsg = replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filePath));
+ const std::wstring errorDescr = formatSystemError(L"open", ec);
+
+ if (ec == EEXIST)
+ throw ErrorTargetExisting(errorMsg, errorDescr);
+ //if (ec == ENOENT) throw ErrorTargetPathMissing(errorMsg, errorDescr);
+
+ throw FileError(errorMsg, errorDescr);
+ }
+ return fileHandle; //pass ownership
+}
+}
+
+
+FileOutput::FileOutput(FileHandle handle, const Zstring& filePath, const IOCallback& notifyUnbufferedIO) :
+ FileBase(handle, filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {}
+
+
+FileOutput::FileOutput(const Zstring& filePath, AccessFlag access, const IOCallback& notifyUnbufferedIO) :
+ FileBase(openHandleForWrite(filePath, access), filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {} //throw FileError, ErrorTargetExisting
+
+
+FileOutput::~FileOutput()
+{
+ try
+ {
+ flushBuffers(); //throw FileError, X
+ }
+ catch (...) { assert(false); }
+}
+
+
+size_t FileOutput::tryWrite(const void* buffer, size_t bytesToWrite) //throw FileError; may return short! CONTRACT: bytesToWrite > 0
+{
+ if (bytesToWrite == 0)
+ throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
+ assert(bytesToWrite <= getBlockSize());
+
+ ssize_t bytesWritten = 0;
+ do
+ {
+ bytesWritten = ::write(getHandle(), buffer, bytesToWrite);
+ }
+ while (bytesWritten < 0 && errno == EINTR);
+
+ if (bytesWritten <= 0)
+ {
+ if (bytesWritten == 0) //comment in safe-read.c suggests to treat this as an error due to buggy drivers
+ errno = ENOSPC;
+
+ THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"write");
+ }
+ if (bytesWritten > static_cast<ssize_t>(bytesToWrite)) //better safe than sorry
+ throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"write: buffer overflow."); //user should never see this
+
+ //if ::write() is interrupted (EINTR) right in the middle, it will return successfully with "bytesWritten < bytesToWrite"!
+ return bytesWritten;
+}
+
+
+void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileError, X
+{
+ auto bufFirst = static_cast<const char*>(buffer);
+ memBuf_.insert(memBuf_.end(), bufFirst, bufFirst + bytesToWrite);
+
+ const size_t blockSize = getBlockSize();
+ size_t bytesRemaining = memBuf_.size();
+ ZEN_ON_SCOPE_EXIT(memBuf_.erase(memBuf_.begin(), memBuf_.end() - bytesRemaining));
+ while (bytesRemaining >= blockSize)
+ {
+ const size_t bytesWritten = tryWrite(&*(memBuf_.end() - bytesRemaining), blockSize); //throw FileError; may return short! CONTRACT: bytesToWrite > 0
+ bytesRemaining -= bytesWritten;
+ if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesWritten); //throw X!
+ }
+}
+
+
+void FileOutput::flushBuffers() //throw FileError, X
+{
+ size_t bytesRemaining = memBuf_.size();
+ ZEN_ON_SCOPE_EXIT(memBuf_.erase(memBuf_.begin(), memBuf_.end() - bytesRemaining));
+ while (bytesRemaining > 0)
+ {
+ const size_t bytesWritten = tryWrite(&*(memBuf_.end() - bytesRemaining), bytesRemaining); //throw FileError; may return short! CONTRACT: bytesToWrite > 0
+ bytesRemaining -= bytesWritten;
+ if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesWritten); //throw X!
+ }
+}
+
+
+void FileOutput::finalize() //throw FileError, X
+{
+ flushBuffers(); //throw FileError, X
+ //~FileBase() calls this one, too, but we want to propagate errors if any:
+ close(); //throw FileError
+}
+
+
+void FileOutput::preAllocateSpaceBestEffort(uint64_t expectedSize) //throw FileError
+{
+ const FileHandle fh = getHandle();
+ //don't use potentially inefficient ::posix_fallocate!
+ const int rv = ::fallocate(fh, //int fd,
+ 0, //int mode,
+ 0, //off_t offset
+ expectedSize); //off_t len
+ if (rv != 0)
+ return; //may fail with EOPNOTSUPP, unlike posix_fallocate
+
+}
|