/* * mptFileIO.cpp * ------------- * Purpose: File I/O wrappers * Notes : (currently none) * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ #include "stdafx.h" #include "mptFileIO.h" #if defined(MPT_ENABLE_FILEIO) #include "mpt/io/io.hpp" #include "mpt/io/io_stdstream.hpp" #if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS #include "mpt/system_error/system_error.hpp" #include "FileReader.h" #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS #endif // MPT_ENABLE_FILEIO #if defined(MPT_ENABLE_FILEIO) #include #endif // MPT_ENABLE_FILEIO #ifdef MODPLUG_TRACKER #if MPT_OS_WINDOWS #include #include #include #endif // MPT_OS_WINDOWS #endif // MODPLUG_TRACKER #if defined(MPT_ENABLE_FILEIO) #if MPT_COMPILER_MSVC #include #include #endif // MPT_COMPILER_MSVC #endif // MPT_ENABLE_FILEIO OPENMPT_NAMESPACE_BEGIN #if defined(MPT_ENABLE_FILEIO) #if !defined(MPT_BUILD_SILENCE_LIBOPENMPT_CONFIGURATION_WARNINGS) #if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR) #if MPT_GCC_BEFORE(9,1,0) MPT_WARNING("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.") #endif // MPT_GCC_AT_LEAST(9,1,0) #endif // MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR #endif // !MPT_BUILD_SILENCE_LIBOPENMPT_CONFIGURATION_WARNINGS #ifdef MODPLUG_TRACKER #if MPT_OS_WINDOWS bool SetFilesystemCompression(HANDLE hFile) { if(hFile == INVALID_HANDLE_VALUE) { return false; } USHORT format = COMPRESSION_FORMAT_DEFAULT; DWORD dummy = 0; BOOL result = DeviceIoControl(hFile, FSCTL_SET_COMPRESSION, (LPVOID)&format, sizeof(format), NULL, 0, &dummy /*required*/ , NULL); return result != FALSE; } bool SetFilesystemCompression(int fd) { if(fd < 0) { return false; } uintptr_t fhandle = _get_osfhandle(fd); HANDLE hFile = (HANDLE)fhandle; if(hFile == INVALID_HANDLE_VALUE) { return false; } return SetFilesystemCompression(hFile); } bool SetFilesystemCompression(const mpt::PathString &filename) { DWORD attributes = GetFileAttributes(filename.AsNativePrefixed().c_str()); if(attributes == INVALID_FILE_ATTRIBUTES) { return false; } if(attributes & FILE_ATTRIBUTE_COMPRESSED) { return true; } HANDLE hFile = CreateFile(filename.AsNativePrefixed().c_str(), GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if(hFile == INVALID_HANDLE_VALUE) { return false; } bool result = SetFilesystemCompression(hFile); CloseHandle(hFile); return result; } #endif // MPT_OS_WINDOWS #endif // MODPLUG_TRACKER #ifdef MODPLUG_TRACKER namespace mpt { #if MPT_COMPILER_MSVC mpt::tstring SafeOutputFile::convert_mode(std::ios_base::openmode mode, FlushMode flushMode) { mpt::tstring fopen_mode; switch(mode & ~(std::ios_base::ate | std::ios_base::binary)) { case std::ios_base::in: fopen_mode = _T("r"); break; case std::ios_base::out: [[fallthrough]]; case std::ios_base::out | std::ios_base::trunc: fopen_mode = _T("w"); break; case std::ios_base::app: [[fallthrough]]; case std::ios_base::out | std::ios_base::app: fopen_mode = _T("a"); break; case std::ios_base::out | std::ios_base::in: fopen_mode = _T("r+"); break; case std::ios_base::out | std::ios_base::in | std::ios_base::trunc: fopen_mode = _T("w+"); break; case std::ios_base::out | std::ios_base::in | std::ios_base::app: [[fallthrough]]; case std::ios_base::in | std::ios_base::app: fopen_mode = _T("a+"); break; } if(fopen_mode.empty()) { return fopen_mode; } if(mode & std::ios_base::binary) { fopen_mode += _T("b"); } if(flushMode == FlushMode::Full) { fopen_mode += _T("c"); // force commit on fflush (MSVC specific) } return fopen_mode; } std::FILE * SafeOutputFile::internal_fopen(const mpt::PathString &filename, std::ios_base::openmode mode, FlushMode flushMode) { m_f = nullptr; mpt::tstring fopen_mode = convert_mode(mode, flushMode); if(fopen_mode.empty()) { return nullptr; } std::FILE *f = #ifdef UNICODE _wfopen(filename.AsNativePrefixed().c_str(), fopen_mode.c_str()) #else std::fopen(filename.AsNativePrefixed().c_str(), fopen_mode.c_str()) #endif ; if(!f) { return nullptr; } if(mode & std::ios_base::ate) { if(std::fseek(f, 0, SEEK_END) != 0) { std::fclose(f); f = nullptr; return nullptr; } } m_f = f; return f; } #endif // MPT_COMPILER_MSVC // cppcheck-suppress exceptThrowInDestructor SafeOutputFile::~SafeOutputFile() noexcept(false) { const bool mayThrow = (std::uncaught_exceptions() == 0); if(!stream()) { #if MPT_COMPILER_MSVC if(m_f) { std::fclose(m_f); } #endif // MPT_COMPILER_MSVC if(mayThrow && (stream().exceptions() & (std::ios::badbit | std::ios::failbit))) { // cppcheck-suppress exceptThrowInDestructor throw std::ios_base::failure(std::string("Error before flushing file buffers.")); } return; } if(!stream().rdbuf()) { #if MPT_COMPILER_MSVC if(m_f) { std::fclose(m_f); } #endif // MPT_COMPILER_MSVC if(mayThrow && (stream().exceptions() & (std::ios::badbit | std::ios::failbit))) { // cppcheck-suppress exceptThrowInDestructor throw std::ios_base::failure(std::string("Error before flushing file buffers.")); } return; } #if MPT_COMPILER_MSVC if(!m_f) { return; } #endif // MPT_COMPILER_MSVC bool errorOnFlush = false; if(m_FlushMode != FlushMode::None) { try { if(stream().rdbuf()->pubsync() != 0) { errorOnFlush = true; } } catch(const std::exception &) { errorOnFlush = true; #if MPT_COMPILER_MSVC if(m_FlushMode != FlushMode::None) { if(std::fflush(m_f) != 0) { errorOnFlush = true; } } if(std::fclose(m_f) != 0) { errorOnFlush = true; } #endif // MPT_COMPILER_MSVC if(mayThrow) { // ignore errorOnFlush here, and re-throw the earlier exception // cppcheck-suppress exceptThrowInDestructor throw; } } } #if MPT_COMPILER_MSVC if(m_FlushMode != FlushMode::None) { if(std::fflush(m_f) != 0) { errorOnFlush = true; } } if(std::fclose(m_f) != 0) { errorOnFlush = true; } #endif // MPT_COMPILER_MSVC if(mayThrow && errorOnFlush && (stream().exceptions() & (std::ios::badbit | std::ios::failbit))) { // cppcheck-suppress exceptThrowInDestructor throw std::ios_base::failure(std::string("Error flushing file buffers.")); } } } // namespace mpt #endif // MODPLUG_TRACKER #ifdef MODPLUG_TRACKER namespace mpt { LazyFileRef & LazyFileRef::operator = (const std::vector &data) { mpt::ofstream file(m_Filename, std::ios::binary); file.exceptions(std::ios_base::failbit | std::ios_base::badbit); mpt::IO::WriteRaw(file, mpt::as_span(data)); mpt::IO::Flush(file); return *this; } LazyFileRef & LazyFileRef::operator = (const std::vector &data) { mpt::ofstream file(m_Filename, std::ios::binary); file.exceptions(std::ios_base::failbit | std::ios_base::badbit); mpt::IO::WriteRaw(file, mpt::as_span(data)); mpt::IO::Flush(file); return *this; } LazyFileRef & LazyFileRef::operator = (const std::string &data) { mpt::ofstream file(m_Filename, std::ios::binary); file.exceptions(std::ios_base::failbit | std::ios_base::badbit); mpt::IO::WriteRaw(file, mpt::as_span(data)); mpt::IO::Flush(file); return *this; } LazyFileRef::operator std::vector () const { mpt::ifstream file(m_Filename, std::ios::binary); if(!mpt::IO::IsValid(file)) { return std::vector(); } file.exceptions(std::ios_base::failbit | std::ios_base::badbit); mpt::IO::SeekEnd(file); std::vector buf(mpt::saturate_cast(mpt::IO::TellRead(file))); mpt::IO::SeekBegin(file); mpt::IO::ReadRaw(file, mpt::as_span(buf)); return buf; } LazyFileRef::operator std::vector () const { mpt::ifstream file(m_Filename, std::ios::binary); if(!mpt::IO::IsValid(file)) { return std::vector(); } file.exceptions(std::ios_base::failbit | std::ios_base::badbit); mpt::IO::SeekEnd(file); std::vector buf(mpt::saturate_cast(mpt::IO::TellRead(file))); mpt::IO::SeekBegin(file); mpt::IO::ReadRaw(file, mpt::as_span(buf)); return buf; } LazyFileRef::operator std::string () const { mpt::ifstream file(m_Filename, std::ios::binary); if(!mpt::IO::IsValid(file)) { return std::string(); } file.exceptions(std::ios_base::failbit | std::ios_base::badbit); mpt::IO::SeekEnd(file); std::vector buf(mpt::saturate_cast(mpt::IO::TellRead(file))); mpt::IO::SeekBegin(file); mpt::IO::ReadRaw(file, mpt::as_span(buf)); return mpt::buffer_cast(buf); } } // namespace mpt #endif // MODPLUG_TRACKER InputFile::InputFile(const mpt::PathString &filename, bool allowWholeFileCaching) : m_IsValid(false) , m_IsCached(false) { MPT_ASSERT(!filename.empty()); Open(filename, allowWholeFileCaching); } InputFile::~InputFile() { return; } bool InputFile::Open(const mpt::PathString &filename, bool allowWholeFileCaching) { m_IsCached = false; m_Cache.resize(0); m_Cache.shrink_to_fit(); m_Filename = filename; m_File.open(m_Filename, std::ios::binary | std::ios::in); if(allowWholeFileCaching) { if(mpt::IO::IsReadSeekable(m_File)) { if(!mpt::IO::SeekEnd(m_File)) { m_File.close(); return false; } mpt::IO::Offset filesize = mpt::IO::TellRead(m_File); if(!mpt::IO::SeekBegin(m_File)) { m_File.close(); return false; } if(mpt::in_range(filesize)) { std::size_t buffersize = mpt::saturate_cast(filesize); m_Cache.resize(buffersize); if(mpt::IO::ReadRaw(m_File, mpt::as_span(m_Cache)).size() != mpt::saturate_cast(filesize)) { m_File.close(); return false; } if(!mpt::IO::SeekBegin(m_File)) { m_File.close(); return false; } m_IsCached = true; m_IsValid = true; return true; } } } m_IsValid = true; return m_File.good(); } bool InputFile::IsValid() const { return m_IsValid && m_File.good(); } bool InputFile::IsCached() const { return m_IsCached; } mpt::PathString InputFile::GetFilename() const { return m_Filename; } std::istream& InputFile::GetStream() { MPT_ASSERT(!m_IsCached); return m_File; } mpt::const_byte_span InputFile::GetCache() { MPT_ASSERT(m_IsCached); return mpt::as_span(m_Cache); } #if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS OnDiskFileWrapper::OnDiskFileWrapper(FileCursor &file, const mpt::PathString &fileNameExtension) : m_IsTempFile(false) { try { file.Rewind(); if(!file.GetOptionalFileName()) { const mpt::PathString tempName = mpt::CreateTempFileName(P_("OpenMPT"), fileNameExtension); #if MPT_OS_WINDOWS && MPT_OS_WINDOWS_WINRT #if (_WIN32_WINNT < 0x0602) #define MPT_ONDISKFILEWRAPPER_NO_CREATEFILE #endif #endif #ifdef MPT_ONDISKFILEWRAPPER_NO_CREATEFILE mpt::ofstream f(tempName, std::ios::binary); if(!f) { throw std::runtime_error("Error creating temporary file."); } while(!file.EndOfFile()) { FileCursor::PinnedView view = file.ReadPinnedView(mpt::IO::BUFFERSIZE_NORMAL); std::size_t towrite = view.size(); std::size_t written = 0; do { std::size_t chunkSize = mpt::saturate_cast(towrite); bool chunkOk = false; chunkOk = mpt::IO::WriteRaw(f, mpt::const_byte_span(view.data() + written, chunkSize)); if(!chunkOk) { throw std::runtime_error("Incomplete Write."); } towrite -= chunkSize; written += chunkSize; } while(towrite > 0); } f.close(); #else // !MPT_ONDISKFILEWRAPPER_NO_CREATEFILE HANDLE hFile = NULL; #if MPT_OS_WINDOWS_WINRT hFile = mpt::windows::CheckFileHANDLE(CreateFile2(tempName.AsNative().c_str(), GENERIC_WRITE, FILE_SHARE_READ, CREATE_ALWAYS, NULL)); #else hFile = mpt::windows::CheckFileHANDLE(CreateFile(tempName.AsNative().c_str(), GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, NULL)); #endif while(!file.EndOfFile()) { FileCursor::PinnedView view = file.ReadPinnedView(mpt::IO::BUFFERSIZE_NORMAL); std::size_t towrite = view.size(); std::size_t written = 0; do { DWORD chunkSize = mpt::saturate_cast(towrite); DWORD chunkDone = 0; try { mpt::windows::CheckBOOL(WriteFile(hFile, view.data() + written, chunkSize, &chunkDone, NULL)); } catch(...) { CloseHandle(hFile); hFile = NULL; throw; } if(chunkDone != chunkSize) { CloseHandle(hFile); hFile = NULL; throw std::runtime_error("Incomplete WriteFile()."); } towrite -= chunkDone; written += chunkDone; } while(towrite > 0); } CloseHandle(hFile); hFile = NULL; #endif // MPT_ONDISKFILEWRAPPER_NO_CREATEFILE m_Filename = tempName; m_IsTempFile = true; } else { m_Filename = file.GetOptionalFileName().value(); } } catch (const std::runtime_error &) { m_IsTempFile = false; m_Filename = mpt::PathString(); } } OnDiskFileWrapper::~OnDiskFileWrapper() { if(m_IsTempFile) { DeleteFile(m_Filename.AsNative().c_str()); m_IsTempFile = false; } m_Filename = mpt::PathString(); } bool OnDiskFileWrapper::IsValid() const { return !m_Filename.empty(); } mpt::PathString OnDiskFileWrapper::GetFilename() const { return m_Filename; } #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS #else // !MPT_ENABLE_FILEIO MPT_MSVC_WORKAROUND_LNK4221(mptFileIO) #endif // MPT_ENABLE_FILEIO OPENMPT_NAMESPACE_END