Skip to content

Commit ea42857

Browse files
committed
Merge bitcoin/bitcoin#29307: util: explicitly close all AutoFiles that have been written
c10e382 flatfile: check whether the file has been closed successfully (Vasil Dimov) 4bb5dd7 util: check that a file has been closed before ~AutoFile() is called (Vasil Dimov) 8bb34f0 Explicitly close all AutoFiles that have been written (Vasil Dimov) a69c409 rpc: 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 bitcoin/bitcoin#29307 (comment). 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: ACK c10e382 achow101: ACK c10e382 hodlinator: re-ACK c10e382 Tree-SHA512: 3994ca57e5b2b649fc84f24dad144173b7500fc0e914e06291d5c32fbbf8d2b1f8eae0040abd7a5f16095ddf4e11fe1636c6092f49058cda34f3eb2ee536d7ba
2 parents 51ccc71 + c10e382 commit ea42857

20 files changed

+195
-54
lines changed

src/addrdb.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <univalue.h>
2525
#include <util/fs.h>
2626
#include <util/fs_helpers.h>
27+
#include <util/syserror.h>
2728
#include <util/translation.h>
2829

2930
namespace {
@@ -61,25 +62,29 @@ bool SerializeFileDB(const std::string& prefix, const fs::path& path, const Data
6162
FILE *file = fsbridge::fopen(pathTmp, "wb");
6263
AutoFile fileout{file};
6364
if (fileout.IsNull()) {
64-
fileout.fclose();
6565
remove(pathTmp);
6666
LogError("%s: Failed to open file %s\n", __func__, fs::PathToString(pathTmp));
6767
return false;
6868
}
6969

7070
// Serialize
7171
if (!SerializeDB(fileout, data)) {
72-
fileout.fclose();
72+
(void)fileout.fclose();
7373
remove(pathTmp);
7474
return false;
7575
}
7676
if (!fileout.Commit()) {
77-
fileout.fclose();
77+
(void)fileout.fclose();
7878
remove(pathTmp);
7979
LogError("%s: Failed to flush file %s\n", __func__, fs::PathToString(pathTmp));
8080
return false;
8181
}
82-
fileout.fclose();
82+
if (fileout.fclose() != 0) {
83+
const int errno_save{errno};
84+
remove(pathTmp);
85+
LogError("Failed to close file %s after commit: %s", fs::PathToString(pathTmp), SysErrorString(errno_save));
86+
return false;
87+
}
8388

8489
// replace existing file, if any, with new file
8590
if (!RenameOver(pathTmp, path)) {

src/bench/streams_findbyte.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ static void FindByte(benchmark::Bench& bench)
2828
});
2929

3030
// Cleanup
31-
file.fclose();
31+
assert(file.fclose() == 0);
3232
fs::remove("streams_tmp");
3333
}
3434

src/flatfile.cpp

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ FILE* FlatFileSeq::Open(const FlatFilePos& pos, bool read_only) const
4646
}
4747
if (pos.nPos && fseek(file, pos.nPos, SEEK_SET)) {
4848
LogPrintf("Unable to seek to position %u of %s\n", pos.nPos, fs::PathToString(path));
49-
fclose(file);
49+
if (fclose(file) != 0) {
50+
LogError("Unable to close file %s", fs::PathToString(path));
51+
}
5052
return nullptr;
5153
}
5254
return file;
@@ -68,7 +70,10 @@ size_t FlatFileSeq::Allocate(const FlatFilePos& pos, size_t add_size, bool& out_
6870
if (file) {
6971
LogDebug(BCLog::VALIDATION, "Pre-allocating up to position 0x%x in %s%05u.dat\n", new_size, m_prefix, pos.nFile);
7072
AllocateFileRange(file, pos.nPos, inc_size);
71-
fclose(file);
73+
if (fclose(file) != 0) {
74+
LogError("Cannot close file %s%05u.dat after extending it with %u bytes", m_prefix, pos.nFile, new_size);
75+
return 0;
76+
}
7277
return inc_size;
7378
}
7479
} else {
@@ -86,17 +91,24 @@ bool FlatFileSeq::Flush(const FlatFilePos& pos, bool finalize) const
8691
return false;
8792
}
8893
if (finalize && !TruncateFile(file, pos.nPos)) {
89-
fclose(file);
9094
LogError("%s: failed to truncate file %d\n", __func__, pos.nFile);
95+
if (fclose(file) != 0) {
96+
LogError("Failed to close file %d", pos.nFile);
97+
}
9198
return false;
9299
}
93100
if (!FileCommit(file)) {
94-
fclose(file);
95101
LogError("%s: failed to commit file %d\n", __func__, pos.nFile);
102+
if (fclose(file) != 0) {
103+
LogError("Failed to close file %d", pos.nFile);
104+
}
96105
return false;
97106
}
98107
DirectoryCommit(m_dir);
99108

100-
fclose(file);
109+
if (fclose(file) != 0) {
110+
LogError("Failed to close file %d after flush", pos.nFile);
111+
return false;
112+
}
101113
return true;
102114
}

src/index/blockfilterindex.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include <node/blockstorage.h>
1414
#include <undo.h>
1515
#include <util/fs_helpers.h>
16+
#include <util/syserror.h>
1617

1718
/* The index database stores three items for each block: the disk location of the encoded filter,
1819
* 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)
159160
}
160161
if (!file.Commit()) {
161162
LogError("%s: Failed to commit filter file %d\n", __func__, pos.nFile);
163+
(void)file.fclose();
164+
return false;
165+
}
166+
if (file.fclose() != 0) {
167+
LogError("Failed to close filter file %d after commit: %s", pos.nFile, SysErrorString(errno));
162168
return false;
163169
}
164170

@@ -213,6 +219,11 @@ size_t BlockFilterIndex::WriteFilterToDisk(FlatFilePos& pos, const BlockFilter&
213219
}
214220
if (!last_file.Commit()) {
215221
LogPrintf("%s: Failed to commit filter file %d\n", __func__, pos.nFile);
222+
(void)last_file.fclose();
223+
return 0;
224+
}
225+
if (last_file.fclose() != 0) {
226+
LogError("Failed to close filter file %d after commit: %s", pos.nFile, SysErrorString(errno));
216227
return 0;
217228
}
218229

@@ -235,6 +246,12 @@ size_t BlockFilterIndex::WriteFilterToDisk(FlatFilePos& pos, const BlockFilter&
235246
}
236247

237248
fileout << filter.GetBlockHash() << filter.GetEncodedFilter();
249+
250+
if (fileout.fclose() != 0) {
251+
LogError("Failed to close filter file %d: %s", pos.nFile, SysErrorString(errno));
252+
return 0;
253+
}
254+
238255
return data_size;
239256
}
240257

src/net.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4005,6 +4005,11 @@ static void CaptureMessageToFile(const CAddress& addr,
40054005
uint32_t size = data.size();
40064006
ser_writedata32(f, size);
40074007
f << data;
4008+
4009+
if (f.fclose() != 0) {
4010+
throw std::ios_base::failure(
4011+
strprintf("Error closing %s after write, file contents are likely incomplete", fs::PathToString(path)));
4012+
}
40084013
}
40094014

40104015
std::function<void(const CAddress& addr,

src/node/blockstorage.cpp

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <util/fs.h>
3434
#include <util/signalinterrupt.h>
3535
#include <util/strencodings.h>
36+
#include <util/syserror.h>
3637
#include <util/translation.h>
3738
#include <validation.h>
3839

@@ -941,13 +942,13 @@ bool BlockManager::WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationSt
941942
return false;
942943
}
943944

945+
// Open history file to append
946+
AutoFile file{OpenUndoFile(pos)};
947+
if (file.IsNull()) {
948+
LogError("OpenUndoFile failed for %s while writing block undo", pos.ToString());
949+
return FatalError(m_opts.notifications, state, _("Failed to write undo data."));
950+
}
944951
{
945-
// Open history file to append
946-
AutoFile file{OpenUndoFile(pos)};
947-
if (file.IsNull()) {
948-
LogError("OpenUndoFile failed for %s while writing block undo", pos.ToString());
949-
return FatalError(m_opts.notifications, state, _("Failed to write undo data."));
950-
}
951952
BufferedWriter fileout{file};
952953

953954
// Write index header
@@ -960,8 +961,13 @@ bool BlockManager::WriteBlockUndo(const CBlockUndo& blockundo, BlockValidationSt
960961
// Write undo data & checksum
961962
fileout << blockundo << hasher.GetHash();
962963
}
964+
// BufferedWriter will flush pending data to file when fileout goes out of scope.
965+
}
963966

964-
fileout.flush(); // Make sure `AutoFile`/`BufferedWriter` go out of scope before we call `FlushUndoFile`
967+
// Make sure that the file is closed before we call `FlushUndoFile`.
968+
if (file.fclose() != 0) {
969+
LogError("Failed to close block undo file %s: %s", pos.ToString(), SysErrorString(errno));
970+
return FatalError(m_opts.notifications, state, _("Failed to close block undo file."));
965971
}
966972

967973
// 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)
10941100
m_opts.notifications.fatalError(_("Failed to write block."));
10951101
return FlatFilePos();
10961102
}
1097-
BufferedWriter fileout{file};
1103+
{
1104+
BufferedWriter fileout{file};
1105+
1106+
// Write index header
1107+
fileout << GetParams().MessageStart() << block_size;
1108+
pos.nPos += STORAGE_HEADER_BYTES;
1109+
// Write block
1110+
fileout << TX_WITH_WITNESS(block);
1111+
}
1112+
1113+
if (file.fclose() != 0) {
1114+
LogError("Failed to close block file %s: %s", pos.ToString(), SysErrorString(errno));
1115+
m_opts.notifications.fatalError(_("Failed to close file when writing block."));
1116+
return FlatFilePos();
1117+
}
10981118

1099-
// Write index header
1100-
fileout << GetParams().MessageStart() << block_size;
1101-
pos.nPos += STORAGE_HEADER_BYTES;
1102-
// Write block
1103-
fileout << TX_WITH_WITNESS(block);
11041119
return pos;
11051120
}
11061121

@@ -1143,6 +1158,11 @@ static auto InitBlocksdirXorKey(const BlockManager::Options& opts)
11431158
#endif
11441159
)};
11451160
xor_key_file << xor_key;
1161+
if (xor_key_file.fclose() != 0) {
1162+
throw std::runtime_error{strprintf("Error closing XOR key file %s: %s",
1163+
fs::PathToString(xor_key_path),
1164+
SysErrorString(errno))};
1165+
}
11461166
}
11471167
// If the user disabled the key, it must be zero.
11481168
if (!opts.use_xor && xor_key != decltype(xor_key){}) {

src/node/mempool_persist.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <util/fs.h>
1818
#include <util/fs_helpers.h>
1919
#include <util/signalinterrupt.h>
20+
#include <util/syserror.h>
2021
#include <util/time.h>
2122
#include <validation.h>
2223

@@ -168,7 +169,8 @@ bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mock
168169

169170
auto mid = SteadyClock::now();
170171

171-
AutoFile file{mockable_fopen_function(dump_path + ".new", "wb")};
172+
const fs::path file_fspath{dump_path + ".new"};
173+
AutoFile file{mockable_fopen_function(file_fspath, "wb")};
172174
if (file.IsNull()) {
173175
return false;
174176
}
@@ -199,9 +201,14 @@ bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mock
199201
LogInfo("Writing %d unbroadcast transactions to file.\n", unbroadcast_txids.size());
200202
file << unbroadcast_txids;
201203

202-
if (!skip_file_commit && !file.Commit())
204+
if (!skip_file_commit && !file.Commit()) {
205+
(void)file.fclose();
203206
throw std::runtime_error("Commit failed");
204-
file.fclose();
207+
}
208+
if (file.fclose() != 0) {
209+
throw std::runtime_error(
210+
strprintf("Error closing %s: %s", fs::PathToString(file_fspath), SysErrorString(errno)));
211+
}
205212
if (!RenameOver(dump_path + ".new", dump_path)) {
206213
throw std::runtime_error("Rename failed");
207214
}
@@ -213,6 +220,7 @@ bool DumpMempool(const CTxMemPool& pool, const fs::path& dump_path, FopenFn mock
213220
fs::file_size(dump_path));
214221
} catch (const std::exception& e) {
215222
LogInfo("Failed to dump mempool: %s. Continuing anyway.\n", e.what());
223+
(void)file.fclose();
216224
return false;
217225
}
218226
return true;

src/policy/fees.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include <uint256.h>
2020
#include <util/fs.h>
2121
#include <util/serfloat.h>
22+
#include <util/syserror.h>
2223
#include <util/time.h>
2324

2425
#include <algorithm>
@@ -963,9 +964,14 @@ void CBlockPolicyEstimator::FlushFeeEstimates()
963964
AutoFile est_file{fsbridge::fopen(m_estimation_filepath, "wb")};
964965
if (est_file.IsNull() || !Write(est_file)) {
965966
LogPrintf("Failed to write fee estimates to %s. Continue anyway.\n", fs::PathToString(m_estimation_filepath));
966-
} else {
967-
LogPrintf("Flushed fee estimates to %s.\n", fs::PathToString(m_estimation_filepath.filename()));
967+
(void)est_file.fclose();
968+
return;
969+
}
970+
if (est_file.fclose() != 0) {
971+
LogError("Failed to close fee estimates file %s: %s. Continuing anyway.", fs::PathToString(m_estimation_filepath), SysErrorString(errno));
972+
return;
968973
}
974+
LogPrintf("Flushed fee estimates to %s.\n", fs::PathToString(m_estimation_filepath.filename()));
969975
}
970976

971977
bool CBlockPolicyEstimator::Write(AutoFile& fileout) const

src/rpc/blockchain.cpp

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
#include <util/check.h>
4848
#include <util/fs.h>
4949
#include <util/strencodings.h>
50+
#include <util/syserror.h>
5051
#include <util/translation.h>
5152
#include <validation.h>
5253
#include <validationinterface.h>
@@ -82,7 +83,7 @@ UniValue WriteUTXOSnapshot(
8283
CCoinsViewCursor* pcursor,
8384
CCoinsStats* maybe_stats,
8485
const CBlockIndex* tip,
85-
AutoFile& afile,
86+
AutoFile&& afile,
8687
const fs::path& path,
8788
const fs::path& temppath,
8889
const std::function<void()>& interruption_point = {});
@@ -3114,7 +3115,14 @@ static RPCHelpMan dumptxoutset()
31143115
}
31153116
}
31163117

3117-
UniValue result = WriteUTXOSnapshot(*chainstate, cursor.get(), &stats, tip, afile, path, temppath, node.rpc_interruption_point);
3118+
UniValue result = WriteUTXOSnapshot(*chainstate,
3119+
cursor.get(),
3120+
&stats,
3121+
tip,
3122+
std::move(afile),
3123+
path,
3124+
temppath,
3125+
node.rpc_interruption_point);
31183126
fs::rename(temppath, path);
31193127

31203128
result.pushKV("path", path.utf8string());
@@ -3166,7 +3174,7 @@ UniValue WriteUTXOSnapshot(
31663174
CCoinsViewCursor* pcursor,
31673175
CCoinsStats* maybe_stats,
31683176
const CBlockIndex* tip,
3169-
AutoFile& afile,
3177+
AutoFile&& afile,
31703178
const fs::path& path,
31713179
const fs::path& temppath,
31723180
const std::function<void()>& interruption_point)
@@ -3225,7 +3233,10 @@ UniValue WriteUTXOSnapshot(
32253233

32263234
CHECK_NONFATAL(written_coins_count == maybe_stats->coins_count);
32273235

3228-
afile.fclose();
3236+
if (afile.fclose() != 0) {
3237+
throw std::ios_base::failure(
3238+
strprintf("Error closing %s: %s", fs::PathToString(temppath), SysErrorString(errno)));
3239+
}
32293240

32303241
UniValue result(UniValue::VOBJ);
32313242
result.pushKV("coins_written", written_coins_count);
@@ -3240,12 +3251,19 @@ UniValue WriteUTXOSnapshot(
32403251
UniValue CreateUTXOSnapshot(
32413252
node::NodeContext& node,
32423253
Chainstate& chainstate,
3243-
AutoFile& afile,
3254+
AutoFile&& afile,
32443255
const fs::path& path,
32453256
const fs::path& tmppath)
32463257
{
32473258
auto [cursor, stats, tip]{WITH_LOCK(::cs_main, return PrepareUTXOSnapshot(chainstate, node.rpc_interruption_point))};
3248-
return WriteUTXOSnapshot(chainstate, cursor.get(), &stats, tip, afile, path, tmppath, node.rpc_interruption_point);
3259+
return WriteUTXOSnapshot(chainstate,
3260+
cursor.get(),
3261+
&stats,
3262+
tip,
3263+
std::move(afile),
3264+
path,
3265+
tmppath,
3266+
node.rpc_interruption_point);
32493267
}
32503268

32513269
static RPCHelpMan loadtxoutset()

src/rpc/blockchain.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ void CalculatePercentilesByWeight(CAmount result[NUM_GETBLOCKSTATS_PERCENTILES],
5151
UniValue CreateUTXOSnapshot(
5252
node::NodeContext& node,
5353
Chainstate& chainstate,
54-
AutoFile& afile,
54+
AutoFile&& afile,
5555
const fs::path& path,
5656
const fs::path& tmppath);
5757

0 commit comments

Comments
 (0)