Skip to content
API Documentation
BastionGuard™
fault_tolerant_reader.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <chrono>
5#include <thread>
6
7bool FaultTolerantReader::open(const std::string& path, std::string& error) {
8 return reader_.open_readonly(path, error);
9}
10
12 std::scoped_lock lock(mutex_);
13 reader_.close();
14 bad_map_ = {};
15}
16
18 return reader_.is_open();
19}
20
21uint64_t FaultTolerantReader::size() const {
22 return reader_.size();
23}
24
28
29const std::string& FaultTolerantReader::path() const {
30 return reader_.path();
31}
32
36
40
41ReadResult FaultTolerantReader::read_at(uint64_t offset, void* buffer, size_t size) {
42 std::scoped_lock lock(mutex_);
43 ReadResult result;
44 if (cancel_cb_ && cancel_cb_()) {
45 result.error = "cancelled";
46 return result;
47 }
48 result.success = reader_.read_at(offset, buffer, size);
49 result.bytes_read = result.success ? size : 0;
50 if (result.success) {
52 } else {
53 result.error = "read failed";
54 }
55 return result;
56}
57
58ReadResult FaultTolerantReader::read_rescue(uint64_t offset, void* buffer, size_t size) {
59 std::scoped_lock lock(mutex_);
60 return read_rescue_recursive(offset, static_cast<unsigned char*>(buffer), size);
61}
62
66
70
71ReadResult FaultTolerantReader::read_rescue_recursive(uint64_t offset, unsigned char* buffer, size_t size) {
72 ReadResult result;
73 if (size == 0) {
74 result.success = true;
75 return result;
76 }
77 if (cancel_cb_ && cancel_cb_()) {
78 result.error = "cancelled";
79 return result;
80 }
81
82 const auto start = std::chrono::steady_clock::now();
83 for (int attempt = 0; attempt < std::max(1, config_.max_retries); ++attempt) {
84 if (reader_.read_at(offset, buffer, size)) {
85 result.success = true;
86 result.bytes_read = size;
88 const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
89 std::chrono::steady_clock::now() - start).count();
90 if (config_.mark_slow_ranges && elapsed > 250) {
91 bad_map_.mark_slow(offset, size);
92 }
93 return result;
94 }
95 std::this_thread::sleep_for(std::chrono::milliseconds(25 * (attempt + 1)));
96 }
97
99 bad_map_.mark_bad(offset, size);
100 result.error = "unreadable range";
101 return result;
102 }
103
104 const size_t left_size = size / 2;
105 const size_t right_size = size - left_size;
106 auto left = read_rescue_recursive(offset, buffer, left_size);
107 auto right = read_rescue_recursive(offset + left_size, buffer + left_size, right_size);
108
109 result.bytes_read = left.bytes_read + right.bytes_read;
110 result.partial = result.bytes_read > 0 && result.bytes_read < size;
111 result.success = (result.bytes_read == size);
112 if (!result.success && result.partial) {
113 result.error = "partial read";
114 } else if (!result.success) {
115 result.error = left.error.empty() ? right.error : left.error;
116 }
117 return result;
118}
void mark_readable(uint64_t offset, uint64_t length)
void mark_slow(uint64_t offset, uint64_t length)
void mark_bad(uint64_t offset, uint64_t length)
bool is_open() const
uint32_t logical_sector_size() const
const std::string & path() const
bool read_at(uint64_t offset, void *buffer, size_t len)
bool open_readonly(const std::string &path, std::string &error)
uint64_t size() const
std::function< bool()> CancelCallback
FaultTolerantReaderConfig config_
const std::string & path() const
bool open(const std::string &path, std::string &error)
uint32_t logical_sector_size() const
void set_cancel_callback(CancelCallback cb)
void set_config(const FaultTolerantReaderConfig &cfg)
ReadResult read_rescue(uint64_t offset, void *buffer, size_t size)
ReadResult read_at(uint64_t offset, void *buffer, size_t size)
ReadResult read_rescue_recursive(uint64_t offset, unsigned char *buffer, size_t size)
const BadBlockMap & bad_block_map() const