summaryrefslogtreecommitdiff
path: root/zen/file_io.cpp
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2022-10-11 15:17:59 +0000
committerB. Stack <bgstack15@gmail.com>2022-10-11 15:17:59 +0000
commit38c826621a39831d1bdc78aa9e45cc592db3e77f (patch)
treea49cfd729d9793681a57fa6f7409b0f0848e9ede /zen/file_io.cpp
parentMerge branch 'b11.25' into 'master' (diff)
parentadd upstream 11.26 (diff)
downloadFreeFileSync-11.26.tar.gz
FreeFileSync-11.26.tar.bz2
FreeFileSync-11.26.zip
Merge branch 'b11.26' into 'master'11.26
add upstream 11.26 See merge request opensource-tracking/FreeFileSync!49
Diffstat (limited to 'zen/file_io.cpp')
-rw-r--r--zen/file_io.cpp411
1 files changed, 207 insertions, 204 deletions
diff --git a/zen/file_io.cpp b/zen/file_io.cpp
index 7eebd2e8..ef3cbebb 100644
--- a/zen/file_io.cpp
+++ b/zen/file_io.cpp
@@ -5,7 +5,6 @@
// *****************************************************************************
#include "file_io.h"
-
#include <sys/stat.h>
#include <fcntl.h> //open
#include <unistd.h> //close, read, write
@@ -13,6 +12,45 @@
using namespace zen;
+size_t FileBase::getBlockSize() //throw FileError
+{
+ if (blockSizeBuf_ == 0)
+ {
+ /* - statfs::f_bsize - "optimal transfer block size"
+ - stat::st_blksize - "blocksize for file system I/O. Writing in smaller chunks may cause an inefficient read-modify-rewrite."
+
+ e.g. local disk: f_bsize 4096 st_blksize 4096
+ USB memory: f_bsize 32768 st_blksize 32768 */
+ const auto st_blksize = getStatBuffered().st_blksize; //throw FileError
+ if (st_blksize > 0) //st_blksize is signed!
+ blockSizeBuf_ = st_blksize; //
+
+ blockSizeBuf_ = std::max(blockSizeBuf_, defaultBlockSize);
+ //ha, convergent evolution! https://github.com/coreutils/coreutils/blob/master/src/ioblksize.h#L74
+ }
+ return blockSizeBuf_;
+}
+
+
+const struct stat& FileBase::getStatBuffered() //throw FileError
+{
+ if (!statBuf_)
+ try
+ {
+ if (hFile_ == invalidFileHandle)
+ throw SysError(L"Contract error: getStatBuffered() called after close().");
+
+ struct stat fileInfo = {};
+ if (::fstat(hFile_, &fileInfo) != 0)
+ THROW_LAST_SYS_ERROR("fstat");
+ statBuf_ = std::move(fileInfo);
+ }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(filePath_)), e.toString()); }
+
+ return *statBuf_;
+}
+
+
FileBase::~FileBase()
{
if (hFile_ != invalidFileHandle)
@@ -21,29 +59,37 @@ FileBase::~FileBase()
close(); //throw FileError
}
catch (FileError&) { assert(false); }
+ warn_static("log it!")
}
void FileBase::close() //throw FileError
{
- if (hFile_ == invalidFileHandle)
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), L"Contract error: close() called more than once.");
- ZEN_ON_SCOPE_EXIT(hFile_ = invalidFileHandle);
-
- if (::close(hFile_) != 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), "close");
+ try
+ {
+ if (hFile_ == invalidFileHandle)
+ throw SysError(L"Contract error: close() called more than once.");
+ if (::close(hFile_) != 0)
+ THROW_LAST_SYS_ERROR("close");
+ hFile_ = invalidFileHandle; //do NOT set on failure! => ~FileOutputPlain() still wants to (try to) delete the file!
+ }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), e.toString()); }
}
//----------------------------------------------------------------------------------------------------
namespace
{
-FileBase::FileHandle openHandleForRead(const Zstring& filePath) //throw FileError, ErrorFileLocked
+ std::pair<FileBase::FileHandle, struct stat>
+openHandleForRead(const Zstring& filePath) //throw FileError, ErrorFileLocked
{
- //caveat: check for file types that block during open(): character device, block device, named pipe
- struct stat fileInfo = {};
- if (::stat(filePath.c_str(), &fileInfo) == 0) //follows symlinks
+ try
{
+ //caveat: check for file types that block during open(): character device, block device, named pipe
+ struct stat fileInfo = {};
+ if (::stat(filePath.c_str(), &fileInfo) != 0) //follows symlinks
+ THROW_LAST_SYS_ERROR("stat");
+
if (!S_ISREG(fileInfo.st_mode) &&
!S_ISDIR(fileInfo.st_mode) && //open() will fail with "EISDIR: Is a directory" => nice
!S_ISLNK(fileInfo.st_mode)) //?? shouldn't be possible after successful stat()
@@ -59,103 +105,77 @@ FileBase::FileHandle openHandleForRead(const Zstring& filePath) //throw FileErro
name += L", ";
return name + printNumber<std::wstring>(L"0%06o", m & S_IFMT);
}();
- throw FileError(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath)),
- _("Unsupported item type.") + L" [" + typeName + L']');
+ throw SysError(_("Unsupported item type.") + L" [" + typeName + L']');
}
- }
- //else: let ::open() fail for errors like "not existing"
- //don't use O_DIRECT: https://yarchive.net/comp/linux/o_direct.html
- const int fdFile = ::open(filePath.c_str(), O_RDONLY | O_CLOEXEC);
- if (fdFile == -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)), "open");
- return fdFile; //pass ownership
+ //don't use O_DIRECT: https://yarchive.net/comp/linux/o_direct.html
+ const int fdFile = ::open(filePath.c_str(), O_RDONLY | O_CLOEXEC);
+ if (fdFile == -1) //don't check "< 0" -> docu seems to allow "-2" to be a valid file handle
+ THROW_LAST_SYS_ERROR("open");
+ return {fdFile /*pass ownership*/, fileInfo};
+ }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(filePath)), e.toString()); }
}
}
-FileInput::FileInput(FileHandle handle, const Zstring& filePath, const IoCallback& notifyUnbufferedIO) :
- FileBase(handle, filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {}
+FileInputPlain::FileInputPlain(const Zstring& filePath) :
+ FileInputPlain(openHandleForRead(filePath), filePath) {} //throw FileError, ErrorFileLocked
+
+
+FileInputPlain::FileInputPlain(const std::pair<FileBase::FileHandle, struct stat>& fileDetails, const Zstring& filePath) :
+ FileInputPlain(fileDetails.first, filePath)
+{
+ setStatBuffered(fileDetails.second);
+}
-FileInput::FileInput(const Zstring& filePath, const IoCallback& notifyUnbufferedIO) :
- FileBase(openHandleForRead(filePath), filePath), //throw FileError, ErrorFileLocked
- notifyUnbufferedIO_(notifyUnbufferedIO)
+FileInputPlain::FileInputPlain(FileHandle handle, const Zstring& filePath) :
+ FileBase(handle, filePath)
{
//optimize read-ahead on input file:
- if (::posix_fadvise(getHandle(), 0, 0, POSIX_FADV_SEQUENTIAL) != 0)
+ if (::posix_fadvise(getHandle(), 0 /*offset*/, 0 /*len*/, POSIX_FADV_SEQUENTIAL) != 0) //"len == 0" means "end of the file"
THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(filePath)), "posix_fadvise(POSIX_FADV_SEQUENTIAL)");
+ /* - POSIX_FADV_SEQUENTIAL is like POSIX_FADV_NORMAL, but with twice the read-ahead buffer size
+ - POSIX_FADV_NOREUSE "since kernel 2.6.18 this flag is a no-op" WTF!?
+ - POSIX_FADV_DONTNEED may be used to clear the OS file system cache (offset and len must be page-aligned!)
+ => does nothing, unless data was already written to disk: https://insights.oetiker.ch/linux/fadvise/
+ - POSIX_FADV_WILLNEED: issue explicit read-ahead; almost the same as readahead(), but with weaker error checking
+ https://unix.stackexchange.com/questions/681188/difference-between-posix-fadvise-and-readahead
+
+ clear file system cache manually: sync; echo 3 > /proc/sys/vm/drop_caches */
+
}
-size_t FileInput::tryRead(void* buffer, size_t bytesToRead) //throw FileError, ErrorFileLocked; may return short, only 0 means EOF!
+//may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0!
+size_t FileInputPlain::tryRead(void* buffer, size_t bytesToRead) //throw FileError, ErrorFileLocked
{
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
+ assert(bytesToRead % getBlockSize() == 0);
+ try
{
- 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
- //EINTR is not checked on macOS' copyfile: https://opensource.apple.com/source/copyfile/copyfile-146/copyfile.c.auto.html
- //read() on macOS: https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man2/read.2.html
-
- if (bytesRead < 0)
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), "read");
- if (static_cast<size_t>(bytesRead) > bytesToRead) //better safe than sorry
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), formatSystemError("ReadFile", L"", L"Buffer overflow."));
-
- //if ::read is interrupted (EINTR) right in the middle, it will return successfully with "bytesRead < bytesToRead"
+ 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
+ //EINTR is not checked on macOS' copyfile: https://opensource.apple.com/source/copyfile/copyfile-173.40.2/copyfile.c.auto.html
+ //read() on macOS: https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man2/read.2.html
- return bytesRead; //"zero indicates end of file"
-}
+ if (bytesRead < 0)
+ THROW_LAST_SYS_ERROR("read");
+ if (makeUnsigned(bytesRead) > bytesToRead) //better safe than sorry
+ throw SysError(formatSystemError("ReadFile", L"", L"Buffer overflow."));
+ //if ::read is interrupted (EINTR) right in the middle, it will return successfully with "bytesRead < bytesToRead"
-size_t FileInput::read(void* buffer, size_t bytesToRead) //throw FileError, ErrorFileLocked, X; return "bytesToRead" bytes unless end of stream!
-{
- /*
- FFS 8.9-9.5 perf issues on macOS: https://freefilesync.org/forum/viewtopic.php?t=4808
- app-level buffering is essential to optimize random data sizes; e.g. "export file list":
- => big perf improvement on Windows, Linux. No significant improvement on macOS in tests
- impact on stream-based file copy:
- => no drawback vs block-wise copy loop on Linux, HOWEVER: big perf issue on macOS!
-
- Possible cause of macOS perf issue unclear:
- - getting rid of std::vector::resize() and std::vector::erase() "fixed" the problem
- => costly zero-initializing memory? problem with inlining? QOI issue of std:vector on clang/macOS?
- - replacing std::copy() with memcpy() also *seems* to have improved speed "somewhat"
- */
-
- const size_t blockSize = getBlockSize();
- assert(memBuf_.size() >= blockSize);
- assert(bufPos_ <= bufPosEnd_ && bufPosEnd_ <= memBuf_.size());
-
- auto it = static_cast<std::byte*>(buffer);
- const auto itEnd = it + bytesToRead;
- for (;;)
- {
- const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), bufPosEnd_ - bufPos_);
- std::memcpy(it, &memBuf_[0] + bufPos_ /*caveat: vector debug checks*/, junkSize);
- bufPos_ += junkSize;
- it += junkSize;
-
- if (it == itEnd)
- break;
- //--------------------------------------------------------------------
- const size_t bytesRead = tryRead(&memBuf_[0], blockSize); //throw FileError, ErrorFileLocked; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0
- bufPos_ = 0;
- bufPosEnd_ = bytesRead;
-
- if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesRead); //throw X
-
- if (bytesRead == 0) //end of file
- break;
+ return bytesRead; //"zero indicates end of file"
}
- return it - static_cast<std::byte*>(buffer);
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getFilePath())), e.toString()); }
}
//----------------------------------------------------------------------------------------------------
@@ -164,170 +184,153 @@ namespace
{
FileBase::FileHandle openHandleForWrite(const Zstring& filePath) //throw FileError, ErrorTargetExisting
{
- //checkForUnsupportedType(filePath); -> not needed, open() + O_WRONLY should fail fast
-
- const mode_t lockFileMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; //0666 => umask will be applied implicitly!
-
- //O_EXCL contains a race condition on NFS file systems: https://linux.die.net/man/2/open
- const int fdFile = ::open(filePath.c_str(), //const char* pathname
- O_CREAT | //int flags
- /*access == FileOutput::ACC_OVERWRITE ? O_TRUNC : */ O_EXCL | O_WRONLY | O_CLOEXEC,
- lockFileMode); //mode_t mode
- if (fdFile == -1)
+ try
{
- 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("open", ec);
+ //checkForUnsupportedType(filePath); -> not needed, open() + O_WRONLY should fail fast
+
+ const mode_t lockFileMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; //0666 => umask will be applied implicitly!
- if (ec == EEXIST)
- throw ErrorTargetExisting(errorMsg, errorDescr);
- //if (ec == ENOENT) throw ErrorTargetPathMissing(errorMsg, errorDescr);
+ //O_EXCL contains a race condition on NFS file systems: https://linux.die.net/man/2/open
+ const int fdFile = ::open(filePath.c_str(), //const char* pathname
+ O_CREAT | //int flags
+ /*access == FileOutput::ACC_OVERWRITE ? O_TRUNC : */ O_EXCL | O_WRONLY | O_CLOEXEC,
+ lockFileMode); //mode_t mode
+ if (fdFile == -1)
+ {
+ const int ec = errno; //copy before making other system calls!
+ if (ec == EEXIST)
+ throw ErrorTargetExisting(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filePath)), formatSystemError("open", ec));
+ //if (ec == ENOENT) throw ErrorTargetPathMissing(errorMsg, errorDescr);
- throw FileError(errorMsg, errorDescr);
+ THROW_LAST_SYS_ERROR("open");
+ }
+ return fdFile; //pass ownership
}
- return fdFile; //pass ownership
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(filePath)), e.toString()); }
}
}
-FileOutput::FileOutput(FileHandle handle, const Zstring& filePath, const IoCallback& notifyUnbufferedIO) :
- FileBase(handle, filePath), notifyUnbufferedIO_(notifyUnbufferedIO)
-{
-}
+FileOutputPlain::FileOutputPlain(const Zstring& filePath) :
+ FileOutputPlain(openHandleForWrite(filePath), filePath) {} //throw FileError, ErrorTargetExisting
-FileOutput::FileOutput(const Zstring& filePath, const IoCallback& notifyUnbufferedIO) :
- FileBase(openHandleForWrite(filePath), filePath), notifyUnbufferedIO_(notifyUnbufferedIO) {} //throw FileError, ErrorTargetExisting
+FileOutputPlain::FileOutputPlain(FileHandle handle, const Zstring& filePath) :
+ FileBase(handle, filePath)
+{
+}
-FileOutput::~FileOutput()
+FileOutputPlain::~FileOutputPlain()
{
if (getHandle() != invalidFileHandle) //not finalized => clean up garbage
- {
- //"deleting while handle is open" == FILE_FLAG_DELETE_ON_CLOSE
- if (::unlink(getFilePath().c_str()) != 0)
+ try
+ {
+ //"deleting while handle is open" == FILE_FLAG_DELETE_ON_CLOSE
+ if (::unlink(getFilePath().c_str()) != 0)
+ THROW_LAST_SYS_ERROR("unlink");
+ }
+ catch (const SysError&)
+ {
assert(false);
- }
+ warn_static("at least log on failure!")
+ }
}
-size_t FileOutput::tryWrite(const void* buffer, size_t bytesToWrite) //throw FileError; may return short! CONTRACT: bytesToWrite > 0
+void FileOutputPlain::reserveSpace(uint64_t expectedSize) //throw FileError
{
- 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);
- //write() on macOS: https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man2/write.2.html
+ //NTFS: "If you set the file allocation info [...] the file contents will be forced into nonresident data, even if it would have fit inside the MFT."
+ if (expectedSize < 1024) //https://docs.microsoft.com/en-us/archive/blogs/askcore/the-four-stages-of-ntfs-file-growth
+ return;
- if (bytesWritten <= 0)
+ try
{
- if (bytesWritten == 0) //comment in safe-read.c suggests to treat this as an error due to buggy drivers
- errno = ENOSPC;
+ //don't use ::posix_fallocate which uses horribly inefficient fallback if FS doesn't support it (EOPNOTSUPP) and changes files size!
+ //FALLOC_FL_KEEP_SIZE => allocate only, file size is NOT changed!
+ if (::fallocate(getHandle(), //int fd
+ FALLOC_FL_KEEP_SIZE, //int mode
+ 0, //off_t offset
+ expectedSize) != 0) //off_t len
+ if (errno != EOPNOTSUPP) //possible, unlike with posix_fallocate()
+ THROW_LAST_SYS_ERROR("fallocate");
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), "write");
}
- if (bytesWritten > static_cast<ssize_t>(bytesToWrite)) //better safe than sorry
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), formatSystemError("write", L"", L"Buffer overflow."));
-
- //if ::write() is interrupted (EINTR) right in the middle, it will return successfully with "bytesWritten < bytesToWrite"!
- return bytesWritten;
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), e.toString()); }
}
-void FileOutput::write(const void* buffer, size_t bytesToWrite) //throw FileError, X
+//may return short! CONTRACT: bytesToWrite > 0
+size_t FileOutputPlain::tryWrite(const void* buffer, size_t bytesToWrite) //throw FileError
{
- const size_t blockSize = getBlockSize();
- assert(memBuf_.size() >= blockSize);
- assert(bufPos_ <= bufPosEnd_ && bufPosEnd_ <= memBuf_.size());
-
- auto it = static_cast<const std::byte*>(buffer);
- const auto itEnd = it + bytesToWrite;
- for (;;)
+ if (bytesToWrite == 0)
+ throw std::logic_error("Contract violation! " + std::string(__FILE__) + ':' + numberTo<std::string>(__LINE__));
+ assert(bytesToWrite % getBlockSize() == 0 || bytesToWrite < getBlockSize());
+ try
{
- if (memBuf_.size() - bufPos_ < blockSize) //support memBuf_.size() > blockSize to reduce memmove()s, but perf test shows: not really needed!
- // || bufPos_ == bufPosEnd_) -> not needed while memBuf_.size() == blockSize
+ ssize_t bytesWritten = 0;
+ do
{
- std::memmove(&memBuf_[0], &memBuf_[0] + bufPos_, bufPosEnd_ - bufPos_);
- bufPosEnd_ -= bufPos_;
- bufPos_ = 0;
+ bytesWritten = ::write(getHandle(), buffer, bytesToWrite);
}
+ while (bytesWritten < 0 && errno == EINTR);
+ //write() on macOS: https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man2/write.2.html
- const size_t junkSize = std::min(static_cast<size_t>(itEnd - it), blockSize - (bufPosEnd_ - bufPos_));
- std::memcpy(&memBuf_[0] + bufPosEnd_ /*caveat: vector debug checks*/, it, junkSize);
- bufPosEnd_ += junkSize;
- it += junkSize;
-
- if (it == itEnd)
- return;
- //--------------------------------------------------------------------
- const size_t bytesWritten = tryWrite(&memBuf_[bufPos_], blockSize); //throw FileError; may return short! CONTRACT: bytesToWrite > 0
- bufPos_ += bytesWritten;
- if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesWritten); //throw X!
- }
-}
+ 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_SYS_ERROR("write");
+ }
+ if (bytesWritten > static_cast<ssize_t>(bytesToWrite)) //better safe than sorry
+ throw SysError(formatSystemError("write", L"", L"Buffer overflow."));
-void FileOutput::flushBuffers() //throw FileError, X
-{
- assert(bufPosEnd_ - bufPos_ <= getBlockSize());
- assert(bufPos_ <= bufPosEnd_ && bufPosEnd_ <= memBuf_.size());
- while (bufPos_ != bufPosEnd_)
- {
- const size_t bytesWritten = tryWrite(&memBuf_[bufPos_], bufPosEnd_ - bufPos_); //throw FileError; may return short! CONTRACT: bytesToWrite > 0
- bufPos_ += bytesWritten;
- if (notifyUnbufferedIO_) notifyUnbufferedIO_(bytesWritten); //throw X!
+ //if ::write() is interrupted (EINTR) right in the middle, it will return successfully with "bytesWritten < bytesToWrite"!
+ return bytesWritten;
}
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), e.toString()); }
}
+//----------------------------------------------------------------------------------------------------
-void FileOutput::finalize() //throw FileError, X
-{
- flushBuffers(); //throw FileError, X
- close(); //throw FileError
- //~FileBase() calls this one, too, but we want to propagate errors if any
-}
-
-
-void FileOutput::reserveSpace(uint64_t expectedSize) //throw FileError
+std::string zen::getFileContent(const Zstring& filePath, const IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X
{
- //NTFS: "If you set the file allocation info [...] the file contents will be forced into nonresident data, even if it would have fit inside the MFT."
- if (expectedSize < 1024) //https://www.sciencedirect.com/topics/computer-science/master-file-table
- return;
-
- //don't use ::posix_fallocate which uses horribly inefficient fallback if FS doesn't support it (EOPNOTSUPP) and changes files size!
- //FALLOC_FL_KEEP_SIZE => allocate only, file size is NOT changed!
- if (::fallocate(getHandle(), //int fd
- FALLOC_FL_KEEP_SIZE, //int mode
- 0, //off_t offset
- expectedSize) != 0) //off_t len
- if (errno != EOPNOTSUPP) //possible, unlike with posix_fallocate()
- THROW_LAST_FILE_ERROR(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getFilePath())), "fallocate");
+ FileInputPlain fileIn(filePath); //throw FileError, ErrorFileLocked
+ return unbufferedLoad<std::string>([&](void* buffer, size_t bytesToRead)
+ {
+ const size_t bytesRead = fileIn.tryRead(buffer, bytesToRead); //throw FileError; may return short, only 0 means EOF! => CONTRACT: bytesToRead > 0!
+ if (notifyUnbufferedIO) notifyUnbufferedIO(bytesRead); //throw X!
+ return bytesRead;
+ },
+ fileIn.getBlockSize()); //throw FileError, X
}
-std::string zen::getFileContent(const Zstring& filePath, const IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X
+void zen::setFileContent(const Zstring& filePath, const std::string& byteStream, const IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X
{
- FileInput streamIn(filePath, notifyUnbufferedIO); //throw FileError, ErrorFileLocked
- return bufferedLoad<std::string>(streamIn); //throw FileError, X
-}
+ const Zstring tmpFilePath = getPathWithTempName(filePath);
+ FileOutputPlain tmpFile(tmpFilePath); //throw FileError, (ErrorTargetExisting)
-void zen::setFileContent(const Zstring& filePath, const std::string& byteStream, const IoCallback& notifyUnbufferedIO /*throw X*/) //throw FileError, X
-{
- TempFileOutput fileOut(filePath, notifyUnbufferedIO); //throw FileError
- if (!byteStream.empty())
+ tmpFile.reserveSpace(byteStream.size()); //throw FileError
+
+ unbufferedSave(byteStream, [&](const void* buffer, size_t bytesToWrite)
{
- //preallocate disk space & reduce fragmentation
- fileOut.reserveSpace(byteStream.size()); //throw FileError
- fileOut.write(&byteStream[0], byteStream.size()); //throw FileError, X
- }
- fileOut.commit(); //throw FileError, X
+ const size_t bytesWritten = tmpFile.tryWrite(buffer, bytesToWrite); //throw FileError; may return short! CONTRACT: bytesToWrite > 0
+ if (notifyUnbufferedIO) notifyUnbufferedIO(bytesWritten); //throw X!
+ return bytesWritten;
+ },
+ tmpFile.getBlockSize()); //throw FileError, X
+
+ tmpFile.close(); //throw FileError
+ //take over ownership:
+ ZEN_ON_SCOPE_FAIL( try { removeFilePlain(tmpFilePath); /*throw FileError*/ }
+ catch (FileError&) {});
+ warn_static("log it!")
+
+ //operation finished: move temp file transactionally
+ moveAndRenameItem(tmpFilePath, filePath, true /*replaceExisting*/); //throw FileError, (ErrorMoveUnsupported), (ErrorTargetExisting)
}
bgstack15