Skip to content

Commit b934e65

Browse files
committed
io/BufferedReader: new class, replacing fopen()/fgets()
1 parent ccb54c0 commit b934e65

16 files changed

+942
-27
lines changed

src/IniFile.cxx

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
#include "IniFile.hxx"
55
#include "lib/fmt/RuntimeError.hxx"
66
#include "lib/fmt/SystemError.hxx"
7+
#include "io/BufferedReader.hxx"
8+
#include "io/FileReader.hxx"
79
#include "util/CharUtil.hxx"
810
#include "util/ScopeExit.hxx"
911
#include "util/StringSplit.hxx"
1012
#include "util/StringStrip.hxx"
1113

1214
#include <string_view>
1315

14-
#include <stdio.h>
15-
1616
static constexpr bool
1717
IsValidSectionNameChar(char ch) noexcept
1818
{
@@ -124,13 +124,12 @@ IniParser::ParseLine(std::string_view line)
124124
}
125125

126126
static IniFile
127-
ReadIniFile(const char *path, FILE *file)
127+
ReadIniFile(const char *path, BufferedReader &reader)
128128
{
129129
IniParser parser;
130130

131131
unsigned no = 1;
132-
char buffer[4096];
133-
while (auto line = fgets(buffer, sizeof(buffer), file)) {
132+
while (const char *line = reader.ReadLine()) {
134133
try {
135134
parser.ParseLine(line);
136135
} catch (...) {
@@ -147,11 +146,8 @@ ReadIniFile(const char *path, FILE *file)
147146
IniFile
148147
ReadIniFile(const char *path)
149148
{
150-
FILE *file = fopen(path, "r");
151-
if (file == nullptr)
152-
throw FmtErrno("Failed to open {:?}", path);
153-
154-
AtScopeExit(file) { fclose(file); };
149+
FileReader file{path};
150+
BufferedReader reader{file};
155151

156-
return ReadIniFile(path, file);
152+
return ReadIniFile(path, reader);
157153
}

src/ReadConfig.cxx

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
#include "ReadConfig.hxx"
55
#include "lib/fmt/RuntimeError.hxx"
6+
#include "io/BufferedReader.hxx"
7+
#include "io/FileReader.hxx"
68
#include "util/Compiler.h"
79
#include "util/ScopeExit.hxx"
810
#include "util/StringStrip.hxx"
@@ -318,25 +320,14 @@ parse_ignore_list_line(std::string_view input)
318320
static IgnoreList*
319321
load_ignore_list(const std::string& path, Config::IgnoreListMap& ignore_lists)
320322
{
321-
322-
FILE *file = fopen(path.c_str(), "r");
323-
if (file == nullptr) {
324-
throw FmtRuntimeError("Cannot load ignore file: cannot open {:?} for reading", path);
325-
}
326-
327-
AtScopeExit(file) { fclose(file); };
323+
FileReader file{path.c_str()};
324+
BufferedReader reader{file};
328325

329326
IgnoreList ignore_list;
330327

331-
char line_buf[4096];
332-
size_t line_num = 0;
333-
while (fgets(line_buf, sizeof(line_buf), file)) {
334-
std::string_view line(line_buf);
335-
if (line.back() == '\n') {
336-
line.remove_suffix(1);
337-
}
328+
while (const char *_line = reader.ReadLine()) {
329+
std::string_view line{_line};
338330

339-
line_num++;
340331
if (line.empty()) {
341332
continue;
342333
}
@@ -363,7 +354,7 @@ load_ignore_list(const std::string& path, Config::IgnoreListMap& ignore_lists)
363354
ignore_list.entries.emplace_back(std::move(entry));
364355
} catch (const std::runtime_error& error) {
365356
throw FmtRuntimeError("Error loading ignore list {:?}: Error parsing line {}: {}",
366-
path, line_num, error.what());
357+
path, reader.GetLineNumber(), error.what());
367358
}
368359
}
369360

src/io/BufferedReader.cxx

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
// SPDX-License-Identifier: BSD-2-Clause
2+
// author: Max Kellermann <[email protected]>
3+
4+
#include "BufferedReader.hxx"
5+
#include "Reader.hxx"
6+
#include "util/TextFile.hxx"
7+
8+
#include <algorithm> // for std::copy_n()
9+
#include <cassert>
10+
#include <cstdint>
11+
#include <stdexcept>
12+
13+
bool
14+
BufferedReader::Fill(bool need_more)
15+
{
16+
if (eof)
17+
return !need_more;
18+
19+
auto w = buffer.Write();
20+
if (w.empty()) {
21+
if (buffer.GetCapacity() >= MAX_SIZE)
22+
return !need_more;
23+
24+
buffer.Grow(buffer.GetCapacity() * 2);
25+
w = buffer.Write();
26+
assert(!w.empty());
27+
}
28+
29+
std::size_t nbytes = reader.Read(w);
30+
if (nbytes == 0) {
31+
eof = true;
32+
return !need_more;
33+
}
34+
35+
buffer.Append(nbytes);
36+
return true;
37+
}
38+
39+
void *
40+
BufferedReader::ReadFull(std::size_t size)
41+
{
42+
while (true) {
43+
auto r = Read();
44+
if (r.size() >= size)
45+
return r.data();
46+
47+
if (!Fill(true))
48+
throw std::runtime_error("Premature end of file");
49+
}
50+
}
51+
52+
std::size_t
53+
BufferedReader::ReadFromBuffer(std::span<std::byte> dest) noexcept
54+
{
55+
const auto src = Read();
56+
std::size_t nbytes = std::min(src.size(), dest.size());
57+
std::copy_n(src.data(), nbytes, dest.data());
58+
Consume(nbytes);
59+
return nbytes;
60+
}
61+
62+
void
63+
BufferedReader::ReadFull(std::span<std::byte> dest)
64+
{
65+
while (true) {
66+
std::size_t nbytes = ReadFromBuffer(dest);
67+
dest = dest.subspan(nbytes);
68+
if (dest.empty())
69+
break;
70+
71+
if (!Fill(true))
72+
throw std::runtime_error("Premature end of file");
73+
}
74+
}
75+
76+
char *
77+
BufferedReader::ReadLine()
78+
{
79+
do {
80+
char *line = ReadBufferedLine(buffer);
81+
if (line != nullptr) {
82+
++line_number;
83+
return line;
84+
}
85+
} while (Fill(true));
86+
87+
if (!eof || buffer.empty())
88+
return nullptr;
89+
90+
auto w = buffer.Write();
91+
if (w.empty()) {
92+
buffer.Grow(buffer.GetCapacity() + 1);
93+
w = buffer.Write();
94+
assert(!w.empty());
95+
}
96+
97+
/* terminate the last line */
98+
w[0] = {};
99+
100+
char *line = reinterpret_cast<char *>(buffer.Read().data());
101+
buffer.Clear();
102+
++line_number;
103+
return line;
104+
}

src/io/BufferedReader.hxx

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// SPDX-License-Identifier: BSD-2-Clause
2+
// author: Max Kellermann <[email protected]>
3+
4+
#pragma once
5+
6+
#include "util/DynamicFifoBuffer.hxx"
7+
8+
#include <cstddef>
9+
#include <span>
10+
11+
class Reader;
12+
13+
class BufferedReader {
14+
static constexpr std::size_t MAX_SIZE = 512 * 1024;
15+
16+
Reader &reader;
17+
18+
DynamicFifoBuffer<std::byte> buffer;
19+
20+
bool eof = false;
21+
22+
unsigned line_number = 0;
23+
24+
public:
25+
explicit BufferedReader(Reader &_reader) noexcept
26+
:reader(_reader), buffer(16384) {}
27+
28+
/**
29+
* Reset the internal state. Should be called after rewinding
30+
* the underlying #Reader.
31+
*/
32+
void Reset() noexcept {
33+
buffer.Clear();
34+
eof = false;
35+
line_number = 0;
36+
}
37+
38+
bool Fill(bool need_more);
39+
40+
[[gnu::pure]]
41+
std::span<std::byte> Read() const noexcept {
42+
return std::as_writable_bytes(buffer.Read());
43+
}
44+
45+
/**
46+
* Read a buffer of exactly the given size (without consuming
47+
* it). Throws std::runtime_error if not enough data is
48+
* available.
49+
*/
50+
void *ReadFull(std::size_t size);
51+
52+
void Consume(std::size_t n) noexcept {
53+
buffer.Consume(n);
54+
}
55+
56+
/**
57+
* Read (and consume) data from the input buffer into the
58+
* given buffer. Does not attempt to refill the buffer.
59+
*/
60+
std::size_t ReadFromBuffer(std::span<std::byte> dest) noexcept;
61+
62+
/**
63+
* Read data into the given buffer and consume it from our
64+
* buffer. Throw an exception if the request cannot be
65+
* forfilled.
66+
*/
67+
void ReadFull(std::span<std::byte> dest);
68+
69+
template<typename T>
70+
void ReadFullT(T &dest) {
71+
ReadFull(std::as_writable_bytes(std::span{&dest, 1}));
72+
}
73+
74+
template<typename T>
75+
T ReadFullT() {
76+
T dest;
77+
ReadFullT<T>(dest);
78+
return dest;
79+
}
80+
81+
char *ReadLine();
82+
83+
unsigned GetLineNumber() const noexcept {
84+
return line_number;
85+
}
86+
};

src/io/FileAt.hxx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// SPDX-License-Identifier: BSD-2-Clause
2+
// Copyright CM4all GmbH
3+
// author: Max Kellermann <[email protected]>
4+
5+
#pragma once
6+
7+
#include "FileDescriptor.hxx"
8+
9+
/**
10+
* Reference to a file by an anchor directory (which can be an
11+
* `O_PATH` descriptor) and a path name relative to it.
12+
*/
13+
struct FileAt {
14+
FileDescriptor directory;
15+
const char *name;
16+
};

src/io/FileReader.cxx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// SPDX-License-Identifier: BSD-2-Clause
2+
// author: Max Kellermann <[email protected]>
3+
4+
#include "FileReader.hxx"
5+
#include "lib/fmt/SystemError.hxx"
6+
#include "io/Open.hxx"
7+
8+
#include <cassert>
9+
10+
#ifdef _WIN32
11+
12+
FileReader::FileReader(const char *path)
13+
:handle(CreateFile(path, GENERIC_READ, FILE_SHARE_READ,
14+
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
15+
nullptr))
16+
{
17+
if (handle == INVALID_HANDLE_VALUE)
18+
throw FmtLastError("Failed to open {}", path);
19+
}
20+
21+
std::size_t
22+
FileReader::Read(std::span<std::byte> dest)
23+
{
24+
assert(IsDefined());
25+
26+
DWORD nbytes;
27+
if (!ReadFile(handle, dest.data(), dest.size(), &nbytes, nullptr))
28+
throw MakeLastError("Failed to read from file");
29+
30+
return nbytes;
31+
}
32+
33+
#else
34+
35+
FileReader::FileReader(const char *path)
36+
:fd(OpenReadOnly(path))
37+
{
38+
}
39+
40+
std::size_t
41+
FileReader::Read(std::span<std::byte> dest)
42+
{
43+
assert(IsDefined());
44+
45+
ssize_t nbytes = fd.Read(dest);
46+
if (nbytes < 0)
47+
throw MakeErrno("Failed to read from file");
48+
49+
return nbytes;
50+
}
51+
52+
#endif

0 commit comments

Comments
 (0)