// license:BSD-3-Clause // copyright-holders:Aaron Giles, Vas Crabb /*************************************************************************** corefile.cpp File access functions. ***************************************************************************/ #include "corefile.h" #include "coretmpl.h" #include "osdcore.h" #include "unicode.h" #include "vecstream.h" #include #include #include #include namespace util { namespace { /*************************************************************************** VALIDATION ***************************************************************************/ #if !defined(CRLF) || (CRLF < 1) || (CRLF > 3) #error CRLF undefined: must be 1 (CR), 2 (LF) or 3 (CR/LF) #endif /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ class core_proxy_file : public core_file { public: core_proxy_file(core_file &file) noexcept : m_file(file) { } 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_some(void *buffer, std::size_t length, std::size_t &actual) noexcept override { return m_file.read_some(buffer, length, actual); } virtual std::error_condition read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept override { return m_file.read_some_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_some(void const *buffer, std::size_t length, std::size_t &actual) noexcept override { return m_file.write_some(buffer, length, actual); } virtual std::error_condition write_some_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override { return m_file.write_some_at(offset, buffer, length, actual); } virtual bool eof() const override { return m_file.eof(); } 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 int puts(std::string_view s) override { return m_file.puts(s); } virtual int vprintf(util::format_argument_pack const &args) override { return m_file.vprintf(args); } virtual std::error_condition truncate(std::uint64_t offset) override { return m_file.truncate(offset); } private: core_file &m_file; }; class core_text_file : public core_file { public: enum class text_file_type { OSD, // OSD dependent encoding format used when BOMs missing UTF8, // UTF-8 UTF16BE, // UTF-16 (big endian) UTF16LE, // UTF-16 (little endian) UTF32BE, // UTF-32 (UCS-4) (big endian) UTF32LE // UTF-32 (UCS-4) (little endian) }; virtual int getc() override; virtual int ungetc(int c) override; virtual char *gets(char *s, int n) override; virtual int puts(std::string_view s) override; virtual int vprintf(util::format_argument_pack const &args) override; protected: core_text_file(std::uint32_t openflags) : m_openflags(openflags) , m_text_type(text_file_type::OSD) , m_back_char_head(0) , m_back_char_tail(0) , m_printf_buffer() { } 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 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 text_file_type m_text_type; // text output format char m_back_chars[UTF8_CHAR_MAX]; // buffer to hold characters for ungetc int m_back_char_head; // head of ungetc buffer int m_back_char_tail; // tail of ungetc buffer ovectorstream m_printf_buffer; // persistent buffer for formatted output }; class core_basic_file : public core_text_file { public: 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_index; return std::error_condition(); } virtual std::error_condition length(std::uint64_t &result) noexcept override { result = m_size; return std::error_condition(); } virtual bool eof() const override; protected: core_basic_file(std::uint32_t openflags, std::uint64_t length) noexcept : core_text_file(openflags) , m_index(0) , m_size(length) { } std::uint64_t index() const noexcept { return m_index; } void add_offset(std::size_t increment) noexcept { m_index += increment; m_size = (std::max)(m_size, m_index); } std::uint64_t size() const noexcept { return m_size; } void set_size(std::uint64_t value) noexcept { m_size = 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) noexcept; private: std::uint64_t m_index; // current file offset std::uint64_t m_size; // total file length }; class core_in_memory_file final : public core_basic_file { public: core_in_memory_file(std::uint32_t openflags, void const *data, std::size_t length, bool copy) noexcept : core_basic_file(openflags, length) , m_data_allocated(false) , m_data(copy ? nullptr : data) { if (copy) { void *const buf = allocate(); if (buf) std::memcpy(buf, data, length); } } ~core_in_memory_file() override { purge(); } virtual std::error_condition read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept override; virtual std::error_condition read_some_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_some(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_some_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; } void const *buffer() const { return m_data; } virtual std::error_condition truncate(std::uint64_t offset) override; protected: void *allocate() noexcept { if (m_data || (std::numeric_limits::max() < size())) return nullptr; void *data = malloc(size()); if (data) { m_data_allocated = true; m_data = data; } return data; } void purge() noexcept { if (m_data && m_data_allocated) free(const_cast(m_data)); m_data_allocated = false; m_data = nullptr; } private: bool m_data_allocated; // was the data allocated by us? void const * m_data; // file data, if RAM-based }; class core_osd_file final : public core_basic_file { public: core_osd_file(std::uint32_t openmode, osd_file::ptr &&file, std::uint64_t length) : core_basic_file(openmode, length) , m_file(std::move(file)) { } ~core_osd_file() override; virtual std::error_condition read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept override; virtual std::error_condition read_some_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_some(void const *buffer, std::size_t length, std::size_t &actual) noexcept override; virtual std::error_condition write_some_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept override; virtual std::error_condition truncate(std::uint64_t offset) override; protected: 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 = 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 }; //************************************************************************** // core_text_file //************************************************************************** //------------------------------------------------- // getc - read a character from a file //------------------------------------------------- int core_text_file::getc() { // refresh buffer, if necessary if (m_back_char_head == m_back_char_tail) { // do we need to check the byte order marks? std::uint64_t pos; if (!tell(pos)) { if (!pos) { std::uint8_t bom[4]; auto const [err, readlen] = read(*this, bom, 4); // FIXME: check for errors if (readlen == 4) { 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 } } // 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]; char32_t uchar = ~char32_t(0); switch (m_text_type) { default: case text_file_type::OSD: { char default_buffer[16]; auto const [err, readlen] = read(*this, default_buffer, sizeof(default_buffer)); if (readlen > 0) { auto const charlen = osd_uchar_from_osdchar(&uchar, default_buffer, readlen / sizeof(default_buffer[0])); seek(std::int64_t(charlen * sizeof(default_buffer[0])) - readlen, SEEK_CUR); } } break; case text_file_type::UTF8: { char utf8_buffer[UTF8_CHAR_MAX]; auto const [err, readlen] = read(*this, utf8_buffer, sizeof(utf8_buffer)); if (readlen > 0) { auto const charlen = uchar_from_utf8(&uchar, utf8_buffer, readlen / sizeof(utf8_buffer[0])); seek(std::int64_t(charlen * sizeof(utf8_buffer[0])) - readlen, SEEK_CUR); } } break; case text_file_type::UTF16BE: { auto const [err, readlen] = read(*this, utf16_buffer, sizeof(utf16_buffer)); if (readlen > 0) { auto const charlen = uchar_from_utf16be(&uchar, utf16_buffer, readlen / sizeof(utf16_buffer[0])); seek(std::int64_t(charlen * sizeof(utf16_buffer[0])) - readlen, SEEK_CUR); } } break; case text_file_type::UTF16LE: { auto const [err, readlen] = read(*this, utf16_buffer, sizeof(utf16_buffer)); if (readlen > 0) { auto const charlen = uchar_from_utf16le(&uchar, utf16_buffer, readlen / sizeof(utf16_buffer[0])); seek(std::int64_t(charlen * sizeof(utf16_buffer[0])) - readlen, SEEK_CUR); } } break; case text_file_type::UTF32BE: { // FIXME: deal with read returning short auto const [err, readlen] = read(*this, &uchar, sizeof(uchar)); if (sizeof(uchar) == readlen) uchar = big_endianize_int32(uchar); } break; case text_file_type::UTF32LE: { // FIXME: deal with read returning short auto const [err, readlen] = read(*this, &uchar, sizeof(uchar)); if (sizeof(uchar) == readlen) uchar = little_endianize_int32(uchar); } break; } if (uchar != ~0) { // place the new character in the ring buffer m_back_char_head = 0; m_back_char_tail = utf8_from_uchar(m_back_chars, std::size(m_back_chars), uchar); //assert(file->back_char_tail != -1); } } // now read from the ring buffer int result; if (m_back_char_head == m_back_char_tail) result = EOF; else { result = m_back_chars[m_back_char_head++]; m_back_char_head %= std::size(m_back_chars); } return result; } //------------------------------------------------- // ungetc - put back a character read from a // file //------------------------------------------------- int core_text_file::ungetc(int c) { m_back_chars[m_back_char_tail++] = char(c); m_back_char_tail %= std::size(m_back_chars); return c; } //------------------------------------------------- // gets - read a line from a text file //------------------------------------------------- char *core_text_file::gets(char *s, int n) { char *cur = s; // loop while we have characters while (n > 0) { int const c = getc(); if (c == EOF) break; else if (c == 0x0d) // if there's a CR, look for an LF afterwards { int const c2 = getc(); if (c2 != 0x0a) ungetc(c2); *cur++ = 0x0d; n--; break; } else if (c == 0x0a) // if there's an LF, reinterp as a CR for consistency { *cur++ = 0x0d; n--; break; } else // otherwise, pop the character in and continue { *cur++ = c; n--; } } // if we put nothing in, return nullptr if (cur == s) return nullptr; /* otherwise, terminate */ if (n > 0) *cur++ = 0; return s; } //------------------------------------------------- // puts - write a string to a text file //------------------------------------------------- int core_text_file::puts(std::string_view s) { // TODO: what to do about write errors? // 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 (!no_bom()) { 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 for (char ch : s) { if (ch == '\n') { if (CRLF == 1) // CR only *pconvbuf++ = 13; else if (CRLF == 2) // LF only *pconvbuf++ = 10; else if (CRLF == 3) // CR+LF { *pconvbuf++ = 13; *pconvbuf++ = 10; } } else *pconvbuf++ = ch; // if we overflow, break into chunks if (pconvbuf >= convbuf + std::size(convbuf) - 10) { auto const [err, written] = write(*this, convbuf, pconvbuf - convbuf); // FIXME: error ignored here count += written; pconvbuf = convbuf; } } // final flush if (pconvbuf != convbuf) { auto const [err, written] = write(*this, convbuf, pconvbuf - convbuf); // FIXME: error ignored here count += written; } return count; } //------------------------------------------------- // vprintf - vfprintf to a text file //------------------------------------------------- int core_text_file::vprintf(util::format_argument_pack const &args) { m_printf_buffer.clear(); m_printf_buffer.reserve(1024); m_printf_buffer.seekp(0, ovectorstream::beg); util::stream_format(m_printf_buffer, args); return puts(buf_to_string_view(m_printf_buffer)); } //************************************************************************** // core_basic_file //************************************************************************** //------------------------------------------------- // seek - seek within a file //------------------------------------------------- std::error_condition core_basic_file::seek(std::int64_t offset, int whence) noexcept { // flush any buffered char 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_index = offset; return std::error_condition(); case SEEK_CUR: if (0 > offset) { if (std::uint64_t(-offset) > m_index) return std::errc::invalid_argument; } else if ((std::numeric_limits::max() - offset) < m_index) { return std::errc::invalid_argument; } m_index += 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::max() - offset) < m_size) { return std::errc::invalid_argument; } m_index = m_size + offset; return std::error_condition(); default: return std::errc::invalid_argument; } } //------------------------------------------------- // eof - return true if we're at the end of file //------------------------------------------------- bool core_basic_file::eof() const { // check for buffered chars if (has_putback()) return false; // if the offset == length, we're at EOF return (m_index >= m_size); } //------------------------------------------------- // safe_buffer_copy - copy safely from one // bounded buffer to another //------------------------------------------------- std::size_t core_basic_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) noexcept { auto const sourceavail = sourcelen - sourceoffs; auto const destavail = destlen - destoffs; auto const bytes_to_copy = (std::min)(sourceavail, destavail); if (bytes_to_copy > 0) { std::memcpy( reinterpret_cast(dest) + destoffs, reinterpret_cast(source) + sourceoffs, bytes_to_copy); } return bytes_to_copy; } //************************************************************************** // core_in_memory_file //************************************************************************** //------------------------------------------------- // read - read from a file //------------------------------------------------- std::error_condition core_in_memory_file::read_some(void *buffer, std::size_t length, std::size_t &actual) noexcept { clear_putback(); // handle RAM-based files if (index() < size()) actual = safe_buffer_copy(m_data, std::size_t(index()), std::size_t(size()), buffer, 0, length); else actual = 0U; add_offset(actual); return std::error_condition(); } std::error_condition core_in_memory_file::read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept { clear_putback(); // handle RAM-based files if (offset < size()) actual = safe_buffer_copy(m_data, std::size_t(offset), std::size_t(size()), buffer, 0, length); else actual = 0U; return std::error_condition(); } //------------------------------------------------- // truncate - truncate a file //------------------------------------------------- std::error_condition core_in_memory_file::truncate(std::uint64_t offset) { if (size() < offset) return std::errc::io_error; // TODO: revisit this error code // adjust to new length and offset set_size(offset); return std::error_condition(); } //************************************************************************** // core_osd_file //************************************************************************** //------------------------------------------------- // closes a file //------------------------------------------------- core_osd_file::~core_osd_file() { // close files and free memory } //------------------------------------------------- // read - read from a file //------------------------------------------------- std::error_condition core_osd_file::read_some(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 declared final, so a derived class can't interfere std::error_condition err = read_some_at(index(), buffer, length, actual); add_offset(actual); return err; } std::error_condition core_osd_file::read_some_at(std::uint64_t offset, void *buffer, std::size_t length, std::size_t &actual) noexcept { if (!m_file) { actual = 0U; return std::errc::bad_file_descriptor; } // flush any buffered char clear_putback(); actual = 0U; std::error_condition err; // if we're within the buffer, consume that first 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 (actual < length) { if ((length - actual) < (sizeof(m_buffer) / 2)) { // read as much as makes sense into the buffer 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 - may need to return short if size_t is larger than 32 bits std::uint32_t const chunk = std::min >(std::numeric_limits::max(), length - actual); std::uint32_t bytes_read; err = m_file->read(reinterpret_cast(buffer) + actual, offset + actual, chunk, bytes_read); if (!err) actual += bytes_read; } } // return any errors return err; } //------------------------------------------------- // write - write to a file //------------------------------------------------- std::error_condition core_osd_file::write_some(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 declared final, so a derived class can't interfere std::error_condition err = write_some_at(index(), buffer, length, actual); add_offset(actual); return err; } std::error_condition core_osd_file::write_some_at(std::uint64_t offset, void const *buffer, std::size_t length, std::size_t &actual) noexcept { // flush any buffered char clear_putback(); // invalidate any buffered data m_bufferbytes = 0U; // do the write - may need to return short if size_t is larger than 32 bits std::uint32_t const chunk = std::min >(std::numeric_limits::max(), length); std::uint32_t bytes_written; std::error_condition err = m_file->write(buffer, offset, chunk, bytes_written); if (err) { // bytes written not valid on error actual = 0U; } else { assert(chunk >= bytes_written); offset += bytes_written; actual = bytes_written; set_size((std::max)(size(), offset)); } return err; } //------------------------------------------------- // truncate - truncate a file //------------------------------------------------- std::error_condition core_osd_file::truncate(std::uint64_t offset) { // truncate file std::error_condition err = m_file->truncate(offset); if (err) return err; // and adjust to new length and offset set_size(offset); return std::error_condition(); } //------------------------------------------------- // flush - flush file buffers //------------------------------------------------- std::error_condition core_osd_file::finalize() noexcept { return std::error_condition(); } std::error_condition core_osd_file::flush() noexcept { // flush any buffered char clear_putback(); // invalidate any buffered data m_bufferbytes = 0U; return m_file->flush(); } } // anonymous namespace //************************************************************************** // core_file //************************************************************************** //------------------------------------------------- // open - open a file for access and // return an error code //------------------------------------------------- std::error_condition core_file::open(std::string_view filename, std::uint32_t openflags, ptr &file) noexcept { // 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(openflags, std::move(f), length); } catch (...) { return std::errc::not_enough_memory; } return std::error_condition(); } //------------------------------------------------- // open_ram - open a RAM-based buffer for file- // 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) noexcept { // can only do this for read access if ((openflags & OPEN_FLAG_WRITE) || (openflags & OPEN_FLAG_CREATE)) return std::errc::invalid_argument; // 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(); } //------------------------------------------------- // open_ram_copy - open a copy of a RAM-based // buffer for file-like access and return an // error code //------------------------------------------------- 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; // 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; std::unique_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(); } //------------------------------------------------- // open_proxy - open a proxy to an existing file // object and return an error code //------------------------------------------------- std::error_condition core_file::open_proxy(core_file &file, ptr &proxy) noexcept { ptr result(new (std::nothrow) core_proxy_file(file)); if (!result) return std::errc::not_enough_memory; proxy = std::move(result); return std::error_condition(); } //------------------------------------------------- // closes a file //------------------------------------------------- core_file::~core_file() { } //------------------------------------------------- // load - open a file with the specified // filename, read it into memory, and return a // pointer //------------------------------------------------- std::error_condition core_file::load(std::string_view filename, void **data, std::size_t &length) noexcept { std::error_condition err; // attempt to open the file ptr file; err = open(filename, OPEN_FLAG_READ, file); if (err) return err; // get the 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 *data = std::malloc(std::size_t(size)); if (!*data) return std::errc::not_enough_memory; length = std::size_t(size); // read the data if (size) { std::size_t actual; std::tie(err, actual) = read(*file, *data, std::size_t(size)); if (err || (size != actual)) { std::free(*data); *data = nullptr; if (err) return err; else return std::errc::io_error; // TODO: revisit this error code - 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 &data) noexcept { std::error_condition err; // attempt to open the file ptr file; err = open(filename, OPEN_FLAG_READ, file); if (err) return err; // get the 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(std::size_t(size)); } catch (...) { return std::errc::not_enough_memory; } // read the data if (size) { std::size_t actual; std::tie(err, actual) = read(*file, &data[0], std::size_t(size)); if (err || (size != actual)) { data.clear(); if (err) return err; else return std::errc::io_error; // TODO: revisit this error code - file truncated out from under us } } // close the file and return data return std::error_condition(); } } // namespace util