mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-06-09 22:28:51 +02:00
Merge bitcoin/bitcoin#29307: util: explicitly close all AutoFiles that have been written
c10e382d2aflatfile: check whether the file has been closed successfully (Vasil Dimov)4bb5dd78eautil: check that a file has been closed before ~AutoFile() is called (Vasil Dimov)8bb34f07dfExplicitly close all AutoFiles that have been written (Vasil Dimov)a69c4098b2rpc: take ownership of the file by WriteUTXOSnapshot() (Hodlinator) Pull request description: `fclose(3)` may fail to flush the previously written data to disk, thus a failing `fclose(3)` is as serious as a failing `fwrite(3)`. Previously the code ignored `fclose(3)` failures. This PR improves that by changing all users of `AutoFile` that use it to write data to explicitly close the file and handle a possible error. --- Other alternatives are: 1. `fflush(3)` after each write to the file (and throw if it fails from the `AutoFile::write()` method) and hope that `fclose(3)` will then always succeed. Assert that it succeeds from the destructor 🙄. Will hurt performance. 2. Throw nevertheless from the destructor. Exception within the exception in C++ I think results in terminating the program without a useful message. 3. (this is implemented in the latest incarnation of this PR) Redesign `AutoFile` so that its destructor cannot fail. Adjust _all_ its users 😭. For example, if the file has been written to, then require the callers to explicitly call the `AutoFile::fclose()` method before the object goes out of scope. In the destructor, as a sanity check, assume/assert that this is indeed the case. Defeats the purpose of a RAII wrapper for `FILE*` which automatically closes the file when it goes out of scope and there are a lot of users of `AutoFile`. 4. Pass a new callback function to the `AutoFile` constructor which will be called from the destructor to handle `fclose()` errors, as described in https://github.com/bitcoin/bitcoin/pull/29307#issuecomment-2243842400. My thinking is that if that callback is going to only log a message, then we can log the message directly from the destructor without needing a callback. If the callback is going to do more complicated error handling then it is easier to do that at the call site by directly calling `AutoFile::fclose()` instead of getting the `AutoFile` object out of scope (so that its destructor is called) and inspecting for side effects done by the callback (e.g. set a variable to indicate a failed `fclose()`). ACKs for top commit: l0rinc: ACKc10e382d2aachow101: ACKc10e382d2ahodlinator: re-ACKc10e382d2aTree-SHA512: 3994ca57e5b2b649fc84f24dad144173b7500fc0e914e06291d5c32fbbf8d2b1f8eae0040abd7a5f16095ddf4e11fe1636c6092f49058cda34f3eb2ee536d7ba
This commit is contained in:
@@ -24,6 +24,7 @@
|
||||
#include <univalue.h>
|
||||
#include <util/fs.h>
|
||||
#include <util/fs_helpers.h>
|
||||
#include <util/syserror.h>
|
||||
#include <util/translation.h>
|
||||
|
||||
namespace {
|
||||
@@ -61,7 +62,6 @@ bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data
|
||||
FILE *file = fsbridge::fopen(pathTmp, "wb");
|
||||
AutoFile fileout{file};
|
||||
if (fileout.IsNull()) {
|
||||
fileout.fclose();
|
||||
remove(pathTmp);
|
||||
LogError("%s: Failed to open file %s\n", __func__, fs::PathToString(pathTmp));
|
||||
return false;
|
||||
@@ -69,17 +69,22 @@ bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data
|
||||
|
||||
// Serialize
|
||||
if (!SerializeDB(fileout, data)) {
|
||||
fileout.fclose();
|
||||
(void)fileout.fclose();
|
||||
remove(pathTmp);
|
||||
return false;
|
||||
}
|
||||
if (!fileout.Commit()) {
|
||||
fileout.fclose();
|
||||
(void)fileout.fclose();
|
||||
remove(pathTmp);
|
||||
LogError("%s: Failed to flush file %s\n", __func__, fs::PathToString(pathTmp));
|
||||
return false;
|
||||
}
|
||||
fileout.fclose();
|
||||
if (fileout.fclose() != 0) {
|
||||
const int errno_save{errno};
|
||||
remove(pathTmp);
|
||||
LogError("Failed to close file %s after commit: %s", fs::PathToString(pathTmp), SysErrorString(errno_save));
|
||||
return false;
|
||||
}
|
||||
|
||||
// replace existing file, if any, with new file
|
||||
if (!RenameOver(pathTmp, path)) {
|
||||
|
||||
@@ -28,7 +28,7 @@ static void FindByte(benchmark::Bench& bench)
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
file.fclose();
|
||||
assert(file.fclose() == 0);
|
||||
fs::remove("streams_tmp");
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,9 @@ FILE* FlatFileSeq::Open(const FlatFilePos& pos, bool read_only) const
|
||||
}
|
||||
if (pos.nPos && fseek(file, pos.nPos, SEEK_SET)) {
|
||||
LogPrintf("Unable to seek to position %u of %s\n", pos.nPos, fs::PathToString(path));
|
||||
fclose(file);
|
||||
if (fclose(file) != 0) {
|
||||
LogError("Unable to close file %s", fs::PathToString(path));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return file;
|
||||
@@ -68,7 +70,10 @@ size_t FlatFileSeq::Allocate(const FlatFilePos& pos, size_t add_size, bool& out_
|
||||
if (file) {
|
||||
LogDebug(BCLog::VALIDATION, "Pre-allocating up to position 0x%x in %s%05u.dat\n", new_size, m_prefix, pos.nFile);
|
||||
AllocateFileRange(file, pos.nPos, inc_size);
|
||||
fclose(file);
|
||||
if (fclose(file) != 0) {
|
||||
LogError("Cannot close file %s%05u.dat after extending it with %u bytes", m_prefix, pos.nFile, new_size);
|
||||
return 0;
|
||||
}
|
||||
return inc_size;
|
||||
}
|
||||
} else {
|
||||
@@ -86,17 +91,24 @@ bool FlatFileSeq::Flush(const FlatFilePos& pos, bool finalize) const
|
||||
return false;
|
||||
}
|
||||
if (finalize && !TruncateFile(file, pos.nPos)) {
|
||||
fclose(file);
|
||||
LogError("%s: failed to truncate file %d\n", __func__, pos.nFile);
|
||||
if (fclose(file) != 0) {
|
||||
LogError("Failed to close file %d", pos.nFile);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!FileCommit(file)) {
|
||||
fclose(file);
|
||||
LogError("%s: failed to commit file %d\n", __func__, pos.nFile);
|
||||
if (fclose(file) != 0) {
|
||||
LogError("Failed to close file %d", pos.nFile);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
DirectoryCommit(m_dir);
|
||||
|
||||
fclose(file);
|
||||
if (fclose(file) != 0) {
|
||||
LogError("Failed to close file %d after flush", pos.nFile);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <node/blockstorage.h>
|
||||
#include <undo.h>
|
||||
#include <util/fs_helpers.h>
|
||||
#include <util/syserror.h>
|
||||
|
||||
/* The index database stores three items for each block: the disk location of the encoded filter,
|
||||
* its dSHA256 hash, and the header. Those belonging to blocks on the active chain are indexed by
|
||||
@@ -159,6 +160,11 @@ bool BlockFilterIndex::CustomCommit(CDBBatch& batch)
|
||||
}
|
||||
if (!file.Commit()) {
|
||||
LogError("%s: Failed to commit filter file %d\n", __func__, pos.nFile);
|
||||
(void)file.fclose();
|
||||
return false;
|
||||
}
|
||||
if (file.fclose() != 0) {
|
||||
LogError("Failed to close filter file %d after commit: %s", pos.nFile, SysErrorString(errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -213,6 +219,11 @@ size_t BlockFilterIndex::WriteFilterToDisk(FlatFilePos& pos, const BlockFilter&
|
||||
}
|
||||
if (!last_file.Commit()) {
|
||||
LogPrintf("%s: Failed to commit filter file %d\n", __func__, pos.nFile);
|
||||
(void)last_file.fclose();
|
||||
return 0;
|
||||
}
|
||||
if (last_file.fclose() != 0) {
|
||||
LogError("Failed to close filter file %d after commit: %s", pos.nFile, SysErrorString(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -235,6 +246,12 @@ size_t BlockFilterIndex::WriteFilterToDisk(FlatFilePos& pos, const BlockFilter&
|
||||
}
|
||||
|
||||
fileout << filter.GetBlockHash() << filter.GetEncodedFilter();
|
||||
|
||||
if (fileout.fclose() != 0) {
|
||||
LogError("Failed to close filter file %d: %s", pos.nFile, SysErrorString(errno));
|
||||
return 0;
|
||||
}
|
||||
|
||||
return data_size;
|
||||
}
|
||||
|
||||
|
||||
@@ -4005,6 +4005,11 @@ static void CaptureMessageToFile(const CAddress& addr,
|
||||
uint32_t size = data.size();
|
||||
ser_writedata32(f, size);
|
||||
f << data;
|
||||
|
||||
if (f.fclose() != 0) {
|
||||
throw std::ios_base::failure(
|
||||
strprintf("Error closing %s after write, file contents are likely incomplete", fs::PathToString(path)));
|
||||
}
|
||||
}
|
||||
|
||||
std::function<void(const CAddress& addr,
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <util/fs.h>
|
||||
#include <util/signalinterrupt.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <util/syserror.h>
|
||||
#include <util/translation.h>
|
||||
#include <validation.h>
|
||||
|
||||
@@ -941,13 +942,13 @@ bool BlockManager::WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationSt
|
||||
return false;
|
||||
}
|
||||
|
||||
// Open history file to append
|
||||
AutoFile file{OpenUndoFile(pos)};
|
||||
if (file.IsNull()) {
|
||||
LogError("OpenUndoFile failed for %s while writing block undo", pos.ToString());
|
||||
return FatalError(m_opts.notifications, state, _("Failed to write undo data."));
|
||||
}
|
||||
{
|
||||
// Open history file to append
|
||||
AutoFile file{OpenUndoFile(pos)};
|
||||
if (file.IsNull()) {
|
||||
LogError("OpenUndoFile failed for %s while writing block undo", pos.ToString());
|
||||
return FatalError(m_opts.notifications, state, _("Failed to write undo data."));
|
||||
}
|
||||
BufferedWriter fileout{file};
|
||||
|
||||
// Write index header
|
||||
@@ -960,8 +961,13 @@ bool BlockManager::WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationSt
|
||||
// Write undo data & checksum
|
||||
fileout << blockundo << hasher.GetHash();
|
||||
}
|
||||
// BufferedWriter will flush pending data to file when fileout goes out of scope.
|
||||
}
|
||||
|
||||
fileout.flush(); // Make sure `AutoFile`/`BufferedWriter` go out of scope before we call `FlushUndoFile`
|
||||
// Make sure that the file is closed before we call `FlushUndoFile`.
|
||||
if (file.fclose() != 0) {
|
||||
LogError("Failed to close block undo file %s: %s", pos.ToString(), SysErrorString(errno));
|
||||
return FatalError(m_opts.notifications, state, _("Failed to close block undo file."));
|
||||
}
|
||||
|
||||
// rev files are written in block height order, whereas blk files are written as blocks come in (often out of order)
|
||||
@@ -1094,13 +1100,22 @@ FlatFilePos BlockManager::WriteBlock(const CBlock& block, int nHeight)
|
||||
m_opts.notifications.fatalError(_("Failed to write block."));
|
||||
return FlatFilePos();
|
||||
}
|
||||
BufferedWriter fileout{file};
|
||||
{
|
||||
BufferedWriter fileout{file};
|
||||
|
||||
// Write index header
|
||||
fileout << GetParams().MessageStart() << block_size;
|
||||
pos.nPos += STORAGE_HEADER_BYTES;
|
||||
// Write block
|
||||
fileout << TX_WITH_WITNESS(block);
|
||||
}
|
||||
|
||||
if (file.fclose() != 0) {
|
||||
LogError("Failed to close block file %s: %s", pos.ToString(), SysErrorString(errno));
|
||||
m_opts.notifications.fatalError(_("Failed to close file when writing block."));
|
||||
return FlatFilePos();
|
||||
}
|
||||
|
||||
// Write index header
|
||||
fileout << GetParams().MessageStart() << block_size;
|
||||
pos.nPos += STORAGE_HEADER_BYTES;
|
||||
// Write block
|
||||
fileout << TX_WITH_WITNESS(block);
|
||||
return pos;
|
||||
}
|
||||
|
||||
@@ -1143,6 +1158,11 @@ static auto InitBlocksdirXorKey(const BlockManager::Options& opts)
|
||||
#endif
|
||||
)};
|
||||
xor_key_file << xor_key;
|
||||
if (xor_key_file.fclose() != 0) {
|
||||
throw std::runtime_error{strprintf("Error closing XOR key file %s: %s",
|
||||
fs::PathToString(xor_key_path),
|
||||
SysErrorString(errno))};
|
||||
}
|
||||
}
|
||||
// If the user disabled the key, it must be zero.
|
||||
if (!opts.use_xor && xor_key != decltype(xor_key){}) {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <util/fs.h>
|
||||
#include <util/fs_helpers.h>
|
||||
#include <util/signalinterrupt.h>
|
||||
#include <util/syserror.h>
|
||||
#include <util/time.h>
|
||||
#include <validation.h>
|
||||
|
||||
@@ -168,7 +169,8 @@ bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mock
|
||||
|
||||
auto mid = SteadyClock::now();
|
||||
|
||||
AutoFile file{mockable_fopen_function(dump_path + ".new", "wb")};
|
||||
const fs::path file_fspath{dump_path + ".new"};
|
||||
AutoFile file{mockable_fopen_function(file_fspath, "wb")};
|
||||
if (file.IsNull()) {
|
||||
return false;
|
||||
}
|
||||
@@ -199,9 +201,14 @@ bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mock
|
||||
LogInfo("Writing %d unbroadcast transactions to file.\n", unbroadcast_txids.size());
|
||||
file << unbroadcast_txids;
|
||||
|
||||
if (!skip_file_commit && !file.Commit())
|
||||
if (!skip_file_commit && !file.Commit()) {
|
||||
(void)file.fclose();
|
||||
throw std::runtime_error("Commit failed");
|
||||
file.fclose();
|
||||
}
|
||||
if (file.fclose() != 0) {
|
||||
throw std::runtime_error(
|
||||
strprintf("Error closing %s: %s", fs::PathToString(file_fspath), SysErrorString(errno)));
|
||||
}
|
||||
if (!RenameOver(dump_path + ".new", dump_path)) {
|
||||
throw std::runtime_error("Rename failed");
|
||||
}
|
||||
@@ -213,6 +220,7 @@ bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mock
|
||||
fs::file_size(dump_path));
|
||||
} catch (const std::exception& e) {
|
||||
LogInfo("Failed to dump mempool: %s. Continuing anyway.\n", e.what());
|
||||
(void)file.fclose();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <uint256.h>
|
||||
#include <util/fs.h>
|
||||
#include <util/serfloat.h>
|
||||
#include <util/syserror.h>
|
||||
#include <util/time.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -963,9 +964,14 @@ void CBlockPolicyEstimator::FlushFeeEstimates()
|
||||
AutoFile est_file{fsbridge::fopen(m_estimation_filepath, "wb")};
|
||||
if (est_file.IsNull() || !Write(est_file)) {
|
||||
LogPrintf("Failed to write fee estimates to %s. Continue anyway.\n", fs::PathToString(m_estimation_filepath));
|
||||
} else {
|
||||
LogPrintf("Flushed fee estimates to %s.\n", fs::PathToString(m_estimation_filepath.filename()));
|
||||
(void)est_file.fclose();
|
||||
return;
|
||||
}
|
||||
if (est_file.fclose() != 0) {
|
||||
LogError("Failed to close fee estimates file %s: %s. Continuing anyway.", fs::PathToString(m_estimation_filepath), SysErrorString(errno));
|
||||
return;
|
||||
}
|
||||
LogPrintf("Flushed fee estimates to %s.\n", fs::PathToString(m_estimation_filepath.filename()));
|
||||
}
|
||||
|
||||
bool CBlockPolicyEstimator::Write(AutoFile& fileout) const
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include <util/check.h>
|
||||
#include <util/fs.h>
|
||||
#include <util/strencodings.h>
|
||||
#include <util/syserror.h>
|
||||
#include <util/translation.h>
|
||||
#include <validation.h>
|
||||
#include <validationinterface.h>
|
||||
@@ -82,7 +83,7 @@ UniValue WriteUTXOSnapshot(
|
||||
CCoinsViewCursor* pcursor,
|
||||
CCoinsStats* maybe_stats,
|
||||
const CBlockIndex* tip,
|
||||
AutoFile& afile,
|
||||
AutoFile&& afile,
|
||||
const fs::path& path,
|
||||
const fs::path& temppath,
|
||||
const std::function<void()>& interruption_point = {});
|
||||
@@ -3114,7 +3115,14 @@ static RPCHelpMan dumptxoutset()
|
||||
}
|
||||
}
|
||||
|
||||
UniValue result = WriteUTXOSnapshot(*chainstate, cursor.get(), &stats, tip, afile, path, temppath, node.rpc_interruption_point);
|
||||
UniValue result = WriteUTXOSnapshot(*chainstate,
|
||||
cursor.get(),
|
||||
&stats,
|
||||
tip,
|
||||
std::move(afile),
|
||||
path,
|
||||
temppath,
|
||||
node.rpc_interruption_point);
|
||||
fs::rename(temppath, path);
|
||||
|
||||
result.pushKV("path", path.utf8string());
|
||||
@@ -3166,7 +3174,7 @@ UniValue WriteUTXOSnapshot(
|
||||
CCoinsViewCursor* pcursor,
|
||||
CCoinsStats* maybe_stats,
|
||||
const CBlockIndex* tip,
|
||||
AutoFile& afile,
|
||||
AutoFile&& afile,
|
||||
const fs::path& path,
|
||||
const fs::path& temppath,
|
||||
const std::function<void()>& interruption_point)
|
||||
@@ -3225,7 +3233,10 @@ UniValue WriteUTXOSnapshot(
|
||||
|
||||
CHECK_NONFATAL(written_coins_count == maybe_stats->coins_count);
|
||||
|
||||
afile.fclose();
|
||||
if (afile.fclose() != 0) {
|
||||
throw std::ios_base::failure(
|
||||
strprintf("Error closing %s: %s", fs::PathToString(temppath), SysErrorString(errno)));
|
||||
}
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
result.pushKV("coins_written", written_coins_count);
|
||||
@@ -3240,12 +3251,19 @@ UniValue WriteUTXOSnapshot(
|
||||
UniValue CreateUTXOSnapshot(
|
||||
node::NodeContext& node,
|
||||
Chainstate& chainstate,
|
||||
AutoFile& afile,
|
||||
AutoFile&& afile,
|
||||
const fs::path& path,
|
||||
const fs::path& tmppath)
|
||||
{
|
||||
auto [cursor, stats, tip]{WITH_LOCK(::cs_main, return PrepareUTXOSnapshot(chainstate, node.rpc_interruption_point))};
|
||||
return WriteUTXOSnapshot(chainstate, cursor.get(), &stats, tip, afile, path, tmppath, node.rpc_interruption_point);
|
||||
return WriteUTXOSnapshot(chainstate,
|
||||
cursor.get(),
|
||||
&stats,
|
||||
tip,
|
||||
std::move(afile),
|
||||
path,
|
||||
tmppath,
|
||||
node.rpc_interruption_point);
|
||||
}
|
||||
|
||||
static RPCHelpMan loadtxoutset()
|
||||
|
||||
@@ -51,7 +51,7 @@ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES],
|
||||
UniValue CreateUTXOSnapshot(
|
||||
node::NodeContext& node,
|
||||
Chainstate& chainstate,
|
||||
AutoFile& afile,
|
||||
AutoFile&& afile,
|
||||
const fs::path& path,
|
||||
const fs::path& tmppath);
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ void AutoFile::write(std::span<const std::byte> src)
|
||||
if (std::fwrite(src.data(), 1, src.size(), m_file) != src.size()) {
|
||||
throw std::ios_base::failure("AutoFile::write: write failed");
|
||||
}
|
||||
m_was_written = true;
|
||||
if (m_position.has_value()) *m_position += src.size();
|
||||
} else {
|
||||
std::array<std::byte, 4096> buf;
|
||||
@@ -107,6 +108,7 @@ void AutoFile::write_buffer(std::span<std::byte> src)
|
||||
if (std::fwrite(src.data(), 1, src.size(), m_file) != src.size()) {
|
||||
throw std::ios_base::failure("AutoFile::write_buffer: write failed");
|
||||
}
|
||||
m_was_written = true;
|
||||
if (m_position) *m_position += src.size();
|
||||
}
|
||||
|
||||
@@ -117,6 +119,7 @@ bool AutoFile::Commit()
|
||||
|
||||
bool AutoFile::Truncate(unsigned size)
|
||||
{
|
||||
m_was_written = true;
|
||||
return ::TruncateFile(m_file, size);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,10 +6,13 @@
|
||||
#ifndef BITCOIN_STREAMS_H
|
||||
#define BITCOIN_STREAMS_H
|
||||
|
||||
#include <logging.h>
|
||||
#include <serialize.h>
|
||||
#include <span.h>
|
||||
#include <support/allocators/zeroafterfree.h>
|
||||
#include <util/check.h>
|
||||
#include <util/overflow.h>
|
||||
#include <util/syserror.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
@@ -387,7 +390,13 @@ public:
|
||||
*
|
||||
* Will automatically close the file when it goes out of scope if not null.
|
||||
* If you're returning the file pointer, return file.release().
|
||||
* If you need to close the file early, use file.fclose() instead of fclose(file).
|
||||
* If you need to close the file early, use autofile.fclose() instead of fclose(underlying_FILE).
|
||||
*
|
||||
* @note If the file has been written to, then the caller must close it
|
||||
* explicitly with the `fclose()` method, check if it returns an error and treat
|
||||
* such an error as if the `write()` method failed. The OS's `fclose(3)` may
|
||||
* fail to flush to disk data that has been previously written, rendering the
|
||||
* file corrupt.
|
||||
*/
|
||||
class AutoFile
|
||||
{
|
||||
@@ -395,11 +404,28 @@ protected:
|
||||
std::FILE* m_file;
|
||||
std::vector<std::byte> m_xor;
|
||||
std::optional<int64_t> m_position;
|
||||
bool m_was_written{false};
|
||||
|
||||
public:
|
||||
explicit AutoFile(std::FILE* file, std::vector<std::byte> data_xor={});
|
||||
|
||||
~AutoFile() { fclose(); }
|
||||
~AutoFile()
|
||||
{
|
||||
if (m_was_written) {
|
||||
// Callers that wrote to the file must have closed it explicitly
|
||||
// with the fclose() method and checked that the close succeeded.
|
||||
// This is because here in the destructor we have no way to signal
|
||||
// errors from fclose() which, after write, could mean the file is
|
||||
// corrupted and must be handled properly at the call site.
|
||||
// Destructors in C++ cannot signal an error to the callers because
|
||||
// they do not return a value and are not allowed to throw exceptions.
|
||||
Assume(IsNull());
|
||||
}
|
||||
|
||||
if (fclose() != 0) {
|
||||
LogError("Failed to close file: %s", SysErrorString(errno));
|
||||
}
|
||||
}
|
||||
|
||||
// Disallow copies
|
||||
AutoFile(const AutoFile&) = delete;
|
||||
@@ -407,7 +433,7 @@ public:
|
||||
|
||||
bool feof() const { return std::feof(m_file); }
|
||||
|
||||
int fclose()
|
||||
[[nodiscard]] int fclose()
|
||||
{
|
||||
if (auto rel{release()}) return std::fclose(rel);
|
||||
return 0;
|
||||
|
||||
@@ -46,6 +46,7 @@ BOOST_AUTO_TEST_CASE(flatfile_open)
|
||||
{
|
||||
AutoFile file{seq.Open(FlatFilePos(0, pos1))};
|
||||
file << LIMITED_STRING(line1, 256);
|
||||
BOOST_REQUIRE_EQUAL(file.fclose(), 0);
|
||||
}
|
||||
|
||||
// Attempt to append to file opened in read-only mode.
|
||||
@@ -58,6 +59,7 @@ BOOST_AUTO_TEST_CASE(flatfile_open)
|
||||
{
|
||||
AutoFile file{seq.Open(FlatFilePos(0, pos2))};
|
||||
file << LIMITED_STRING(line2, 256);
|
||||
BOOST_REQUIRE_EQUAL(file.fclose(), 0);
|
||||
}
|
||||
|
||||
// Read text from file in read-only mode.
|
||||
@@ -79,6 +81,7 @@ BOOST_AUTO_TEST_CASE(flatfile_open)
|
||||
|
||||
file >> LIMITED_STRING(text, 256);
|
||||
BOOST_CHECK_EQUAL(text, line2);
|
||||
BOOST_REQUIRE_EQUAL(file.fclose(), 0);
|
||||
}
|
||||
|
||||
// Ensure another file in the sequence has no data.
|
||||
@@ -86,6 +89,7 @@ BOOST_AUTO_TEST_CASE(flatfile_open)
|
||||
std::string text;
|
||||
AutoFile file{seq.Open(FlatFilePos(1, pos2))};
|
||||
BOOST_CHECK_THROW(file >> LIMITED_STRING(text, 256), std::ios_base::failure);
|
||||
BOOST_REQUIRE_EQUAL(file.fclose(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ FUZZ_TARGET(autofile)
|
||||
}
|
||||
},
|
||||
[&] {
|
||||
auto_file.fclose();
|
||||
(void)auto_file.fclose();
|
||||
},
|
||||
[&] {
|
||||
ReadFromStream(fuzzed_data_provider, auto_file);
|
||||
@@ -62,5 +62,7 @@ FUZZ_TARGET(autofile)
|
||||
if (f != nullptr) {
|
||||
fclose(f);
|
||||
}
|
||||
} else {
|
||||
(void)auto_file.fclose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,5 +111,6 @@ FUZZ_TARGET(policy_estimator, .init = initialize_policy_estimator)
|
||||
AutoFile fuzzed_auto_file{fuzzed_file_provider.open()};
|
||||
block_policy_estimator.Write(fuzzed_auto_file);
|
||||
block_policy_estimator.Read(fuzzed_auto_file);
|
||||
(void)fuzzed_auto_file.fclose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,4 +32,5 @@ FUZZ_TARGET(policy_estimator_io, .init = initialize_policy_estimator_io)
|
||||
if (block_policy_estimator.Read(fuzzed_auto_file)) {
|
||||
block_policy_estimator.Write(fuzzed_auto_file);
|
||||
}
|
||||
(void)fuzzed_auto_file.fclose();
|
||||
}
|
||||
|
||||
@@ -150,6 +150,7 @@ void utxo_snapshot_fuzz(FuzzBufferType buffer)
|
||||
WriteCompactSize(outfile, 999); // index of coin
|
||||
outfile << Coin{coinbase->vout[0], /*nHeightIn=*/999, /*fCoinBaseIn=*/0};
|
||||
}
|
||||
assert(outfile.fclose() == 0);
|
||||
}
|
||||
|
||||
const auto ActivateFuzzedSnapshot{[&] {
|
||||
|
||||
@@ -39,6 +39,7 @@ BOOST_AUTO_TEST_CASE(xor_file)
|
||||
#endif
|
||||
AutoFile xor_file{raw_file(mode), xor_pat};
|
||||
xor_file << test1 << test2;
|
||||
BOOST_REQUIRE_EQUAL(xor_file.fclose(), 0);
|
||||
}
|
||||
{
|
||||
// Read raw from disk
|
||||
@@ -379,8 +380,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file)
|
||||
// by the rewind window (relative to our farthest read position, 40).
|
||||
BOOST_CHECK(bf.GetPos() <= 30U);
|
||||
|
||||
// We can explicitly close the file, or the destructor will do it.
|
||||
file.fclose();
|
||||
BOOST_REQUIRE_EQUAL(file.fclose(), 0);
|
||||
|
||||
fs::remove(streams_test_filename);
|
||||
}
|
||||
@@ -430,7 +430,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file_skip)
|
||||
bf.SkipTo(13);
|
||||
BOOST_CHECK_EQUAL(bf.GetPos(), 13U);
|
||||
|
||||
file.fclose();
|
||||
BOOST_REQUIRE_EQUAL(file.fclose(), 0);
|
||||
fs::remove(streams_test_filename);
|
||||
}
|
||||
|
||||
@@ -551,6 +551,7 @@ BOOST_AUTO_TEST_CASE(streams_buffered_file_rand)
|
||||
if (maxPos < currentPos)
|
||||
maxPos = currentPos;
|
||||
}
|
||||
BOOST_REQUIRE_EQUAL(file.fclose(), 0);
|
||||
}
|
||||
fs::remove(streams_test_filename);
|
||||
}
|
||||
@@ -566,7 +567,9 @@ BOOST_AUTO_TEST_CASE(buffered_reader_matches_autofile_random_content)
|
||||
|
||||
// Write out the file with random content
|
||||
{
|
||||
AutoFile{test_file.Open(pos, /*read_only=*/false), obfuscation}.write(m_rng.randbytes<std::byte>(file_size));
|
||||
AutoFile f{test_file.Open(pos, /*read_only=*/false), obfuscation};
|
||||
f.write(m_rng.randbytes<std::byte>(file_size));
|
||||
BOOST_REQUIRE_EQUAL(f.fclose(), 0);
|
||||
}
|
||||
BOOST_CHECK_EQUAL(fs::file_size(test_file.FileName(pos)), file_size);
|
||||
|
||||
@@ -623,18 +626,21 @@ BOOST_AUTO_TEST_CASE(buffered_writer_matches_autofile_random_content)
|
||||
AutoFile direct_file{test_direct.Open(pos, /*read_only=*/false), obfuscation};
|
||||
|
||||
AutoFile buffered_file{test_buffered.Open(pos, /*read_only=*/false), obfuscation};
|
||||
BufferedWriter buffered{buffered_file, buf_size};
|
||||
{
|
||||
BufferedWriter buffered{buffered_file, buf_size};
|
||||
|
||||
for (size_t total_written{0}; total_written < file_size;) {
|
||||
const size_t write_size{Assert(std::min(1 + m_rng.randrange(m_rng.randbool() ? buf_size : 2 * buf_size), file_size - total_written))};
|
||||
for (size_t total_written{0}; total_written < file_size;) {
|
||||
const size_t write_size{Assert(std::min(1 + m_rng.randrange(m_rng.randbool() ? buf_size : 2 * buf_size), file_size - total_written))};
|
||||
|
||||
auto current_span = std::span{test_data}.subspan(total_written, write_size);
|
||||
direct_file.write(current_span);
|
||||
buffered.write(current_span);
|
||||
auto current_span = std::span{test_data}.subspan(total_written, write_size);
|
||||
direct_file.write(current_span);
|
||||
buffered.write(current_span);
|
||||
|
||||
total_written += write_size;
|
||||
total_written += write_size;
|
||||
}
|
||||
}
|
||||
// Destructors of AutoFile and BufferedWriter will flush/close here
|
||||
BOOST_REQUIRE_EQUAL(buffered_file.fclose(), 0);
|
||||
BOOST_REQUIRE_EQUAL(direct_file.fclose(), 0);
|
||||
}
|
||||
|
||||
// Compare the resulting files
|
||||
@@ -671,12 +677,14 @@ BOOST_AUTO_TEST_CASE(buffered_writer_reader)
|
||||
const fs::path test_file{m_args.GetDataDirBase() / "test_buffered_write_read.bin"};
|
||||
|
||||
// Write out the values through a precisely sized BufferedWriter
|
||||
AutoFile file{fsbridge::fopen(test_file, "w+b")};
|
||||
{
|
||||
AutoFile file{fsbridge::fopen(test_file, "w+b")};
|
||||
BufferedWriter f(file, sizeof(v1) + sizeof(v2) + sizeof(v3));
|
||||
f << v1 << v2;
|
||||
f.write(std::as_bytes(std::span{&v3, 1}));
|
||||
}
|
||||
BOOST_REQUIRE_EQUAL(file.fclose(), 0);
|
||||
|
||||
// Read back and verify using BufferedReader
|
||||
{
|
||||
uint32_t _v1{0}, _v2{0}, _v3{0};
|
||||
|
||||
@@ -47,8 +47,11 @@ CreateAndActivateUTXOSnapshot(
|
||||
FILE* outfile{fsbridge::fopen(snapshot_path, "wb")};
|
||||
AutoFile auto_outfile{outfile};
|
||||
|
||||
UniValue result = CreateUTXOSnapshot(
|
||||
node, node.chainman->ActiveChainstate(), auto_outfile, snapshot_path, snapshot_path);
|
||||
UniValue result = CreateUTXOSnapshot(node,
|
||||
node.chainman->ActiveChainstate(),
|
||||
std::move(auto_outfile), // Will close auto_outfile.
|
||||
snapshot_path,
|
||||
snapshot_path);
|
||||
LogPrintf(
|
||||
"Wrote UTXO snapshot to %s: %s\n", fs::PathToString(snapshot_path.make_preferred()), result.write());
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ FUZZ_TARGET(wallet_bdb_parser, .init = initialize_wallet_bdb_parser)
|
||||
{
|
||||
AutoFile outfile{fsbridge::fopen(wallet_path, "wb")};
|
||||
outfile << std::span{buffer};
|
||||
assert(outfile.fclose() == 0);
|
||||
}
|
||||
|
||||
const DatabaseOptions options{};
|
||||
|
||||
Reference in New Issue
Block a user