Fix NUL characters file corruption after power outrages (1st step solution)

=========
Sernario:
=========
When a user modifies a file in Notepad++, and the time of periodic backup (defaulted to 7 seconds) is reached, the backup of the modified file is being written. However, if a power outage occurs during this precise moment while the file is being written, file corruption may occur.

=======
Remedy:
=======
The goal is to maintain a non-corrupted file sample even during power outages. Here are the steps:

0. Begin
1. Write the file A as A.temp
2. Replace A by A.temp
3. End

During these steps, the cutoff can happen at any moment, but the user will always have a non-corrupted file sample (either A or A.temp).

=====
Note:
=====
The solution is only applied to "new #" files, since these files are generally small in length and do not have a second "physical" file existing on the hard drive.

ref: https://github.com/notepad-plus-plus/notepad-plus-plus/issues/6133#issuecomment-1987037043

Fix #6133, close #14860
This commit is contained in:
Don Ho 2024-03-14 18:03:19 +01:00
parent aa3777786d
commit 590ea74bee

View File

@ -1028,7 +1028,7 @@ bool FileManager::backupCurrentBuffer()
hasModifForSession = true;
}
TCHAR fullpath[MAX_PATH];
TCHAR fullpath[MAX_PATH]{};
::GetFullPathName(backupFilePath.c_str(), MAX_PATH, fullpath, NULL);
if (wcschr(fullpath, '~'))
{
@ -1038,8 +1038,9 @@ bool FileManager::backupCurrentBuffer()
// Make sure the backup file is not read only
removeReadOnlyFlagFromFileAttributes(fullpath);
if (UnicodeConvertor.openFile(fullpath))
std::wstring fullpathTemp = fullpath;
fullpathTemp += L".tmp";
if (UnicodeConvertor.openFile(buffer->isUntitled() ? fullpathTemp.c_str() : fullpath)) // Use temp only for "new #" due to they don't have the original physical existance on the hard drive
{
size_t lengthDoc = _pNotepadPlus->_pEditView->getCurrentDocLen();
char* buf = (char*)_pNotepadPlus->_pEditView->execute(SCI_GETCHARACTERPOINTER); //to get characters directly from Scintilla buffer
@ -1074,6 +1075,14 @@ bool FileManager::backupCurrentBuffer()
if (isWrittenSuccessful) // backup file has been saved
{
if (buffer->isUntitled()) // "new #" file is saved successfully, then we replace its only physical existence by its temp
{
if (::PathFileExists(fullpath))
::ReplaceFile(fullpath, fullpathTemp.c_str(), nullptr, REPLACEFILE_IGNORE_MERGE_ERRORS | REPLACEFILE_IGNORE_ACL_ERRORS, 0, 0);
else
::MoveFileEx(fullpathTemp.c_str(), fullpath, MOVEFILE_REPLACE_EXISTING);
}
buffer->setModifiedStatus(false);
result = true; //all done
}