diff options
author | 2021-10-05 03:34:45 +1100 | |
---|---|---|
committer | 2021-10-05 03:34:45 +1100 | |
commit | aeb9eae87469e67bc6a91caf3840c34c11d959fc (patch) | |
tree | 45bffedf374dbce47caca633ad7bda547592e6c9 /src/lib/util/corefile.cpp | |
parent | 33723892a3f06e678d817b36aef84364c32848ec (diff) |
util: Further API cleanups: (#8661)
* Turned `core_file` into an implementation of `random_read_write`.
* Turned PNG errors into a standard error category.
* Added a helper for generating what look like derived classes on-the-fly.
Diffstat (limited to 'src/lib/util/corefile.cpp')
-rw-r--r-- | src/lib/util/corefile.cpp | 581 |
1 files changed, 374 insertions, 207 deletions
diff --git a/src/lib/util/corefile.cpp b/src/lib/util/corefile.cpp index 0c72e1337be..6406aea49f4 100644 --- a/src/lib/util/corefile.cpp +++ b/src/lib/util/corefile.cpp @@ -21,6 +21,7 @@ #include <cctype> #include <cstring> #include <iterator> +#include <limits> namespace util { @@ -45,26 +46,30 @@ class core_proxy_file : public core_file { public: core_proxy_file(core_file &file) noexcept : m_file(file) { } - virtual ~core_proxy_file() override { } - virtual int seek(std::int64_t offset, int whence) override { return m_file.seek(offset, whence); } - virtual std::uint64_t tell() const override { return m_file.tell(); } + virtual std::error_condition seek(std::int64_t offset, int whence) noexcept override { return m_file.seek(offset, whence); } + virtual std::error_condition tell(std::uint64_t &result) noexcept override { return m_file.tell(result); } + virtual std::error_condition length(std::uint64_t &result) noexcept override { return m_file.length(result); } + + virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override { return m_file.read(buffer, length, actual); } + virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override { return m_file.read_at(offset, buffer, length, actual); } + + virtual std::error_condition finalize() noexcept override { return m_file.finalize(); } + virtual std::error_condition flush() noexcept override { return m_file.flush(); } + virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override { return m_file.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 { return m_file.write_at(offset, buffer, length, actual); } + virtual bool eof() const override { return m_file.eof(); } - virtual std::uint64_t size() const override { return m_file.size(); } - virtual std::uint32_t read(void *buffer, std::uint32_t length) override { return m_file.read(buffer, length); } virtual int getc() override { return m_file.getc(); } virtual int ungetc(int c) override { return m_file.ungetc(c); } virtual char *gets(char *s, int n) override { return m_file.gets(s, n); } - virtual const void *buffer() override { return m_file.buffer(); } + virtual void const *buffer() override { return m_file.buffer(); } - virtual std::uint32_t write(const void *buffer, std::uint32_t length) override { return m_file.write(buffer, length); } virtual int puts(std::string_view s) override { return m_file.puts(s); } virtual int vprintf(util::format_argument_pack<std::ostream> const &args) override { return m_file.vprintf(args); } virtual std::error_condition truncate(std::uint64_t offset) override { return m_file.truncate(offset); } - virtual std::error_condition flush() override { return m_file.flush(); } - private: core_file &m_file; }; @@ -99,12 +104,12 @@ protected: { } - bool read_access() const { return 0U != (m_openflags & OPEN_FLAG_READ); } - bool write_access() const { return 0U != (m_openflags & OPEN_FLAG_WRITE); } - bool no_bom() const { return 0U != (m_openflags & OPEN_FLAG_NO_BOM); } + bool read_access() const noexcept { return 0U != (m_openflags & OPEN_FLAG_READ); } + bool write_access() const noexcept { return 0U != (m_openflags & OPEN_FLAG_WRITE); } + bool no_bom() const noexcept { return 0U != (m_openflags & OPEN_FLAG_NO_BOM); } - bool has_putback() const { return m_back_char_head != m_back_char_tail; } - void clear_putback() { m_back_char_head = m_back_char_tail = 0; } + bool has_putback() const noexcept { return m_back_char_head != m_back_char_tail; } + void clear_putback() noexcept { m_back_char_head = m_back_char_tail = 0; } private: std::uint32_t const m_openflags; // flags we were opened with @@ -119,7 +124,7 @@ private: class core_in_memory_file : public core_text_file { public: - core_in_memory_file(std::uint32_t openflags, void const *data, std::size_t length, bool copy) + core_in_memory_file(std::uint32_t openflags, void const *data, std::size_t length, bool copy) noexcept : core_text_file(openflags) , m_data_allocated(false) , m_data(copy ? nullptr : data) @@ -136,20 +141,26 @@ public: ~core_in_memory_file() override { purge(); } - virtual int seek(std::int64_t offset, int whence) override; - virtual std::uint64_t tell() const override { return m_offset; } + virtual std::error_condition seek(std::int64_t offset, int whence) noexcept override; + virtual std::error_condition tell(std::uint64_t &result) noexcept override { result = m_offset; return std::error_condition(); } + virtual std::error_condition length(std::uint64_t &result) noexcept override { result = m_length; return std::error_condition(); } + + virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override; + virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override; + + virtual std::error_condition finalize() noexcept override { return std::error_condition(); } + virtual std::error_condition flush() noexcept override { clear_putback(); return std::error_condition(); } + virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override { actual = 0; return std::errc::bad_file_descriptor; } + virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override { actual = 0; return std::errc::bad_file_descriptor; } + virtual bool eof() const override; - virtual std::uint64_t size() const override { return m_length; } - virtual std::uint32_t read(void *buffer, std::uint32_t length) override; virtual void const *buffer() override { return m_data; } - virtual std::uint32_t write(void const *buffer, std::uint32_t length) override { return 0; } virtual std::error_condition truncate(std::uint64_t offset) override; - virtual std::error_condition flush() override { clear_putback(); return std::error_condition(); } protected: - core_in_memory_file(std::uint32_t openflags, std::uint64_t length) + core_in_memory_file(std::uint32_t openflags, std::uint64_t length) noexcept : core_text_file(openflags) , m_data_allocated(false) , m_data(nullptr) @@ -158,10 +169,11 @@ protected: { } - bool is_loaded() const { return nullptr != m_data; } - void *allocate() + bool is_loaded() const noexcept { return nullptr != m_data; } + + void *allocate() noexcept { - if (m_data) + if (m_data || (std::numeric_limits<std::size_t>::max() < m_length)) return nullptr; void *data = malloc(m_length); if (data) @@ -171,7 +183,8 @@ protected: } return data; } - void purge() + + void purge() noexcept { if (m_data && m_data_allocated) free(const_cast<void *>(m_data)); @@ -179,14 +192,14 @@ protected: m_data = nullptr; } - std::uint64_t offset() const { return m_offset; } - void add_offset(std::uint32_t increment) { m_offset += increment; m_length = (std::max)(m_length, m_offset); } - std::uint64_t length() const { return m_length; } - void set_length(std::uint64_t value) { m_length = value; m_offset = (std::min)(m_offset, m_length); } + std::uint64_t offset() const noexcept { return m_offset; } + void add_offset(std::size_t increment) noexcept { m_offset += increment; m_length = (std::max)(m_length, m_offset); } + std::uint64_t length() const noexcept { return m_length; } + void set_length(std::uint64_t value) noexcept { m_length = value; } static std::size_t safe_buffer_copy( void const *source, std::size_t sourceoffs, std::size_t sourcelen, - void *dest, std::size_t destoffs, std::size_t destlen); + void *dest, std::size_t destoffs, std::size_t destlen) noexcept; private: bool m_data_allocated; // was the data allocated by us? @@ -196,35 +209,38 @@ private: }; -class core_osd_file : public core_in_memory_file +class core_osd_file final : public core_in_memory_file { public: core_osd_file(std::uint32_t openmode, osd_file::ptr &&file, std::uint64_t length) : core_in_memory_file(openmode, length) , m_file(std::move(file)) - , m_bufferbase(0) - , m_bufferbytes(0) { } ~core_osd_file() override; - virtual std::uint32_t read(void *buffer, std::uint32_t length) override; + virtual std::error_condition read(void *buffer, std::size_t length, std::size_t &actual) noexcept override; + virtual std::error_condition read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override; + + virtual std::error_condition finalize() noexcept override; + virtual std::error_condition flush() noexcept override; + virtual std::error_condition write(void const *buffer, std::size_t length, std::size_t &actual) noexcept override; + virtual std::error_condition write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override; + virtual void const *buffer() override; - virtual std::uint32_t write(void const *buffer, std::uint32_t length) override; virtual std::error_condition truncate(std::uint64_t offset) override; - virtual std::error_condition flush() override; protected: - bool is_buffered() const { return (offset() >= m_bufferbase) && (offset() < (m_bufferbase + m_bufferbytes)); } + bool is_buffered(std::uint64_t offset) const noexcept { return (offset >= m_bufferbase) && (offset < (m_bufferbase + m_bufferbytes)); } private: static constexpr std::size_t FILE_BUFFER_SIZE = 512; osd_file::ptr m_file; // OSD file handle - std::uint64_t m_bufferbase; // base offset of internal buffer - std::uint32_t m_bufferbytes; // bytes currently loaded into buffer + std::uint64_t m_bufferbase = 0U; // base offset of internal buffer + std::uint32_t m_bufferbytes = 0U; // bytes currently loaded into buffer std::uint8_t m_buffer[FILE_BUFFER_SIZE]; // buffer data }; @@ -240,54 +256,57 @@ private: int core_text_file::getc() { - int result; - // refresh buffer, if necessary if (m_back_char_head == m_back_char_tail) { // do we need to check the byte order marks? - if (tell() == 0) + std::uint64_t pos; + if (!tell(pos)) { - std::uint8_t bom[4]; - int pos = 0; - - if (read(bom, 4) == 4) + if (!pos) { - if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) - { - m_text_type = text_file_type::UTF8; - pos = 3; - } - else if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == 0xfe && bom[3] == 0xff) - { - m_text_type = text_file_type::UTF32BE; - pos = 4; - } - else if (bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0x00 && bom[3] == 0x00) + std::size_t readlen; + std::uint8_t bom[4]; + read(bom, 4, readlen); + if (readlen == 4) { - m_text_type = text_file_type::UTF32LE; - pos = 4; - } - else if (bom[0] == 0xfe && bom[1] == 0xff) - { - m_text_type = text_file_type::UTF16BE; - pos = 2; - } - else if (bom[0] == 0xff && bom[1] == 0xfe) - { - m_text_type = text_file_type::UTF16LE; - pos = 2; - } - else - { - m_text_type = text_file_type::OSD; - pos = 0; + if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) + { + m_text_type = text_file_type::UTF8; + pos = 3; + } + else if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == 0xfe && bom[3] == 0xff) + { + m_text_type = text_file_type::UTF32BE; + pos = 4; + } + else if (bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0x00 && bom[3] == 0x00) + { + m_text_type = text_file_type::UTF32LE; + pos = 4; + } + else if (bom[0] == 0xfe && bom[1] == 0xff) + { + m_text_type = text_file_type::UTF16BE; + pos = 2; + } + else if (bom[0] == 0xff && bom[1] == 0xfe) + { + m_text_type = text_file_type::UTF16LE; + pos = 2; + } + else + { + m_text_type = text_file_type::OSD; + pos = 0; + } } + seek(pos, SEEK_SET); // FIXME: don't assume seeking is possible, check for errors } - seek(pos, SEEK_SET); } // fetch the next character + // FIXME: all of this plays fast and loose with error checking and seeks backwards far too frequently char16_t utf16_buffer[UTF16_CHAR_MAX]; auto uchar = char32_t(~0); switch (m_text_type) @@ -296,7 +315,8 @@ int core_text_file::getc() case text_file_type::OSD: { char default_buffer[16]; - auto const readlen = read(default_buffer, sizeof(default_buffer)); + std::size_t readlen; + read(default_buffer, sizeof(default_buffer), readlen); if (readlen > 0) { auto const charlen = osd_uchar_from_osdchar(&uchar, default_buffer, readlen / sizeof(default_buffer[0])); @@ -308,7 +328,8 @@ int core_text_file::getc() case text_file_type::UTF8: { char utf8_buffer[UTF8_CHAR_MAX]; - auto const readlen = read(utf8_buffer, sizeof(utf8_buffer)); + std::size_t readlen; + read(utf8_buffer, sizeof(utf8_buffer), readlen); if (readlen > 0) { auto const charlen = uchar_from_utf8(&uchar, utf8_buffer, readlen / sizeof(utf8_buffer[0])); @@ -319,7 +340,8 @@ int core_text_file::getc() case text_file_type::UTF16BE: { - auto const readlen = read(utf16_buffer, sizeof(utf16_buffer)); + std::size_t readlen; + read(utf16_buffer, sizeof(utf16_buffer), readlen); if (readlen > 0) { auto const charlen = uchar_from_utf16be(&uchar, utf16_buffer, readlen / sizeof(utf16_buffer[0])); @@ -330,7 +352,8 @@ int core_text_file::getc() case text_file_type::UTF16LE: { - auto const readlen = read(utf16_buffer, sizeof(utf16_buffer)); + std::size_t readlen; + read(utf16_buffer, sizeof(utf16_buffer), readlen); if (readlen > 0) { auto const charlen = uchar_from_utf16le(&uchar, utf16_buffer, readlen / sizeof(utf16_buffer[0])); @@ -340,13 +363,23 @@ int core_text_file::getc() break; case text_file_type::UTF32BE: - if (read(&uchar, sizeof(uchar)) == sizeof(uchar)) - uchar = big_endianize_int32(uchar); + { + // FIXME: deal with read returning short + std::size_t readlen; + read(&uchar, sizeof(uchar), readlen); + if (sizeof(uchar) == readlen) + uchar = big_endianize_int32(uchar); + } break; case text_file_type::UTF32LE: - if (read(&uchar, sizeof(uchar)) == sizeof(uchar)) - uchar = little_endianize_int32(uchar); + { + // FIXME: deal with read returning short + std::size_t readlen; + read(&uchar, sizeof(uchar), readlen); + if (sizeof(uchar) == readlen) + uchar = little_endianize_int32(uchar); + } break; } @@ -360,6 +393,7 @@ int core_text_file::getc() } // now read from the ring buffer + int result; if (m_back_char_head == m_back_char_tail) result = EOF; else @@ -433,21 +467,32 @@ char *core_text_file::gets(char *s, int n) /*------------------------------------------------- - puts - write a line to a text file + puts - write a string to a text file -------------------------------------------------*/ int core_text_file::puts(std::string_view s) { + // TODO: what to do about write errors or short writes (interrupted)? + // The API doesn't lend itself to reporting the error as the return + // value includes extra bytes inserted like the UTF-8 marker and + // carriage returns. char convbuf[1024]; char *pconvbuf = convbuf; int count = 0; // is this the beginning of the file? if so, write a byte order mark - if (tell() == 0 && !no_bom()) + if (!no_bom()) { - *pconvbuf++ = char(0xef); - *pconvbuf++ = char(0xbb); - *pconvbuf++ = char(0xbf); + std::uint64_t offset; + if (!tell(offset)) + { + if (!offset) + { + *pconvbuf++ = char(0xef); + *pconvbuf++ = char(0xbb); + *pconvbuf++ = char(0xbf); + } + } } // convert '\n' to platform dependant line endings @@ -471,14 +516,20 @@ int core_text_file::puts(std::string_view s) // if we overflow, break into chunks if (pconvbuf >= convbuf + std::size(convbuf) - 10) { - count += write(convbuf, pconvbuf - convbuf); + std::size_t written; + write(convbuf, pconvbuf - convbuf, written); // FIXME: error ignored here + count += written; pconvbuf = convbuf; } } // final flush if (pconvbuf != convbuf) - count += write(convbuf, pconvbuf - convbuf); + { + std::size_t written; + write(convbuf, pconvbuf - convbuf, written); // FIXME: error ignored here + count += written; + } return count; } @@ -507,27 +558,49 @@ int core_text_file::vprintf(util::format_argument_pack<std::ostream> const &args seek - seek within a file -------------------------------------------------*/ -int core_in_memory_file::seek(std::int64_t offset, int whence) +std::error_condition core_in_memory_file::seek(std::int64_t offset, int whence) noexcept { // flush any buffered char - clear_putback(); + clear_putback(); // TODO: report errors; also, should the argument check happen before this? // switch off the relative location switch (whence) { case SEEK_SET: + if (0 > offset) + return std::errc::invalid_argument; m_offset = offset; - break; + return std::error_condition(); case SEEK_CUR: + if (0 > offset) + { + if (std::uint64_t(-offset) > m_offset) + return std::errc::invalid_argument; + } + else if ((std::numeric_limits<std::uint64_t>::max() - offset) < m_offset) + { + return std::errc::invalid_argument; + } m_offset += offset; - break; + return std::error_condition(); case SEEK_END: + if (0 > offset) + { + if (std::uint64_t(-offset) > m_length) + return std::errc::invalid_argument; + } + else if ((std::numeric_limits<std::uint64_t>::max() - offset) < m_length) + { + return std::errc::invalid_argument; + } m_offset = m_length + offset; - break; + return std::error_condition(); + + default: + return std::errc::invalid_argument; } - return 0; } @@ -550,14 +623,29 @@ bool core_in_memory_file::eof() const read - read from a file -------------------------------------------------*/ -std::uint32_t core_in_memory_file::read(void *buffer, std::uint32_t length) +std::error_condition core_in_memory_file::read(void *buffer, std::size_t length, std::size_t &actual) noexcept { clear_putback(); // handle RAM-based files - auto const bytes_read = safe_buffer_copy(m_data, std::size_t(m_offset), std::size_t(m_length), buffer, 0, length); - m_offset += bytes_read; - return bytes_read; + if (m_offset < m_length) + actual = safe_buffer_copy(m_data, std::size_t(m_offset), std::size_t(m_length), buffer, 0, length); + else + actual = 0U; + m_offset += actual; + return std::error_condition(); +} + +std::error_condition core_in_memory_file::read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept +{ + clear_putback(); + + // handle RAM-based files + if (offset < m_length) + actual = safe_buffer_copy(m_data, std::size_t(offset), std::size_t(m_length), buffer, 0, length); + else + actual = 0U; + return std::error_condition(); } @@ -583,7 +671,7 @@ std::error_condition core_in_memory_file::truncate(std::uint64_t offset) std::size_t core_in_memory_file::safe_buffer_copy( void const *source, std::size_t sourceoffs, std::size_t sourcelen, - void *dest, std::size_t destoffs, std::size_t destlen) + void *dest, std::size_t destoffs, std::size_t destlen) noexcept { auto const sourceavail = sourcelen - sourceoffs; auto const destavail = destlen - destoffs; @@ -618,45 +706,65 @@ core_osd_file::~core_osd_file() read - read from a file -------------------------------------------------*/ -std::uint32_t core_osd_file::read(void *buffer, std::uint32_t length) +std::error_condition core_osd_file::read(void *buffer, std::size_t length, std::size_t &actual) noexcept +{ + // since osd_file works like pread/pwrite, implement in terms of read_at + // core_osd_file is delcared final, so a derived class can't interfere + std::error_condition err = read_at(offset(), buffer, length, actual); + add_offset(actual); + return err; +} + + +std::error_condition core_osd_file::read_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept { if (!m_file || is_loaded()) - return core_in_memory_file::read(buffer, length); + return core_in_memory_file::read_at(offset, buffer, length, actual); // flush any buffered char clear_putback(); - std::uint32_t bytes_read = 0; + actual = 0U; + std::error_condition err; // if we're within the buffer, consume that first - if (is_buffered()) - bytes_read += safe_buffer_copy(m_buffer, offset() - m_bufferbase, m_bufferbytes, buffer, bytes_read, length); + if (is_buffered(offset)) + actual += safe_buffer_copy(m_buffer, offset - m_bufferbase, m_bufferbytes, buffer, actual, length); // if we've got a small amount left, read it into the buffer first - if (bytes_read < length) + if (actual < length) { - if ((length - bytes_read) < (sizeof(m_buffer) / 2)) + if ((length - actual) < (sizeof(m_buffer) / 2)) { // read as much as makes sense into the buffer - m_bufferbase = offset() + bytes_read; - m_bufferbytes = 0; - m_file->read(m_buffer, m_bufferbase, sizeof(m_buffer), m_bufferbytes); - - // do a bounded copy from the buffer to the destination - bytes_read += safe_buffer_copy(m_buffer, 0, m_bufferbytes, buffer, bytes_read, length); + m_bufferbase = offset + actual; + err = m_file->read(m_buffer, m_bufferbase, sizeof(m_buffer), m_bufferbytes); + + // do a bounded copy from the buffer to the destination if it succeeded + if (!err) + actual += safe_buffer_copy(m_buffer, 0, m_bufferbytes, buffer, actual, length); + else + m_bufferbytes = 0U; } else { // read the remainder directly from the file - std::uint32_t new_bytes_read = 0; - m_file->read(reinterpret_cast<std::uint8_t *>(buffer) + bytes_read, offset() + bytes_read, length - bytes_read, new_bytes_read); - bytes_read += new_bytes_read; + do + { + // may need to split into chunks if size_t is larger than 32 bits + 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 - actual); + std::uint32_t bytes_read; + err = m_file->read(reinterpret_cast<std::uint8_t *>(buffer) + actual, offset + actual, chunk, bytes_read); + if (err || !bytes_read) + break; + actual += bytes_read; + } + while (actual < length); } } - // return the number of bytes read - add_offset(bytes_read); - return bytes_read; + // return any errors + return err; } @@ -672,17 +780,29 @@ void const *core_osd_file::buffer() if (!is_loaded() && length()) { // allocate some memory - void *buf = allocate(); + void *const buf = allocate(); if (!buf) return nullptr; // read the file - std::uint32_t read_length = 0; - auto const filerr = m_file->read(buf, 0, length(), read_length); - if (filerr || (read_length != length())) - purge(); - else - m_file.reset(); // close the file because we don't need it anymore + std::uint64_t bytes_read = 0; + std::uint64_t remaining = length(); + std::uint8_t *ptr = reinterpret_cast<std::uint8_t *>(buf); + while (remaining) + { + std::uint32_t const chunk = std::min<std::common_type_t<std::uint32_t, std::size_t> >(std::numeric_limits<std::uint32_t>::max(), remaining); + std::uint32_t read_length; + std::error_condition const filerr = m_file->read(ptr, bytes_read, chunk, read_length); + if (filerr || !read_length) + { + purge(); + return core_in_memory_file::buffer(); + } + bytes_read += read_length; + remaining -= read_length; + ptr += read_length; + } + m_file.reset(); // close the file because we don't need it anymore } return core_in_memory_file::buffer(); } @@ -692,25 +812,46 @@ void const *core_osd_file::buffer() write - write to a file -------------------------------------------------*/ -std::uint32_t core_osd_file::write(void const *buffer, std::uint32_t length) +std::error_condition core_osd_file::write(void const *buffer, std::size_t length, std::size_t &actual) noexcept +{ + // since osd_file works like pread/pwrite, implement in terms of write_at + // core_osd_file is delcared final, so a derived class can't interfere + std::error_condition err = write_at(offset(), buffer, length, actual); + add_offset(actual); + return err; +} + + +std::error_condition core_osd_file::write_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept { // can't write to RAM-based stuff if (is_loaded()) - return core_in_memory_file::write(buffer, length); + return core_in_memory_file::write_at(offset, buffer, length, actual); // flush any buffered char clear_putback(); // invalidate any buffered data - m_bufferbytes = 0; + m_bufferbytes = 0U; - // do the write - std::uint32_t bytes_written = 0; - m_file->write(buffer, offset(), length, bytes_written); - - // return the number of bytes written - add_offset(bytes_written); - return bytes_written; + // do the write - may need to split into chunks if size_t is larger than 32 bits + actual = 0U; + while (length) + { + // bytes written 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 bytes_written; + std::error_condition err = m_file->write(buffer, offset, chunk, bytes_written); + if (err) + return err; + assert(chunk >= bytes_written); + offset += bytes_written; + buffer = reinterpret_cast<std::uint8_t const *>(buffer) + bytes_written; + length -= bytes_written; + actual += bytes_written; + set_length((std::max)(this->length(), offset)); + } + return std::error_condition(); } @@ -724,7 +865,7 @@ std::error_condition core_osd_file::truncate(std::uint64_t offset) return core_in_memory_file::truncate(offset); // truncate file - auto const err = m_file->truncate(offset); + std::error_condition err = m_file->truncate(offset); if (err) return err; @@ -738,7 +879,16 @@ std::error_condition core_osd_file::truncate(std::uint64_t offset) flush - flush file buffers -------------------------------------------------*/ -std::error_condition core_osd_file::flush() +std::error_condition core_osd_file::finalize() noexcept +{ + if (is_loaded()) + return core_in_memory_file::finalize(); + + return std::error_condition(); +} + + +std::error_condition core_osd_file::flush() noexcept { if (is_loaded()) return core_in_memory_file::flush(); @@ -747,7 +897,7 @@ std::error_condition core_osd_file::flush() clear_putback(); // invalidate any buffered data - m_bufferbytes = 0; + m_bufferbytes = 0U; return m_file->flush(); } @@ -765,24 +915,19 @@ std::error_condition core_osd_file::flush() return an error code -------------------------------------------------*/ -std::error_condition core_file::open(std::string_view filename, std::uint32_t openflags, ptr &file) +std::error_condition core_file::open(std::string_view filename, std::uint32_t openflags, ptr &file) noexcept { - try - { - // attempt to open the file - osd_file::ptr f; - std::uint64_t length = 0; - auto const filerr = osd_file::open(std::string(filename), openflags, f, length); // FIXME: allow osd_file to accept std::string_view - if (filerr) - return filerr; - - file = std::make_unique<core_osd_file>(openflags, std::move(f), length); - return std::error_condition(); - } - catch (...) - { - return std::errc::not_enough_memory; - } + // attempt to open the file + osd_file::ptr f; + std::uint64_t length = 0; + auto const filerr = osd_file::open(std::string(filename), openflags, f, length); // FIXME: allow osd_file to accept std::string_view + if (filerr) + return filerr; + + try { file = std::make_unique<core_osd_file>(openflags, std::move(f), length); } + catch (...) { return std::errc::not_enough_memory; } + + return std::error_condition(); } @@ -791,21 +936,26 @@ std::error_condition core_file::open(std::string_view filename, std::uint32_t op like access and return an error code -------------------------------------------------*/ -std::error_condition core_file::open_ram(void const *data, std::size_t length, std::uint32_t openflags, ptr &file) +std::error_condition core_file::open_ram(void const *data, std::size_t length, std::uint32_t openflags, ptr &file) noexcept { // can only do this for read access if ((openflags & OPEN_FLAG_WRITE) || (openflags & OPEN_FLAG_CREATE)) return std::errc::invalid_argument; - try - { - file.reset(new core_in_memory_file(openflags, data, length, false)); - return std::error_condition(); - } - catch (...) - { + // if length is non-zero, data must be non-null + if (length && !data) + return std::errc::invalid_argument; + + // platforms where size_t is larger than 64 bits are theoretically possible + if (std::uint64_t(length) != length) + return std::errc::file_too_large; + + ptr result(new (std::nothrow) core_in_memory_file(openflags, data, length, false)); + if (!result) return std::errc::not_enough_memory; - } + + file = std::move(result); + return std::error_condition(); } @@ -815,25 +965,26 @@ std::error_condition core_file::open_ram(void const *data, std::size_t length, s error code -------------------------------------------------*/ -std::error_condition core_file::open_ram_copy(void const *data, std::size_t length, std::uint32_t openflags, ptr &file) +std::error_condition core_file::open_ram_copy(void const *data, std::size_t length, std::uint32_t openflags, ptr &file) noexcept { // can only do this for read access if ((openflags & OPEN_FLAG_WRITE) || (openflags & OPEN_FLAG_CREATE)) return std::errc::invalid_argument; - try - { - ptr result(new core_in_memory_file(openflags, data, length, true)); - if (!result->buffer()) - return std::errc::not_enough_memory; + // if length is non-zero, data must be non-null + if (length && !data) + return std::errc::invalid_argument; - file = std::move(result); - return std::error_condition(); - } - catch (...) - { + // platforms where size_t is larger than 64 bits are theoretically possible + if (std::uint64_t(length) != length) + return std::errc::file_too_large; + + ptr result(new (std::nothrow) core_in_memory_file(openflags, data, length, true)); + if (!result || !result->buffer()) return std::errc::not_enough_memory; - } + + file = std::move(result); + return std::error_condition(); } @@ -842,7 +993,7 @@ std::error_condition core_file::open_ram_copy(void const *data, std::size_t leng object and return an error code -------------------------------------------------*/ -std::error_condition core_file::open_proxy(core_file &file, ptr &proxy) +std::error_condition core_file::open_proxy(core_file &file, ptr &proxy) noexcept { ptr result(new (std::nothrow) core_proxy_file(file)); if (!result) @@ -868,75 +1019,91 @@ core_file::~core_file() pointer -------------------------------------------------*/ -std::error_condition core_file::load(std::string_view filename, void **data, std::uint32_t &length) +std::error_condition core_file::load(std::string_view filename, void **data, std::uint32_t &length) noexcept { - ptr file; + std::error_condition err; // attempt to open the file - auto const err = open(filename, OPEN_FLAG_READ, file); + ptr file; + err = open(filename, OPEN_FLAG_READ, file); if (err) return err; // get the size - auto const size = file->size(); - if (std::uint32_t(size) != size) + std::uint64_t size; + err = file->length(size); + if (err) + return err; + else if (std::uint32_t(size) != size) // TODO: change interface to use size_t rather than uint32_t for output size return std::errc::file_too_large; // allocate memory - *data = malloc(size); + *data = std::malloc(std::size_t(size)); if (!*data) return std::errc::not_enough_memory; length = std::uint32_t(size); // read the data - if (file->read(*data, size) != size) + if (size) { - free(*data); - return std::errc::io_error; // TODO: revisit this error code + std::size_t actual; + err = file->read(*data, std::size_t(size), actual); + if (err || (size != actual)) + { + std::free(*data); + *data = nullptr; + if (err) + return err; + else + return std::errc::io_error; // TODO: revisit this error code - either interrupted by an async signal or file truncated out from under us + } } // close the file and return data return std::error_condition(); } -std::error_condition core_file::load(std::string_view filename, std::vector<uint8_t> &data) +std::error_condition core_file::load(std::string_view filename, std::vector<uint8_t> &data) noexcept { - ptr file; + std::error_condition err; // attempt to open the file - auto const err = open(filename, OPEN_FLAG_READ, file); + ptr file; + err = open(filename, OPEN_FLAG_READ, file); if (err) return err; // get the size - auto const size = file->size(); - if (std::uint32_t(size) != size) + std::uint64_t size; + err = file->length(size); + if (err) + return err; + else if (std::size_t(size) != size) return std::errc::file_too_large; // allocate memory - try { data.resize(size); } + try { data.resize(std::size_t(size)); } catch (...) { return std::errc::not_enough_memory; } // read the data - if (file->read(&data[0], size) != size) + if (size) { - data.clear(); - return std::errc::io_error; // TODO: revisit this error code + std::size_t actual; + err = file->read(&data[0], std::size_t(size), actual); + if (err || (size != actual)) + { + data.clear(); + if (err) + return err; + else + return std::errc::io_error; // TODO: revisit this error code - either interrupted by an async signal or file truncated out from under us + } } // close the file and return data return std::error_condition(); } - -/*------------------------------------------------- - protected constructor --------------------------------------------------*/ - -core_file::core_file() -{ -} - } // namespace util |