mirror of
https://github.com/Retropex/bitcoin.git
synced 2025-06-03 07:52:33 +02:00
util: explicitly close all AutoFiles that have been written
There is no way to report a close error from `AutoFile` destructor. Such an error could be serious if the file has been written to because it may mean the file is now corrupted (same as if write fails). So, change all users of `AutoFile` that use it to write data to explicitly close the file and handle a possible error. Github-Pull: #29307 Rebased-From: 11be9f4103ea219f801a1f0fe1385f66ca70ad22
This commit is contained in:
parent
55bd5d8015
commit
d2ea6ab60b
@ -26,6 +26,7 @@
|
||||
#include <univalue.h>
|
||||
#include <util/fs.h>
|
||||
#include <util/fs_helpers.h>
|
||||
#include <util/syserror.h>
|
||||
#include <util/translation.h>
|
||||
|
||||
namespace {
|
||||
@ -62,23 +63,26 @@ 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);
|
||||
return error("%s: Failed to open file %s", __func__, fs::PathToString(pathTmp));
|
||||
}
|
||||
|
||||
// Serialize
|
||||
if (!SerializeDB(fileout, data)) {
|
||||
fileout.fclose();
|
||||
(void)fileout.fclose();
|
||||
remove(pathTmp);
|
||||
return false;
|
||||
}
|
||||
if (!FileCommit(fileout.Get())) {
|
||||
fileout.fclose();
|
||||
(void)fileout.fclose();
|
||||
remove(pathTmp);
|
||||
return error("%s: Failed to flush file %s", __func__, fs::PathToString(pathTmp));
|
||||
}
|
||||
fileout.fclose();
|
||||
if (fileout.fclose() != 0) {
|
||||
remove(pathTmp);
|
||||
LogError("%s: Failed to close file %s: %s\n", __func__, fs::PathToString(pathTmp), SysErrorString(errno));
|
||||
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();
|
||||
(void)file.fclose();
|
||||
fs::remove("streams_tmp");
|
||||
}
|
||||
|
||||
|
@ -139,7 +139,7 @@ bool BlockFilterIndex::CustomCommit(CDBBatch& batch)
|
||||
if (file.IsNull()) {
|
||||
return error("%s: Failed to open filter file %d", __func__, pos.nFile);
|
||||
}
|
||||
if (!FileCommit(file.Get())) {
|
||||
if (!FileCommit(file.Get()) || file.fclose() != 0) {
|
||||
return error("%s: Failed to commit filter file %d", __func__, pos.nFile);
|
||||
}
|
||||
|
||||
@ -188,7 +188,7 @@ size_t BlockFilterIndex::WriteFilterToDisk(FlatFilePos& pos, const BlockFilter&
|
||||
LogPrintf("%s: Failed to truncate filter file %d\n", __func__, pos.nFile);
|
||||
return 0;
|
||||
}
|
||||
if (!FileCommit(last_file.Get())) {
|
||||
if (!FileCommit(last_file.Get()) || last_file.fclose() != 0) {
|
||||
LogPrintf("%s: Failed to commit filter file %d\n", __func__, pos.nFile);
|
||||
return 0;
|
||||
}
|
||||
@ -212,6 +212,12 @@ size_t BlockFilterIndex::WriteFilterToDisk(FlatFilePos& pos, const BlockFilter&
|
||||
}
|
||||
|
||||
fileout << filter.GetBlockHash() << filter.GetEncodedFilter();
|
||||
|
||||
if (fileout.fclose() != 0) {
|
||||
LogPrintf("%s: Failed to close filter file %d\n", __func__, pos.nFile);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return data_size;
|
||||
}
|
||||
|
||||
|
@ -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,7 +201,10 @@ bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mock
|
||||
|
||||
if (!skip_file_commit && !FileCommit(file.Get()))
|
||||
throw std::runtime_error("FileCommit 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");
|
||||
}
|
||||
@ -210,6 +215,7 @@ bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mock
|
||||
Ticks<SecondsDouble>(last - mid));
|
||||
} catch (const std::exception& e) {
|
||||
LogPrintf("Failed to dump mempool: %s. Continuing anyway.\n", e.what());
|
||||
(void)file.fclose();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -3890,6 +3890,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 is likely incomplete", fs::PathToString(path)));
|
||||
}
|
||||
}
|
||||
|
||||
std::function<void(const CAddress& addr,
|
||||
|
@ -692,6 +692,11 @@ bool BlockManager::UndoWriteToDisk(const CBlockUndo& blockundo, FlatFilePos& pos
|
||||
hasher << blockundo;
|
||||
fileout << hasher.GetHash();
|
||||
|
||||
if (fileout.fclose() != 0) {
|
||||
LogError("%s: fclose failed\n", __func__);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -980,6 +985,11 @@ bool BlockManager::WriteBlockToDisk(const CBlock& block, FlatFilePos& pos) const
|
||||
pos.nPos = (unsigned int)fileOutPos;
|
||||
fileout << TX_WITH_WITNESS(block);
|
||||
|
||||
if (fileout.fclose() != 0) {
|
||||
LogError("WriteBlockToDisk: fclose failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -950,7 +950,7 @@ void CBlockPolicyEstimator::Flush() {
|
||||
void CBlockPolicyEstimator::FlushFeeEstimates()
|
||||
{
|
||||
AutoFile est_file{fsbridge::fopen(m_estimation_filepath, "wb")};
|
||||
if (est_file.IsNull() || !Write(est_file)) {
|
||||
if (est_file.IsNull() || !Write(est_file) || est_file.fclose() != 0) {
|
||||
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()));
|
||||
|
@ -42,6 +42,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>
|
||||
@ -2683,7 +2684,10 @@ UniValue CreateUTXOSnapshot(
|
||||
pcursor->Next();
|
||||
}
|
||||
|
||||
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", maybe_stats->coins_count);
|
||||
|
@ -63,4 +63,5 @@ void AutoFile::write(Span<const std::byte> src)
|
||||
current_pos += buf_now.size();
|
||||
}
|
||||
}
|
||||
m_was_written = true;
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
#include <serialize.h>
|
||||
#include <span.h>
|
||||
#include <support/allocators/zeroafterfree.h>
|
||||
#include <util/check.h>
|
||||
#include <util/overflow.h>
|
||||
|
||||
#include <algorithm>
|
||||
@ -383,18 +384,37 @@ 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
|
||||
{
|
||||
protected:
|
||||
std::FILE* m_file;
|
||||
std::vector<std::byte> m_xor;
|
||||
bool m_was_written{false};
|
||||
|
||||
public:
|
||||
explicit AutoFile(std::FILE* file, std::vector<std::byte> data_xor={}) : m_file{file}, m_xor{std::move(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 from the destructor we have no way to signal
|
||||
// error due to close which, after write, could mean the file is
|
||||
// corrupted and must be handled properly at the call site.
|
||||
Assume(IsNull());
|
||||
}
|
||||
|
||||
(void)fclose();
|
||||
}
|
||||
|
||||
// Disallow copies
|
||||
AutoFile(const AutoFile&) = delete;
|
||||
@ -402,7 +422,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);
|
||||
@ -63,5 +63,10 @@ FUZZ_TARGET(autofile)
|
||||
if (f != nullptr) {
|
||||
fclose(f);
|
||||
}
|
||||
} else {
|
||||
// FuzzedFileProvider::close() is expected to fail sometimes. Don't let
|
||||
// the destructor of AutoFile be upset by a failing fclose(). Close it
|
||||
// explicitly (and ignore any errors) so that the destructor is a noop.
|
||||
(void)auto_file.fclose();
|
||||
}
|
||||
}
|
||||
|
@ -95,5 +95,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();
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ FUZZ_TARGET(utxo_snapshot, .init = initialize_chain)
|
||||
AutoFile outfile{fsbridge::fopen(snapshot_path, "wb")};
|
||||
const auto file_data{ConsumeRandomLengthByteVector(fuzzed_data_provider)};
|
||||
outfile << Span{file_data};
|
||||
assert(outfile.fclose() == 0);
|
||||
}
|
||||
|
||||
const auto ActivateFuzzedSnapshot{[&] {
|
||||
|
@ -38,6 +38,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
|
||||
@ -378,8 +379,8 @@ 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();
|
||||
// Explicitly close the file and check that the close succeeds.
|
||||
BOOST_REQUIRE_EQUAL(file.fclose(), 0);
|
||||
|
||||
fs::remove(streams_test_filename);
|
||||
}
|
||||
@ -429,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);
|
||||
}
|
||||
|
||||
@ -550,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);
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ CreateAndActivateUTXOSnapshot(
|
||||
AutoFile auto_outfile{outfile};
|
||||
|
||||
UniValue result = CreateUTXOSnapshot(
|
||||
node, node.chainman->ActiveChainstate(), auto_outfile, snapshot_path, snapshot_path);
|
||||
node, node.chainman->ActiveChainstate(), auto_outfile, snapshot_path, snapshot_path); // Will close auto_outfile.
|
||||
LogPrintf(
|
||||
"Wrote UTXO snapshot to %s: %s\n", fs::PathToString(snapshot_path.make_preferred()), result.write());
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user