// license:BSD-3-Clause // copyright-holders:Olivier Galibert, R. Belmont, Vas Crabb //============================================================ // // sdlfile.c - SDL file access functions // // SDLMAME by Olivier Galibert and R. Belmont // //============================================================ #ifndef _LARGEFILE64_SOURCE #define _LARGEFILE64_SOURCE #endif #ifdef __linux__ #ifndef __USE_LARGEFILE64 #define __USE_LARGEFILE64 #endif #ifndef __USE_BSD #define __USE_BSD #endif #endif #ifdef WIN32 #define _FILE_OFFSET_BITS 64 #endif #if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__bsdi__) && !defined(__DragonFly__) #ifdef _XOPEN_SOURCE #if _XOPEN_SOURCE < 500 #undef _XOPEN_SOURCE #endif #endif #ifndef _XOPEN_SOURCE #define _XOPEN_SOURCE 500 #endif #endif // Fix for MacOS compilation errors #if defined(__APPLE__) && !defined(_DARWIN_C_SOURCE) #define _DARWIN_C_SOURCE #endif // MAME headers #include "posixfile.h" #include "osdcore.h" #include "unicode.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace { //============================================================ // CONSTANTS //============================================================ #if defined(WIN32) constexpr char PATHSEPCH = '\\'; constexpr char INVPATHSEPCH = '/'; #else constexpr char PATHSEPCH = '/'; #endif class posix_osd_file : public osd_file { public: posix_osd_file(posix_osd_file const &) = delete; posix_osd_file(posix_osd_file &&) = delete; posix_osd_file& operator=(posix_osd_file const &) = delete; posix_osd_file& operator=(posix_osd_file &&) = delete; posix_osd_file(int fd) noexcept : m_fd(fd) { assert(m_fd >= 0); } virtual ~posix_osd_file() override { ::close(m_fd); } virtual std::error_condition read(void *buffer, std::uint64_t offset, std::uint32_t count, std::uint32_t &actual) noexcept override { ssize_t result; #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__) || defined(__EMSCRIPTEN__) || defined(__ANDROID__) result = ::pread(m_fd, buffer, size_t(count), off_t(std::make_unsigned_t(offset))); #elif defined(WIN32) || defined(SDLMAME_NO64BITIO) if (lseek(m_fd, off_t(std::make_unsigned_t(offset)), SEEK_SET) < 0) return std::error_condition(errno, std::generic_category()); result = ::read(m_fd, buffer, size_t(count)); #else result = ::pread64(m_fd, buffer, size_t(count), off64_t(offset)); #endif if (result < 0) return std::error_condition(errno, std::generic_category()); actual = std::uint32_t(std::size_t(result)); return std::error_condition(); } virtual std::error_condition write(void const *buffer, std::uint64_t offset, std::uint32_t count, std::uint32_t &actual) noexcept override { ssize_t result; #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__) || defined(__EMSCRIPTEN__) || defined(__ANDROID__) result = ::pwrite(m_fd, buffer, size_t(count), off_t(std::make_unsigned_t(offset))); #elif defined(WIN32) || defined(SDLMAME_NO64BITIO) if (lseek(m_fd, off_t(std::make_unsigned_t(offset)), SEEK_SET) < 0) return std::error_condition(errno, std::generic_category()); result = ::write(m_fd, buffer, size_t(count)); #else result = ::pwrite64(m_fd, buffer, size_t(count), off64_t(offset)); #endif if (result < 0) return std::error_condition(errno, std::generic_category()); actual = std::uint32_t(std::size_t(result)); return std::error_condition(); } virtual std::error_condition truncate(std::uint64_t offset) noexcept override { int result; #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__) || defined(__EMSCRIPTEN__) || defined(WIN32) || defined(SDLMAME_NO64BITIO) || defined(__ANDROID__) result = ::ftruncate(m_fd, off_t(std::make_unsigned_t(offset))); #else result = ::ftruncate64(m_fd, off64_t(offset)); #endif if (result < 0) return std::error_condition(errno, std::generic_category()); return std::error_condition(); } virtual std::error_condition flush() noexcept override { // no user-space buffering on unistd I/O return std::error_condition(); } private: int m_fd; }; //============================================================ // is_path_separator //============================================================ bool is_path_separator(char c) noexcept { #if defined(WIN32) return (c == PATHSEPCH) || (c == INVPATHSEPCH); #else return c == PATHSEPCH; #endif } //============================================================ // create_path_recursive //============================================================ std::error_condition create_path_recursive(std::string_view path) noexcept { // if there's still a separator, and it's not the root, nuke it and recurse auto const sep = path.rfind(PATHSEPCH); if ((sep != std::string_view::npos) && (sep > 0) && (path[sep - 1] != PATHSEPCH)) { std::error_condition err = create_path_recursive(path.substr(0, sep)); if (err) return err; } // need a NUL-terminated version of the subpath std::string p; try { p = path; } catch (...) { return std::errc::not_enough_memory; } // if the path already exists, we're done struct stat st; if (!::stat(p.c_str(), &st)) return std::error_condition(); // create the path #ifdef WIN32 if (mkdir(p.c_str()) < 0) #else if (mkdir(p.c_str(), 0777) < 0) #endif return std::error_condition(errno, std::generic_category()); else return std::error_condition(); } } // anonymous namespace //============================================================ // osd_file::open //============================================================ std::error_condition osd_file::open(std::string const &path, std::uint32_t openflags, ptr &file, std::uint64_t &filesize) noexcept { std::string dst; if (posix_check_socket_path(path)) return posix_open_socket(path, openflags, file, filesize); else if (posix_check_ptty_path(path)) return posix_open_ptty(openflags, file, filesize, dst); else if (posix_check_domain_path(path)) return posix_open_domain(path, openflags, file, filesize); // select the file open modes int access; if (openflags & OPEN_FLAG_WRITE) { access = (openflags & OPEN_FLAG_READ) ? O_RDWR : O_WRONLY; access |= (openflags & OPEN_FLAG_CREATE) ? (O_CREAT | O_TRUNC) : 0; } else if (openflags & OPEN_FLAG_READ) { access = O_RDONLY; } else { return std::errc::invalid_argument; } #if defined(WIN32) access |= O_BINARY; #endif // convert the path into something compatible dst = path; #if defined(WIN32) for (auto it = dst.begin(); it != dst.end(); ++it) *it = (INVPATHSEPCH == *it) ? PATHSEPCH : *it; #endif try { osd_subst_env(dst, dst); } catch (...) { return std::errc::not_enough_memory; } // attempt to open the file int fd = -1; #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__) || defined(__HAIKU__) || defined(WIN32) || defined(SDLMAME_NO64BITIO) || defined(__ANDROID__) fd = ::open(dst.c_str(), access, 0666); #else fd = ::open64(dst.c_str(), access, 0666); #endif if (fd < 0) { // save the error from the first attempt to open the file std::error_condition openerr(errno, std::generic_category()); // create the path if necessary if ((openflags & OPEN_FLAG_CREATE) && (openflags & OPEN_FLAG_CREATE_PATHS)) { auto const pathsep = dst.rfind(PATHSEPCH); if (pathsep != std::string::npos) { // create the path up to the file std::error_condition const createrr = create_path_recursive(dst.substr(0, pathsep)); // attempt to reopen the file if (!createrr) { #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__) || defined(__HAIKU__) || defined(WIN32) || defined(SDLMAME_NO64BITIO) || defined(__ANDROID__) fd = ::open(dst.c_str(), access, 0666); #else fd = ::open64(dst.c_str(), access, 0666); #endif } if (fd < 0) { openerr.assign(errno, std::generic_category()); } } } // if we still failed, clean up and free if (fd < 0) { return openerr; } } // get the file size #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__) || defined(__HAIKU__) || defined(WIN32) || defined(SDLMAME_NO64BITIO) || defined(__ANDROID__) struct stat st; if (::fstat(fd, &st) < 0) #else struct stat64 st; if (::fstat64(fd, &st) < 0) #endif { std::error_condition staterr(errno, std::generic_category()); ::close(fd); return staterr; } osd_file::ptr result(new (std::nothrow) posix_osd_file(fd)); if (!result) { ::close(fd); return std::errc::not_enough_memory; } file = std::move(result); filesize = std::uint64_t(std::make_unsigned_t(st.st_size)); return std::error_condition(); } //============================================================ // osd_file::openpty //============================================================ std::error_condition osd_file::openpty(ptr &file, std::string &name) noexcept { std::uint64_t filesize; return posix_open_ptty(OPEN_FLAG_READ | OPEN_FLAG_WRITE, file, filesize, name); } //============================================================ // osd_file::remove //============================================================ std::error_condition osd_file::remove(std::string const &filename) noexcept { if (::unlink(filename.c_str()) < -1) return std::error_condition(errno, std::generic_category()); else return std::error_condition(); } //============================================================ // osd_get_physical_drive_geometry //============================================================ bool osd_get_physical_drive_geometry(const char *filename, uint32_t *cylinders, uint32_t *heads, uint32_t *sectors, uint32_t *bps) noexcept { return false; // no, no way, huh-uh, forget it } //============================================================ // osd_stat //============================================================ std::unique_ptr osd_stat(const std::string &path) { #if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__bsdi__) || defined(__DragonFly__) || defined(__HAIKU__) || defined(WIN32) || defined(SDLMAME_NO64BITIO) || defined(__ANDROID__) struct stat st; int const err = ::stat(path.c_str(), &st); #else struct stat64 st; int const err = ::stat64(path.c_str(), &st); #endif if (err < 0) return nullptr; // create an osd_directory_entry; be sure to make sure that the caller can // free all resources by just freeing the resulting osd_directory_entry osd::directory::entry *result; try { result = reinterpret_cast(::operator new(sizeof(*result) + path.length() + 1)); } catch (...) { return nullptr; } new (result) osd::directory::entry; std::strcpy(reinterpret_cast(result) + sizeof(*result), path.c_str()); result->name = reinterpret_cast(result) + sizeof(*result); result->type = S_ISDIR(st.st_mode) ? osd::directory::entry::entry_type::DIR : osd::directory::entry::entry_type::FILE; result->size = std::uint64_t(std::make_unsigned_t(st.st_size)); result->last_modified = std::chrono::system_clock::from_time_t(st.st_mtime); return std::unique_ptr(result); } //============================================================ // osd_get_full_path //============================================================ std::error_condition osd_get_full_path(std::string &dst, std::string const &path) noexcept { try { #if defined(WIN32) std::vector path_buffer(MAX_PATH); if (::_fullpath(&path_buffer[0], path.c_str(), MAX_PATH)) { dst = &path_buffer[0]; return std::error_condition(); } else { return std::errc::io_error; // TODO: better error reporting? } #else std::unique_ptr canonical(::realpath(path.c_str(), nullptr), &std::free); if (canonical) { dst = canonical.get(); return std::error_condition(); } std::vector path_buffer(PATH_MAX); if (::realpath(path.c_str(), &path_buffer[0])) { dst = &path_buffer[0]; return std::error_condition(); } else if (path[0] == PATHSEPCH) { dst = path; return std::error_condition(); } else { while (!::getcwd(&path_buffer[0], path_buffer.size())) { if (errno != ERANGE) return std::error_condition(errno, std::generic_category()); else path_buffer.resize(path_buffer.size() * 2); } dst.assign(&path_buffer[0]).push_back(PATHSEPCH); dst.append(path); return std::error_condition(); } #endif } catch (...) { return std::errc::not_enough_memory; } } //============================================================ // osd_is_absolute_path //============================================================ bool osd_is_absolute_path(std::string const &path) noexcept { if (!path.empty() && is_path_separator(path[0])) return true; #if !defined(WIN32) else if (!path.empty() && (path[0] == '.') && (!path[1] || is_path_separator(path[1]))) // FIXME: why is this even here? foo/./bar is a valid way to refer to foo/bar return true; #elif !defined(UNDER_CE) else if ((path.length() > 1) && (path[1] == ':')) return true; #endif else return false; } //============================================================ // osd_get_volume_name //============================================================ std::string osd_get_volume_name(int idx) { if (idx == 0) return "/"; else return std::string(); } //============================================================ // osd_get_volume_names //============================================================ std::vector osd_get_volume_names() { return std::vector{ "/" }; } //============================================================ // osd_is_valid_filename_char //============================================================ bool osd_is_valid_filename_char(char32_t uchar) noexcept { // The only one that's actually invalid is the slash // The other two are just problematic because they're the escape character and path separator return osd_is_valid_filepath_char(uchar) #if defined(WIN32) && uchar != PATHSEPCH && uchar != INVPATHSEPCH #else && uchar != PATHSEPCH && uchar != '\\' #endif && uchar != ':'; } //============================================================ // osd_is_valid_filepath_char //============================================================ bool osd_is_valid_filepath_char(char32_t uchar) noexcept { // One could argue that colon should be in here too because it functions as path separator return uchar >= 0x20 && !(uchar >= '\x7F' && uchar <= '\x9F') #if defined(WIN32) && uchar != '<' && uchar != '>' && uchar != '\"' && uchar != '|' && uchar != '?' && uchar != '*' #endif && uchar_isvalid(uchar); }