Skip to content

Conversation

@hebasto
Copy link
Member

@hebasto hebasto commented Aug 9, 2025

On the master branch, the fallback variant of AllocateFileRange, introduced in #1677, overwrites the file's content. This causes issues on some systems during some "reindex" scenarios. Additionally, the recently introduced feature_reindex_init.py test is also broken on these systems.

The affected systems include: OpenBSD, NetBSD, OmniOS, OpenIndiana.

This PR avoids such overwriting.

Fixes #33128 and feature_reindex_init.py test on affected systems.

@DrahtBot
Copy link
Contributor

DrahtBot commented Aug 9, 2025

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/33164.

Reviews

See the guideline for information on the review process.
A summary of reviews will appear here.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #33228 (util: Address various issues in AllocateFileRange by luke-jr)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

Copy link
Member

@luke-jr luke-jr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description for AllocateFileRange says "the range specified in the arguments will never contain live data", so I'm not sure this is the right approach.

if (0 == posix_fallocate(fileno(file), 0, nEndPos)) return;
#endif
// Fallback version
// TODO: just write one byte per block
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why lose this?

if (fseek(file, 0, SEEK_END)) {
return;
}
const int64_t filesize = std::ftell(file);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks safe to mix std::ftell with C file i/o functions, but is there any reason to do so?

return;
}
while (length > 0) {
unsigned int inc_size = offset + length - static_cast<unsigned int>(filesize);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably better to use size_t or at least long

return;
}
while (length > 0) {
unsigned int inc_size = offset + length - static_cast<unsigned int>(filesize);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change of behaviour: This can allocate before offset if the file is smaller. If that's desired behaviour, there's no point to the offset parameter at all...?

Comment on lines +220 to +222
if (fseek(file, filesize, SEEK_SET)) {
return;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Redundant?

@cedwies
Copy link

cedwies commented Aug 19, 2025

Clarification: The doc for AllocateFileRange says: “the range specified … will never contain live data.” But in reality some call paths (during reindex on a few OSes) did pass ranges that overlapped live bytes? The old fallback then zero‑wrote inside the file and corrupted data. This PR makes the fallback defensive by appending after EOF only, so even if a caller violates the promise, we still don’t overwrite (?).

But what about the guarantee from the doc of AllocateFileRange? Should we dismiss this guarantee? Maybe we should clarify which assumptions we can make e.g. filesize < offset (only appending at the end) or offset + length > filesize (no shrinking because if I am not mistaken SetEndOfFile / ftruncate will then shrink the file, potentially erasing live data). So we should guard against that?

// TODO: just write one byte per block
static const char buf[65536] = {};
if (fseek(file, offset, SEEK_SET)) {
if (fseek(file, 0, SEEK_END)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, in strict ISO C that's UB. My understanding is that in our case this code only runs on POSIX systems (not Win/macOS), where fseek(..., SEEK_END) is well-defined for regular files. Or am I missing an issue here?

Copy link

@winterrdog winterrdog Sep 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cedwies @luke-jr
since this part of the code's gonna be run on POSIX systems only, i think trying out the lseek() system call can be of help. it is reliable on large files, sparse file aware and portable across Unix systems since it is a POSIX-standard syscall.

for instance something like this:

int fd = fileno(file);
if (fd == -1) return;

off_t file_size = lseek(fd, 0, SEEK_END);
if (file_size == static_cast<off_t>-1) return;

what do you think of this?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be a bit hesitant about mixing lseek with FILE*, since stdio has its own buffering. From what I read, a safer option might be to stick with the stdio layer (fseeko/ftello) or call fstat on the fd after flushing. Even though I expect mixing the two layers to work most of the time, it can be risky because stdio may have buffered data or be tracking the file position separately from the kernel. I think it's safer to either stick to stdio or _fd-only.

Copy link

@winterrdog winterrdog Oct 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for this context, it will be invaluable as I work on developing a solution for this issue. (#33128)

i will reach out to the relevant authors of the 3 PRs that try to resolve this issue and collaborate with them, rather than submitting an additional PR for review at this stage.

then i will return with a more actionable and concrete proposal rather than a simple suggestion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OpenBSD, NetBSD: -reindex is broken

5 participants