OSDN Git Service

Avoid infinite loops in the RegularExpression::subst() function when the length of...
[winmerge-jp/winmerge-jp.git] / Src / ConfigLog.cpp
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** 
3  * @file  ConfigLog.cpp
4  *
5  * @brief CConfigLog implementation
6  */
7
8 #include "pch.h"
9 #include "ConfigLog.h"
10 #include <cassert>
11 #include <windows.h>
12 #include <memory>
13 #include "Constants.h"
14 #include "VersionInfo.h"
15 #include "UniFile.h"
16 #include "Plugins.h"
17 #include "TFile.h"
18 #include "paths.h"
19 #include "locality.h"
20 #include "unicoder.h"
21 #include "Environment.h"
22 #include "MergeApp.h"
23 #include "OptionsMgr.h"
24 #include "TempFile.h"
25 #include "UniFile.h"
26 #include "RegKey.h"
27
28 CConfigLog::CConfigLog()
29 : m_pfile(new UniStdioFile())
30 {
31 }
32
33 CConfigLog::~CConfigLog()
34 {
35         CloseFile();
36 }
37
38
39 /** 
40  * @brief Get details of the Compiler Version from _MSC_VER, etc.
41  *              Infer the Visual Studio version
42  */
43 static String GetCompilerVersion()
44 {
45         String sVisualStudio = _T(""); 
46 #ifdef _MSC_VER
47 #if     _MSC_VER <  1800
48         # error "** Unknown OLD Version of Visual Studio **"
49 #elif   _MSC_VER == 1800
50         sVisualStudio = _T("VS.2013 (12.0) - "); 
51 #elif   _MSC_VER == 1900
52         sVisualStudio = _T("VS.2015 (14.0) - "); 
53 #elif   _MSC_VER == 1910
54         sVisualStudio = _T("VS.2017 (15.0) - "); 
55 #elif   _MSC_VER == 1911
56         sVisualStudio = _T("VS.2017 (15.3) - "); 
57 #elif   _MSC_VER >= 1912 && _MSC_VER <  1920
58         sVisualStudio = strutils::format(_T("VS.2017 (15.%d) - "), 5 + (_MSC_VER - 1912));
59 #elif   _MSC_VER >= 1920 && _MSC_VER <  1930
60         sVisualStudio = strutils::format(_T("VS.2019 (16.%d) - "), (_MSC_VER - 1920));
61 #elif   _MSC_VER >= 1930 && _MSC_VER <  2000
62         sVisualStudio = strutils::format(_T("VS.2022 (17.%d) - "), (_MSC_VER - 1930));
63 #elif   _MSC_VER >= 2000
64         # error "** Unknown NEW Version of Visual Studio **"
65 #endif
66 #endif
67
68         return strutils::format(_T("%sC/C++ Compiler %02i.%02i.%05i.%i"),
69                 sVisualStudio,
70                 (int)(_MSC_VER / 100), (int)(_MSC_VER % 100), (int)(_MSC_FULL_VER % 100000), _MSC_BUILD
71         );
72 }
73
74 /** 
75  * @brief Get the Modified time of fully qualified file path and name
76  */
77 static String GetLastModified(const String &path) 
78 {
79         String sPath2 = path;
80         if (sPath2[0] == '.')
81         {
82                 CVersionInfo EXEversion;
83                 String sEXEPath = paths::GetPathOnly(paths::GetLongPath(EXEversion.GetFullFileName(), false));
84                 sPath2 = sEXEPath + _T("\\") + sPath2;
85         }
86         TFile file(sPath2);
87
88         String sModifiedTime = _T("");
89         if (file.exists())
90         {
91                 Poco::Timestamp mtime(file.getLastModified());
92
93                 const int64_t r = (mtime.epochTime());
94                 sModifiedTime = locality::TimeString(&r);
95         }
96         return sModifiedTime;
97 }
98
99 /** 
100  * @brief Write plugin names
101  */
102 void CConfigLog::WritePluginsInLogFile(const wchar_t *transformationEvent)
103 {
104         CVersionInfo EXEversion;
105         String sEXEPath = paths::GetPathOnly(paths::GetLongPath(EXEversion.GetFullFileName(), false));
106
107         // get an array with the available scripts
108         PluginArray * piPluginArray; 
109
110         piPluginArray = 
111                 CAllThreadsScripts::GetActiveSet()->GetAvailableScripts(transformationEvent);
112
113         for (size_t iPlugin = 0 ; iPlugin < piPluginArray->size() ; iPlugin++)
114         {
115                 const PluginInfoPtr& plugin = piPluginArray->at(iPlugin);
116                 String sPluginText;
117                 if (plugin->m_filepath.find(':') != String::npos)
118                 {
119                         String sFileName = paths::GetLongPath(plugin->m_filepath);
120                         if (sFileName.length() > sEXEPath.length())
121                                 if (sFileName.substr(0, sEXEPath.length()) == sEXEPath)
122                                         sFileName = _T(".") + sFileName.erase(0, sEXEPath.length());
123
124                         String sModifiedTime = _T("");
125                         sModifiedTime = GetLastModified(plugin->m_filepath);
126                         if (!sModifiedTime.empty())
127                                 sModifiedTime = _T("[") + sModifiedTime + _T("]");
128
129                         sPluginText = strutils::format
130                         (_T("\r\n  %s%-36s path=%s  %s"),
131                                 plugin->m_disabled ? _T("!") : _T(" "),
132                                 plugin->m_name,
133                                 sFileName,
134                                 sModifiedTime
135                         );
136                 }
137                 else
138                 {
139                         sPluginText = strutils::format
140                         (_T("\r\n  %s%-36s"),
141                                 plugin->m_disabled ? _T("!") : _T(" "),
142                                 plugin->m_name
143                         );
144                 }
145                 m_pfile->WriteString(sPluginText);
146         }
147 }
148
149 /**
150  * @brief String wrapper around API call GetLocaleInfo
151  */
152 static String GetLocaleString(LCID locid, LCTYPE lctype)
153 {
154         tchar_t buffer[512];
155         if (!GetLocaleInfo(locid, lctype, buffer, sizeof(buffer)/sizeof(buffer[0])))
156                 buffer[0] = 0;
157         return buffer;
158 }
159
160 /**
161  * @brief Write string item
162  */
163 void CConfigLog::WriteItem(int indent, const String& key, const tchar_t *value /*= nullptr*/)
164 {
165         String text = strutils::format(value ? _T("%*.0s%s: %s\r\n") : _T("%*.0s%s:\r\n"), indent, key, key, value);
166         m_pfile->WriteString(text);
167 }
168
169 /**
170  * @brief Write string item
171  */
172 void CConfigLog::WriteItem(int indent, const String& key, const String &str)
173 {
174         WriteItem(indent, key, str.c_str());
175 }
176
177 /**
178  * @brief Write int item
179  */
180 void CConfigLog::WriteItem(int indent, const String& key, long value)
181 {
182         String text = strutils::format(_T("%*.0s%s: %ld\r\n"), indent, key, key, value);
183         m_pfile->WriteString(text);
184 }
185
186 /**
187  * @brief Write out various possibly relevant windows locale information
188  */
189 void CConfigLog::WriteLocaleSettings(unsigned locid, const String& title)
190 {
191         WriteItem(1, title);
192         WriteItem(2, _T("Def ANSI codepage"), GetLocaleString(locid, LOCALE_IDEFAULTANSICODEPAGE));
193         WriteItem(2, _T("Def OEM codepage"), GetLocaleString(locid, LOCALE_IDEFAULTCODEPAGE));
194         WriteItem(2, _T("Country"), GetLocaleString(locid, LOCALE_SENGCOUNTRY));
195         WriteItem(2, _T("Language"), GetLocaleString(locid, LOCALE_SENGLANGUAGE));
196         WriteItem(2, _T("Language code"), GetLocaleString(locid, LOCALE_ILANGUAGE));
197         WriteItem(2, _T("ISO Language code"), GetLocaleString(locid, LOCALE_SISO639LANGNAME));
198 }
199
200 /**
201  * @brief Write version of a single executable file
202  */
203 void CConfigLog::WriteVersionOf1(int indent, const String& path)
204 {
205         String path2 = path;
206         if (path2.find(_T(".\\")) == 0)
207         {
208                 // Remove "relative path" info for Win API calls.
209                 const tchar_t *pf = path2.c_str();
210                 path2 = String(pf+2);
211         }
212         String name = paths::FindFileName(path2);
213         CVersionInfo vi(path2.c_str(), true);
214         String sModifiedTime = _T("");
215         if (name != path)
216         {
217                 sModifiedTime = GetLastModified(path);
218                 if (!sModifiedTime.empty())
219                         sModifiedTime = _T("  [") + sModifiedTime + _T("]");
220         }
221         String text = strutils::format
222         (
223                 name == path
224                         ?       _T(" %*s%-18s %s=%u.%02u %s=%04u\r\n")
225                         :       _T(" %*s%-18s %s=%u.%02u %s=%04u path=%s%s\r\n"),
226                 indent,
227                 // Tilde prefix for modules currently mapped into WinMerge
228                 GetModuleHandle(path2.c_str()) 
229                         ? _T("~") 
230                         : _T("")/*name*/,
231                 name,
232                 vi.m_dvi.cbSize > FIELD_OFFSET(DLLVERSIONINFO, dwMajorVersion)
233                         ?       _T("dllversion")
234                         :       _T("version"),
235                 vi.m_dvi.dwMajorVersion,
236                 vi.m_dvi.dwMinorVersion,
237                 vi.m_dvi.cbSize > FIELD_OFFSET(DLLVERSIONINFO, dwBuildNumber)
238                         ?       _T("dllbuild")
239                         :       _T("build"),
240                 vi.m_dvi.dwBuildNumber,
241                 path,
242                 sModifiedTime
243         );
244         m_pfile->WriteString(text);
245 }
246
247 /**
248  * @brief Write winmerge configuration
249  */
250 void CConfigLog::WriteWinMergeConfig()
251 {
252         TempFile tmpfile;
253         String tmppath = tmpfile.Create();
254         GetOptionsMgr()->ExportOptions(tmppath, true);
255         UniMemFile ufile;
256         if (!ufile.OpenReadOnly(tmppath))
257                 return;
258         String line;
259         bool lossy;
260         while (ufile.ReadString(line, &lossy)) 
261         {
262                 String prefix = _T("  ");
263                 if (line[0] == _T('[') )
264                         prefix = _T(" ");
265                 FileWriteString(prefix + line + _T("\r\n"));
266         }
267         ufile.Close();
268 }
269
270 /** 
271  * @brief Write logfile
272  */
273 bool CConfigLog::DoFile(String &sError)
274 {
275         CVersionInfo version;
276         String text;
277
278         String sFileName = paths::ConcatPath(env::GetMyDocuments(), WinMergeDocumentsFolder);
279         paths::CreateIfNeeded(sFileName);
280         m_sFileName = paths::ConcatPath(sFileName, _T("WinMerge.txt"));
281
282         if (!m_pfile->OpenCreateUtf8(m_sFileName))
283         {
284                 m_sFileName = paths::ConcatPath(env::GetTemporaryPath(), _T("WinMerge.txt"));
285                 if (!m_pfile->OpenCreateUtf8(m_sFileName))
286                 {
287                         const UniFile::UniError& err = m_pfile->GetLastUniError();
288                         sError = err.GetError();
289                         return false;
290                 }
291         }
292         m_pfile->SetBom(true);
293         m_pfile->WriteBom();
294
295 // Begin log
296         FileWriteString(_T("WinMerge Configuration Log\r\n"));
297         FileWriteString(_T("--------------------------\r\n"));
298         FileWriteString(_T("\r\nLog Saved to:         "));
299         FileWriteString(m_sFileName);
300         FileWriteString(_T("\r\n                >> >> Please add this information (or attach this file) when reporting bugs << <<"));
301
302 // Platform stuff
303         
304         FileWriteString(_T("\r\n\r\nWindows Info:         "));
305         text = GetWindowsVer();
306         FileWriteString(text);
307         text = GetProcessorInfo();
308         if (!text.empty())
309         {               
310                 FileWriteString(_T("\r\n Processor:           "));
311                 FileWriteString(text);
312         }
313
314 // WinMerge stuff
315
316         FileWriteString(_T("\r\n\r\nWinMerge Info:"));
317         String sEXEFullFileName = paths::GetLongPath(version.GetFullFileName(), false);
318         FileWriteString(_T("\r\n Code File:           "));
319         FileWriteString(sEXEFullFileName);
320
321         FileWriteString(_T("\r\n Version:             "));
322         FileWriteString(version.GetProductVersion());
323         String privBuild = version.GetPrivateBuild();
324         if (!privBuild.empty())
325         {
326                 FileWriteString(_T(" + ") + privBuild);
327         }
328
329         FileWriteString(_T("\r\n Code File Modified:  "));
330         FileWriteString(GetLastModified(sEXEFullFileName));
331
332         FileWriteString(_T("\r\n Build Config:       "));
333         FileWriteString(GetBuildFlags());
334
335         FileWriteString(_T("\r\n Build Software:      "));
336         FileWriteString(GetCompilerVersion());
337
338         const tchar_t* szCmdLine = ::GetCommandLine();
339         assert(szCmdLine != nullptr);
340
341         // Skip the quoted executable file name.
342         if (szCmdLine != nullptr)
343         {
344                 szCmdLine = tc::tcschr(szCmdLine, '"');
345                 if (szCmdLine != nullptr)
346                 {
347                         szCmdLine += 1; // skip the opening quote.
348                         szCmdLine = tc::tcschr(szCmdLine, '"');
349                         if (szCmdLine != nullptr)
350                         {
351                                 szCmdLine += 1; // skip the closing quote.
352                         }
353                 }
354         }
355
356         // The command line include a space after the executable file name,
357         // which mean that empty command line will have length of one.
358         if (!szCmdLine || lstrlen(szCmdLine) < 2)
359         {
360                 szCmdLine = _T(" none");
361         }
362
363         FileWriteString(_T("\r\n\r\nCommand Line:        "));
364         FileWriteString(szCmdLine);
365
366         tchar_t szCurrentDirectory[MAX_PATH]{};
367         GetCurrentDirectory(sizeof(szCurrentDirectory) / sizeof(tchar_t), szCurrentDirectory);
368         FileWriteString(_T("\r\n\r\nCurrent Directory:    "));
369         FileWriteString(szCurrentDirectory);
370
371         FileWriteString(_T("\r\n\r\nModule Names:         '~' prefix indicates module is loaded into the WinMerge process.\r\n"));
372         FileWriteString(_T(" Windows:\r\n"));
373         WriteVersionOf1(2, _T("kernel32.dll"));
374         WriteVersionOf1(2, _T("shell32.dll"));
375         WriteVersionOf1(2, _T("shlwapi.dll"));
376         WriteVersionOf1(2, _T("COMCTL32.dll"));
377         WriteVersionOf1(2, _T("msvcrt.dll"));
378         FileWriteString(_T(        " WinMerge:            Path names are relative to the Code File's directory.\r\n"));
379         WriteVersionOf1(2, _T(".\\ShellExtensionU.dll"));
380         WriteVersionOf1(2, _T(".\\ShellExtensionX64.dll"));
381         WriteVersionOf1(2, _T(".\\ShellExtensionARM.dll"));
382         WriteVersionOf1(2, _T(".\\ShellExtensionARM64.dll"));
383         WriteVersionOf1(2, _T(".\\WinMergeContextMenu.dll"));
384         WriteVersionOf1(2, _T(".\\Frhed\\hekseditU.dll"));
385         WriteVersionOf1(2, _T(".\\WinIMerge\\WinIMergeLib.dll"));
386         WriteVersionOf1(2, _T(".\\WinWebDiff\\WinWebDiffLib.dll"));
387         WriteVersionOf1(2, _T(".\\Merge7z\\7z.dll"));
388
389 // System settings
390         FileWriteString(_T("\r\nSystem Settings:\r\n"));
391         FileWriteString(_T(" Codepage Settings:\r\n"));
392         WriteItem(2, _T("ANSI codepage"), GetACP());
393         WriteItem(2, _T("OEM codepage"), GetOEMCP());
394 #ifndef UNICODE
395         WriteItem(2, _T("multibyte codepage"), _getmbcp());
396 #endif
397         WriteLocaleSettings(GetThreadLocale(), _T("Locale (Thread)"));
398         WriteLocaleSettings(LOCALE_USER_DEFAULT, _T("Locale (User)"));
399         WriteLocaleSettings(LOCALE_SYSTEM_DEFAULT, _T("Locale (System)"));
400
401 // Plugins
402         FileWriteString(_T("\r\nPlugins:                                '!' Prefix indicates the plugin is Disabled.\r\n"));
403         FileWriteString(    _T(" Unpackers:                             Path names are relative to the Code File's directory."));
404         WritePluginsInLogFile(L"URL_PACK_UNPACK");
405         WritePluginsInLogFile(L"FILE_PACK_UNPACK");
406         WritePluginsInLogFile(L"BUFFER_PACK_UNPACK");
407         WritePluginsInLogFile(L"FILE_FOLDER_PACK_UNPACK");
408         FileWriteString(_T("\r\n Prediffers: "));
409         WritePluginsInLogFile(L"FILE_PREDIFF");
410         WritePluginsInLogFile(L"BUFFER_PREDIFF");
411         FileWriteString(_T("\r\n Editor scripts: "));
412         WritePluginsInLogFile(L"EDITOR_SCRIPT");
413         if (!plugin::IsWindowsScriptThere())
414                 FileWriteString(_T("\r\n .sct scripts disabled (Windows Script Host not found)\r\n"));
415
416         FileWriteString(_T("\r\n\r\n"));
417
418 // WinMerge settings
419         FileWriteString(_T("\r\nWinMerge configuration:\r\n"));
420         WriteWinMergeConfig();
421
422         CloseFile();
423
424         return true;
425 }
426
427 /** 
428  * @brief Parse Windows version data to string.
429  * @return String describing Windows version.
430  */
431 String CConfigLog::GetWindowsVer()
432 {
433         CRegKeyEx key;
434         if (key.QueryRegMachine(_T("Software\\Microsoft\\Windows NT\\CurrentVersion")))
435         {
436                 String productName = key.ReadString(_T("ProductName"), _T("Unknown OS"));
437                 if (HMODULE hModule = GetModuleHandle(_T("ntdll.dll")))
438                 {
439                         using RtlGetNtVersionNumbersFunc = void (WINAPI*)(DWORD*, DWORD*, DWORD*);
440                         DWORD dwMajor = 0, dwMinor = 0, dwBuildNumber = 0;
441                         if (RtlGetNtVersionNumbersFunc RtlGetNtVersionNumbers =
442                                 reinterpret_cast<RtlGetNtVersionNumbersFunc>(GetProcAddress(hModule, "RtlGetNtVersionNumbers")))
443                         {
444                                 RtlGetNtVersionNumbers(&dwMajor, &dwMinor, &dwBuildNumber);
445                                 dwBuildNumber &= ~0xF0000000;
446                                 if (dwMajor == 10 && dwMinor == 0 && dwBuildNumber >= 22000)
447                                         strutils::replace(productName, _T("Windows 10"), _T("Windows 11"));
448                         }
449                 }
450                 return productName;
451         }
452         return _T("Unknown OS");
453 }
454
455
456 /** 
457  * @brief Parse Processor Information data to string.
458  * @return String describing Windows version.
459  */
460 String CConfigLog::GetProcessorInfo()
461 {
462         CRegKeyEx key;
463         String sProductName = _T("");
464         if (key.QueryRegMachine(_T("Hardware\\Description\\System\\CentralProcessor\\0")))
465                 sProductName = key.ReadString(_T("Identifier"), _T(""));
466         if (!sProductName.empty())
467         {
468                 // This is the full identifier of the processor
469                 //      (e.g. "Intel64 Family 6 Model 158 Stepping 9")
470                 //      but we'll only keep the first word (e.g. "Intel64")
471                 int x = (int)sProductName.find_first_of(_T(' '));
472                 sProductName = sProductName.substr(0, x);
473         }
474
475
476         // Number of processors, Amount of memory
477         SYSTEM_INFO siSysInfo;
478         ::GetSystemInfo(&siSysInfo); 
479
480         MEMORYSTATUSEX GlobalMemoryBuffer = {sizeof (GlobalMemoryBuffer)};
481         ::GlobalMemoryStatusEx(&GlobalMemoryBuffer);
482         ULONG lInstalledMemory = (ULONG)(GlobalMemoryBuffer.ullTotalPhys / (1024*1024));
483
484         tchar_t buf[MAX_PATH];
485         swprintf_s(buf, MAX_PATH, _T("%u Logical Processors, %u MB Memory"), 
486                         siSysInfo.dwNumberOfProcessors, lInstalledMemory); 
487
488         return sProductName + _T(", ") + String(buf);
489 }
490         
491 /** 
492  * @brief Return string representation of build flags (for reporting in config log)
493  */
494 String CConfigLog::GetBuildFlags()
495 {
496         String flags;
497
498 #if defined WIN64
499         flags += _T(" WIN64 ");
500 #elif defined WIN32
501         flags += _T(" WIN32 ");
502 #endif
503
504 #if defined UNICODE
505         flags += _T(" UNICODE ");
506 #endif
507
508 #if defined _DEBUG
509         flags += _T(" _DEBUG ");
510 #endif
511
512 #if defined TEST_WINMERGE
513         flags += _T(" TEST_WINMERGE ");
514 #endif
515
516         return flags;
517 }
518
519 bool CConfigLog::WriteLogFile(String &sError)
520 {
521         CloseFile();
522
523         return DoFile(sError);
524 }
525
526 /// Write line to file (if writing configuration log)
527 void
528 CConfigLog::FileWriteString(const String& lpsz)
529 {
530         m_pfile->WriteString(lpsz);
531 }
532
533 /**
534  * @brief Close any open file
535  */
536 void
537 CConfigLog::CloseFile()
538 {
539         if (m_pfile->IsOpen())
540                 m_pfile->Close();
541 }
542