blob: eb81a4a556b559b5dcf107a63ee4c7d734a2c065 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_FILE_H_
#define SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_FILE_H_
#include <lib/fit/result.h>
#include <lib/stdcompat/span.h>
#include <optional>
#include <string_view>
#include <type_traits>
#include <utility>
#include "diagnostics.h"
namespace elfldltl {
template <typename Handle>
inline auto DefaultMakeInvalidHandle() {
return Handle();
}
// elfldltl::File<Handle, Offset, Read, MakeInvalidHandle> implements the File
// API (see memory.h) by holding a Handle object and calling Read as a function
// fit::result<...>(Handle&, Offset, cpp20::span<byte>) that returns some error
// value that Diagnostics::SystemError can handle. MakeInvalidHandle can be
// supplied if default-construction isn't the way.
template <class Diagnostics, typename Handle, typename Offset, auto Read,
auto MakeInvalidHandle = &DefaultMakeInvalidHandle<Handle>>
class File {
public:
static_assert(std::is_invocable_v<decltype(Read), Handle&, Offset, cpp20::span<std::byte>>);
static_assert(std::is_convertible_v<decltype(MakeInvalidHandle()), Handle>);
using offset_type = Offset;
File(const File&) noexcept(std::is_nothrow_copy_constructible_v<Handle>) = default;
File(File&&) noexcept(std::is_nothrow_move_constructible_v<Handle>) = default;
explicit File(Diagnostics& diag) : diag_(diag) {}
File(Handle handle, Diagnostics& diag) noexcept(std::is_nothrow_move_constructible_v<Handle>)
: handle_(std::move(handle)), diag_(diag) {}
File& operator=(const File&) noexcept(std::is_nothrow_copy_assignable_v<Handle>) = default;
File& operator=(File&&) noexcept(std::is_nothrow_move_assignable_v<Handle>) = default;
const Handle& get() const { return handle_; }
Handle release() { return std::exchange(handle_, MakeInvalidHandle()); }
template <typename T>
std::optional<T> ReadFromFile(Offset offset) {
std::optional<T> result{std::in_place};
cpp20::span<T> data{std::addressof(result.value()), 1};
FinishReadFromFile(offset, data, result);
return result;
}
template <typename T, typename Allocator>
auto ReadArrayFromFile(off_t offset, Allocator&& allocator, size_t count) {
auto result = allocator(count);
if (result) {
cpp20::span<T> data = *result;
FinishReadFromFile(offset, data, result);
}
return result;
}
protected:
Handle& handle() { return handle_; }
const Handle& handle() const { return handle_; }
private:
template <typename T, typename Result>
void FinishReadFromFile(Offset offset, cpp20::span<T> data, Result& result) {
using namespace std::string_view_literals;
auto read = Read(handle_, offset, cpp20::as_writable_bytes(data));
if (read.is_error()) {
// FileOffset takes an unsigned type, but off_t is signed.
// No value passed to Read should be negative.
auto diagnose = [offset = static_cast<std::make_unsigned_t<Offset>>(offset),
bytes = data.size_bytes(), this](auto&& error) {
diag_.SystemError("cannot read "sv, bytes, " bytes"sv, FileOffset{offset}, ": "sv,
std::move(error));
};
auto error = std::move(read).error_value();
if (error == decltype(error){}) {
// The default-initialized error type is used to mean EOF.
diagnose("reached end of file"sv);
} else {
diagnose(std::move(error));
}
result = Result();
}
}
Handle handle_ = MakeInvalidHandle();
Diagnostics& diag_;
};
} // namespace elfldltl
#endif // SRC_LIB_ELFLDLTL_INCLUDE_LIB_ELFLDLTL_FILE_H_