33#include <system_error>
36#include "iceberg/file_io.h"
37#include "iceberg/result.h"
38#include "iceberg/util/macros.h"
40namespace iceberg::test {
44inline Result<std::streamsize> ToStreamSize(
size_t size) {
45 if (size >
static_cast<size_t>(std::numeric_limits<std::streamsize>::max())) {
46 return InvalidArgument(
"Buffer size {} exceeds streamsize max", size);
48 return static_cast<std::streamsize
>(size);
51inline Result<int64_t> ToInt64FileSize(uintmax_t size, std::string_view location) {
52 if (size >
static_cast<uintmax_t
>(std::numeric_limits<int64_t>::max())) {
53 return Invalid(
"File size for {} exceeds int64_t max", location);
55 return static_cast<int64_t
>(size);
63 : location_(std::move(location)), file_(location_, std::ios::binary) {}
65 bool is_open()
const {
return file_.is_open(); }
68 auto position = file_.tellg();
70 return IOError(
"Failed to get read position for: {}", location_);
72 return static_cast<int64_t
>(position);
75 Status
Seek(int64_t position)
override {
77 return InvalidArgument(
"Cannot seek to negative position {}", position);
80 file_.seekg(position);
82 return IOError(
"Failed to seek to {} in file: {}", position, location_);
87 Result<int64_t>
Read(std::span<std::byte> out)
override {
91 ICEBERG_ASSIGN_OR_RAISE(
auto read_size, detail::ToStreamSize(out.size()));
92 file_.read(
reinterpret_cast<char*
>(out.data()), read_size);
93 auto bytes_read = file_.gcount();
95 if (file_.bad() || !file_.eof()) {
96 return IOError(
"Failed to read from file: {}", location_);
100 if (bytes_read < 0) {
101 return IOError(
"Failed to read from file: {}", location_);
103 return static_cast<int64_t
>(bytes_read);
106 Status
ReadFully(int64_t position, std::span<std::byte> out)
override {
108 return InvalidArgument(
"Cannot read from negative position {}", position);
113 ICEBERG_ASSIGN_OR_RAISE(
auto original_position,
Position());
114 auto seek_status =
Seek(position);
115 if (!seek_status.has_value()) {
116 static_cast<void>(
Seek(original_position));
120 Status read_status = {};
121 size_t total_read = 0;
122 while (total_read < out.size()) {
123 auto read_result =
Read(out.subspan(total_read));
124 if (!read_result.has_value()) {
125 read_status = std::unexpected<Error>(read_result.error());
128 if (read_result.value() == 0) {
130 IOError(
"Failed to read {} bytes from file: {}", out.size(), location_);
133 total_read +=
static_cast<size_t>(read_result.value());
136 auto restore_status =
Seek(original_position);
137 ICEBERG_RETURN_UNEXPECTED(read_status);
138 return restore_status;
142 if (!file_.is_open()) {
147 return IOError(
"Failed to close file: {}", location_);
153 std::string location_;
154 mutable std::ifstream file_;
160 : location_(std::move(location)),
161 file_(location_, std::ios::binary | std::ios::out | std::ios::trunc) {}
163 bool is_open()
const {
return file_.is_open(); }
166 auto position = file_.tellp();
168 return IOError(
"Failed to get write position for: {}", location_);
170 return static_cast<int64_t
>(position);
173 Status
Write(std::span<const std::byte> data)
override {
177 ICEBERG_ASSIGN_OR_RAISE(
auto write_size, detail::ToStreamSize(data.size()));
178 file_.write(
reinterpret_cast<const char*
>(data.data()), write_size);
180 return IOError(
"Failed to write to file: {}", location_);
188 return IOError(
"Failed to flush file: {}", location_);
194 if (!file_.is_open()) {
199 return IOError(
"Failed to close file: {}", location_);
205 std::string location_;
206 mutable std::ofstream file_;
212 std::optional<int64_t> file_size = std::nullopt)
213 : location_(std::move(
location)), file_size_(file_size) {}
215 std::string_view
location()
const override {
return location_; }
217 Result<int64_t>
Size()
const override {
218 if (file_size_.has_value()) {
222 auto size = std::filesystem::file_size(location_, ec);
224 return IOError(
"Failed to get file size for {}: {}", location_, ec.message());
226 return detail::ToInt64FileSize(size, location_);
229 Result<std::unique_ptr<SeekableInputStream>>
Open()
override {
230 auto stream = std::make_unique<StdSeekableInputStream>(location_);
231 if (!stream->is_open()) {
232 return IOError(
"Failed to open file for reading: {}", location_);
238 std::string location_;
239 std::optional<int64_t> file_size_;
246 std::string_view
location()
const override {
return location_; }
248 Result<std::unique_ptr<PositionOutputStream>>
Create()
override {
257 Result<std::unique_ptr<PositionOutputStream>>
Create(
bool overwrite) {
258 std::filesystem::path path(location_);
260 auto exists = std::filesystem::exists(path, ec);
262 return IOError(
"Failed to check file existence for {}: {}", location_,
265 if (!overwrite && exists) {
266 return AlreadyExists(
"File already exists: {}", location_);
268 if (path.has_parent_path()) {
269 std::filesystem::create_directories(path.parent_path(), ec);
271 return IOError(
"Failed to create parent directories for {}: {}", location_,
275 auto stream = std::make_unique<StdPositionOutputStream>(location_);
276 if (!stream->is_open()) {
277 return IOError(
"Failed to open file for writing: {}", location_);
282 std::string location_;
291 Result<std::unique_ptr<InputFile>>
NewInputFile(std::string file_location)
override {
292 return std::make_unique<StdInputFile>(std::move(file_location));
295 Result<std::unique_ptr<InputFile>>
NewInputFile(std::string file_location,
296 size_t length)
override {
297 if (length >
static_cast<size_t>(std::numeric_limits<int64_t>::max())) {
298 return InvalidArgument(
"File length {} exceeds int64_t max", length);
300 return std::make_unique<StdInputFile>(std::move(file_location),
301 static_cast<int64_t
>(length));
304 Result<std::unique_ptr<OutputFile>>
NewOutputFile(std::string file_location)
override {
305 return std::make_unique<StdOutputFile>(std::move(file_location));
308 Status
DeleteFile(
const std::string& file_location)
override {
310 if (!std::filesystem::remove(file_location, ec)) {
312 return IOError(
"Failed to delete file {}: {}", file_location, ec.message());
314 return IOError(
"File does not exist: {}", file_location);
Pluggable module for reading, writing, and deleting files.
Definition file_io.h:115
Handle for creating a writable file.
Definition file_io.h:94
Positioned byte stream for writing file contents.
Definition file_io.h:61
Simple local filesystem FileIO implementation for testing.
Definition std_io.h:289
Result< std::unique_ptr< InputFile > > NewInputFile(std::string file_location) override
Create an input file handle for the given location.
Definition std_io.h:291
Status DeleteFile(const std::string &file_location) override
Delete a file at the given location.
Definition std_io.h:308
Result< std::unique_ptr< OutputFile > > NewOutputFile(std::string file_location) override
Create an output file handle for the given location.
Definition std_io.h:304
Result< std::unique_ptr< InputFile > > NewInputFile(std::string file_location, size_t length) override
Create an input file handle for the given location with a known length.
Definition std_io.h:295
std::string_view location() const override
File location represented by this handle.
Definition std_io.h:246
Result< std::unique_ptr< PositionOutputStream > > CreateOrOverwrite() override
Create a new output stream, replacing any existing file.
Definition std_io.h:252
Result< std::unique_ptr< PositionOutputStream > > Create() override
Create a new output stream and fail if the file already exists.
Definition std_io.h:248
Status Write(std::span< const std::byte > data) override
Write all bytes in data at the current position.
Definition std_io.h:173
Result< int64_t > Position() const override
Return the current write position.
Definition std_io.h:165
Status Flush() override
Flush buffered data to the underlying store.
Definition std_io.h:185
Status Close() override
Close the stream. Implementations should allow repeated Close calls.
Definition std_io.h:193