123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608 |
- /*
- * 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 <stdexcept>
- #endif // MPT_ENABLE_FILEIO
- #ifdef MODPLUG_TRACKER
- #if MPT_OS_WINDOWS
- #include <windows.h>
- #include <WinIoCtl.h>
- #include <io.h>
- #endif // MPT_OS_WINDOWS
- #endif // MODPLUG_TRACKER
- #if defined(MPT_ENABLE_FILEIO)
- #if MPT_COMPILER_MSVC
- #include <stdio.h>
- #include <tchar.h>
- #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<std::byte> &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<char> &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<std::byte> () const
- {
- mpt::ifstream file(m_Filename, std::ios::binary);
- if(!mpt::IO::IsValid(file))
- {
- return std::vector<std::byte>();
- }
- file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
- mpt::IO::SeekEnd(file);
- std::vector<std::byte> buf(mpt::saturate_cast<std::size_t>(mpt::IO::TellRead(file)));
- mpt::IO::SeekBegin(file);
- mpt::IO::ReadRaw(file, mpt::as_span(buf));
- return buf;
- }
- LazyFileRef::operator std::vector<char> () const
- {
- mpt::ifstream file(m_Filename, std::ios::binary);
- if(!mpt::IO::IsValid(file))
- {
- return std::vector<char>();
- }
- file.exceptions(std::ios_base::failbit | std::ios_base::badbit);
- mpt::IO::SeekEnd(file);
- std::vector<char> buf(mpt::saturate_cast<std::size_t>(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<char> buf(mpt::saturate_cast<std::size_t>(mpt::IO::TellRead(file)));
- mpt::IO::SeekBegin(file);
- mpt::IO::ReadRaw(file, mpt::as_span(buf));
- return mpt::buffer_cast<std::string>(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<std::size_t>(filesize))
- {
- std::size_t buffersize = mpt::saturate_cast<std::size_t>(filesize);
- m_Cache.resize(buffersize);
- if(mpt::IO::ReadRaw(m_File, mpt::as_span(m_Cache)).size() != mpt::saturate_cast<std::size_t>(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<std::size_t>(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<DWORD>(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
|