/* * mptFileIO.h * ----------- * Purpose: A wrapper around std::fstream, enforcing usage of mpt::PathString. * Notes : You should only ever use these wrappers instead of plain std::fstream classes. * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #pragma once #include "openmpt/all/BuildSettings.hpp" #if defined(MPT_ENABLE_FILEIO) #include "mpt/io_read/filecursor_memory.hpp" #include "mpt/io_read/filecursor_stdstream.hpp" #include "../common/mptString.h" #include "../common/mptPathString.h" #include "../common/FileReaderFwd.h" #if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR) #if MPT_GCC_AT_LEAST(9,1,0) #include #endif // MPT_GCC_AT_LEAST(9,1,0) #endif // MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR #include #include #include #include #include #if MPT_COMPILER_MSVC #include #endif // !MPT_COMPILER_MSVC #ifdef MODPLUG_TRACKER #if MPT_OS_WINDOWS #include #endif // MPT_OS_WINDOWS #endif // MODPLUG_TRACKER #endif // MPT_ENABLE_FILEIO OPENMPT_NAMESPACE_BEGIN #if defined(MPT_ENABLE_FILEIO) // Sets the NTFS compression attribute on the file or directory. // Requires read and write permissions for already opened files. // Returns true if the attribute has been set. // In almost all cases, the return value should be ignored because most filesystems other than NTFS do not support compression. #ifdef MODPLUG_TRACKER #if MPT_OS_WINDOWS bool SetFilesystemCompression(HANDLE hFile); bool SetFilesystemCompression(int fd); bool SetFilesystemCompression(const mpt::PathString &filename); #endif // MPT_OS_WINDOWS #endif // MODPLUG_TRACKER namespace mpt { namespace detail { template inline void fstream_open(Tbase & base, const mpt::PathString & filename, std::ios_base::openmode mode) { #if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR) #if MPT_GCC_AT_LEAST(9,1,0) base.open(static_cast(filename.AsNative()), mode); #else // !MPT_GCC_AT_LEAST(9,1,0) // Warning: MinGW with GCC earlier than 9.1 detected. Standard library does neither provide std::fstream wchar_t overloads nor std::filesystem with wchar_t support. Unicode filename support is thus unavailable. base.open(mpt::ToCharset(mpt::Charset::Locale, filename.AsNative()).c_str(), mode); #endif // MPT_GCC_AT_LEAST(9,1,0) #else // !MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR base.open(filename.AsNativePrefixed().c_str(), mode); #endif // MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR } } // namespace detail // We cannot rely on implicit conversion of mpt::PathString to std::filesystem::path when constructing std::fstream // because of broken overload implementation in GCC libstdc++ 8, 9, 10. // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95642 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90704 class fstream : public std::fstream { private: typedef std::fstream Tbase; public: fstream() {} fstream(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { detail::fstream_open(*this, filename, mode); } #if MPT_COMPILER_MSVC protected: fstream(std::FILE * file) : std::fstream(file) { } #endif // MPT_COMPILER_MSVC public: void open(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) { detail::fstream_open(*this, filename, mode); } void open(const char * filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete; void open(const std::string & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete; #if MPT_OS_WINDOWS void open(const wchar_t * filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete; void open(const std::wstring & filename, std::ios_base::openmode mode = std::ios_base::in | std::ios_base::out) = delete; #endif }; class ifstream : public std::ifstream { private: typedef std::ifstream Tbase; public: ifstream() {} ifstream(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::in) { detail::fstream_open(*this, filename, mode); } #if MPT_COMPILER_MSVC protected: ifstream(std::FILE * file) : std::ifstream(file) { } #endif // MPT_COMPILER_MSVC public: void open(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::in) { detail::fstream_open(*this, filename, mode); } void open(const char * filename, std::ios_base::openmode mode = std::ios_base::in) = delete; void open(const std::string & filename, std::ios_base::openmode mode = std::ios_base::in) = delete; #if MPT_OS_WINDOWS void open(const wchar_t * filename, std::ios_base::openmode mode = std::ios_base::in) = delete; void open(const std::wstring & filename, std::ios_base::openmode mode = std::ios_base::in) = delete; #endif }; class ofstream : public std::ofstream { private: typedef std::ofstream Tbase; public: ofstream() {} ofstream(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::out) { detail::fstream_open(*this, filename, mode); } #if MPT_COMPILER_MSVC protected: ofstream(std::FILE * file) : std::ofstream(file) { } #endif // MPT_COMPILER_MSVC public: void open(const mpt::PathString & filename, std::ios_base::openmode mode = std::ios_base::out) { detail::fstream_open(*this, filename, mode); } void open(const char * filename, std::ios_base::openmode mode = std::ios_base::out) = delete; void open(const std::string & filename, std::ios_base::openmode mode = std::ios_base::out) = delete; #if MPT_OS_WINDOWS void open(const wchar_t * filename, std::ios_base::openmode mode = std::ios_base::out) = delete; void open(const std::wstring & filename, std::ios_base::openmode mode = std::ios_base::out) = delete; #endif }; enum class FlushMode { None = 0, // no explicit flushes at all Single = 1, // explicitly flush higher-leverl API layers Full = 2, // explicitly flush *all* layers, up to and including disk write caches }; inline FlushMode FlushModeFromBool(bool flush) { return flush ? FlushMode::Full : FlushMode::None; } #ifdef MODPLUG_TRACKER class SafeOutputFile { private: FlushMode m_FlushMode; #if MPT_COMPILER_MSVC std::FILE *m_f = nullptr; #else // !MPT_COMPILER_MSVC mpt::ofstream m_s; #endif // MPT_COMPILER_MSVC #if MPT_COMPILER_MSVC class FILEostream : public mpt::ofstream { public: FILEostream(std::FILE * file) : mpt::ofstream(file) { return; } }; FILEostream m_s; static mpt::tstring convert_mode(std::ios_base::openmode mode, FlushMode flushMode); std::FILE * internal_fopen(const mpt::PathString &filename, std::ios_base::openmode mode, FlushMode flushMode); #endif // MPT_COMPILER_MSVC public: SafeOutputFile() = delete; explicit SafeOutputFile(const mpt::PathString &filename, std::ios_base::openmode mode = std::ios_base::out, FlushMode flushMode = FlushMode::Full) : m_FlushMode(flushMode) #if MPT_COMPILER_MSVC , m_s(internal_fopen(filename, mode | std::ios_base::out, flushMode)) #else // !MPT_COMPILER_MSVC , m_s(filename, mode) #endif // MPT_COMPILER_MSVC { if(!stream().is_open()) { stream().setstate(mpt::ofstream::failbit); } } mpt::ofstream& stream() { return m_s; } operator mpt::ofstream& () { return stream(); } const mpt::ofstream& stream() const { return m_s; } operator const mpt::ofstream& () const { return stream(); } operator bool() const { return stream() ? true : false; } bool operator!() const { return stream().operator!(); } ~SafeOutputFile() noexcept(false); }; #endif // MODPLUG_TRACKER #ifdef MODPLUG_TRACKER // LazyFileRef is a simple reference to an on-disk file by the means of a // filename which allows easy assignment of the whole file contents to and from // byte buffers. class LazyFileRef { private: const mpt::PathString m_Filename; public: LazyFileRef(const mpt::PathString &filename) : m_Filename(filename) { return; } public: LazyFileRef & operator = (const std::vector &data); LazyFileRef & operator = (const std::vector &data); LazyFileRef & operator = (const std::string &data); operator std::vector () const; operator std::vector () const; operator std::string () const; }; #endif // MODPLUG_TRACKER } // namespace mpt class InputFile { private: mpt::PathString m_Filename; mpt::ifstream m_File; bool m_IsValid; bool m_IsCached; std::vector m_Cache; public: InputFile(const mpt::PathString &filename, bool allowWholeFileCaching = false); ~InputFile(); bool IsValid() const; bool IsCached() const; mpt::PathString GetFilename() const; std::istream& GetStream(); mpt::const_byte_span GetCache(); private: bool Open(const mpt::PathString &filename, bool allowWholeFileCaching = false); }; template inline FileCursor make_FileCursor(Targ1 &&arg1) { return mpt::IO::make_FileCursor(std::forward(arg1)); } template inline FileCursor make_FileCursor(Targ1 &&arg1, Targ2 &&arg2) { return mpt::IO::make_FileCursor(std::forward(arg1), std::forward(arg2)); } // templated in order to reduce header inter-dependencies class InputFile; template ::value, bool> = true> inline FileCursor make_FileCursor(TInputFile &file) { if(!file.IsValid()) { return FileCursor(); } if(file.IsCached()) { return mpt::IO::make_FileCursor(file.GetCache(), std::make_shared(file.GetFilename())); } else { return mpt::IO::make_FileCursor(file.GetStream(), std::make_shared(file.GetFilename())); } } template inline FileCursor GetFileReader(Targ1 &&arg1) { return make_FileCursor(std::forward(arg1)); } template inline FileCursor GetFileReader(Targ1 &&arg1, Targ2 &&arg2) { return make_FileCursor(std::forward(arg1), std::forward(arg2)); } #if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS class OnDiskFileWrapper { private: mpt::PathString m_Filename; bool m_IsTempFile; public: OnDiskFileWrapper(FileCursor& file, const mpt::PathString& fileNameExtension = P_("tmp")); ~OnDiskFileWrapper(); public: bool IsValid() const; mpt::PathString GetFilename() const; }; // class OnDiskFileWrapper #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS #endif // MPT_ENABLE_FILEIO OPENMPT_NAMESPACE_END