From 1bf01cd41a5ac408acb93ccb4a544d2196c6ea88 Mon Sep 17 00:00:00 2001 From: "METANEOCORTEX\\Kotti" Date: Mon, 4 May 2026 20:36:06 +0200 Subject: [PATCH] fix: hardening of INI file handling for multi instances workflows --- src/Config/Config.cpp | 58 +++++++++++++++++++++++++++++++++++++++++++ src/Config/Config.h | 3 +++ src/Notepad3.c | 3 +++ 3 files changed, 64 insertions(+) diff --git a/src/Config/Config.cpp b/src/Config/Config.cpp index 82098e094..fb11c9444 100644 --- a/src/Config/Config.cpp +++ b/src/Config/Config.cpp @@ -251,6 +251,48 @@ bool ReleaseFileLock(HANDLE hFile, OVERLAPPED& rOvrLpd) return bUnLocked; } +// ============================================================================ +// +// Cross-instance INI save serialization +// +static HANDLE s_hMtxIniFileSave = NULL; + +static unsigned long _HashIniPath(LPCWSTR path) +{ + unsigned long hash = 5381; + for (; *path; ++path) { + WCHAR ch = *path; + if (ch >= L'A' && ch <= L'Z') { ch += 32; } + if (ch == L'/') { ch = L'\\'; } + hash = ((hash << 5) + hash) ^ (unsigned long)ch; + } + return hash; +} + +extern "C" void InitIniFileSaveMutex(void) +{ + if (Path_IsEmpty(Paths.IniFile)) { + return; + } + if (IS_VALID_HANDLE(s_hMtxIniFileSave)) { + CloseHandle(s_hMtxIniFileSave); + s_hMtxIniFileSave = NULL; + } + WCHAR szMutexName[80]; + unsigned long const hash = _HashIniPath(Path_Get(Paths.IniFile)); + StringCchPrintfW(szMutexName, COUNTOF(szMutexName), + L"Local\\Notepad3_INI_%08lX", hash); + s_hMtxIniFileSave = CreateMutexW(NULL, FALSE, szMutexName); +} + +extern "C" void CloseIniFileSaveMutex(void) +{ + if (IS_VALID_HANDLE(s_hMtxIniFileSave)) { + CloseHandle(s_hMtxIniFileSave); + s_hMtxIniFileSave = NULL; + } +} + // ============================================================================ static CSimpleIni s_TMPINI(s_bIsUTF8, s_bUseMultiKey, s_bUseMultiLine); @@ -2437,6 +2479,15 @@ bool SaveAllSettings(bool bForceSaveSettings) return false; } + // Cross-instance serialization: acquire mutex so only one instance + // performs the load-modify-save cycle at a time. + DWORD dwMtxWait = WAIT_OBJECT_0; + if (IS_VALID_HANDLE(s_hMtxIniFileSave)) { + dwMtxWait = WaitForSingleObject(s_hMtxIniFileSave, 10000); + } + // Force fresh reload from disk to pick up changes from other instances + ResetIniFileCache(); + WCHAR tchMsg[80]; GetLngString(IDS_MUI_SAVINGSETTINGS, tchMsg, COUNTOF(tchMsg)); @@ -2489,6 +2540,13 @@ bool SaveAllSettings(bool bForceSaveSettings) Globals.bIniFileFromScratch = false; // INI has content now + // Release cross-instance save mutex (INI is flushed to disk) + if (IS_VALID_HANDLE(s_hMtxIniFileSave)) { + if (dwMtxWait == WAIT_OBJECT_0 || dwMtxWait == WAIT_ABANDONED) { + ReleaseMutex(s_hMtxIniFileSave); + } + } + // maybe separate INI files for Style-Themes if (Globals.uCurrentThemeIndex > 0) { Style_SaveSettings(bForceSaveSettings); diff --git a/src/Config/Config.h b/src/Config/Config.h index e5425e3e6..aed895a71 100644 --- a/src/Config/Config.h +++ b/src/Config/Config.h @@ -36,6 +36,9 @@ bool SaveWindowPositionSettings(bool bClearSettings); bool SaveAllSettings(bool bForceSaveSettings); void CmdSaveSettingsNow(); +void InitIniFileSaveMutex(void); +void CloseIniFileSaveMutex(void); + bool OpenSettingsFile(LPCSTR fctname); bool CloseSettingsFile(LPCSTR fctname, bool bSaveSettings); diff --git a/src/Notepad3.c b/src/Notepad3.c index df170437f..e3b1e4838 100644 --- a/src/Notepad3.c +++ b/src/Notepad3.c @@ -1149,6 +1149,8 @@ int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, } LoadSettings(); + InitIniFileSaveMutex(); + PrivateSetCurrentProcessExplicitAppUserModelID(Settings2.AppUserModelID); (void)CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_SPEED_OVER_MEMORY); @@ -3385,6 +3387,7 @@ LRESULT MsgEndSession(HWND hwnd, UINT umsg, WPARAM wParam, LPARAM lParam) // call SaveAllSettings() when Globals.hwndToolbar is still valid SaveAllSettings(false); + CloseIniFileSaveMutex(); // Remove tray icon in any case ShowNotifyIcon(hwnd, false);