From 9aa9ecb664bd03f3a61cc42891f125492767e066 Mon Sep 17 00:00:00 2001 From: Pavel Nedev Date: Thu, 19 Sep 2019 14:24:58 +0300 Subject: [PATCH] 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 --- PowerEditor/src/MISC/Common/Common.cpp | 19 +-- PowerEditor/src/MISC/Common/FileInterface.cpp | 142 ++++++++++++++++++ PowerEditor/src/MISC/Common/FileInterface.h | 64 ++++++++ PowerEditor/src/ScintillaComponent/Buffer.cpp | 36 ++--- PowerEditor/src/Utf8_16.cpp | 46 +++--- PowerEditor/src/Utf8_16.h | 12 +- PowerEditor/visual.net/notepadPlus.vcxproj | 2 + 7 files changed, 268 insertions(+), 53 deletions(-) create mode 100644 PowerEditor/src/MISC/Common/FileInterface.cpp create mode 100644 PowerEditor/src/MISC/Common/FileInterface.h diff --git a/PowerEditor/src/MISC/Common/Common.cpp b/PowerEditor/src/MISC/Common/Common.cpp index cb38ce789..a64d54263 100644 --- a/PowerEditor/src/MISC/Common/Common.cpp +++ b/PowerEditor/src/MISC/Common/Common.cpp @@ -23,6 +23,8 @@ #include "StaticDialog.h" #include "CustomFileDialog.h" + +#include "FileInterface.h" #include "Common.h" #include "Utf8.h" #include @@ -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(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(strlen(log2write))); } diff --git a/PowerEditor/src/MISC/Common/FileInterface.cpp b/PowerEditor/src/MISC/Common/FileInterface.cpp new file mode 100644 index 000000000..17a868e5d --- /dev/null +++ b/PowerEditor/src/MISC/Common/FileInterface.cpp @@ -0,0 +1,142 @@ +// This file is part of Notepad++ project +// Copyright (C)2021 Don HO + +// 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 . + + +#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(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) + } +} diff --git a/PowerEditor/src/MISC/Common/FileInterface.h b/PowerEditor/src/MISC/Common/FileInterface.h new file mode 100644 index 000000000..17f3bf643 --- /dev/null +++ b/PowerEditor/src/MISC/Common/FileInterface.h @@ -0,0 +1,64 @@ +// This file is part of Notepad++ project +// Copyright (C)2021 Don HO + +// 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 . + + +#pragma once + +#include +#include +#include + + +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}; +}; diff --git a/PowerEditor/src/ScintillaComponent/Buffer.cpp b/PowerEditor/src/ScintillaComponent/Buffer.cpp index 64d4d2004..c0688f127 100644 --- a/PowerEditor/src/ScintillaComponent/Buffer.cpp +++ b/PowerEditor/src/ScintillaComponent/Buffer.cpp @@ -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(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(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(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(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(); } diff --git a/PowerEditor/src/Utf8_16.cpp b/PowerEditor/src/Utf8_16.cpp index 98cf4ffa8..d481dc9c6 100644 --- a/PowerEditor/src/Utf8_16.cpp +++ b/PowerEditor/src/Utf8_16.cpp @@ -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(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(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; } diff --git a/PowerEditor/src/Utf8_16.h b/PowerEditor/src/Utf8_16.h index 938aaae8e..a43add716 100644 --- a/PowerEditor/src/Utf8_16.h +++ b/PowerEditor/src/Utf8_16.h @@ -27,6 +27,10 @@ #pragma warning(disable: 4514) // nreferenced inline function has been removed #endif +#include +#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(m_pNewBuf); } protected: UniMode m_eEncoding; - FILE* m_pFile; + std::unique_ptr m_pFile; ubyte* m_pNewBuf; size_t m_nBufSize; bool m_bFirstWrite; diff --git a/PowerEditor/visual.net/notepadPlus.vcxproj b/PowerEditor/visual.net/notepadPlus.vcxproj index 790d5167a..831212c46 100755 --- a/PowerEditor/visual.net/notepadPlus.vcxproj +++ b/PowerEditor/visual.net/notepadPlus.vcxproj @@ -421,6 +421,7 @@ copy ..\src\contextMenu.xml ..\binarm64\contextMenu.xml + @@ -708,6 +709,7 @@ copy ..\src\contextMenu.xml ..\binarm64\contextMenu.xml +