Fix NUL file-corruption issue after system shutting down brutally

Uses native Win32 IO API (CreateFile, ReadFile, WriteFile, CloseHandle) instead of POSIX ones (fopen, fread, fwrite, fclose) for remedying NUL file-corruption problem due to system shutting down brutally.

Fix #6133, close #10598
This commit is contained in:
Pavel Nedev 2019-09-19 14:24:58 +03:00 committed by Don Ho
parent 49c3e5d553
commit 9aa9ecb664
7 changed files with 268 additions and 53 deletions

View File

@ -23,6 +23,8 @@
#include "StaticDialog.h"
#include "CustomFileDialog.h"
#include "FileInterface.h"
#include "Common.h"
#include "Utf8.h"
#include <Parameters.h>
@ -113,20 +115,19 @@ generic_string relativeFilePathToFullFilePath(const TCHAR *relativeFilePath)
void writeFileContent(const TCHAR *file2write, const char *content2write)
{
FILE *f = generic_fopen(file2write, TEXT("w+c"));
fwrite(content2write, sizeof(content2write[0]), strlen(content2write), f);
fflush(f);
fclose(f);
CFile file(file2write, CFile::Mode::WRITE);
if (file.IsOpened())
file.Write(content2write, static_cast<unsigned long>(strlen(content2write)));
}
void writeLog(const TCHAR *logFileName, const char *log2write)
{
FILE *f = generic_fopen(logFileName, TEXT("a+c"));
fwrite(log2write, sizeof(log2write[0]), strlen(log2write), f);
fputc('\n', f);
fflush(f);
fclose(f);
CFile file(logFileName, CFile::Mode::APPEND);
if (file.IsOpened())
file.Write(log2write, static_cast<unsigned long>(strlen(log2write)));
}

View File

@ -0,0 +1,142 @@
// This file is part of Notepad++ project
// Copyright (C)2021 Don HO <don.h@free.fr>
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#include "FileInterface.h"
CFile::CFile(const char *fname, Mode fmode) : _hMode(fmode)
{
if (fname)
{
DWORD access, share, disp, attrib;
fillCreateParams(access, share, disp, attrib);
_hFile = ::CreateFileA(fname, access, share, NULL, disp, attrib, NULL);
}
if ((_hFile != INVALID_HANDLE_VALUE) && (_hMode == Mode::APPEND))
{
LARGE_INTEGER offset;
offset.QuadPart = 0;
::SetFilePointerEx(_hFile, offset, NULL, FILE_END);
}
}
CFile::CFile(const wchar_t *fname, Mode fmode) : _hMode(fmode)
{
if (fname)
{
DWORD access, share, disp, attrib;
fillCreateParams(access, share, disp, attrib);
_hFile = ::CreateFileW(fname, access, share, NULL, disp, attrib, NULL);
}
if ((_hFile != INVALID_HANDLE_VALUE) && (_hMode == Mode::APPEND))
{
LARGE_INTEGER offset;
offset.QuadPart = 0;
::SetFilePointerEx(_hFile, offset, NULL, FILE_END);
}
}
void CFile::Close()
{
if (IsOpened())
{
if (_written)
{
::SetEndOfFile(_hFile);
::FlushFileBuffers(_hFile);
}
::CloseHandle(_hFile);
_hFile = INVALID_HANDLE_VALUE;
}
}
int_fast64_t CFile::GetSize()
{
LARGE_INTEGER r;
r.QuadPart = -1;
if (IsOpened())
::GetFileSizeEx(_hFile, &r);
return static_cast<int_fast64_t>(r.QuadPart);
}
unsigned long CFile::Read(void *rbuf, unsigned long buf_size)
{
if (!IsOpened() || (rbuf == nullptr) || (buf_size == 0))
return 0;
DWORD bytes_read = 0;
if (::ReadFile(_hFile, rbuf, buf_size, &bytes_read, NULL) == FALSE)
return 0;
return bytes_read;
}
bool CFile::Write(const void *wbuf, unsigned long buf_size)
{
if (!IsOpened() || (wbuf == nullptr) || (buf_size == 0) || ((_hMode != Mode::WRITE) && (_hMode != Mode::APPEND)))
return false;
DWORD bytes_written = 0;
if (::WriteFile(_hFile, wbuf, buf_size, &bytes_written, NULL) == FALSE)
return false;
if (!_written && (bytes_written != 0))
_written = true;
return (bytes_written == buf_size);
}
// Helper function to auto-fill CreateFile params optimized for Notepad++ usage.
void CFile::fillCreateParams(DWORD &access, DWORD &share, DWORD &disp, DWORD &attrib)
{
access = GENERIC_READ;
attrib = FILE_ATTRIBUTE_NORMAL | FILE_FLAG_POSIX_SEMANTICS; // Distinguish between upper/lower case in name
if (_hMode == Mode::READ)
{
share = FILE_SHARE_READ;
disp = OPEN_EXISTING; // Open only if file exists and is not locked by other process
attrib |= FILE_FLAG_SEQUENTIAL_SCAN; // Optimize caching for sequential read
}
else
{
share = 0;
disp = OPEN_ALWAYS; // Open existing file for writing without destroying it or create new
access |= GENERIC_WRITE;
attrib |= FILE_FLAG_WRITE_THROUGH; // Write cached data directly to disk (no lazy writer)
}
}

View File

@ -0,0 +1,64 @@
// This file is part of Notepad++ project
// Copyright (C)2021 Don HO <don.h@free.fr>
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#pragma once
#include <windows.h>
#include <tchar.h>
#include <cstdint>
class CFile
{
public:
enum class Mode
{
READ,
WRITE,
APPEND
};
CFile(const char *fname, Mode fmode = Mode::READ);
CFile(const wchar_t *fname, Mode fmode = Mode::READ);
~CFile()
{
Close();
}
bool IsOpened()
{
return (_hFile != INVALID_HANDLE_VALUE);
}
void Close();
int_fast64_t GetSize();
unsigned long Read(void *rbuf, unsigned long buf_size);
bool Write(const void *wbuf, unsigned long buf_size);
private:
CFile(const CFile&) = delete;
CFile& operator=(const CFile&) = delete;
void fillCreateParams(DWORD &access, DWORD &share, DWORD &disp, DWORD &attrib);
HANDLE _hFile {INVALID_HANDLE_VALUE};
Mode _hMode {Mode::READ};
bool _written {false};
};

View File

@ -25,6 +25,7 @@
#include "ScintillaEditView.h"
#include "EncodingMapper.h"
#include "uchardet.h"
#include "FileInterface.h"
static const int blockSize = 128 * 1024 + 4;
static const int CR = 0x0D;
@ -868,15 +869,14 @@ bool FileManager::backupCurrentBuffer()
::SetFileAttributes(fullpath, dwFileAttribs);
}
FILE *fp = UnicodeConvertor.fopen(fullpath, TEXT("wbc"));
if (fp)
if (UnicodeConvertor.fopen(fullpath))
{
int lengthDoc = _pNotepadPlus->_pEditView->getCurrentDocLen();
char* buf = (char*)_pNotepadPlus->_pEditView->execute(SCI_GETCHARACTERPOINTER); //to get characters directly from Scintilla buffer
size_t items_written = 0;
long items_written = 0;
if (encoding == -1) //no special encoding; can be handled directly by Utf8_16_Write
{
items_written = UnicodeConvertor.fwrite(buf, lengthDoc);
items_written = UnicodeConvertor.fwrite(buf, static_cast<unsigned long>(lengthDoc));
if (lengthDoc == 0)
items_written = 1;
}
@ -894,7 +894,7 @@ bool FileManager::backupCurrentBuffer()
int incompleteMultibyteChar = 0;
const char *newData = wmc.encode(SC_CP_UTF8, encoding, buf+i, grabSize, &newDataLen, &incompleteMultibyteChar);
grabSize -= incompleteMultibyteChar;
items_written = UnicodeConvertor.fwrite(newData, newDataLen);
items_written = UnicodeConvertor.fwrite(newData, static_cast<unsigned long>(newDataLen));
}
if (lengthDoc == 0)
items_written = 1;
@ -993,22 +993,16 @@ SavingStatus FileManager::saveBuffer(BufferID id, const TCHAR * filename, bool i
int encoding = buffer->getEncoding();
FILE *fp = UnicodeConvertor.fopen(fullpath, TEXT("wbc"));
if (!fp)
{
return SavingStatus::SaveOpenFailed;
}
else
if (UnicodeConvertor.fopen(fullpath))
{
_pscratchTilla->execute(SCI_SETDOCPOINTER, 0, buffer->_doc); //generate new document
int lengthDoc = _pscratchTilla->getCurrentDocLen();
char* buf = (char*)_pscratchTilla->execute(SCI_GETCHARACTERPOINTER); //to get characters directly from Scintilla buffer
size_t items_written = 0;
long items_written = 0;
if (encoding == -1) //no special encoding; can be handled directly by Utf8_16_Write
{
items_written = UnicodeConvertor.fwrite(buf, lengthDoc);
items_written = UnicodeConvertor.fwrite(buf, static_cast<unsigned long>(lengthDoc));
if (lengthDoc == 0)
items_written = 1;
}
@ -1026,7 +1020,7 @@ SavingStatus FileManager::saveBuffer(BufferID id, const TCHAR * filename, bool i
int incompleteMultibyteChar = 0;
const char *newData = wmc.encode(SC_CP_UTF8, encoding, buf+i, grabSize, &newDataLen, &incompleteMultibyteChar);
grabSize -= incompleteMultibyteChar;
items_written = UnicodeConvertor.fwrite(newData, newDataLen);
items_written = UnicodeConvertor.fwrite(newData, static_cast<unsigned long>(newDataLen));
}
if (lengthDoc == 0)
items_written = 1;
@ -1071,6 +1065,10 @@ SavingStatus FileManager::saveBuffer(BufferID id, const TCHAR * filename, bool i
return SavingStatus::SaveOK;
}
else
{
return SavingStatus::SaveOpenFailed;
}
}
size_t FileManager::nextUntitledNewNumber() const
@ -1478,12 +1476,8 @@ BufferID FileManager::getBufferFromDocument(Document doc)
bool FileManager::createEmptyFile(const TCHAR * path)
{
FILE * file = generic_fopen(path, TEXT("wbc"));
if (!file)
return false;
fflush(file);
fclose(file);
return true;
CFile file(path, CFile::Mode::WRITE);
return file.IsOpened();
}

View File

@ -278,7 +278,6 @@ UniMode Utf8_16_Read::determineEncoding(const unsigned char *buf, size_t bufLen)
Utf8_16_Write::Utf8_16_Write()
{
m_eEncoding = uni8Bit;
m_pFile = NULL;
m_pNewBuf = NULL;
m_bFirstWrite = true;
m_nBufSize = 0;
@ -289,16 +288,25 @@ Utf8_16_Write::~Utf8_16_Write()
fclose();
}
FILE * Utf8_16_Write::fopen(const TCHAR *_name, const TCHAR *_type)
bool Utf8_16_Write::fopen(const TCHAR *name)
{
m_pFile = ::generic_fopen(_name, _type);
m_pFile = std::make_unique<CFile>(name, CFile::Mode::WRITE);
if (!m_pFile)
return false;
if (!m_pFile->IsOpened())
{
m_pFile = nullptr;
return false;
}
m_bFirstWrite = true;
return m_pFile;
return true;
}
size_t Utf8_16_Write::fwrite(const void* p, size_t _size)
unsigned long Utf8_16_Write::fwrite(const void* p, unsigned long _size)
{
// no file open
if (!m_pFile)
@ -306,19 +314,19 @@ size_t Utf8_16_Write::fwrite(const void* p, size_t _size)
return 0;
}
size_t ret = 0;
if (m_bFirstWrite)
{
switch (m_eEncoding)
{
case uniUTF8: {
::fwrite(k_Boms[m_eEncoding], 3, 1, m_pFile);
if (!m_pFile->Write(k_Boms[m_eEncoding], 3))
return 0;
break;
}
case uni16BE:
case uni16LE:
::fwrite(k_Boms[m_eEncoding], 2, 1, m_pFile);
if (!m_pFile->Write(k_Boms[m_eEncoding], 2))
return 0;
break;
default:
// nothing to do
@ -326,7 +334,9 @@ size_t Utf8_16_Write::fwrite(const void* p, size_t _size)
}
m_bFirstWrite = false;
}
unsigned long ret = 0;
switch (m_eEncoding)
{
case uni7Bit:
@ -334,27 +344,28 @@ size_t Utf8_16_Write::fwrite(const void* p, size_t _size)
case uniCookie:
case uniUTF8: {
// Normal write
ret = ::fwrite(p, _size, 1, m_pFile);
if (m_pFile->Write(p, _size))
ret = 1;
break;
}
case uni16BE_NoBOM:
case uni16LE_NoBOM:
case uni16BE:
case uni16LE: {
static const int bufSize = 64*1024;
static const unsigned int bufSize = 64*1024;
utf16 buf[bufSize];
Utf8_Iter iter8;
iter8.set(static_cast<const ubyte*>(p), _size, m_eEncoding);
int bufIndex = 0;
unsigned int bufIndex = 0;
while (iter8) {
++iter8;
while ((bufIndex < bufSize) && iter8.canGet())
iter8.get(&buf [bufIndex++]);
if (bufIndex == bufSize || !iter8) {
if (!::fwrite(buf, bufIndex*sizeof(utf16), 1, m_pFile)) return 0;
if (!m_pFile->Write(buf, bufIndex*sizeof(utf16))) return 0;
bufIndex = 0;
}
}
@ -374,6 +385,7 @@ size_t Utf8_16_Write::convert(char* p, size_t _size)
if (m_pNewBuf)
{
delete [] m_pNewBuf;
m_pNewBuf = NULL;
}
switch (m_eEncoding)
@ -445,11 +457,7 @@ void Utf8_16_Write::fclose()
}
if (m_pFile)
{
::fflush(m_pFile);
::fclose(m_pFile);
m_pFile = NULL;
}
m_pFile = nullptr;
}

View File

@ -27,6 +27,10 @@
#pragma warning(disable: 4514) // nreferenced inline function has been removed
#endif
#include <memory>
#include "FileInterface.h"
class Utf8_16 {
public:
typedef unsigned short utf16; // 16 bits
@ -135,16 +139,16 @@ public:
void setEncoding(UniMode eType);
FILE * fopen(const TCHAR *_name, const TCHAR *_type);
size_t fwrite(const void* p, size_t _size);
void fclose();
bool fopen(const TCHAR *name);
unsigned long fwrite(const void* p, unsigned long _size);
void fclose();
size_t convert(char* p, size_t _size);
char* getNewBuf() { return reinterpret_cast<char*>(m_pNewBuf); }
protected:
UniMode m_eEncoding;
FILE* m_pFile;
std::unique_ptr<CFile> m_pFile;
ubyte* m_pNewBuf;
size_t m_nBufSize;
bool m_bFirstWrite;

View File

@ -421,6 +421,7 @@ copy ..\src\contextMenu.xml ..\binarm64\contextMenu.xml
<ClCompile Include="..\src\WinControls\ColourPicker\ColourPopup.cpp" />
<ClCompile Include="..\src\ScintillaComponent\columnEditor.cpp" />
<ClCompile Include="..\src\MISC\Common\Common.cpp" />
<ClCompile Include="..\src\MISC\Common\FileInterface.cpp" />
<ClCompile Include="..\src\WinControls\ContextMenu\ContextMenu.cpp" />
<ClCompile Include="..\src\WinControls\OpenSaveFileDialog\CustomFileDialog.cpp" />
<ClCompile Include="..\src\WinControls\PluginsAdmin\pluginsAdmin.cpp" />
@ -708,6 +709,7 @@ copy ..\src\contextMenu.xml ..\binarm64\contextMenu.xml
<ClInclude Include="..\src\WinControls\ColourPicker\ColourPopup.h" />
<ClInclude Include="..\src\ScintillaComponent\columnEditor.h" />
<ClInclude Include="..\src\MISC\Common\Common.h" />
<ClInclude Include="..\src\MISC\Common\FileInterface.h" />
<ClInclude Include="..\src\WinControls\ContextMenu\ContextMenu.h" />
<ClInclude Include="..\src\WinControls\OpenSaveFileDialog\CustomFileDialog.h" />
<ClInclude Include="..\src\WinControls\PluginsAdmin\pluginsAdmin.h" />