summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/util/ioprocs.cpp
diff options
context:
space:
mode:
author Vas Crabb <cuavas@users.noreply.github.com>2021-08-22 09:06:15 +1000
committer GitHub <noreply@github.com>2021-08-22 09:06:15 +1000
commite8bbea1fc6e94e14768509d322f6c624403ffb36 (patch)
tree74dd1606a900d83de8aecff17a6737af4113308d /src/lib/util/ioprocs.cpp
parente319bde5fc3696d7f48f62b15b6366c4377fe5d1 (diff)
formats, osd, util: Started refactoring file I/O stuff. (#8456)
Added more modern generic I/O interfaces with implementation backed by stdio, osd_file and core_file, replacing io_generic. Also replaced core_file's build-in zlib compression with a filter. unzip.cpp, un7z.cpp: Added option to supply abstract I/O interface rather than filename. Converted osd_file, core_file, archive_file, chd_file and device_image_interface to use std::error_condition rather than their own error enums. Allow mounting TI-99 RPK from inside archives.
Diffstat (limited to 'src/lib/util/ioprocs.cpp')
-rw-r--r--src/lib/util/ioprocs.cpp1248
1 files changed, 1248 insertions, 0 deletions
diff --git a/src/lib/util/ioprocs.cpp b/src/lib/util/ioprocs.cpp
new file mode 100644
index 00000000000..1f52a3daf97
--- /dev/null
+++ b/src/lib/util/ioprocs.cpp
@@ -0,0 +1,1248 @@
+// license:BSD-3-Clause
+// copyright-holders:Vas Crabb
+/***************************************************************************
+
+ ioprocs.h
+
+ I/O interfaces
+
+***************************************************************************/
+
+#include "ioprocs.h"
+
+#include "corefile.h"
+#include "ioprocsfill.h"
+
+#include "osdfile.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cerrno>
+#include <cstring>
+#include <iterator>
+#include <limits>
+#include <type_traits>
+
+
+namespace util {
+
+namespace {
+
+// helper for holding a block of memory and deallocating it (or not) as necessary
+
+template <typename T, bool Owned>
+class ram_adapter_base : public virtual random_access
+{
+public:
+ virtual ~ram_adapter_base()
+ {
+ if constexpr (Owned)
+ std::free(m_data);
+ }
+
+ virtual std::error_condition seek(std::int64_t offset, int whence) noexcept override
+ {
+ switch (whence)
+ {
+ case SEEK_SET:
+ if (0 > offset)
+ return std::errc::invalid_argument;
+ m_pointer = std::uint64_t(offset);
+ return std::error_condition();
+
+ case SEEK_CUR:
+ if (0 > offset)
+ {
+ if (std::uint64_t(-offset) > m_pointer)
+ return std::errc::invalid_argument;
+ }
+ else if ((std::numeric_limits<std::uint64_t>::max() - offset) < m_pointer)
+ {
+ return std::errc::invalid_argument;
+ }
+ m_pointer += offset;
+ return std::error_condition();
+
+ case SEEK_END:
+ if (0 > offset)
+ {
+ if (std::uint64_t(-offset) > m_size)
+ return std::errc::invalid_argument;
+ }
+ else if ((std::numeric_limits<std::uint64_t>::max() - offset) < m_size)
+ {
+ return std::errc::invalid_argument;
+ }
+ m_pointer = std::uint64_t(m_size) + offset;
+ return std::error_condition();
+
+ default:
+ return std::errc::invalid_argument;
+ }
+ }
+
+ virtual std::error_condition tell(std::uint64_t &result) noexcept override
+ {
+ result = m_pointer;
+ return std::error_condition();
+ }
+
+ virtual std::error_condition length(std::uint64_t &result) noexcept override
+ {
+ if (std::numeric_limits<uint64_t>::max() < m_size)
+ return std::errc::file_too_large;
+
+ result = m_size;
+ return std::error_condition();
+ }
+
+protected:
+ template <typename U>
+ ram_adapter_base(U *data, std::size_t size) noexcept : m_data(reinterpret_cast<T>(data)), m_size(size)
+ {
+ static_assert(sizeof(*m_data) == 1U, "Element type must be byte-sized");
+ assert(m_data || !m_size);
+ }
+
+ T m_data;
+ std::uint64_t m_pointer = 0U;
+ std::size_t m_size;
+};
+
+
+// RAM read implementation
+
+template <typename T, bool Owned>
+class ram_read_adapter : public ram_adapter_base<T, Owned>, public virtual random_read
+{
+public:
+ template <typename U>
+ ram_read_adapter(U *data, std::size_t size) noexcept : ram_adapter_base<T, Owned>(data, size)
+ {
+ }
+
+ virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ do_read(this->m_pointer, buffer, length, actual);
+ this->m_pointer += actual;
+ return std::error_condition();
+ }
+
+ virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ do_read(offset, buffer, length, actual);
+ return std::error_condition();
+ }
+
+private:
+ void do_read(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) const noexcept
+ {
+ if ((offset < this->m_size) && length)
+ {
+ actual = std::min(std::size_t(this->m_size - offset), length);
+ if constexpr (Owned)
+ std::memcpy(buffer, this->m_data + offset, actual);
+ else
+ std::memmove(buffer, this->m_data + offset, actual);
+ }
+ else
+ {
+ actual = 0U;
+ }
+ }
+};
+
+
+// helper for holding a stdio FILE and closing it (or not) as necessary
+
+class stdio_adapter_base : public virtual random_access
+{
+public:
+ virtual ~stdio_adapter_base()
+ {
+ if (m_close)
+ std::fclose(m_file);
+ }
+
+ virtual std::error_condition seek(std::int64_t offset, int whence) noexcept override
+ {
+ if ((std::numeric_limits<long>::max() < offset) || (std::numeric_limits<long>::min() > offset))
+ return std::errc::invalid_argument;
+ else if (!std::fseek(m_file, long(offset), whence))
+ return std::error_condition();
+ else
+ return std::error_condition(errno, std::generic_category());
+ }
+
+ virtual std::error_condition tell(std::uint64_t &result) noexcept override
+ {
+ long const pos = std::ftell(m_file);
+ if (0 > pos)
+ return std::error_condition(errno, std::generic_category());
+ result = static_cast<unsigned long>(pos);
+ return std::error_condition();
+ }
+
+ virtual std::error_condition length(std::uint64_t &result) noexcept override
+ {
+ std::fpos_t oldpos;
+ if (std::fgetpos(m_file, &oldpos))
+ return std::error_condition(errno, std::generic_category());
+
+ long endpos = -1;
+ if (!std::fseek(m_file, 0, SEEK_END))
+ {
+ m_dangling_read = m_dangling_write = false;
+ endpos = std::ftell(m_file);
+ }
+ std::error_condition err;
+ if (0 > endpos)
+ err.assign(errno, std::generic_category());
+ else if (std::numeric_limits<std::uint64_t>::max() < static_cast<unsigned long>(endpos))
+ err = std::errc::file_too_large;
+ else
+ result = static_cast<unsigned long>(endpos);
+
+ if (!std::fsetpos(m_file, &oldpos))
+ m_dangling_read = m_dangling_write = false;
+ else if (!err)
+ err.assign(errno, std::generic_category());
+
+ return err;
+ }
+
+protected:
+ stdio_adapter_base(FILE *file, bool close) noexcept : m_file(file), m_close(close)
+ {
+ assert(m_file);
+ }
+
+ FILE *file() noexcept
+ {
+ return m_file;
+ }
+
+private:
+ FILE *const m_file;
+ bool const m_close;
+
+protected:
+ bool m_dangling_read = true, m_dangling_write = true;
+};
+
+
+// stdio read implementation
+
+class stdio_read_adapter : public stdio_adapter_base, public virtual random_read
+{
+public:
+ stdio_read_adapter(FILE *file, bool close) noexcept : stdio_adapter_base(file, close)
+ {
+ }
+
+ virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ if (m_dangling_write)
+ {
+ if (std::fflush(file()))
+ {
+ std::clearerr(file());
+ actual = 0U;
+ return std::error_condition(errno, std::generic_category());
+ }
+ m_dangling_write = false;
+ }
+
+ actual = std::fread(buffer, sizeof(std::uint8_t), length, file());
+ m_dangling_read = true;
+ if (length != actual)
+ {
+ if (std::ferror(file()))
+ {
+ std::clearerr(file());
+ return std::error_condition(errno, std::generic_category());
+ }
+ else if (std::feof(file()))
+ {
+ m_dangling_read = false;
+ }
+ }
+
+ return std::error_condition();
+ }
+
+ virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ actual = 0U;
+
+ if (static_cast<unsigned long>(std::numeric_limits<long>::max()) < offset)
+ return std::errc::invalid_argument;
+
+ std::fpos_t oldpos;
+ if (std::fgetpos(file(), &oldpos))
+ return std::error_condition(errno, std::generic_category());
+
+ std::error_condition err;
+ if (std::fseek(file(), long(static_cast<unsigned long>(offset)), SEEK_SET))
+ {
+ err.assign(errno, std::generic_category());
+ }
+ else
+ {
+ m_dangling_write = false;
+ actual = std::fread(buffer, sizeof(std::uint8_t), length, file());
+ m_dangling_read = true;
+ if (length != actual)
+ {
+ if (std::ferror(file()))
+ {
+ err.assign(errno, std::generic_category());
+ std::clearerr(file());
+ }
+ else if (std::feof(file()))
+ {
+ m_dangling_read = false;
+ }
+ }
+ }
+
+ if (!std::fsetpos(file(), &oldpos))
+ m_dangling_read = m_dangling_write = false;
+ else if (!err)
+ err.assign(errno, std::generic_category());
+
+ return err;
+ }
+};
+
+
+// stdio read/write implementation
+
+class stdio_read_write_adapter : public stdio_read_adapter, public random_read_write
+{
+public:
+ using stdio_read_adapter::stdio_read_adapter;
+
+ virtual std::error_condition finalize() noexcept override
+ {
+ return std::error_condition();
+ }
+
+ virtual std::error_condition flush() noexcept override
+ {
+ if (std::fflush(file()))
+ {
+ std::clearerr(file());
+ return std::error_condition(errno, std::generic_category());
+ }
+ m_dangling_write = false;
+ return std::error_condition();
+ }
+
+ virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ if (m_dangling_read)
+ {
+ if (std::fseek(file(), 0, SEEK_CUR))
+ {
+ actual = 0U;
+ return std::error_condition(errno, std::generic_category());
+ }
+ m_dangling_read = false;
+ }
+
+ actual = std::fwrite(buffer, sizeof(std::uint8_t), length, file());
+ m_dangling_write = true;
+ if (length != actual)
+ {
+ std::clearerr(file());
+ return std::error_condition(errno, std::generic_category());
+ }
+
+ return std::error_condition();
+ }
+
+ virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ actual = 0U;
+
+ if (static_cast<unsigned long>(std::numeric_limits<long>::max()) < offset)
+ return std::errc::invalid_argument;
+
+ std::fpos_t oldpos;
+ if (std::fgetpos(file(), &oldpos))
+ return std::error_condition(errno, std::generic_category());
+
+ std::error_condition err;
+ if (std::fseek(file(), long(static_cast<unsigned long>(offset)), SEEK_SET))
+ {
+ err.assign(errno, std::generic_category());
+ }
+ else
+ {
+ m_dangling_read = false;
+ actual = std::fwrite(buffer, sizeof(std::uint8_t), length, file());
+ m_dangling_write = true;
+ if (length != actual)
+ {
+ err.assign(errno, std::generic_category());
+ std::clearerr(file());
+ }
+ }
+
+ if (!std::fsetpos(file(), &oldpos))
+ m_dangling_read = m_dangling_write = false;
+ else if (!err)
+ err.assign(errno, std::generic_category());
+
+ return err;
+ }
+};
+
+
+// stdio helper that fills space when writing past the end-of-file
+
+class stdio_read_write_filler : public random_read_fill_wrapper<stdio_read_write_adapter>
+{
+public:
+ stdio_read_write_filler(FILE *file, bool close, std::uint8_t fill) noexcept : random_read_fill_wrapper<stdio_read_write_adapter>(file, close)
+ {
+ set_filler(fill);
+ }
+
+ virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ actual = 0U;
+
+ long const offset = std::ftell(file());
+ if (0 > offset)
+ return std::error_condition(errno, std::generic_category());
+
+ if (std::fseek(file(), 0, SEEK_END))
+ {
+ std::error_condition err(errno, std::generic_category());
+ if (!std::fseek(file(), offset, SEEK_SET))
+ m_dangling_read = m_dangling_write = false;
+ return err;
+ }
+ m_dangling_read = m_dangling_write = false;
+ long endpos = std::ftell(file());
+ if (0 > endpos)
+ {
+ std::error_condition err(errno, std::generic_category());
+ std::fseek(file(), offset, SEEK_SET);
+ return err;
+ }
+
+ if (offset > endpos)
+ {
+ std::uint8_t block[1024];
+ std::fill_n(
+ block,
+ std::min<std::common_type_t<std::size_t, std::uint64_t, long> >(std::size(block), offset - endpos),
+ get_filler());
+ do
+ {
+ std::size_t const chunk = std::min<std::common_type_t<std::size_t, std::uint64_t, long> >(std::size(block), offset - endpos);
+ std::size_t const filled = std::fwrite(block, sizeof(block[0]), chunk, file());
+ endpos += filled;
+ m_dangling_write = true;
+ if (chunk != filled)
+ {
+ std::error_condition err(errno, std::generic_category());
+ std::clearerr(file());
+ if (!std::fseek(file(), offset, SEEK_SET))
+ m_dangling_write = false;
+ return err;
+ }
+ }
+ while (static_cast<unsigned long>(endpos) < offset);
+ }
+ else if ((offset < endpos) && std::fseek(file(), offset, SEEK_SET))
+ {
+ return std::error_condition(errno, std::generic_category());
+ }
+
+ actual = std::fwrite(buffer, sizeof(std::uint8_t), length, file());
+ m_dangling_write = true;
+ if (length != actual)
+ {
+ std::clearerr(file());
+ return std::error_condition(errno, std::generic_category());
+ }
+
+ return std::error_condition();
+ }
+
+ virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ actual = 0U;
+
+ if (static_cast<unsigned long>(std::numeric_limits<long>::max()) < offset)
+ return std::errc::invalid_argument;
+
+ std::fpos_t oldpos;
+ if (std::fgetpos(file(), &oldpos))
+ return std::error_condition(errno, std::generic_category());
+
+ std::error_condition err;
+ if (std::fseek(file(), 0, SEEK_END))
+ {
+ err.assign(errno, std::generic_category());
+ }
+ else
+ {
+ m_dangling_read = m_dangling_write = false;
+ long endpos = std::ftell(file());
+ if (0 > endpos)
+ {
+ err.assign(errno, std::generic_category());
+ }
+ else if (static_cast<unsigned long>(endpos) > offset)
+ {
+ if (std::fseek(file(), long(static_cast<unsigned long>(offset)), SEEK_SET))
+ err.assign(errno, std::generic_category());
+ }
+ else if (static_cast<unsigned long>(endpos) < offset)
+ {
+ std::uint8_t block[1024];
+ std::fill_n(
+ block,
+ std::min<std::common_type_t<std::size_t, std::uint64_t, long> >(std::size(block), offset - endpos),
+ get_filler());
+ do
+ {
+ std::size_t const chunk = std::min<std::common_type_t<std::size_t, std::uint64_t, long> >(std::size(block), offset - endpos);
+ std::size_t const filled = std::fwrite(block, sizeof(block[0]), chunk, file());
+ endpos += filled;
+ m_dangling_write = true;
+ if (chunk != filled)
+ {
+ err.assign(errno, std::generic_category());
+ std::clearerr(file());
+ }
+ }
+ while (!err && (static_cast<unsigned long>(endpos) < offset));
+ }
+ }
+
+ if (!err)
+ {
+ actual = std::fwrite(buffer, sizeof(std::uint8_t), length, file());
+ m_dangling_write = true;
+ if (length != actual)
+ {
+ err.assign(errno, std::generic_category());
+ std::clearerr(file());
+ }
+ }
+
+ if (!std::fsetpos(file(), &oldpos))
+ m_dangling_read = m_dangling_write = false;
+ else if (!err)
+ err.assign(errno, std::generic_category());
+
+ return err;
+ }
+};
+
+
+// helper class for holding an osd_file and closing it (or not) as necessary
+
+class osd_file_adapter_base : public virtual random_access
+{
+public:
+ virtual ~osd_file_adapter_base()
+ {
+ if (m_close)
+ delete m_file;
+ }
+
+ virtual std::error_condition seek(std::int64_t offset, int whence) noexcept override
+ {
+ switch (whence)
+ {
+ case SEEK_SET:
+ if (0 > offset)
+ return std::errc::invalid_argument;
+ m_pointer = std::uint64_t(offset);
+ return std::error_condition();
+
+ case SEEK_CUR:
+ if (0 > offset)
+ {
+ if (std::uint64_t(-offset) > m_pointer)
+ return std::errc::invalid_argument;
+ }
+ else if ((std::numeric_limits<std::uint64_t>::max() - offset) < m_pointer)
+ {
+ return std::errc::invalid_argument;
+ }
+ m_pointer += offset;
+ return std::error_condition();
+
+ // TODO: add SEEK_END when osd_file can support it - should it return a different error?
+ default:
+ return std::errc::invalid_argument;
+ }
+ }
+
+ virtual std::error_condition tell(std::uint64_t &result) noexcept override
+ {
+ result = m_pointer;
+ return std::error_condition();
+ }
+
+ virtual std::error_condition length(std::uint64_t &result) noexcept override
+ {
+ // not supported by osd_file
+ return std::errc::not_supported; // TODO: revisit this error code
+ }
+
+protected:
+ osd_file_adapter_base(osd_file::ptr &&file) noexcept : m_file(file.release()), m_close(true)
+ {
+ assert(m_file);
+ }
+
+ osd_file_adapter_base(osd_file &file) noexcept : m_file(&file), m_close(false)
+ {
+ }
+
+ osd_file &file() noexcept
+ {
+ return *m_file;
+ }
+
+ std::uint64_t m_pointer = 0U;
+
+private:
+ osd_file *const m_file;
+ bool const m_close;
+};
+
+
+// osd_file read implementation
+
+class osd_file_read_adapter : public osd_file_adapter_base, public virtual random_read
+{
+public:
+ osd_file_read_adapter(osd_file::ptr &&file) noexcept : osd_file_adapter_base(std::move(file))
+ {
+ }
+
+ osd_file_read_adapter(osd_file &file) noexcept : osd_file_adapter_base(file)
+ {
+ }
+
+ virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ // TODO: should the client have to deal with reading less than expected even if EOF isn't hit?
+ if (std::numeric_limits<std::uint32_t>::max() < length)
+ {
+ actual = 0U;
+ return std::errc::invalid_argument;
+ }
+
+ // actual length not valid on error
+ std::uint32_t count;
+ std::error_condition err = file().read(buffer, m_pointer, std::uint32_t(length), count);
+ if (!err)
+ {
+ m_pointer += count;
+ actual = std::size_t(count);
+ }
+ else
+ {
+ actual = 0U;
+ }
+ return err;
+ }
+
+ virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ // TODO: should the client have to deal with reading less than expected even if EOF isn't hit?
+ if (std::numeric_limits<std::uint32_t>::max() < length)
+ {
+ actual = 0U;
+ return std::errc::invalid_argument;
+ }
+
+ // actual length not valid on error
+ std::uint32_t count;
+ std::error_condition err = file().read(buffer, offset, std::uint32_t(length), count);
+ if (!err)
+ actual = std::size_t(count);
+ else
+ actual = 0U;
+ return err;
+ }
+};
+
+
+// osd_file read/write implementation
+
+class osd_file_read_write_adapter : public osd_file_read_adapter, public random_read_write
+{
+public:
+ using osd_file_read_adapter::osd_file_read_adapter;
+
+ virtual std::error_condition finalize() noexcept override
+ {
+ return std::error_condition();
+ }
+
+ virtual std::error_condition flush() noexcept override
+ {
+ return file().flush();
+ }
+
+ virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ actual = 0U;
+ while (length)
+ {
+ // actual length not valid on error
+ std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), length);
+ std::uint32_t written;
+ std::error_condition err = file().write(buffer, m_pointer, chunk, written);
+ if (err)
+ return err;
+ m_pointer += written;
+ buffer = reinterpret_cast<std::uint8_t const *>(buffer) + written;
+ length -= written;
+ actual += written;
+ }
+ return std::error_condition();
+ }
+
+ virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ actual = 0U;
+ while (length)
+ {
+ // actual length not valid on error
+ std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), length);
+ std::uint32_t written;
+ std::error_condition err = file().write(buffer, offset, chunk, written);
+ if (err)
+ return err;
+ offset += written;
+ buffer = reinterpret_cast<std::uint8_t const *>(buffer) + written;
+ length -= written;
+ actual += written;
+ }
+ return std::error_condition();
+ }
+};
+
+
+// helper class for holding a core_file and closing it (or not) as necessary
+
+class core_file_adapter_base : public virtual random_access
+{
+public:
+ virtual ~core_file_adapter_base()
+ {
+ if (m_close)
+ delete m_file;
+ }
+
+ virtual std::error_condition seek(std::int64_t offset, int whence) noexcept override
+ {
+ if (!m_file->seek(offset, whence))
+ return std::error_condition();
+ else
+ return std::errc::invalid_argument; // TODO: better error reporting for core_file
+ }
+
+ virtual std::error_condition tell(std::uint64_t &result) noexcept override
+ {
+ // no error reporting
+ result = m_file->tell();
+ return std::error_condition();
+ }
+
+ virtual std::error_condition length(std::uint64_t &result) noexcept override
+ {
+ // no error reporting
+ result = m_file->size();
+ return std::error_condition();
+ }
+
+protected:
+ core_file_adapter_base(core_file::ptr &&file) noexcept : m_file(file.release()), m_close(true)
+ {
+ assert(m_file);
+ }
+
+ core_file_adapter_base(core_file &file) noexcept : m_file(&file), m_close(false)
+ {
+ }
+
+ core_file &file() noexcept
+ {
+ return *m_file;
+ }
+
+private:
+ core_file *const m_file;
+ bool const m_close;
+};
+
+
+// core_file read implementation
+
+class core_file_read_adapter : public core_file_adapter_base, public virtual random_read
+{
+public:
+ core_file_read_adapter(core_file::ptr &&file) noexcept : core_file_adapter_base(std::move(file))
+ {
+ }
+
+ core_file_read_adapter(core_file &file) noexcept : core_file_adapter_base(file)
+ {
+ }
+
+ virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ // TODO: should the client have to deal with reading less than expected even if EOF isn't hit?
+ if (std::numeric_limits<std::uint32_t>::max() < length)
+ {
+ actual = 0U;
+ return std::errc::invalid_argument;
+ }
+
+ // no way to distinguish between EOF and error
+ actual = file().read(buffer, std::uint32_t(length));
+ return std::error_condition();
+ }
+
+ virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ actual = 0U;
+
+ // TODO: should the client have to deal with reading less than expected even if EOF isn't hit?
+ if (std::numeric_limits<std::uint32_t>::max() < length)
+ return std::errc::invalid_argument;
+
+ // no error reporting for tell
+ std::uint64_t const oldpos = file().tell();
+ if (file().seek(offset, SEEK_SET))
+ {
+ file().seek(oldpos, SEEK_SET);
+ return std::errc::invalid_argument; // TODO: better error reporting for core_file
+ }
+
+ // no way to distinguish between EOF and error
+ actual = file().read(buffer, std::uint32_t(length));
+
+ if (!file().seek(oldpos, SEEK_SET))
+ return std::error_condition();
+ else
+ return std::errc::invalid_argument; // TODO: better error reporting for core_file
+ }
+};
+
+
+// core_file read/write implementation
+
+class core_file_read_write_adapter : public core_file_read_adapter, public random_read_write
+{
+public:
+ using core_file_read_adapter::core_file_read_adapter;
+
+ virtual std::error_condition finalize() noexcept override
+ {
+ return std::error_condition();
+ }
+
+ virtual std::error_condition flush() noexcept override
+ {
+ return file().flush();
+ }
+
+ virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ actual = 0U;
+ while (length)
+ {
+ std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), length);
+ std::uint32_t const written = file().write(buffer, chunk);
+ actual += written;
+ if (written < chunk)
+ return std::errc::io_error; // TODO: better error reporting for core_file
+ buffer = reinterpret_cast<std::uint8_t const *>(buffer) + written;
+ length -= written;
+ }
+ return std::error_condition();
+ }
+
+ virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ actual = 0U;
+
+ // no error reporting for tell
+ std::uint64_t const oldpos = file().tell();
+ if (file().seek(offset, SEEK_SET))
+ {
+ file().seek(oldpos, SEEK_SET);
+ return std::errc::invalid_argument; // TODO: better error reporting for core_file
+ }
+
+ std::error_condition err;
+ while (!err && length)
+ {
+ std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), length);
+ std::uint32_t const written = file().write(buffer, chunk);
+ actual += written;
+ if (written < chunk)
+ {
+ err = std::errc::io_error; // TODO: better error reporting for core_file
+ }
+ else
+ {
+ buffer = reinterpret_cast<std::uint8_t const *>(buffer) + written;
+ length -= written;
+ }
+ }
+
+ if (!file().seek(oldpos, SEEK_SET) || err)
+ return err;
+ else
+ return std::errc::invalid_argument; // TODO: better error reporting for core_file
+ }
+};
+
+
+// core_file helper that fills space when writing past the end-of-file
+
+class core_file_read_write_filler : public random_read_fill_wrapper<core_file_read_write_adapter>
+{
+public:
+ core_file_read_write_filler(core_file::ptr &&file, std::uint8_t fill) noexcept : random_read_fill_wrapper<core_file_read_write_adapter>(std::move(file))
+ {
+ set_filler(fill);
+ }
+
+ core_file_read_write_filler(core_file &file, std::uint8_t fill) noexcept : random_read_fill_wrapper<core_file_read_write_adapter>(file)
+ {
+ set_filler(fill);
+ }
+
+ virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ // no error reporting for tell or size
+ std::uint64_t const offset = file().tell();
+ std::uint64_t endpos = file().size();
+ if (endpos < offset)
+ {
+ if (file().seek(endpos, SEEK_SET))
+ {
+ file().seek(offset, SEEK_SET);
+ actual = 0U;
+ return std::errc::invalid_argument; // TODO: better error reporting for core_file
+ }
+ std::uint8_t block[1024];
+ std::fill_n(
+ block,
+ std::min<std::common_type_t<std::size_t, std::uint64_t> >(std::size(block), offset - endpos),
+ get_filler());
+ do
+ {
+ std::uint32_t const chunk = std::min<std::common_type_t<std::size_t, std::uint64_t> >(sizeof(block), offset - endpos);
+ std::uint32_t const filled = file().write(block, chunk);
+ endpos += filled;
+ if (chunk != filled)
+ {
+ file().seek(offset, SEEK_SET);
+ actual = 0U;
+ return std::errc::io_error; // TODO: better error reporting for core_file
+ }
+ }
+ while (endpos < offset);
+ }
+
+ return core_file_read_write_adapter::write(buffer, length, actual);
+ }
+
+ virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override
+ {
+ actual = 0U;
+
+ // no error reporting for tell
+ std::uint64_t const oldpos = file().tell();
+ if (file().seek(0, SEEK_END))
+ {
+ file().seek(oldpos, SEEK_SET);
+ return std::errc::invalid_argument; // TODO: better error reporting for core_file
+ }
+ std::uint64_t endpos = file().tell();
+
+ std::error_condition err;
+ if (endpos > offset)
+ {
+ if (file().seek(offset, SEEK_SET))
+ err = std::errc::invalid_argument; // TODO: better error reporting for core_file
+ }
+ else if (endpos < offset)
+ {
+ std::uint8_t block[1024];
+ std::fill_n(
+ block,
+ std::min<std::common_type_t<std::size_t, std::uint64_t> >(std::size(block), offset - endpos),
+ get_filler());
+ do
+ {
+ std::uint32_t const chunk = std::min<std::common_type_t<std::size_t, std::uint64_t> >(sizeof(block), offset - endpos);
+ std::uint32_t const filled = file().write(block, chunk);
+ endpos += filled;
+ if (chunk != filled)
+ err = std::errc::io_error; // TODO: better error reporting for core_file
+ }
+ while (!err && (endpos < offset));
+ }
+
+ while (!err && length)
+ {
+ std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), length);
+ std::uint32_t const written = file().write(buffer, chunk);
+ actual += written;
+ if (written < chunk)
+ {
+ err = std::errc::io_error; // TODO: better error reporting for core_file
+ }
+ else
+ {
+ buffer = reinterpret_cast<std::uint8_t const *>(buffer) + written;
+ length -= written;
+ }
+ }
+
+ if (!file().seek(oldpos, SEEK_SET) || err)
+ return err;
+ else
+ return std::errc::invalid_argument; // TODO: better error reporting for core_file
+ }
+};
+
+} // anonymous namespace
+
+
+// creating RAM read adapters
+
+random_read::ptr ram_read(void const *data, std::size_t size) noexcept
+{
+ random_read::ptr result;
+ if (data || !size)
+ result.reset(new (std::nothrow) ram_read_adapter<std::uint8_t const *const, false>(data, size));
+ return result;
+}
+
+random_read::ptr ram_read(void const *data, std::size_t size, std::uint8_t filler) noexcept
+{
+ std::unique_ptr<random_read_fill_wrapper<ram_read_adapter<std::uint8_t const *const, false> > > result;
+ if (data || !size)
+ result.reset(new (std::nothrow) decltype(result)::element_type(data, size));
+ if (result)
+ result->set_filler(filler);
+ return result;
+}
+
+random_read::ptr ram_read_copy(void const *data, std::size_t size) noexcept
+{
+ random_read::ptr result;
+ void *const copy = size ? std::malloc(size) : nullptr;
+ if (copy)
+ std::memcpy(copy, data, size);
+ if (copy || !size)
+ result.reset(new (std::nothrow) ram_read_adapter<std::uint8_t *const, true>(copy, size));
+ if (!result)
+ std::free(copy);
+ return result;
+}
+
+random_read::ptr ram_read_copy(void const *data, std::size_t size, std::uint8_t filler) noexcept
+{
+ std::unique_ptr<random_read_fill_wrapper<ram_read_adapter<std::uint8_t *const, true> > > result;
+ void *const copy = size ? std::malloc(size) : nullptr;
+ if (copy)
+ std::memcpy(copy, data, size);
+ if (copy || !size)
+ result.reset(new (std::nothrow) decltype(result)::element_type(copy, size));
+ if (!result)
+ std::free(copy);
+ return result;
+}
+
+
+// creating stdio read adapters
+
+random_read::ptr stdio_read(FILE *file) noexcept
+{
+ random_read::ptr result;
+ if (file)
+ result.reset(new (std::nothrow) stdio_read_adapter(file, true));
+ return result;
+}
+
+random_read::ptr stdio_read(FILE *file, std::uint8_t filler) noexcept
+{
+ std::unique_ptr<random_read_fill_wrapper<stdio_read_adapter> > result;
+ if (file)
+ result.reset(new (std::nothrow) decltype(result)::element_type(file, true));
+ if (result)
+ result->set_filler(filler);
+ return result;
+}
+
+random_read::ptr stdio_read_noclose(FILE *file) noexcept
+{
+ random_read::ptr result;
+ if (file)
+ result.reset(new (std::nothrow) stdio_read_adapter(file, false));
+ return result;
+}
+
+random_read::ptr stdio_read_noclose(FILE *file, std::uint8_t filler) noexcept
+{
+ std::unique_ptr<random_read_fill_wrapper<stdio_read_adapter> > result;
+ if (file)
+ result.reset(new (std::nothrow) decltype(result)::element_type(file, false));
+ if (result)
+ result->set_filler(filler);
+ return result;
+}
+
+
+// creating stdio read/write adapters
+
+random_read_write::ptr stdio_read_write(FILE *file) noexcept
+{
+ random_read_write::ptr result;
+ if (file)
+ result.reset(new (std::nothrow) stdio_read_write_adapter(file, true));
+ return result;
+}
+
+random_read_write::ptr stdio_read_write(FILE *file, std::uint8_t filler) noexcept
+{
+ random_read_write::ptr result;
+ if (file)
+ result.reset(new (std::nothrow) stdio_read_write_filler(file, true, filler));
+ return result;
+}
+
+random_read_write::ptr stdio_read_write_noclose(FILE *file) noexcept
+{
+ random_read_write::ptr result;
+ if (file)
+ result.reset(new (std::nothrow) stdio_read_write_adapter(file, false));
+ return result;
+}
+
+random_read_write::ptr stdio_read_write_noclose(FILE *file, std::uint8_t filler) noexcept
+{
+ random_read_write::ptr result;
+ if (file)
+ result.reset(new (std::nothrow) stdio_read_write_filler(file, false, filler));
+ return result;
+}
+
+
+// creating osd_file read adapters
+
+random_read::ptr osd_file_read(osd_file::ptr &&file) noexcept
+{
+ random_read::ptr result;
+ if (file)
+ result.reset(new (std::nothrow) osd_file_read_adapter(std::move(file)));
+ return result;
+}
+
+random_read::ptr osd_file_read(osd_file &file) noexcept
+{
+ return random_read::ptr(new (std::nothrow) osd_file_read_adapter(file));
+}
+
+
+// creating osd_file read/write adapters
+
+random_read_write::ptr osd_file_read_write(osd_file::ptr &&file) noexcept
+{
+ random_read_write::ptr result;
+ if (file)
+ result.reset(new (std::nothrow) osd_file_read_write_adapter(std::move(file)));
+ return result;
+}
+
+random_read_write::ptr osd_file_read_write(osd_file &file) noexcept
+{
+ return random_read_write::ptr(new (std::nothrow) osd_file_read_write_adapter(file));
+}
+
+
+// creating core_file read adapters
+
+random_read::ptr core_file_read(core_file::ptr &&file) noexcept
+{
+ random_read::ptr result;
+ if (file)
+ result.reset(new (std::nothrow) core_file_read_adapter(std::move(file)));
+ return result;
+}
+
+random_read::ptr core_file_read(core_file::ptr &&file, std::uint8_t filler) noexcept
+{
+ std::unique_ptr<random_read_fill_wrapper<core_file_read_adapter> > result;
+ if (file)
+ result.reset(new (std::nothrow) decltype(result)::element_type(std::move(file)));
+ if (result)
+ result->set_filler(filler);
+ return result;
+}
+
+random_read::ptr core_file_read(core_file &file) noexcept
+{
+ return random_read::ptr(new (std::nothrow) core_file_read_adapter(file));
+}
+
+random_read::ptr core_file_read(core_file &file, std::uint8_t filler) noexcept
+{
+ std::unique_ptr<random_read_fill_wrapper<core_file_read_adapter> > result;
+ result.reset(new (std::nothrow) decltype(result)::element_type(file));
+ if (result)
+ result->set_filler(filler);
+ return result;
+}
+
+
+// creating core_file read/write adapters
+
+random_read_write::ptr core_file_read_write(core_file::ptr &&file) noexcept
+{
+ random_read_write::ptr result;
+ if (file)
+ result.reset(new (std::nothrow) core_file_read_write_adapter(std::move(file)));
+ return result;
+}
+
+random_read_write::ptr core_file_read_write(core_file::ptr &&file, std::uint8_t filler) noexcept
+{
+ random_read_write::ptr result;
+ if (file)
+ result.reset(new (std::nothrow) core_file_read_write_filler(std::move(file), filler));
+ return result;
+}
+
+random_read_write::ptr core_file_read_write(core_file &file) noexcept
+{
+ return random_read_write::ptr(new (std::nothrow) core_file_read_write_adapter(file));
+}
+
+random_read_write::ptr core_file_read_write(core_file &file, std::uint8_t filler) noexcept
+{
+ return random_read_write::ptr(new (std::nothrow) core_file_read_write_filler(file, filler));
+}
+
+} // namespace util