Skip to content

Commit 4bb5dd7

Browse files
committed
util: check that a file has been closed before ~AutoFile() is called
If an `AutoFile` has been written to, then expect callers to have closed it explicitly via the `AutoFile::fclose()` method. This is because if the destructor calls `std::fclose()` and encounters an error, then it is too late to indicate this to the caller in a meaningful way.
1 parent 8bb34f0 commit 4bb5dd7

File tree

2 files changed

+32
-3
lines changed

2 files changed

+32
-3
lines changed

src/streams.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ void AutoFile::write(std::span<const std::byte> src)
8585
if (std::fwrite(src.data(), 1, src.size(), m_file) != src.size()) {
8686
throw std::ios_base::failure("AutoFile::write: write failed");
8787
}
88+
m_was_written = true;
8889
if (m_position.has_value()) *m_position += src.size();
8990
} else {
9091
std::array<std::byte, 4096> buf;
@@ -107,6 +108,7 @@ void AutoFile::write_buffer(std::span<std::byte> src)
107108
if (std::fwrite(src.data(), 1, src.size(), m_file) != src.size()) {
108109
throw std::ios_base::failure("AutoFile::write_buffer: write failed");
109110
}
111+
m_was_written = true;
110112
if (m_position) *m_position += src.size();
111113
}
112114

@@ -117,6 +119,7 @@ bool AutoFile::Commit()
117119

118120
bool AutoFile::Truncate(unsigned size)
119121
{
122+
m_was_written = true;
120123
return ::TruncateFile(m_file, size);
121124
}
122125

src/streams.h

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@
66
#ifndef BITCOIN_STREAMS_H
77
#define BITCOIN_STREAMS_H
88

9+
#include <logging.h>
910
#include <serialize.h>
1011
#include <span.h>
1112
#include <support/allocators/zeroafterfree.h>
13+
#include <util/check.h>
1214
#include <util/overflow.h>
15+
#include <util/syserror.h>
1316

1417
#include <algorithm>
1518
#include <cassert>
@@ -386,27 +389,50 @@ class BitStreamWriter
386389
*
387390
* Will automatically close the file when it goes out of scope if not null.
388391
* If you're returning the file pointer, return file.release().
389-
* If you need to close the file early, use file.fclose() instead of fclose(file).
392+
* If you need to close the file early, use autofile.fclose() instead of fclose(underlying_FILE).
393+
*
394+
* @note If the file has been written to, then the caller must close it
395+
* explicitly with the `fclose()` method, check if it returns an error and treat
396+
* such an error as if the `write()` method failed. The OS's `fclose(3)` may
397+
* fail to flush to disk data that has been previously written, rendering the
398+
* file corrupt.
390399
*/
391400
class AutoFile
392401
{
393402
protected:
394403
std::FILE* m_file;
395404
std::vector<std::byte> m_xor;
396405
std::optional<int64_t> m_position;
406+
bool m_was_written{false};
397407

398408
public:
399409
explicit AutoFile(std::FILE* file, std::vector<std::byte> data_xor={});
400410

401-
~AutoFile() { fclose(); }
411+
~AutoFile()
412+
{
413+
if (m_was_written) {
414+
// Callers that wrote to the file must have closed it explicitly
415+
// with the fclose() method and checked that the close succeeded.
416+
// This is because here in the destructor we have no way to signal
417+
// errors from fclose() which, after write, could mean the file is
418+
// corrupted and must be handled properly at the call site.
419+
// Destructors in C++ cannot signal an error to the callers because
420+
// they do not return a value and are not allowed to throw exceptions.
421+
Assume(IsNull());
422+
}
423+
424+
if (fclose() != 0) {
425+
LogError("Failed to close file: %s", SysErrorString(errno));
426+
}
427+
}
402428

403429
// Disallow copies
404430
AutoFile(const AutoFile&) = delete;
405431
AutoFile& operator=(const AutoFile&) = delete;
406432

407433
bool feof() const { return std::feof(m_file); }
408434

409-
int fclose()
435+
[[nodiscard]] int fclose()
410436
{
411437
if (auto rel{release()}) return std::fclose(rel);
412438
return 0;

0 commit comments

Comments
 (0)