New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make (Read/Write)BinaryFile work with char vector, use AutoFile #29229
Conversation
The following sections might be updated with supplementary metadata relevant to reviewers and maintainers. Code CoverageFor detailed information about the code coverage, see the test coverage report. ReviewsSee the guideline for information on the review process. ConflictsReviewers, this pull request conflicts with the following ones:
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. |
cc @vasild |
634f29f
to
9b7be63
Compare
Added That error doesn't happen on my machine running macOS clang 15, nor on Ubuntu gcc 13.2 - maybe a specific configure warning flag? Update: that didn't work, still not sure how to reproduce.
|
🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the Possibly this is due to a silent merge conflict (the changes in this pull request being Leave a comment here, if you need help tracking down a confusing failure. |
18d18ac
to
257b14c
Compare
I switched to using |
257b14c
to
21618ea
Compare
Had to |
21618ea
to
ac5a83b
Compare
I guess that by "plain text" here you mean More relevant in this case is that |
I haven't looked in detail, but writing bytes to a file can be achieved with one line of code: CAutoFile{...} << Span{data}; |
I like the idea of operating on The keys used in #28983 are not as important as wallet keys, but if we add a generic method to store a |
I think the way |
That would be a nice simplification. Almost (?) to the point of not needing these helper functions. |
WriteBinaryFile is unused right now either way outside of tests, so I guess this could be removed regardless, as future code can just use the in-line one-liner? |
@maflcko But I might still close this PR if all that's needed is one-liner. |
ac5a83b
to
7eb6fa4
Compare
It's not quite a one-liner because you still need to open a close a However, |
(De)serialization of vectors or strings assumes the run-time length to be encoded first. Only arrays and spans assume no length, because it is assumed to be known at compile-time. |
7eb6fa4
to
5e82c45
Compare
🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the Possibly this is due to a silent merge conflict (the changes in this pull request being Leave a comment here, if you need help tracking down a confusing failure. |
Ok, fixed that issue by first getting the file size and then resizing the That doesn't generalise nicely to known-size things like (this is ready for review even without CKey support, which I'll add in a separate commit and/or PR when I get to it) |
5e82c45
to
e4d0e3d
Compare
I tried to find some existing code that could use the So that might be a good reason to kill that variant and only support loading an std::string of unknown size for now. |
src/util/readwritefile.cpp
Outdated
std::FILE *f = fsbridge::fopen(filename, "wb"); | ||
if (f == nullptr) return false; | ||
try { | ||
AutoFile{f} << Span{data}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::FILE *f = fsbridge::fopen(filename, "wb"); | |
if (f == nullptr) return false; | |
try { | |
AutoFile{f} << Span{data}; | |
try { | |
AutoFile{fsbridge::fopen(filename, "wb")} << Span{data}; |
nit: Can be written shorter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Took me a while to wrap my head around the various calls involved, but I guess the nullptr check is handled in read()
, which is called by the various ser_read...
functions in seralize.h
, which is called by the Unserialize
implementations, which is called by <<
. (presumably the same for writing)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the read side I'm keeping the explicit nullptr check for now, so I don't have to catch fs::file_size
failure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At the point WriteBinaryFile
is so small you might as well have the called do the try - catch. But might as well keep if around as long as ReadBinaryFile
can't be made smaller.
ReadBinaryFile and WriteBinaryFile currently work with std::string. This commit adds support for std::vector<unsigned char>>. It uses a template and leverages the fact that both std::string and std::vector<unsigned char>> have an insert() method that can take a char array. Also switch to using AutoFile.
e4d0e3d
to
f9b134e
Compare
size_t file_size = fs::file_size(filename); | ||
output.resize(std::min(file_size, maxsize)); | ||
try { | ||
AutoFile{f} >> Span{output}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I liked more the previous version which called fread(3)
here. It was simple stupid. This >>
is now hard to follow, especially given that it depends on T
. For vector
it ends up calling AutoFile::detail_fread()
. It does not check whether ferror(3)
occurred.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For vector it ends up calling AutoFile::detail_fread(). It does not check whether ferror(3) occurred.
It does. If the return value of detail_fread
is not output.size()
, operator>>
will fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It does not check whether ferror(3) occurred.
It does.
Where? There is no ferror(3)
call. From the man page: "The function fread() does not distinguish between end-of-file and error, and callers must use feof(3) and ferror(3) to determine which occurred."
If the return value of
detail_fread
is notoutput.size()
,operator>>
will fail.
Yes, but it can be equal to the desired size under two conditions: eof, or error. The previous code distinguished between both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the return value of
detail_fread
is notoutput.size()
,operator>>
will fail.Yes, but it can be equal to the desired size under two conditions: eof, or error.
No, the eof-error would only be raised if read past the desired size, not to it. Unless I am missing something?
I am asking, because if there was a bug, it should be fixed, or at least an issue should be filed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That "must" in the man page is pretty clear: "callers must use feof(3) and ferror(3) to determine which occurred". A ferror(3)
check can't hurt and it is better to have an extra check that always returns "no error" than a missing check, failing to detect an IO error. The previous code was doing that - a dumb fread(3)
followed by an unconditional ferror(3)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is called to determine which error occurred, see
Line 27 in e69796c
throw std::ios_base::failure(feof() ? "AutoFile::read: end of file" : "AutoFile::read: fread failed"); |
Again, if there is a bug in the current code in master, it should be fixed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would extend the check to:
void AutoFile::read(Span<std::byte> dst)
{
- if (detail_fread(dst) != dst.size()) {
+ if (detail_fread(dst) != dst.size() || std::ferror(m_file)) {
throw std::ios_base::failure(feof() ? "AutoFile::read: end of file" : "AutoFile::read: fread failed");
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if there is a bug in the current code in master, it should be fixed.
Done in #29307
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Glad something useful came out of this PR :-)
} | ||
if (fclose(f) != 0) { | ||
try { | ||
AutoFile{fsbridge::fopen(filename, "wb")} << Span{data}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, the dumb version was easier to follow. The new one does not check whether fclose(3)
failed, but it should. I think that is a serious deficiency in AutoFile
itself.
fwrite(3)
may succeed, but if a subsequent fclose(3)
fails we should consider the data did not make it safely to disk and that the file is corrupted (fclose(3)
writes any buffered data to disk using fflush(3)
, so a failure at fclose(3)
is as bad as failure at fwrite(3)
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that is a serious deficiency in
AutoFile
itself
Logged as #29307
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vasild I would also prefer to fix things in AutoFile, but perhaps add a few comments to explain what happens under the hood?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could happen:
- fwrite() succeeds, returns ok the caller, some of the data is buffered in the OS and not yet on disk
- fclose() is called, it tries to fflush() the buffered data to disk but fails due to IO error. The caller ignores the error returned by fclose()
- the program continues with the wrong assumption that the data is safely on disk
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's not good. I guess we also don't want to sync to disk, and block for that to complete, for every field that's >>
'd to a file though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right. That's why I don't see a good solution. It is a design issue with AutoFile
to flush/close from the destructor which can't signal the failure to the caller.
The PR didn't seem to attract much attention in the past. Also, the issue seems not important enough right now to keep it sitting around idle in the list of open PRs. Closing due to lack of interest. |
ReadBinaryFile and WriteBinaryFile current work with
std::string
. This PR adds support forstd::vector<unsigned char>>
.It also uses
AutoFile
now.This is [update: probably not] used in #28983 to store the static key for the Template Provider, in a manner very similar to how we store the Tor v3 and i2p key. However it made no sense to me to store a
CKey
as plain text. See commit "Persist static key for Template Provider" for how it's used.It uses a template and leverages the fact that both
std::string
andstd::vector<unsigned char>>
have aninsert()
method that can take a char array.The
unsigned char
support is not used in this PR, but tests do cover it.