View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0001157 | OpenMPT | General | public | 2018-10-23 05:24 | 2018-11-09 07:34 |
Reporter | fuzion_mixer | Assigned To | manx | ||
Priority | high | Severity | major | Reproducibility | sometimes |
Status | resolved | Resolution | fixed | ||
Platform | x64 | OS | Windows | OS Version | 10 |
Product Version | OpenMPT 1.27.10.00 / libopenmpt 0.3.12 (upgrade first) | ||||
Target Version | OpenMPT 1.28.01.00 / libopenmpt 0.4.0 (upgrade first) | Fixed in Version | OpenMPT 1.28.01.00 / libopenmpt 0.4.0 (upgrade first) | ||
Summary | 0001157: Corrupted filesave | ||||
Description | Since my laptop has a battery problem where I can only turn it on via AC, I had no choice but to shut it down and start it up a couple times a day. However, doing this sometimes results in a corrupted save. | ||||
Steps To Reproduce |
| ||||
Additional Information | It might be due to the repeated shutting down / starting up that's messing with the file contents. | ||||
Tags | No tags attached. | ||||
Attached Files | file-flush-v2.patch (14,421 bytes)
Index: common/mptFileIO.h =================================================================== --- common/mptFileIO.h (revision 10955) +++ common/mptFileIO.h (working copy) @@ -23,6 +23,14 @@ #include <streambuf> #include <utility> +#if MPT_COMPILER_MSVC +#include <cstdio> +#endif // !MPT_COMPILER_MSVC + +#if MPT_COMPILER_MSVC +#include <stdio.h> +#endif // !MPT_COMPILER_MSVC + #endif // MPT_ENABLE_FILEIO @@ -125,6 +133,14 @@ { detail::fstream_open<Tbase>(*this, filename, mode); } +#if MPT_COMPILER_MSVC +protected: + ofstream(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<Tbase>(*this, filename, mode); @@ -137,8 +153,123 @@ #endif }; +class SafeOutputFile + : public mpt::ofstream +{ +private: + typedef std::ofstream Tbase; +#if MPT_COMPILER_MSVC + FILE *m_f; +#endif // MPT_COMPILER_MSVC +#if MPT_COMPILER_MSVC + static mpt::tstring convert_mode(std::ios_base::openmode mode) + { + mpt::tstring fopen_mode; + switch(mode & ~(std::ios_base::ate | std::ios_base::binary)) + { + case std::ios_base::in: + fopen_mode = TEXT("r"); + break; + case std::ios_base::out: + MPT_FALLTHROUGH; + case std::ios_base::out | std::ios_base::trunc: + fopen_mode = TEXT("w"); + break; + case std::ios_base::app: + MPT_FALLTHROUGH; + case std::ios_base::out | std::ios_base::app: + fopen_mode = TEXT("a"); + break; + case std::ios_base::out | std::ios_base::in: + fopen_mode = TEXT("r+"); + break; + case std::ios_base::out | std::ios_base::in | std::ios_base::trunc: + fopen_mode = TEXT("w+"); + break; + case std::ios_base::out | std::ios_base::in | std::ios_base::app: + MPT_FALLTHROUGH; + case std::ios_base::in | std::ios_base::app: + fopen_mode = TEXT("a+"); + break; + } + if(fopen_mode.empty()) + { + return fopen_mode; + } + if(mode & std::ios_base::binary) + { + fopen_mode += TEXT("b"); + } + fopen_mode += TEXT("c"); // force commit on fflush (MSVC specific) + return fopen_mode; + } + FILE * internal_fopen(const mpt::PathString &filename, std::ios_base::openmode mode) + { + mpt::tstring fopen_mode = convert_mode(mode); + if(fopen_mode.empty()) + { + return nullptr; + } + FILE *f = +#ifdef UNICODE + _wfopen(filename.AsNativePrefixed().c_str(), fopen_mode.c_str()) +#else + fopen(filename.AsNativePrefixed().c_str(), fopen_mode.c_str()) +#endif + ; + if(!f) + { + return nullptr; + } + if(mode & std::ios_base::ate) + { + if(fseek(f, 0, SEEK_END) != 0) + { + fclose(f); + f = nullptr; + return nullptr; + } + } + m_f = f; + return f; + } +#endif // MPT_COMPILER_MSVC +public: + SafeOutputFile() = delete; + explicit SafeOutputFile(const mpt::PathString &filename, std::ios_base::openmode mode = std::ios_base::out) +#if MPT_COMPILER_MSVC + : mpt::ofstream(internal_fopen(filename, mode | std::ios_base::out)) +#else // !MPT_COMPILER_MSVC + : mpt::ofstream(filename, mode) +#endif // MPT_COMPILER_MSVC + { + } + ~SafeOutputFile() + { + if(!*this) + { + return; + } + if(!rdbuf()) + { + return; + } + #if MPT_COMPILER_MSVC + if(!m_f) + { + return; + } + #endif // MPT_COMPILER_MSVC + rdbuf()->pubsync(); + #if MPT_COMPILER_MSVC + fflush(m_f); + fclose(m_f); + #endif // MPT_COMPILER_MSVC + } +}; + // 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. @@ -223,5 +354,6 @@ #endif // MPT_ENABLE_FILEIO + OPENMPT_NAMESPACE_END Index: mptrack/CommandSet.cpp =================================================================== --- mptrack/CommandSet.cpp (revision 10955) +++ mptrack/CommandSet.cpp (working copy) @@ -1510,7 +1510,7 @@ ... */ - mpt::ofstream f(filename); + mpt::SafeOutputFile f(filename); if(!f) { ErrorBox(IDS_CANT_OPEN_FILE_FOR_WRITING); Index: mptrack/ExceptionHandler.cpp =================================================================== --- mptrack/ExceptionHandler.cpp (revision 10955) +++ mptrack/ExceptionHandler.cpp (working copy) @@ -268,13 +268,13 @@ errorMessage += MPT_ULITERAL("\n\n"); { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("error.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("error.txt"), std::ios::binary); f.imbue(std::locale::classic()); f << mpt::String::Replace(mpt::ToCharset(mpt::CharsetUTF8, errorMessage), "\n", "\r\n"); } { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("threads.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("threads.txt"), std::ios::binary); f.imbue(std::locale::classic()); f << mpt::format("current : %1")(mpt::fmt::hex0<8>(GetCurrentThreadId())) << "\r\n"; f << mpt::format("GUI : %1")(mpt::fmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindGUI))) << "\r\n"; @@ -290,7 +290,7 @@ }; { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("active-settings.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("active-settings.txt"), std::ios::binary); f.imbue(std::locale::classic()); if(&theApp.GetSettings()) { @@ -357,7 +357,7 @@ */ { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("about-openmpt.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("about-openmpt.txt"), std::ios::binary); f.imbue(std::locale::classic()); f << mpt::ToCharset(mpt::CharsetUTF8, CAboutDlg::GetTabText(0)); } Index: mptrack/mod2midi.cpp =================================================================== --- mptrack/mod2midi.cpp (revision 10955) +++ mptrack/mod2midi.cpp (working copy) @@ -695,7 +695,7 @@ void CDoMidiConvert::Run() { - mpt::ofstream f(m_fileName, std::ios::binary); + mpt::SafeOutputFile f(m_fileName, std::ios::binary); if(!f.good()) { Reporting::Error("Could not open file for writing. Is it open in another application?"); Index: mptrack/Mod2wave.cpp =================================================================== --- mptrack/Mod2wave.cpp (revision 10955) +++ mptrack/Mod2wave.cpp (working copy) @@ -949,7 +949,7 @@ normalizeFile.open(normalizeFileName, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); } - mpt::ofstream fileStream(m_lpszFileName, std::ios::binary); + mpt::SafeOutputFile fileStream(m_lpszFileName, std::ios::binary); if(!fileStream) { Index: mptrack/Moddoc.cpp =================================================================== --- mptrack/Moddoc.cpp (revision 10955) +++ mptrack/Moddoc.cpp (working copy) @@ -278,7 +278,7 @@ return FALSE; BOOL ok = FALSE; - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(f) { BeginWaitCursor(); @@ -1898,7 +1898,7 @@ if(!dlg.Show()) return; filename = dlg.GetFirstFile(); - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) return; Index: mptrack/Modedit.cpp =================================================================== --- mptrack/Modedit.cpp (revision 10955) +++ mptrack/Modedit.cpp (working copy) @@ -1099,7 +1099,7 @@ CStringA s; EnvelopeToString(s, pIns->GetEnvelope(nEnv)); - mpt::ofstream f(fileName, std::ios::binary); + mpt::SafeOutputFile f(fileName, std::ios::binary); if(!f) { EndWaitCursor(); Index: mptrack/Settings.cpp =================================================================== --- mptrack/Settings.cpp (revision 10955) +++ mptrack/Settings.cpp (working copy) @@ -490,7 +490,7 @@ static void WriteFileUTF16LE(const mpt::PathString &filename, const std::wstring &str) { STATIC_ASSERT(sizeof(wchar_t) == 2); - mpt::ofstream inifile(filename, std::ios::binary); + mpt::SafeOutputFile inifile(filename, std::ios::binary); const uint8 UTF16LE_BOM[] = { 0xff, 0xfe }; inifile.write(reinterpret_cast<const char*>(UTF16LE_BOM), 2); inifile.write(reinterpret_cast<const char*>(str.c_str()), str.length() * sizeof(std::wstring::value_type)); Index: mptrack/TrackerSettings.cpp =================================================================== --- mptrack/TrackerSettings.cpp (revision 10955) +++ mptrack/TrackerSettings.cpp (working copy) @@ -786,7 +786,7 @@ if(!fn.FileOrDirectoryExists()) { CTuning * pT = CSoundFile::CreateTuning12TET("12TET"); - mpt::ofstream f(fn, std::ios::binary); + mpt::SafeOutputFile f(fn, std::ios::binary); pT->Serialize(f); f.close(); delete pT; @@ -798,7 +798,7 @@ if(!fn.FileOrDirectoryExists()) { CTuning * pT = CSoundFile::CreateTuning12TET("12TET [[fs15 1.17.02.49]]"); - mpt::ofstream f(fn, std::ios::binary); + mpt::SafeOutputFile f(fn, std::ios::binary); pT->Serialize(f); f.close(); delete pT; Index: mptrack/TuningDialog.cpp =================================================================== --- mptrack/TuningDialog.cpp (revision 10955) +++ mptrack/TuningDialog.cpp (working copy) @@ -617,7 +617,7 @@ BeginWaitCursor(); - mpt::ofstream fout(dlg.GetFirstFile(), std::ios::binary); + mpt::SafeOutputFile fout(dlg.GetFirstFile(), std::ios::binary); if(tuningFilter != -1 && filterIndex == tuningFilter) { @@ -691,7 +691,7 @@ SanitizeFilename(nameW); fileNameW = mpt::String::Replace(fileNameW, MPT_USTRING("%tuning_name%"), nameW); fileName = mpt::PathString::FromUnicode(fileNameW); - mpt::ofstream fout(fileName, std::ios::binary); + mpt::SafeOutputFile fout(fileName, std::ios::binary); if(tuning.Serialize(fout) != Tuning::SerializationResult::Success) { failure = true; Index: soundlib/Load_it.cpp =================================================================== --- soundlib/Load_it.cpp (revision 10955) +++ soundlib/Load_it.cpp (working copy) @@ -1364,7 +1364,6 @@ bool CSoundFile::SaveIT(std::ostream &f, const mpt::PathString &filename, bool compatibilityExport) { - if(!f) return false; const CModSpecifications &specs = (GetType() == MOD_TYPE_MPT ? ModSpecs::mptm : (compatibilityExport ? ModSpecs::it : ModSpecs::itEx)); Index: soundlib/Load_mod.cpp =================================================================== --- soundlib/Load_mod.cpp (revision 10955) +++ soundlib/Load_mod.cpp (working copy) @@ -2184,7 +2184,10 @@ bool CSoundFile::SaveMod(std::ostream &f) const { - if(!f || m_nChannels == 0) return false; + if(m_nChannels == 0) + { + return false; + } // Write song title { Index: soundlib/Load_s3m.cpp =================================================================== --- soundlib/Load_s3m.cpp (revision 10955) +++ soundlib/Load_s3m.cpp (working copy) @@ -610,7 +610,10 @@ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, }; - if(!f || m_nChannels == 0) return false; + if(m_nChannels == 0) + { + return false; + } const bool saveMuteStatus = #ifdef MODPLUG_TRACKER Index: soundlib/Load_xm.cpp =================================================================== --- soundlib/Load_xm.cpp (revision 10955) +++ soundlib/Load_xm.cpp (working copy) @@ -749,7 +749,6 @@ bool CSoundFile::SaveXM(std::ostream &f, bool compatibilityExport) { - if(!f) return false; bool addChannel = false; // avoid odd channel count for FT2 compatibility Index: soundlib/plugins/PlugInterface.cpp =================================================================== --- soundlib/plugins/PlugInterface.cpp (revision 10955) +++ soundlib/plugins/PlugInterface.cpp (working copy) @@ -652,7 +652,7 @@ bool bank = (dlg.GetExtension() == MPT_PATHSTRING("fxb")); - mpt::ofstream f(dlg.GetFirstFile(), std::ios::binary); + mpt::SafeOutputFile f(dlg.GetFirstFile(), std::ios::binary); if(f.good() && VSTPresets::SaveFile(f, *this, bank)) { return true; Index: soundlib/SampleFormatFLAC.cpp =================================================================== --- soundlib/SampleFormatFLAC.cpp (revision 10955) +++ soundlib/SampleFormatFLAC.cpp (working copy) @@ -533,7 +533,7 @@ { #ifdef MPT_WITH_FLAC - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) { return false; Index: soundlib/SampleFormats.cpp =================================================================== --- soundlib/SampleFormats.cpp (revision 10955) +++ soundlib/SampleFormats.cpp (working copy) @@ -539,7 +539,7 @@ #ifndef MODPLUG_NO_FILESAVE bool CSoundFile::SaveWAVSample(SAMPLEINDEX nSample, const mpt::PathString &filename) const { - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) { return false; @@ -829,7 +829,7 @@ bool CSoundFile::SaveRAWSample(SAMPLEINDEX nSample, const mpt::PathString &filename) const { - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) { return false; @@ -1170,7 +1170,7 @@ bool CSoundFile::SaveS3ISample(SAMPLEINDEX smp, const mpt::PathString &filename) const { - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) return false; @@ -1342,7 +1342,7 @@ return false; } - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) { return false; @@ -2160,7 +2160,7 @@ { return false; } - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f.good()) { return false; @@ -3255,7 +3255,7 @@ ModInstrument *pIns = Instruments[nInstr]; if((!pIns) || filename.empty()) return false; - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) { return false; Index: soundlib/tuningCollection.cpp =================================================================== --- soundlib/tuningCollection.cpp (revision 10955) +++ soundlib/tuningCollection.cpp (working copy) @@ -281,7 +281,7 @@ error = true; } else { - mpt::ofstream fout(fn, std::ios::binary); + mpt::SafeOutputFile fout(fn, std::ios::binary); if(tuning.Serialize(fout) != Tuning::SerializationResult::Success) { error = true; file-flush-v4.patch (19,391 bytes)
Index: common/mptFileIO.cpp =================================================================== --- common/mptFileIO.cpp (revision 10963) +++ common/mptFileIO.cpp (working copy) @@ -18,7 +18,13 @@ #endif // MPT_OS_WINDOWS #endif // MODPLUG_TRACKER +#if defined(MPT_ENABLE_FILEIO) +#if MPT_COMPILER_MSVC +#include <tchar.h> +#endif // MPT_COMPILER_MSVC +#endif // MPT_ENABLE_FILEIO + OPENMPT_NAMESPACE_BEGIN @@ -80,7 +86,62 @@ 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: + MPT_FALLTHROUGH; + case std::ios_base::out | std::ios_base::trunc: + fopen_mode = _T("w"); + break; + case std::ios_base::app: + MPT_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: + MPT_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; +} + +#endif // MPT_COMPILER_MSVC + +} // namespace mpt + + + +namespace mpt { + LazyFileRef & LazyFileRef::operator = (const std::vector<mpt::byte> &data) { mpt::ofstream file(m_Filename, std::ios::binary); Index: common/mptFileIO.h =================================================================== --- common/mptFileIO.h (revision 10963) +++ common/mptFileIO.h (working copy) @@ -23,6 +23,14 @@ #include <streambuf> #include <utility> +#if MPT_COMPILER_MSVC +#include <cstdio> +#endif // !MPT_COMPILER_MSVC + +#if MPT_COMPILER_MSVC +#include <stdio.h> +#endif // !MPT_COMPILER_MSVC + #endif // MPT_ENABLE_FILEIO @@ -125,6 +133,14 @@ { detail::fstream_open<Tbase>(*this, filename, mode); } +#if MPT_COMPILER_MSVC +protected: + ofstream(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<Tbase>(*this, filename, mode); @@ -137,8 +153,103 @@ #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 +}; +static inline FlushMode FlushModeFromBool(bool flush) +{ + return flush ? FlushMode::Full : FlushMode::None; +} +class SafeOutputFile + : public mpt::ofstream +{ +private: + typedef std::ofstream Tbase; + FlushMode m_FlushMode; +#if MPT_COMPILER_MSVC + FILE *m_f; +#endif // MPT_COMPILER_MSVC +#if MPT_COMPILER_MSVC + static mpt::tstring convert_mode(std::ios_base::openmode mode, FlushMode flushMode); + FILE * internal_fopen(const mpt::PathString &filename, std::ios_base::openmode mode, FlushMode flushMode) + { + mpt::tstring fopen_mode = convert_mode(mode, flushMode); + if(fopen_mode.empty()) + { + return nullptr; + } + FILE *f = +#ifdef UNICODE + _wfopen(filename.AsNativePrefixed().c_str(), fopen_mode.c_str()) +#else + fopen(filename.AsNativePrefixed().c_str(), fopen_mode.c_str()) +#endif + ; + if(!f) + { + return nullptr; + } + if(mode & std::ios_base::ate) + { + if(fseek(f, 0, SEEK_END) != 0) + { + fclose(f); + f = nullptr; + return nullptr; + } + } + m_f = f; + return f; + } +#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) +#if MPT_COMPILER_MSVC + : mpt::ofstream(internal_fopen(filename, mode | std::ios_base::out, flushMode)) +#else // !MPT_COMPILER_MSVC + : mpt::ofstream(filename, mode) +#endif // MPT_COMPILER_MSVC + , m_FlushMode(flushMode) + { + } + ~SafeOutputFile() + { + if(!*this) + { + return; + } + if(!rdbuf()) + { + return; + } + #if MPT_COMPILER_MSVC + if(!m_f) + { + return; + } + #endif // MPT_COMPILER_MSVC + if(m_FlushMode != FlushMode::None) + { + rdbuf()->pubsync(); + } + #if MPT_COMPILER_MSVC + if(m_FlushMode != FlushMode::None) + { + fflush(m_f); + } + fclose(m_f); + #endif // MPT_COMPILER_MSVC + } +}; + + + // 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. @@ -223,5 +334,6 @@ #endif // MPT_ENABLE_FILEIO + OPENMPT_NAMESPACE_END Index: mptrack/CommandSet.cpp =================================================================== --- mptrack/CommandSet.cpp (revision 10963) +++ mptrack/CommandSet.cpp (working copy) @@ -1510,7 +1510,7 @@ ... */ - mpt::ofstream f(filename); + mpt::SafeOutputFile f(filename, std::ios::out, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(!f) { ErrorBox(IDS_CANT_OPEN_FILE_FOR_WRITING); Index: mptrack/ExceptionHandler.cpp =================================================================== --- mptrack/ExceptionHandler.cpp (revision 10963) +++ mptrack/ExceptionHandler.cpp (working copy) @@ -268,13 +268,13 @@ errorMessage += MPT_ULITERAL("\n\n"); { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("error.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("error.txt"), std::ios::binary, mpt::FlushMode::Full); f.imbue(std::locale::classic()); f << mpt::String::Replace(mpt::ToCharset(mpt::CharsetUTF8, errorMessage), "\n", "\r\n"); } { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("threads.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("threads.txt"), std::ios::binary, mpt::FlushMode::Full); f.imbue(std::locale::classic()); f << mpt::format("current : %1")(mpt::fmt::hex0<8>(GetCurrentThreadId())) << "\r\n"; f << mpt::format("GUI : %1")(mpt::fmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindGUI))) << "\r\n"; @@ -290,7 +290,7 @@ }; { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("active-settings.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("active-settings.txt"), std::ios::binary, mpt::FlushMode::Full); f.imbue(std::locale::classic()); if(&theApp.GetSettings()) { @@ -357,13 +357,13 @@ */ { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("about-openmpt.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("about-openmpt.txt"), std::ios::binary, mpt::FlushMode::Full); f.imbue(std::locale::classic()); f << mpt::ToCharset(mpt::CharsetUTF8, CAboutDlg::GetTabText(0)); } { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("about-components.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("about-components.txt"), std::ios::binary, mpt::FlushMode::Full); f.imbue(std::locale::classic()); f << mpt::ToCharset(mpt::CharsetUTF8, CAboutDlg::GetTabText(1)); } Index: mptrack/mod2midi.cpp =================================================================== --- mptrack/mod2midi.cpp (revision 10963) +++ mptrack/mod2midi.cpp (working copy) @@ -695,7 +695,7 @@ void CDoMidiConvert::Run() { - mpt::ofstream f(m_fileName, std::ios::binary); + mpt::SafeOutputFile f(m_fileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(!f.good()) { Reporting::Error("Could not open file for writing. Is it open in another application?"); Index: mptrack/Mod2wave.cpp =================================================================== --- mptrack/Mod2wave.cpp (revision 10963) +++ mptrack/Mod2wave.cpp (working copy) @@ -949,7 +949,7 @@ normalizeFile.open(normalizeFileName, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); } - mpt::ofstream fileStream(m_lpszFileName, std::ios::binary); + mpt::SafeOutputFile fileStream(m_lpszFileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(!fileStream) { Index: mptrack/Moddoc.cpp =================================================================== --- mptrack/Moddoc.cpp (revision 10963) +++ mptrack/Moddoc.cpp (working copy) @@ -278,7 +278,7 @@ return FALSE; BOOL ok = FALSE; - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(f) { BeginWaitCursor(); @@ -1898,7 +1898,7 @@ if(!dlg.Show()) return; filename = dlg.GetFirstFile(); - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(!f) return; Index: mptrack/Modedit.cpp =================================================================== --- mptrack/Modedit.cpp (revision 10963) +++ mptrack/Modedit.cpp (working copy) @@ -1099,7 +1099,7 @@ CStringA s; EnvelopeToString(s, pIns->GetEnvelope(nEnv)); - mpt::ofstream f(fileName, std::ios::binary); + mpt::SafeOutputFile f(fileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(!f) { EndWaitCursor(); Index: mptrack/Settings.cpp =================================================================== --- mptrack/Settings.cpp (revision 10963) +++ mptrack/Settings.cpp (working copy) @@ -490,7 +490,7 @@ static void WriteFileUTF16LE(const mpt::PathString &filename, const std::wstring &str) { STATIC_ASSERT(sizeof(wchar_t) == 2); - mpt::ofstream inifile(filename, std::ios::binary); + mpt::SafeOutputFile inifile(filename, std::ios::binary, mpt::FlushMode::Full); const uint8 UTF16LE_BOM[] = { 0xff, 0xfe }; inifile.write(reinterpret_cast<const char*>(UTF16LE_BOM), 2); inifile.write(reinterpret_cast<const char*>(str.c_str()), str.length() * sizeof(std::wstring::value_type)); Index: mptrack/TrackerSettings.cpp =================================================================== --- mptrack/TrackerSettings.cpp (revision 10963) +++ mptrack/TrackerSettings.cpp (working copy) @@ -222,6 +222,7 @@ , MiscAllowMultipleCommandsPerKey(conf, MPT_USTRING("Misc"), MPT_USTRING("AllowMultipleCommandsPerKey"), false) , MiscDistinguishModifiers(conf, MPT_USTRING("Misc"), MPT_USTRING("DistinguishModifiers"), false) , MiscProcessPriorityClass(conf, MPT_USTRING("Misc"), MPT_USTRING("ProcessPriorityClass"), ProcessPriorityClassNORMAL) + , MiscFlushFileBuffersOnSave(conf, MPT_USTRING("Misc"), MPT_USTRING("FlushFileBuffersOnSave"), true) // Sound Settings , m_SoundShowDeprecatedDevices(conf, MPT_USTRING("Sound Settings"), MPT_USTRING("ShowDeprecatedDevices"), true) , m_SoundShowNotRecommendedDeviceWarning(conf, MPT_USTRING("Sound Settings"), MPT_USTRING("ShowNotRecommendedDeviceWarning"), true) @@ -786,7 +787,7 @@ if(!fn.FileOrDirectoryExists()) { CTuning * pT = CSoundFile::CreateTuning12TET("12TET"); - mpt::ofstream f(fn, std::ios::binary); + mpt::SafeOutputFile f(fn, std::ios::binary, mpt::FlushMode::Full); pT->Serialize(f); f.close(); delete pT; @@ -798,7 +799,7 @@ if(!fn.FileOrDirectoryExists()) { CTuning * pT = CSoundFile::CreateTuning12TET("12TET [[fs15 1.17.02.49]]"); - mpt::ofstream f(fn, std::ios::binary); + mpt::SafeOutputFile f(fn, std::ios::binary, mpt::FlushMode::Full); pT->Serialize(f); f.close(); delete pT; Index: mptrack/TrackerSettings.h =================================================================== --- mptrack/TrackerSettings.h (revision 10963) +++ mptrack/TrackerSettings.h (working copy) @@ -637,6 +637,7 @@ CachedSetting<bool> MiscAllowMultipleCommandsPerKey; CachedSetting<bool> MiscDistinguishModifiers; Setting<ProcessPriorityClass> MiscProcessPriorityClass; + Setting<bool> MiscFlushFileBuffersOnSave; // Sound Settings Index: mptrack/TuningDialog.cpp =================================================================== --- mptrack/TuningDialog.cpp (revision 10963) +++ mptrack/TuningDialog.cpp (working copy) @@ -617,7 +617,7 @@ BeginWaitCursor(); - mpt::ofstream fout(dlg.GetFirstFile(), std::ios::binary); + mpt::SafeOutputFile fout(dlg.GetFirstFile(), std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(tuningFilter != -1 && filterIndex == tuningFilter) { @@ -691,7 +691,7 @@ SanitizeFilename(nameW); fileNameW = mpt::String::Replace(fileNameW, MPT_USTRING("%tuning_name%"), nameW); fileName = mpt::PathString::FromUnicode(fileNameW); - mpt::ofstream fout(fileName, std::ios::binary); + mpt::SafeOutputFile fout(fileName, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(tuning.Serialize(fout) != Tuning::SerializationResult::Success) { failure = true; Index: soundlib/Load_it.cpp =================================================================== --- soundlib/Load_it.cpp (revision 10963) +++ soundlib/Load_it.cpp (working copy) @@ -1364,7 +1364,6 @@ bool CSoundFile::SaveIT(std::ostream &f, const mpt::PathString &filename, bool compatibilityExport) { - if(!f) return false; const CModSpecifications &specs = (GetType() == MOD_TYPE_MPT ? ModSpecs::mptm : (compatibilityExport ? ModSpecs::it : ModSpecs::itEx)); Index: soundlib/Load_mod.cpp =================================================================== --- soundlib/Load_mod.cpp (revision 10963) +++ soundlib/Load_mod.cpp (working copy) @@ -2184,7 +2184,10 @@ bool CSoundFile::SaveMod(std::ostream &f) const { - if(!f || m_nChannels == 0) return false; + if(m_nChannels == 0) + { + return false; + } // Write song title { Index: soundlib/Load_s3m.cpp =================================================================== --- soundlib/Load_s3m.cpp (revision 10963) +++ soundlib/Load_s3m.cpp (working copy) @@ -610,7 +610,10 @@ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, }; - if(!f || m_nChannels == 0) return false; + if(m_nChannels == 0) + { + return false; + } const bool saveMuteStatus = #ifdef MODPLUG_TRACKER Index: soundlib/Load_xm.cpp =================================================================== --- soundlib/Load_xm.cpp (revision 10963) +++ soundlib/Load_xm.cpp (working copy) @@ -749,7 +749,6 @@ bool CSoundFile::SaveXM(std::ostream &f, bool compatibilityExport) { - if(!f) return false; bool addChannel = false; // avoid odd channel count for FT2 compatibility Index: soundlib/plugins/PlugInterface.cpp =================================================================== --- soundlib/plugins/PlugInterface.cpp (revision 10963) +++ soundlib/plugins/PlugInterface.cpp (working copy) @@ -652,7 +652,7 @@ bool bank = (dlg.GetExtension() == MPT_PATHSTRING("fxb")); - mpt::ofstream f(dlg.GetFirstFile(), std::ios::binary); + mpt::SafeOutputFile f(dlg.GetFirstFile(), std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(f.good() && VSTPresets::SaveFile(f, *this, bank)) { return true; Index: soundlib/SampleFormatFLAC.cpp =================================================================== --- soundlib/SampleFormatFLAC.cpp (revision 10963) +++ soundlib/SampleFormatFLAC.cpp (working copy) @@ -533,7 +533,15 @@ { #ifdef MPT_WITH_FLAC - mpt::ofstream f(filename, std::ios::binary); + const mpt::FlushMode flushMode = + #ifdef MODPLUG_TRACKER + mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave) + #else + mpt::FlushMode::Full + #endif + ; + + mpt::SafeOutputFile f(filename, std::ios::binary, flushMode); if(!f) { return false; Index: soundlib/SampleFormats.cpp =================================================================== --- soundlib/SampleFormats.cpp (revision 10963) +++ soundlib/SampleFormats.cpp (working copy) @@ -39,6 +39,22 @@ OPENMPT_NAMESPACE_BEGIN +#ifndef MODPLUG_NO_FILESAVE + +static mpt::FlushMode GetSampleFileFlushMode() +{ + return + #ifdef MODPLUG_TRACKER + mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave) + #else + mpt::FlushMode::Full + #endif + ; +} + +#endif // !MODPLUG_NO_FILESAVE + + bool CSoundFile::ReadSampleFromFile(SAMPLEINDEX nSample, FileReader &file, bool mayNormalize, bool includeInstrumentFormats) { if(!nSample || nSample >= MAX_SAMPLES) return false; @@ -539,7 +555,7 @@ #ifndef MODPLUG_NO_FILESAVE bool CSoundFile::SaveWAVSample(SAMPLEINDEX nSample, const mpt::PathString &filename) const { - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, GetSampleFileFlushMode()); if(!f) { return false; @@ -829,7 +845,7 @@ bool CSoundFile::SaveRAWSample(SAMPLEINDEX nSample, const mpt::PathString &filename) const { - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, GetSampleFileFlushMode()); if(!f) { return false; @@ -1170,7 +1186,7 @@ bool CSoundFile::SaveS3ISample(SAMPLEINDEX smp, const mpt::PathString &filename) const { - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, GetSampleFileFlushMode()); if(!f) return false; @@ -1342,7 +1358,7 @@ return false; } - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, GetSampleFileFlushMode()); if(!f) { return false; @@ -2160,7 +2176,7 @@ { return false; } - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, GetSampleFileFlushMode()); if(!f.good()) { return false; @@ -3255,7 +3271,7 @@ ModInstrument *pIns = Instruments[nInstr]; if((!pIns) || filename.empty()) return false; - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary, GetSampleFileFlushMode()); if(!f) { return false; Index: soundlib/tuningCollection.cpp =================================================================== --- soundlib/tuningCollection.cpp (revision 10963) +++ soundlib/tuningCollection.cpp (working copy) @@ -15,6 +15,9 @@ #include <algorithm> #include "../common/mptFileIO.h" #include "Loaders.h" +#ifdef MODPLUG_TRACKER +#include "../mptrack/TrackerSettings.h" +#endif //MODPLUG_TRACKER OPENMPT_NAMESPACE_BEGIN @@ -281,7 +284,7 @@ error = true; } else { - mpt::ofstream fout(fn, std::ios::binary); + mpt::SafeOutputFile fout(fn, std::ios::binary, mpt::FlushModeFromBool(TrackerSettings::Instance().MiscFlushFileBuffersOnSave)); if(tuning.Serialize(fout) != Tuning::SerializationResult::Success) { error = true; | ||||
Has the bug occurred in previous versions? | The only other version I was using whilst working on the module is v1.27.09.00. | ||||
Tested code revision (in case you know it) | |||||
related to | 0001161 | resolved | Saga Musix | Notify user about file write failures |
Does this happen when you are properly shutting down Windows (i.e. using shutdown or reboot from the start menu)? Or are you talking about a hard power loss without shutting down Windows properly (i.e. pressing the power button 5 seconds, or unplugging the power cable with no battery (or immediately failing battery), or is the battery too weak to properly finish shutting down?)? Do you save the file to the internal C: drive or to some secondary internal drive or external drive like a USB key, SD card, external HDD/SSD, or network drive? The file you provided is completely empty (i.e. it contains absolutely no recoverable data). This obviously should never happen, however I cannot imagine how that could happen if you are properly closing OpenMPT and then properly shutting down Windows. Hence the questions about the precise situation. There is something that OpenMPT could do here: flushing the file buffers after saving. However that would (in theory) only make a difference if an actual Windows crash (as opposed to proper shutdown) happens right after saving. |
|
Hard power loss i.e removing AC without shutting the laptop down. As for where I saved it, I saved it in a desktop directory where i keep all my module stuff [Desktop\MOD Stuff\Ideas (Modules)]. |
|
@Saga Musix @{U4} : This change implements file buffer flushing. This is a good idea in any case, not in particular because of this very issue, but because obscure audio hardware has at least a somewhat increased tendency to cause BSODs which will hit the exact same problem. Disadvantage of the change is being slightly slower file saving ((filesize / device_write_speed) + device_sync_time(about 30ms on mechanical media)). You should really try to avoid hard power losses if at all possible. This can corrupt all kinds of other software's state as well. file-flush-v1.patch (14,929 bytes)
Index: common/mptFileIO.h =================================================================== --- common/mptFileIO.h (revision 10938) +++ common/mptFileIO.h (working copy) @@ -125,6 +125,14 @@ { detail::fstream_open<Tbase>(*this, filename, mode); } +#if MPT_COMPILER_MSVC +protected: + ofstream(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<Tbase>(*this, filename, mode); @@ -137,8 +145,123 @@ #endif }; +class SafeOutputFile + : public mpt::ofstream +{ +private: + typedef std::ofstream Tbase; +#if MPT_COMPILER_MSVC + FILE *m_f; +#endif // MPT_COMPILER_MSVC +#if MPT_COMPILER_MSVC + static mpt::tstring convert_mode(std::ios_base::openmode mode) + { + mpt::tstring fopen_mode; + switch(mode & ~(std::ios_base::ate | std::ios_base::binary)) + { + case std::ios_base::in: + fopen_mode = TEXT("r"); + break; + case std::ios_base::out: + MPT_FALLTHROUGH; + case std::ios_base::out | std::ios_base::trunc: + fopen_mode = TEXT("w"); + break; + case std::ios_base::app: + MPT_FALLTHROUGH; + case std::ios_base::out | std::ios_base::app: + fopen_mode = TEXT("a"); + break; + case std::ios_base::out | std::ios_base::in: + fopen_mode = TEXT("r+"); + break; + case std::ios_base::out | std::ios_base::in | std::ios_base::trunc: + fopen_mode = TEXT("w+"); + break; + case std::ios_base::out | std::ios_base::in | std::ios_base::app: + MPT_FALLTHROUGH; + case std::ios_base::in | std::ios_base::app: + fopen_mode = TEXT("a+"); + break; + } + if(fopen_mode.empty()) + { + return fopen_mode; + } + if(mode & std::ios_base::binary) + { + fopen_mode += TEXT("b"); + } + fopen_mode += TEXT("c"); // force commit on fflush (MSVC specific) + return fopen_mode; + } + FILE * internal_fopen(const mpt::PathString &filename, std::ios_base::openmode mode) + { + mpt::tstring fopen_mode = convert_mode(mode); + if(fopen_mode.empty()) + { + return nullptr; + } + FILE *f = +#ifdef UNICODE + _wfopen(filename.AsNativePrefixed().c_str(), fopen_mode.c_str()) +#else + fopen(filename.AsNativePrefixed().c_str(), fopen_mode.c_str()) +#endif + ; + if(!f) + { + return nullptr; + } + if(mode & std::ios_base::ate) + { + if(fseek(f, 0, SEEK_END) != 0) + { + fclose(f); + f = nullptr; + return nullptr; + } + } + m_f = f; + return f; + } +#endif // MPT_COMPILER_MSVC +public: + SafeOutputFile() = delete; + explicit SafeOutputFile(const mpt::PathString &filename, std::ios_base::openmode mode = std::ios_base::out) +#if MPT_COMPILER_MSVC + : mpt::ofstream(internal_fopen(filename, mode | std::ios_base::out)) +#else // !MPT_COMPILER_MSVC + : mpt::ofstream(filename, mode) +#endif // MPT_COMPILER_MSVC + { + } + ~SafeOutputFile() + { + if(!*this) + { + return; + } + if(!rdbuf()) + { + return; + } + #if MPT_COMPILER_MSVC + if(!m_f) + { + return; + } + #endif // MPT_COMPILER_MSVC + rdbuf()->pubsync(); + #if MPT_COMPILER_MSVC + fflush(m_f); + fclose(m_f); + #endif // MPT_COMPILER_MSVC + } +}; + // 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. @@ -223,5 +346,16 @@ #endif // MPT_ENABLE_FILEIO + +#ifdef MODPLUG_TRACKER +inline bool SyncFileData(mpt::ofstream & f) +{ + f.rdbuf()->pubsync(); + return true; +} +#endif // MODPLUG_TRACKER + + + OPENMPT_NAMESPACE_END Index: mptrack/CommandSet.cpp =================================================================== --- mptrack/CommandSet.cpp (revision 10938) +++ mptrack/CommandSet.cpp (working copy) @@ -1510,7 +1510,7 @@ ... */ - mpt::ofstream f(filename); + mpt::SafeOutputFile f(filename); if(!f) { ErrorBox(IDS_CANT_OPEN_FILE_FOR_WRITING); Index: mptrack/ExceptionHandler.cpp =================================================================== --- mptrack/ExceptionHandler.cpp (revision 10938) +++ mptrack/ExceptionHandler.cpp (working copy) @@ -268,13 +268,13 @@ errorMessage += MPT_ULITERAL("\n\n"); { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("error.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("error.txt"), std::ios::binary); f.imbue(std::locale::classic()); f << mpt::String::Replace(mpt::ToCharset(mpt::CharsetUTF8, errorMessage), "\n", "\r\n"); } { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("threads.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("threads.txt"), std::ios::binary); f.imbue(std::locale::classic()); f << mpt::format("current : %1")(mpt::fmt::hex0<8>(GetCurrentThreadId())) << "\r\n"; f << mpt::format("GUI : %1")(mpt::fmt::hex0<8>(mpt::log::Trace::GetThreadId(mpt::log::Trace::ThreadKindGUI))) << "\r\n"; @@ -290,7 +290,7 @@ }; { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("active-settings.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("active-settings.txt"), std::ios::binary); f.imbue(std::locale::classic()); if(&theApp.GetSettings()) { @@ -357,7 +357,7 @@ */ { - mpt::ofstream f(crashDirectory.path + MPT_PATHSTRING("about-openmpt.txt"), std::ios::binary); + mpt::SafeOutputFile f(crashDirectory.path + MPT_PATHSTRING("about-openmpt.txt"), std::ios::binary); f.imbue(std::locale::classic()); f << mpt::ToCharset(mpt::CharsetUTF8, CAboutDlg::GetTabText(0)); } Index: mptrack/mod2midi.cpp =================================================================== --- mptrack/mod2midi.cpp (revision 10938) +++ mptrack/mod2midi.cpp (working copy) @@ -710,7 +710,7 @@ void CDoMidiConvert::Run() { - mpt::ofstream f(m_fileName, std::ios::binary); + mpt::SafeOutputFile f(m_fileName, std::ios::binary); if(!f.good()) { Reporting::Error("Could not open file for writing. Is it open in another application?"); Index: mptrack/Mod2wave.cpp =================================================================== --- mptrack/Mod2wave.cpp (revision 10938) +++ mptrack/Mod2wave.cpp (working copy) @@ -949,7 +949,7 @@ normalizeFile.open(normalizeFileName, std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); } - mpt::ofstream fileStream(m_lpszFileName, std::ios::binary); + mpt::SafeOutputFile fileStream(m_lpszFileName, std::ios::binary); if(!fileStream) { Index: mptrack/Modedit.cpp =================================================================== --- mptrack/Modedit.cpp (revision 10938) +++ mptrack/Modedit.cpp (working copy) @@ -1099,7 +1099,7 @@ CStringA s; EnvelopeToString(s, pIns->GetEnvelope(nEnv)); - mpt::ofstream f(fileName, std::ios::binary); + mpt::SafeOutputFile f(fileName, std::ios::binary); if(!f) { EndWaitCursor(); Index: mptrack/Settings.cpp =================================================================== --- mptrack/Settings.cpp (revision 10938) +++ mptrack/Settings.cpp (working copy) @@ -490,7 +490,7 @@ static void WriteFileUTF16LE(const mpt::PathString &filename, const std::wstring &str) { STATIC_ASSERT(sizeof(wchar_t) == 2); - mpt::ofstream inifile(filename, std::ios::binary); + mpt::SafeOutputFile inifile(filename, std::ios::binary); const uint8 UTF16LE_BOM[] = { 0xff, 0xfe }; inifile.write(reinterpret_cast<const char*>(UTF16LE_BOM), 2); inifile.write(reinterpret_cast<const char*>(str.c_str()), str.length() * sizeof(std::wstring::value_type)); Index: mptrack/TrackerSettings.cpp =================================================================== --- mptrack/TrackerSettings.cpp (revision 10938) +++ mptrack/TrackerSettings.cpp (working copy) @@ -786,7 +786,7 @@ if(!fn.FileOrDirectoryExists()) { CTuning * pT = CSoundFile::CreateTuning12TET("12TET"); - mpt::ofstream f(fn, std::ios::binary); + mpt::SafeOutputFile f(fn, std::ios::binary); pT->Serialize(f); f.close(); delete pT; @@ -798,7 +798,7 @@ if(!fn.FileOrDirectoryExists()) { CTuning * pT = CSoundFile::CreateTuning12TET("12TET [[fs15 1.17.02.49]]"); - mpt::ofstream f(fn, std::ios::binary); + mpt::SafeOutputFile f(fn, std::ios::binary); pT->Serialize(f); f.close(); delete pT; Index: mptrack/TuningDialog.cpp =================================================================== --- mptrack/TuningDialog.cpp (revision 10938) +++ mptrack/TuningDialog.cpp (working copy) @@ -617,7 +617,7 @@ BeginWaitCursor(); - mpt::ofstream fout(dlg.GetFirstFile(), std::ios::binary); + mpt::SafeOutputFile fout(dlg.GetFirstFile(), std::ios::binary); if(tuningFilter != -1 && filterIndex == tuningFilter) { @@ -691,7 +691,7 @@ SanitizeFilename(nameW); fileNameW = mpt::String::Replace(fileNameW, MPT_USTRING("%tuning_name%"), nameW); fileName = mpt::PathString::FromUnicode(fileNameW); - mpt::ofstream fout(fileName, std::ios::binary); + mpt::SafeOutputFile fout(fileName, std::ios::binary); if(tuning.Serialize(fout) != Tuning::SerializationResult::Success) { failure = true; Index: soundlib/Load_it.cpp =================================================================== --- soundlib/Load_it.cpp (revision 10938) +++ soundlib/Load_it.cpp (working copy) @@ -1375,7 +1375,7 @@ { return false; } - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) { return false; Index: soundlib/Load_mod.cpp =================================================================== --- soundlib/Load_mod.cpp (revision 10938) +++ soundlib/Load_mod.cpp (working copy) @@ -2186,7 +2186,7 @@ { if(m_nChannels == 0 || filename.empty()) return false; - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) { return false; Index: soundlib/Load_s3m.cpp =================================================================== --- soundlib/Load_s3m.cpp (revision 10938) +++ soundlib/Load_s3m.cpp (working copy) @@ -611,7 +611,7 @@ }; if(m_nChannels == 0 || filename.empty()) return false; - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) { return false; Index: soundlib/Load_xm.cpp =================================================================== --- soundlib/Load_xm.cpp (revision 10938) +++ soundlib/Load_xm.cpp (working copy) @@ -753,7 +753,7 @@ { return false; } - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) { return false; Index: soundlib/plugins/PlugInterface.cpp =================================================================== --- soundlib/plugins/PlugInterface.cpp (revision 10938) +++ soundlib/plugins/PlugInterface.cpp (working copy) @@ -669,7 +669,7 @@ bool bank = (dlg.GetExtension() == MPT_PATHSTRING("fxb")); - mpt::ofstream f(dlg.GetFirstFile(), std::ios::binary); + mpt::SafeOutputFile f(dlg.GetFirstFile(), std::ios::binary); if(f.good() && VSTPresets::SaveFile(f, *this, bank)) { return true; Index: soundlib/SampleFormatFLAC.cpp =================================================================== --- soundlib/SampleFormatFLAC.cpp (revision 10938) +++ soundlib/SampleFormatFLAC.cpp (working copy) @@ -470,11 +470,11 @@ struct FLAC__StreamEncoder_RAII { FLAC__StreamEncoder *encoder; - mpt::ofstream f; + mpt::ofstream & f; operator FLAC__StreamEncoder *() { return encoder; } - FLAC__StreamEncoder_RAII() : encoder(FLAC__stream_encoder_new()) { } + FLAC__StreamEncoder_RAII(mpt::ofstream & f) : encoder(FLAC__stream_encoder_new()), f(f) { } ~FLAC__StreamEncoder_RAII() { FLAC__stream_encoder_delete(encoder); @@ -532,7 +532,12 @@ bool CSoundFile::SaveFLACSample(SAMPLEINDEX nSample, const mpt::PathString &filename) const { #ifdef MPT_WITH_FLAC - FLAC__StreamEncoder_RAII encoder; + mpt::SafeOutputFile f(filename, std::ios::binary); + if(!f) + { + return false; + } + FLAC__StreamEncoder_RAII encoder(f); if(encoder == nullptr) { return false; @@ -668,8 +673,7 @@ FLAC__int32 *sampleData = nullptr; SmpLength numSamples = 0; - encoder.f.open(filename, std::ios::binary); - if(!encoder.f || FLAC__stream_encoder_init_stream(encoder, &FLAC__StreamEncoder_RAII::StreamEncoderWriteCallback, &FLAC__StreamEncoder_RAII::StreamEncoderSeekCallback, &FLAC__StreamEncoder_RAII::StreamEncoderTellCallback, nullptr, &encoder.f) != FLAC__STREAM_ENCODER_INIT_STATUS_OK) + if(FLAC__stream_encoder_init_stream(encoder, &FLAC__StreamEncoder_RAII::StreamEncoderWriteCallback, &FLAC__StreamEncoder_RAII::StreamEncoderSeekCallback, &FLAC__StreamEncoder_RAII::StreamEncoderTellCallback, nullptr, &encoder.f) != FLAC__STREAM_ENCODER_INIT_STATUS_OK) { goto fail; } Index: soundlib/SampleFormats.cpp =================================================================== --- soundlib/SampleFormats.cpp (revision 10938) +++ soundlib/SampleFormats.cpp (working copy) @@ -539,7 +539,7 @@ #ifndef MODPLUG_NO_FILESAVE bool CSoundFile::SaveWAVSample(SAMPLEINDEX nSample, const mpt::PathString &filename) const { - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) { return false; @@ -829,7 +829,7 @@ bool CSoundFile::SaveRAWSample(SAMPLEINDEX nSample, const mpt::PathString &filename) const { - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) { return false; @@ -1170,7 +1170,7 @@ bool CSoundFile::SaveS3ISample(SAMPLEINDEX smp, const mpt::PathString &filename) const { - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) return false; @@ -1342,7 +1342,7 @@ return false; } - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) { return false; @@ -2160,7 +2160,7 @@ { return false; } - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f.good()) { return false; @@ -3255,7 +3255,7 @@ ModInstrument *pIns = Instruments[nInstr]; if((!pIns) || filename.empty()) return false; - mpt::ofstream f(filename, std::ios::binary); + mpt::SafeOutputFile f(filename, std::ios::binary); if(!f) { return false; Index: soundlib/tuningCollection.cpp =================================================================== --- soundlib/tuningCollection.cpp (revision 10938) +++ soundlib/tuningCollection.cpp (working copy) @@ -281,7 +281,7 @@ error = true; } else { - mpt::ofstream fout(fn, std::ios::binary); + mpt::SafeOutputFile fout(fn, std::ios::binary); if(tuning.Serialize(fout) != Tuning::SerializationResult::Success) { error = true; |
|
I'm not a huge fan of changin the operating system's implicit storage strategy, in particular since saving happens in the UI thread and can already block for a measurable amount of time if there are large (compressed) samples - which is annoying in particular when auto-saving - but if we can determine that it won't delay the process much more, I guess it's safe to do. @fuzion_mixer: There really is no need to make this issue private. Private issues are meant for reporting issues that are relevant to security. |
|
Flushing file buffers when saving user data is considered common sense best practice: When saving is signaled as done by the UI, the user expects the saved state to survive any crash that happens later. Performance considerations, especially for autosave of course also apply. I guess we could also add an option so that users who know their system is unconditionally stable can disable the flushing for improved performance. We could even go a step further by writing to a temp file first, flushing it, and then renaming the temp file onto the to-be-overwritten file. This is not as important on Windows as it is on Linux for crash resilience because directory contents is implicitly flushed on Windows. However it is still useful to guard against crashes (Windows or OpenMPT) and i/o errors during overwriting data (in which case the old data will be lost and the new data will not be finished yet). I still have a Windows 7 box with mechanical storage. Precise measurements will be difficult, but I can probably provide some real-world ballpark numbers. |
|
Generally I think it might make sense to move file creation outside of the module savers (i.e. pass a file handle rather than a filename) - this makes error handling when file creation fails more consistent, and it would give us the possibility to handle autosave and regular save differently without having to pass even more parameters to each export function. I suggest we do that as part of this improvement. |
|
I agree, that will simplify things. |
|
Module savers now take a stream handle rather than a filename. |
|
This updated patch adds configurable file buffer flushing. Hidden option is |
|
Another somewhat related thing we might want to consider is catching I/O errors when saving (in particular write failures in case the disk is full). Since it would make the export code unnecessarily complex when checking every write call there, we could enable iostream exceptions for badbit/failbit and catch them (both outside of the loader of course). |
|
Agreed, ultimately, we should have that goal in mind. I do not think we can achieve that for 1.28 though. There are still code paths that are not 100% exception-safe. This also probably requires another round of consolidation of all the different file i/o code paths that we have. For 1.28, I think just the buffer flushing as in the provided patch, is probably enough. Otherwise this would delay 1.28 even more. |
|
I will create a separate issue for it then. |
|
I have tested the code both with an internal SSD and an external (USB 2) HDD. The extra time can be nicely observed by the time between the busy mouse cursor turing back into a regular mouse cursor and the save button in the main toolbar getting depressed (this is due to EndWaitCursor being called before the file stream is destructed). |
|
Do you have enabled caching for the external HDD? Windows does handle I/O operations differently, if either caching is enabled or fast-remove is enabled. |
|
I did not do any precise measurements yet, however I tested similarly sized modules on a 1TB 7200rpm SATA drive, and the delays are (of course) still noticeable, but about 4 times shorter than what you are seeing on an USB2 HDD. Which totally makes sense, as this is effectively limited by USB2 transfer speeds at that point. I think we should commit the patch as is. The improved safety for user data warrants the slight performance regression. People who are annoyed by the regression in file saving performance can simply disable the flushing via the hidden option. This probably needs a release-notes entry though. @StarWolf3000 The whole point of the change is to explicitly flush caches, such that data is permanently stored on disk and is crash resilient. I.e. we explicitly want the non-write-caching behaviour in all cases (even if caching is enabled). |
|
Yes, let's do it like that. I think the patch can be merged as-is. |
|
Implemented in r10965. |
|
Date Modified | Username | Field | Change |
---|---|---|---|
2018-10-23 05:24 | fuzion_mixer | New Issue | |
2018-10-23 06:33 | manx | Note Added: 0003684 | |
2018-10-23 06:37 | manx | Assigned To | => manx |
2018-10-23 06:37 | manx | Priority | normal => high |
2018-10-23 06:37 | manx | Severity | minor => major |
2018-10-23 06:37 | manx | Status | new => assigned |
2018-10-23 06:46 | fuzion_mixer | Note Added: 0003685 | |
2018-10-23 09:55 | manx | File Added: file-flush-v1.patch | |
2018-10-23 09:55 | manx | Note Added: 0003686 | |
2018-10-23 10:08 | manx | Status | assigned => acknowledged |
2018-10-23 10:08 | manx | Target Version | => OpenMPT 1.28.01.00 / libopenmpt 0.4.0 (upgrade first) |
2018-10-23 11:32 | Saga Musix | Note Added: 0003687 | |
2018-10-23 11:32 | Saga Musix | Note View State: 0003685: public | |
2018-10-23 12:07 | manx | Note Added: 0003688 | |
2018-10-23 12:10 | Saga Musix | Note Added: 0003689 | |
2018-10-23 13:00 | manx | Note Added: 0003690 | |
2018-10-28 20:50 | Saga Musix | View Status | private => public |
2018-10-28 20:50 | Saga Musix | Description Updated | |
2018-10-28 20:50 | Saga Musix | Note Added: 0003693 | |
2018-11-03 08:16 | manx | File Added: file-flush-v2.patch | |
2018-11-04 11:25 | manx | File Added: file-flush-v4.patch | |
2018-11-04 11:25 | manx | Note Added: 0003696 | |
2018-11-06 18:46 | Saga Musix | Note Added: 0003698 | |
2018-11-06 18:58 | manx | Note Added: 0003699 | |
2018-11-06 18:58 | Saga Musix | Note Added: 0003700 | |
2018-11-06 19:16 | manx | Relationship added | related to 0001161 |
2018-11-07 21:56 | Saga Musix | Note Added: 0003703 | |
2018-11-08 07:05 | StarWolf3000 | Note Added: 0003704 | |
2018-11-08 13:29 | manx | Note Added: 0003705 | |
2018-11-08 18:15 | Saga Musix | Note Added: 0003706 | |
2018-11-09 07:34 | manx | Status | acknowledged => resolved |
2018-11-09 07:34 | manx | Resolution | open => fixed |
2018-11-09 07:34 | manx | Fixed in Version | => OpenMPT 1.28.01.00 / libopenmpt 0.4.0 (upgrade first) |
2018-11-09 07:34 | manx | Note Added: 0003707 |