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:
parent
49c3e5d553
commit
9aa9ecb664
|
@ -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)));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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};
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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" />
|
||||
|
|
Loading…
Reference in New Issue