// license:BSD-3-Clause // copyright-holders:Aaron Giles, Vas Crabb /*************************************************************************** corefile.c File access functions. ***************************************************************************/ #include "corefile.h" #include "coretmpl.h" #include "osdcore.h" #include "path.h" #include "unicode.h" #include "vecstream.h" #include #include #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(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 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 void const *buffer() override { return m_file.buffer(); } 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_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) noexcept : core_text_file(openflags) , m_data_allocated(false) , m_data(copy ? nullptr : data) , m_offset(0) , m_length(length) { if (copy) { void *const buf = allocate(); if (buf) std::memcpy(buf, data, length); } } ~core_in_memory_file() override { purge(); } 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 void const *buffer() override { return m_data; } virtual std::error_condition truncate(std::uint64_t offset) override; protected: core_in_memory_file(std::uint32_t openflags, std::uint64_t length) noexcept : core_text_file(openflags) , m_data_allocated(false) , m_data(nullptr) , m_offset(0) , m_length(length) { } bool is_loaded() const noexcept { return nullptr != m_data; } void *allocate() noexcept { if (m_data || (std::numeric_limits::max() < m_length)) return nullptr; void *data = malloc(m_length); 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; } 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) noexcept; private: bool m_data_allocated; // was the data allocated by us? void const * m_data; // file data, if RAM-based std::uint64_t m_offset; // current file offset std::uint64_t m_length; // total file length }; 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)) { } ~core_osd_file() 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::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::size_t readlen; std::uint8_t bom[4]; read(bom, 4, readlen); 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]; auto uchar = char32_t(~0); switch (m_text_type) { default: case text_file_type::OSD: { char default_buffer[16]; 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])); seek(std::int64_t(charlen * sizeof(default_buffer[0])) - readlen, SEEK_CUR); } } break; case text_file_type::UTF8: { char utf8_buffer[UTF8_CHAR_MAX]; 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])); seek(std::int64_t(charlen * sizeof(utf8_buffer[0])) - readlen, SEEK_CUR); } } break; case text_file_type::UTF16BE: { 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])); seek(std::int64_t(charlen * sizeof(utf16_buffer[0])) - readlen, SEEK_CUR); } } break; case text_file_type::UTF16LE: { 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])); seek(std::int64_t(charlen * sizeof(utf16_buffer[0])) - readlen, SEEK_CUR); } } break; case text_file_type::UTF32BE: { // 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: { // 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; } 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 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 (!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) { std::size_t written; write(convbuf, pconvbuf - convbuf, written); // FIXME: error ignored here count += written; pconvbuf = convbuf; } } // final flush if (pconvbuf != convbuf) { std::size_t written; write(convbuf, pconvbuf - convbuf, written); // 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_in_memory_file ***************************************************************************/ /*------------------------------------------------- seek - seek within a file -------------------------------------------------*/ std::error_condition core_in_memory_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_offset = offset; 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::max() - offset) < m_offset) { return std::errc::invalid_argument; } m_offset += offset; 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::max() - offset) < m_length) { return std::errc::invalid_argument; } m_offset = m_length + offset; return std::error_condition(); default: return std::errc::invalid_argument; } } /*------------------------------------------------- eof - return 1 if we're at the end of file -------------------------------------------------*/ bool core_in_memory_file::eof() const { // check for buffered chars if (has_putback()) return false; // if the offset == length, we're at EOF return (m_offset >= m_length); } /*------------------------------------------------- read - read from a file -------------------------------------------------*/ 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 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(); } /*------------------------------------------------- truncate - truncate a file -------------------------------------------------*/ std::error_condition core_in_memory_file::truncate(std::uint64_t offset) { if (m_length < offset) return std::errc::io_error; // TODO: revisit this error code // adjust to new length and offset set_length(offset); return std::error_condition(); } /*------------------------------------------------- safe_buffer_copy - copy safely from one bounded buffer to another -------------------------------------------------*/ 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) 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_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(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_at(offset, buffer, length, actual); // 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 do { // may need to split into chunks 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 || !bytes_read) break; actual += bytes_read; } while (actual < length); } } // return any errors return err; } /*------------------------------------------------- buffer - return a pointer to the file buffer; if it doesn't yet exist, load the file into RAM first -------------------------------------------------*/ void const *core_osd_file::buffer() { // if we already have data, just return it if (!is_loaded() && length()) { // allocate some memory void *const buf = allocate(); if (!buf) return nullptr; // read the file std::uint64_t bytes_read = 0; std::uint64_t remaining = length(); std::uint8_t *ptr = reinterpret_cast(buf); while (remaining) { std::uint32_t const chunk = std::min >(std::numeric_limits::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(); } /*------------------------------------------------- write - write to a file -------------------------------------------------*/ 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_at(offset, buffer, length, actual); // flush any buffered char clear_putback(); // invalidate any buffered data m_bufferbytes = 0U; // 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::numeric_limits::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(buffer) + bytes_written; length -= bytes_written; actual += bytes_written; set_length((std::max)(this->length(), offset)); } return std::error_condition(); } /*------------------------------------------------- truncate - truncate a file -------------------------------------------------*/ std::error_condition core_osd_file::truncate(std::uint64_t offset) { if (is_loaded()) return core_in_memory_file::truncate(offset); // truncate file std::error_condition err = m_file->truncate(offset); if (err) return err; // and adjust to new length and offset set_length(offset); return std::error_condition(); } /*------------------------------------------------- flush - flush file buffers -------------------------------------------------*/ 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(); // 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; 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::uint32_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::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 = std::malloc(std::size_t(size)); if (!*data) return std::errc::not_enough_memory; length = std::uint32_t(size); // read the data if (size) { 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 &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; 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(); } } // namespace util /*************************************************************************** FILENAME UTILITIES ***************************************************************************/ // ------------------------------------------------- // core_filename_extract_base - extract the base // name from a filename; note that this makes // assumptions about path separators // ------------------------------------------------- std::string_view core_filename_extract_base(std::string_view name, bool strip_extension) noexcept { // find the start of the basename auto const start = std::find_if(name.rbegin(), name.rend(), &util::is_directory_separator); if (start == name.rbegin()) return std::string_view(); // find the end of the basename auto const chop_position = strip_extension ? std::find(name.rbegin(), start, '.') : start; auto const end = ((chop_position != start) && (std::next(chop_position) != start)) ? std::next(chop_position) : name.rbegin(); return std::string_view(&*start.base(), end.base() - start.base()); } // ------------------------------------------------- // core_filename_extract_extension // ------------------------------------------------- std::string_view core_filename_extract_extension(std::string_view filename, bool strip_period) noexcept { auto loc = filename.find_last_of('.'); if (loc != std::string_view::npos) return filename.substr(loc + (strip_period ? 1 : 0)); else return std::string_view(); } // ------------------------------------------------- // core_filename_ends_with - does the given // filename end with the specified extension? // ------------------------------------------------- bool core_filename_ends_with(std::string_view filename, std::string_view extension) noexcept { auto namelen = filename.length(); auto extlen = extension.length(); // first if the extension is bigger than the name, we definitely don't match bool matches = namelen >= extlen; // work backwards checking for a match while (matches && extlen > 0 && namelen > 0) { if (tolower((uint8_t)filename[--namelen]) != tolower((uint8_t)extension[--extlen])) matches = false; } return matches; }