diff options
| author | Odd Stranne <odd@mullvad.net> | 2018-09-04 13:08:13 +0200 |
|---|---|---|
| committer | Odd Stranne <odd@mullvad.net> | 2018-09-04 13:08:13 +0200 |
| commit | 89361724121530e09d40c9a03cfd6413369cd32b (patch) | |
| tree | 81f72b0f9ada7e277a4041994c9a3d93ced60f36 | |
| parent | 1d5be836d3aea89ce3e75eabcae8a3e7d5df1c83 (diff) | |
| parent | 5021eff0588d090dae32d9552b648ee0ce130e2c (diff) | |
| download | mullvadvpn-89361724121530e09d40c9a03cfd6413369cd32b.tar.xz mullvadvpn-89361724121530e09d40c9a03cfd6413369cd32b.zip | |
Merge branch 'installer-log'
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | dist-assets/windows/installer.nsh | 51 | ||||
| -rw-r--r-- | windows/nsis-plugins/nsis-plugins.sln | 12 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/driverlogic/context.cpp | 79 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/driverlogic/driverlogic.cpp | 10 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/driverlogic/driverlogic.vcxproj | 8 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/log/dllmain.cpp | 11 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/log/log.cpp | 220 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/log/log.def | 10 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/log/log.h | 22 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/log/log.vcxproj | 125 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/log/log.vcxproj.filters | 18 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/log/logger.cpp | 111 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/log/logger.h | 62 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/log/stdafx.cpp | 8 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/log/stdafx.h | 16 | ||||
| -rw-r--r-- | windows/nsis-plugins/src/log/targetver.h | 12 |
17 files changed, 762 insertions, 14 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 707d6413d1..0796482bc4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ Line wrap the file at 100 chars. Th #### Windows - Extend uninstaller to also remove logs, cache and optionally settings. +- Add installation log (%PROGRAMDATA%\Mullvad VPN\install.log). ### Fixed - Fix incorrect window position when using external display. diff --git a/dist-assets/windows/installer.nsh b/dist-assets/windows/installer.nsh index 675aae28f3..d2817360d0 100644 --- a/dist-assets/windows/installer.nsh +++ b/dist-assets/windows/installer.nsh @@ -78,9 +78,12 @@ Var /GLOBAL InstallDriver_BaselineStatus + log::Log "InstallDriver()" + Push $0 Push $1 + log::Log "Listing virtual adapters" nsExec::ExecToStack '"$TEMP\driver\tapinstall.exe" hwids ${TAP_HARDWARE_ID}' Pop $0 @@ -88,16 +91,21 @@ ${If} $0 != 0 StrCpy $R0 "Failed to list virtual adapters: error $0" + log::LogWithDetails $R0 $1 Goto InstallDriver_return ${EndIf} + log::LogWithDetails "Virtual adapters listing" $1 + + log::Log "Calling on plugin to parse adapter data" driverlogic::EstablishBaseline $1 Pop $0 Pop $1 ${If} $0 == ${EB_GENERAL_ERROR} - StrCpy $R0 "Failed to parse virtual adapter data: $1" + StrCpy $R0 "Failed to parse adapter data: $1" + log::Log $R0 Goto InstallDriver_return ${EndIf} @@ -110,6 +118,7 @@ # Driver is already installed and there are one or several virtual adapters present. # Update driver. # + log::Log "TAP driver is already installed - Updating to latest version" nsExec::ExecToStack '"$TEMP\driver\tapinstall.exe" update "$TEMP\driver\OemVista.inf" ${TAP_HARDWARE_ID}' Pop $0 @@ -117,10 +126,14 @@ ${If} $0 != 0 StrCpy $R0 "Failed to update TAP driver: error $0" + log::LogWithDetails $R0 $1 Goto InstallDriver_return ${EndIf} - - IntCmp $InstallDriver_BaselineStatus ${EB_MULLVAD_INTERFACE_PRESENT} InstallDriver_return_success + + ${If} $InstallDriver_BaselineStatus == ${EB_MULLVAD_INTERFACE_PRESENT} + log::Log "Virtual adapter named $\"Mullvad$\" already present on system" + Goto InstallDriver_return_success + ${EndIf} InstallDriver_install_driver: @@ -128,16 +141,19 @@ # Install driver and create a virtual adapter. # If the driver is already installed, this just creates another virtual adapter. # + log::Log "Creating new virtual adapter (this also installs the TAP driver, as necessary)" nsExec::ExecToStack '"$TEMP\driver\tapinstall.exe" install "$TEMP\driver\OemVista.inf" ${TAP_HARDWARE_ID}' Pop $0 Pop $1 ${If} $0 != 0 - StrCpy $R0 "Failed to install TAP driver: error $0" + StrCpy $R0 "Failed to create virtual adapter: error $0" + log::LogWithDetails $R0 $1 Goto InstallDriver_return ${EndIf} + log::Log "Listing virtual adapters" nsExec::ExecToStack '"$TEMP\driver\tapinstall.exe" hwids ${TAP_HARDWARE_ID}' Pop $0 @@ -145,22 +161,27 @@ ${If} $0 != 0 StrCpy $R0 "Failed to list virtual adapters: error $0" + log::LogWithDetails $R0 $1 Goto InstallDriver_return ${EndIf} + log::LogWithDetails "Updated virtual adapters listing" $1 + + log::Log "Calling on plugin to diff adapter listings" driverlogic::IdentifyNewInterface $1 Pop $0 Pop $1 ${If} $0 != ${INI_SUCCESS} - StrCpy $R0 "Failed to identify virtual adapter: $1" + StrCpy $R0 "Failed to identify new virtual adapter: $1" + log::Log $R0 Goto InstallDriver_return ${EndIf} - # - # Rename the newly added virtual adapter to "Mullvad". - # + log::Log "New virtual adapter is named $\"$1$\"" + + log::Log "Renaming adapter to $\"Mullvad$\"" nsExec::ExecToStack '"netsh.exe" interface set interface name = "$1" newname = "Mullvad"' Pop $0 @@ -168,11 +189,14 @@ ${If} $0 != 0 StrCpy $R0 "Failed to rename virtual adapter: error $0" + log::LogWithDetails $R0 $1 Goto InstallDriver_return ${EndIf} InstallDriver_return_success: + log::Log "InstallDriver() completed successfully" + Push 0 Pop $R0 @@ -194,9 +218,12 @@ # !macro InstallService + log::Log "InstallService()" + Push $0 Push $1 + log::Log "Running $\"mullvad-daemon$\" for it to self-register as a service" nsExec::ExecToStack '"$INSTDIR\resources\mullvad-daemon.exe" --register-service' Pop $0 @@ -204,9 +231,11 @@ ${If} $0 != 0 StrCpy $R0 "Failed to install Mullvad service: error $0" + log::LogWithDetails $R0 $1 Goto InstallService_return ${EndIf} + log::Log "Starting service" nsExec::ExecToStack '"sc.exe" start mullvadvpn' Pop $0 @@ -215,9 +244,12 @@ ${If} $0 != ${SERVICE_STARTED} ${AndIf} $0 != ${SERVICE_START_PENDING} StrCpy $R0 "Failed to start Mullvad service: error $0" + log::LogWithDetails $R0 $1 Goto InstallService_return ${EndIf} + log::Log "InstallService() completed successfully" + Push 0 Pop $R0 @@ -282,6 +314,9 @@ Push $R0 + log::Initialize + log::Log "Running installer for ${PRODUCT_NAME} ${VERSION}" + # # The electron-builder NSIS logic, that runs before 'customInstall' is activated, # makes a copy of the installer file: diff --git a/windows/nsis-plugins/nsis-plugins.sln b/windows/nsis-plugins/nsis-plugins.sln index 0f2f5533f2..e1c6eefd47 100644 --- a/windows/nsis-plugins/nsis-plugins.sln +++ b/windows/nsis-plugins/nsis-plugins.sln @@ -5,9 +5,15 @@ MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "driverlogic", "src\driverlogic\driverlogic.vcxproj", "{AABA9AB7-A7D0-4BB5-A1FA-92F566023E0D}" ProjectSection(ProjectDependencies) = postProject {B52E2D10-A94A-4605-914A-2DCEF6A757EF} = {B52E2D10-A94A-4605-914A-2DCEF6A757EF} + {1344152F-2BAD-4198-8E51-31AAC32BFBB2} = {1344152F-2BAD-4198-8E51-31AAC32BFBB2} EndProjectSection EndProject -Project("{54BCC44A-7EAA-4BB3-8CF5-564137999875}") = "cleanup", "src\cleanup\cleanup.vcxproj", "{47B5C1C1-67D7-4544-9037-8E7F44C1E5BD}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cleanup", "src\cleanup\cleanup.vcxproj", "{47B5C1C1-67D7-4544-9037-8E7F44C1E5BD}" + ProjectSection(ProjectDependencies) = postProject + {B52E2D10-A94A-4605-914A-2DCEF6A757EF} = {B52E2D10-A94A-4605-914A-2DCEF6A757EF} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "log", "src\log\log.vcxproj", "{1344152F-2BAD-4198-8E51-31AAC32BFBB2}" ProjectSection(ProjectDependencies) = postProject {B52E2D10-A94A-4605-914A-2DCEF6A757EF} = {B52E2D10-A94A-4605-914A-2DCEF6A757EF} EndProjectSection @@ -28,6 +34,10 @@ Global {47B5C1C1-67D7-4544-9037-8E7F44C1E5BD}.Debug|x86.Build.0 = Debug|Win32 {47B5C1C1-67D7-4544-9037-8E7F44C1E5BD}.Release|x86.ActiveCfg = Release|Win32 {47B5C1C1-67D7-4544-9037-8E7F44C1E5BD}.Release|x86.Build.0 = Release|Win32 + {1344152F-2BAD-4198-8E51-31AAC32BFBB2}.Debug|x86.ActiveCfg = Debug|Win32 + {1344152F-2BAD-4198-8E51-31AAC32BFBB2}.Debug|x86.Build.0 = Debug|Win32 + {1344152F-2BAD-4198-8E51-31AAC32BFBB2}.Release|x86.ActiveCfg = Release|Win32 + {1344152F-2BAD-4198-8E51-31AAC32BFBB2}.Release|x86.Build.0 = Release|Win32 {B52E2D10-A94A-4605-914A-2DCEF6A757EF}.Debug|x86.ActiveCfg = Debug|Win32 {B52E2D10-A94A-4605-914A-2DCEF6A757EF}.Debug|x86.Build.0 = Debug|Win32 {B52E2D10-A94A-4605-914A-2DCEF6A757EF}.Release|x86.ActiveCfg = Release|Win32 diff --git a/windows/nsis-plugins/src/driverlogic/context.cpp b/windows/nsis-plugins/src/driverlogic/context.cpp index d2481e3e67..9c003b440c 100644 --- a/windows/nsis-plugins/src/driverlogic/context.cpp +++ b/windows/nsis-plugins/src/driverlogic/context.cpp @@ -4,6 +4,7 @@ #include <libcommon/wmi/connection.h> #include <libcommon/wmi/resultset.h> #include <libcommon/wmi/wmi.h> +#include <log/log.h> #include <vector> #include <stdexcept> #include <algorithm> @@ -26,6 +27,81 @@ std::vector<std::wstring> BlockToRows(const std::wstring &textBlock) return common::string::Tokenize(textBlock, L"\r\n"); } +void LogAllAdapters(wmi::Connection &connection) +{ + auto resultset = connection.query(L"SELECT * from Win32_NetworkAdapter"); + + struct NetworkAdapter + { + size_t interfaceIndex; + std::wstring manufacturer; + std::wstring name; + std::wstring pnpDeviceId; + std::wstring alias; + }; + + std::vector<NetworkAdapter> adapters; + + // + // Find all adapters and extract the most important data. + // + + auto StringOrNa = [](const _variant_t &variant) + { + if (VT_BSTR == V_VT(&variant)) + { + return std::wstring(V_BSTR(&variant)); + } + + return std::wstring(L"n/a"); + }; + + while(resultset.advance()) + { + auto interfaceIndex = wmi::WmiGetPropertyAlways(resultset.result(), L"InterfaceIndex"); + auto manufacturer = wmi::WmiGetProperty(resultset.result(), L"Manufacturer"); + auto name = wmi::WmiGetProperty(resultset.result(), L"Name"); + auto pnpDeviceId = wmi::WmiGetProperty(resultset.result(), L"PNPDeviceID"); + auto alias = wmi::WmiGetProperty(resultset.result(), L"NetConnectionID"); + + NetworkAdapter adapter; + + adapter.interfaceIndex = static_cast<size_t>(V_UI8(&interfaceIndex)); + adapter.manufacturer = StringOrNa(manufacturer); + adapter.name = StringOrNa(name); + adapter.pnpDeviceId = StringOrNa(pnpDeviceId); + adapter.alias = StringOrNa(alias); + + adapters.emplace_back(adapter); + } + + // + // Flatten the adapter information so we can log it more easily. + // + + std::vector<std::wstring> details; + + for (const auto &adapter : adapters) + { + details.emplace_back(L"Adapter"); + + { + std::wstringstream ss; + + ss << L" InterfaceIndex: " << adapter.interfaceIndex; + + details.emplace_back(ss.str()); + } + + details.emplace_back(std::wstring(L" Manufacturer: ").append(adapter.manufacturer)); + details.emplace_back(std::wstring(L" Name: ").append(adapter.name)); + details.emplace_back(std::wstring(L" PnpDeviceId: ").append(adapter.pnpDeviceId)); + details.emplace_back(std::wstring(L" Alias: ").append(adapter.alias)); + } + + PluginLogWithDetails(L"Adapters known to WMI", details); +} + } // anonymous namespace Context::BaselineStatus Context::establishBaseline(const std::wstring &textBlock) @@ -123,6 +199,9 @@ std::wstring Context::GetNicAlias(const std::wstring &name) if (false == resultset.advance()) { + PluginLog(std::wstring(L"WMI query failed for adapter: ").append(name)); + LogAllAdapters(connection); + throw std::runtime_error("Unable to look up virtual adapter using WMI"); } diff --git a/windows/nsis-plugins/src/driverlogic/driverlogic.cpp b/windows/nsis-plugins/src/driverlogic/driverlogic.cpp index 5604001645..4a8911572c 100644 --- a/windows/nsis-plugins/src/driverlogic/driverlogic.cpp +++ b/windows/nsis-plugins/src/driverlogic/driverlogic.cpp @@ -52,7 +52,15 @@ void PinDll() throw std::runtime_error("Failed to pin plugin module"); } - LoadLibraryW(self); + // + // NSIS sometimes frees a plugin module more times than it loads it. + // This hasn't been observed for this particular plugin but let's up the + // reference count a bit extra anyway. + // + for (int i = 0; i < 100; ++i) + { + LoadLibraryW(self); + } } } // anonymous namespace diff --git a/windows/nsis-plugins/src/driverlogic/driverlogic.vcxproj b/windows/nsis-plugins/src/driverlogic/driverlogic.vcxproj index 1e6e89d2e2..55f6b5efc1 100644 --- a/windows/nsis-plugins/src/driverlogic/driverlogic.vcxproj +++ b/windows/nsis-plugins/src/driverlogic/driverlogic.vcxproj @@ -61,7 +61,7 @@ <SDLCheck>true</SDLCheck> <PreprocessorDefinitions>WIN32;_DEBUG;DRIVERLOGIC_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> <ConformanceMode>true</ConformanceMode> - <AdditionalIncludeDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/;$(ProjectDir)../../../windows-libraries/src/</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/;$(ProjectDir)../../../windows-libraries/src/;$(ProjectDir)../</AdditionalIncludeDirectories> <LanguageStandard>stdcpplatest</LanguageStandard> <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> </ClCompile> @@ -70,7 +70,7 @@ <GenerateDebugInformation>true</GenerateDebugInformation> <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> <AdditionalLibraryDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/nsis/;$(SolutionDir)bin\$(Platform)-$(Configuration)\</AdditionalLibraryDirectories> - <AdditionalDependencies>libcommon.lib;pluginapi-x86-unicode.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>log.lib;libcommon.lib;pluginapi-x86-unicode.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies> <IgnoreSpecificDefaultLibraries>libc.lib</IgnoreSpecificDefaultLibraries> <ModuleDefinitionFile>driverlogic.def</ModuleDefinitionFile> </Link> @@ -85,7 +85,7 @@ <SDLCheck>true</SDLCheck> <PreprocessorDefinitions>WIN32;NDEBUG;DRIVERLOGIC_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> <ConformanceMode>true</ConformanceMode> - <AdditionalIncludeDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/;$(ProjectDir)../../../windows-libraries/src/</AdditionalIncludeDirectories> + <AdditionalIncludeDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/;$(ProjectDir)../../../windows-libraries/src/;$(ProjectDir)../</AdditionalIncludeDirectories> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> <LanguageStandard>stdcpplatest</LanguageStandard> </ClCompile> @@ -96,7 +96,7 @@ <GenerateDebugInformation>true</GenerateDebugInformation> <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> <AdditionalLibraryDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/nsis/;$(SolutionDir)bin\$(Platform)-$(Configuration)\</AdditionalLibraryDirectories> - <AdditionalDependencies>libcommon.lib;pluginapi-x86-unicode.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <AdditionalDependencies>log.lib;libcommon.lib;pluginapi-x86-unicode.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies> <IgnoreSpecificDefaultLibraries>libc.lib</IgnoreSpecificDefaultLibraries> <ModuleDefinitionFile>driverlogic.def</ModuleDefinitionFile> </Link> diff --git a/windows/nsis-plugins/src/log/dllmain.cpp b/windows/nsis-plugins/src/log/dllmain.cpp new file mode 100644 index 0000000000..a5a44613dd --- /dev/null +++ b/windows/nsis-plugins/src/log/dllmain.cpp @@ -0,0 +1,11 @@ +#include "stdafx.h" +#include <windows.h> + +BOOL APIENTRY DllMain(HMODULE, DWORD reason, LPVOID) +{ + // + // Avoid doing work in DllMain since the loader lock is held + // + + return TRUE; +} diff --git a/windows/nsis-plugins/src/log/log.cpp b/windows/nsis-plugins/src/log/log.cpp new file mode 100644 index 0000000000..f4d255b190 --- /dev/null +++ b/windows/nsis-plugins/src/log/log.cpp @@ -0,0 +1,220 @@ +#include "stdafx.h" +#include "logger.h" +#include <libcommon/string.h> +#include <libcommon/filesystem.h> +#include <windows.h> +#include <nsis/pluginapi.h> +#include <string> +#include <vector> +#include <memory> +#include <experimental/filesystem> + +Logger *g_logger = nullptr; + +namespace +{ + +std::wstring PopString() +{ + // + // NSIS functions popstring() and popstringn() require that you definitely size the buffer + // before popping the string. Let's do it ourselves instead. + // + + if (!g_stacktop || !*g_stacktop) + { + throw std::runtime_error("NSIS variable stack is corrupted"); + } + + stack_t *th = *g_stacktop; + + std::wstring copy(th->text); + + *g_stacktop = th->next; + GlobalFree((HGLOBAL)th); + + return copy; +} + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; + +void PinDll() +{ + // + // Apparently NSIS loads and unloads the plugin module for EVERY call it makes to the plugin. + // This makes it kind of difficult to maintain state. + // + // We can work around this by incrementing the module reference count. + // When NSIS calls FreeLibrary() the reference count decrements and becomes one. + // + + wchar_t self[MAX_PATH]; + + if (0 == GetModuleFileNameW((HINSTANCE)&__ImageBase, self, _countof(self))) + { + throw std::runtime_error("Failed to pin plugin module"); + } + + // + // For some reason, NSIS frees this particular DLL more times than it loads it + // so we have to up the reference count significantly. + // + for (int i = 0; i < 100; ++i) + { + LoadLibraryW(self); + } +} + +std::vector<std::wstring> BlockToRows(const std::wstring &textBlock) +{ + // + // This is such a hack :-( + // + // It only works because the tokenizer is greedy and because we don't care about + // empty lines for this usage. + // + return common::string::Tokenize(textBlock, L"\r\n"); +} + +} // anonymous namespace + +// +// Initialize +// +// Opens and maintains an open handle to the log file. +// +void __declspec(dllexport) NSISCALL Initialize +( + HWND hwndParent, + int string_size, + LPTSTR variables, + stack_t **stacktop, + extra_parameters *extra, + ... +) +{ + EXDLL_INIT(); + + try + { + PinDll(); + + auto logfile = std::experimental::filesystem::path(common::fs::GetKnownFolderPath( + FOLDERID_ProgramData, 0, nullptr)); + + logfile.append(L"Mullvad VPN").append(L"install.log"); + + g_logger = new Logger(std::make_unique<AnsiFileLogSink>(logfile)); + } + catch (...) + { + } +} + +// +// Log +// +// Writes a message to the log file. +// +void __declspec(dllexport) NSISCALL Log +( + HWND hwndParent, + int string_size, + LPTSTR variables, + stack_t **stacktop, + extra_parameters *extra, + ... +) +{ + EXDLL_INIT(); + + try + { + const auto message = PopString(); + + if (g_logger != nullptr) + { + g_logger->log(message); + } + } + catch (...) + { + } +} + +// +// LogWithDetails +// +// Writes a message to the log file. +// +void __declspec(dllexport) NSISCALL LogWithDetails +( + HWND hwndParent, + int string_size, + LPTSTR variables, + stack_t **stacktop, + extra_parameters *extra, + ... +) +{ + EXDLL_INIT(); + + try + { + const auto message = PopString(); + const auto details = PopString(); + + if (g_logger != nullptr) + { + g_logger->log(message, BlockToRows(details)); + } + } + catch (...) + { + } +} + +// +// PluginLog +// +// Writes a message to the log file. +// Use from other plugins to avoid passing messages like this: +// other plugin -> NSIS -> log plugin +// +void __declspec(dllexport) NSISCALL PluginLog +( + const std::wstring &message +) +{ + try + { + if (g_logger != nullptr) + { + g_logger->log(message); + } + } + catch (...) + { + } +} + +// +// PluginLogWithDetails +// +void __declspec(dllexport) NSISCALL PluginLogWithDetails +( + const std::wstring &message, + const std::vector<std::wstring> &details +) +{ + try + { + if (g_logger != nullptr) + { + g_logger->log(message, details); + } + } + catch (...) + { + } +} diff --git a/windows/nsis-plugins/src/log/log.def b/windows/nsis-plugins/src/log/log.def new file mode 100644 index 0000000000..581efdab9d --- /dev/null +++ b/windows/nsis-plugins/src/log/log.def @@ -0,0 +1,10 @@ +LIBRARY log + +EXPORTS + +Initialize +Log +LogWithDetails + +PluginLog +PluginLogWithDetails diff --git a/windows/nsis-plugins/src/log/log.h b/windows/nsis-plugins/src/log/log.h new file mode 100644 index 0000000000..1e2c0e40f3 --- /dev/null +++ b/windows/nsis-plugins/src/log/log.h @@ -0,0 +1,22 @@ +#pragma once + +#include <string> +#include <vector> + +// +// Import-only header +// +// Note on interfaces: While it's safer to use plain types for arguments, this is OK +// since the plugins are all built at the same time, and have the same interpretation of used types. +// + +void __declspec(dllimport) __stdcall PluginLog +( + const std::wstring &message +); + +void __declspec(dllimport) __stdcall PluginLogWithDetails +( + const std::wstring &message, + const std::vector<std::wstring> &details +); diff --git a/windows/nsis-plugins/src/log/log.vcxproj b/windows/nsis-plugins/src/log/log.vcxproj new file mode 100644 index 0000000000..a8506699d8 --- /dev/null +++ b/windows/nsis-plugins/src/log/log.vcxproj @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <VCProjectVersion>15.0</VCProjectVersion> + <ProjectGuid>{1344152F-2BAD-4198-8E51-31AAC32BFBB2}</ProjectGuid> + <Keyword>Win32Proj</Keyword> + <RootNamespace>log</RootNamespace> + <WindowsTargetPlatformVersion>10.0.16299.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>true</UseDebugLibraries> + <PlatformToolset>v141</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <UseDebugLibraries>false</UseDebugLibraries> + <PlatformToolset>v141</PlatformToolset> + <WholeProgramOptimization>true</WholeProgramOptimization> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Label="Shared"> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>true</LinkIncremental> + <OutDir>$(SolutionDir)bin\$(Platform)-$(Configuration)\</OutDir> + <IntDir>$(SolutionDir)bin\temp\$(Platform)-$(Configuration)\$(ProjectName)\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(SolutionDir)bin\$(Platform)-$(Configuration)\</OutDir> + <IntDir>$(SolutionDir)bin\temp\$(Platform)-$(Configuration)\$(ProjectName)\</IntDir> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <PrecompiledHeader>Use</PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>Disabled</Optimization> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>WIN32;_DEBUG;LOG_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + <AdditionalIncludeDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/;$(ProjectDir)../../../windows-libraries/src/</AdditionalIncludeDirectories> + <LanguageStandard>stdcpplatest</LanguageStandard> + <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> + </ClCompile> + <Link> + <SubSystem>Windows</SubSystem> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/nsis/;$(SolutionDir)bin\$(Platform)-$(Configuration)\</AdditionalLibraryDirectories> + <AdditionalDependencies>libcommon.lib;pluginapi-x86-unicode.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <IgnoreSpecificDefaultLibraries>libc.lib</IgnoreSpecificDefaultLibraries> + <ModuleDefinitionFile>log.def</ModuleDefinitionFile> + </Link> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <PrecompiledHeader>Use</PrecompiledHeader> + <WarningLevel>Level3</WarningLevel> + <Optimization>MaxSpeed</Optimization> + <FunctionLevelLinking>true</FunctionLevelLinking> + <IntrinsicFunctions>true</IntrinsicFunctions> + <SDLCheck>true</SDLCheck> + <PreprocessorDefinitions>WIN32;NDEBUG;LOG_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <ConformanceMode>true</ConformanceMode> + <AdditionalIncludeDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/;$(ProjectDir)../../../windows-libraries/src/</AdditionalIncludeDirectories> + <RuntimeLibrary>MultiThreaded</RuntimeLibrary> + <LanguageStandard>stdcpplatest</LanguageStandard> + </ClCompile> + <Link> + <SubSystem>Windows</SubSystem> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <OptimizeReferences>true</OptimizeReferences> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>$(ProjectDir)../../../../dist-assets/binaries/windows/nsis/;$(SolutionDir)bin\$(Platform)-$(Configuration)\</AdditionalLibraryDirectories> + <AdditionalDependencies>libcommon.lib;pluginapi-x86-unicode.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies> + <IgnoreSpecificDefaultLibraries>libc.lib</IgnoreSpecificDefaultLibraries> + <ModuleDefinitionFile>log.def</ModuleDefinitionFile> + </Link> + </ItemDefinitionGroup> + <ItemGroup> + <ClInclude Include="log.h" /> + <ClInclude Include="logger.h" /> + <ClInclude Include="stdafx.h" /> + <ClInclude Include="targetver.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="dllmain.cpp" /> + <ClCompile Include="log.cpp" /> + <ClCompile Include="logger.cpp" /> + <ClCompile Include="stdafx.cpp"> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader> + <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader> + </ClCompile> + </ItemGroup> + <ItemGroup> + <None Include="log.def" /> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/windows/nsis-plugins/src/log/log.vcxproj.filters b/windows/nsis-plugins/src/log/log.vcxproj.filters new file mode 100644 index 0000000000..b1f09f54f0 --- /dev/null +++ b/windows/nsis-plugins/src/log/log.vcxproj.filters @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClInclude Include="stdafx.h" /> + <ClInclude Include="targetver.h" /> + <ClInclude Include="log.h" /> + <ClInclude Include="logger.h" /> + </ItemGroup> + <ItemGroup> + <ClCompile Include="dllmain.cpp" /> + <ClCompile Include="stdafx.cpp" /> + <ClCompile Include="log.cpp" /> + <ClCompile Include="logger.cpp" /> + </ItemGroup> + <ItemGroup> + <None Include="log.def" /> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/windows/nsis-plugins/src/log/logger.cpp b/windows/nsis-plugins/src/log/logger.cpp new file mode 100644 index 0000000000..f22bf84bf1 --- /dev/null +++ b/windows/nsis-plugins/src/log/logger.cpp @@ -0,0 +1,111 @@ +#include "stdafx.h" +#include "logger.h" +#include <libcommon/error.h> +#include <libcommon/string.h> +#include <sstream> +#include <iomanip> + +AnsiFileLogSink::AnsiFileLogSink(const std::wstring &file, bool append, bool flush) + : m_flush(flush) +{ + const DWORD creationDisposition = (append ? OPEN_ALWAYS : CREATE_ALWAYS); + + m_logfile = CreateFileW(file.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, nullptr, + creationDisposition, FILE_ATTRIBUTE_NORMAL, nullptr); + + THROW_GLE_IF(INVALID_HANDLE_VALUE, m_logfile, "Open/create log file"); + + if (append && ERROR_ALREADY_EXISTS == GetLastError()) + { + LARGE_INTEGER offset = { 0 }; + + const auto seekStatus = SetFilePointerEx(m_logfile, offset, nullptr, FILE_END); + + THROW_GLE_IF(FALSE, seekStatus, "Seek to end offset in existing log file"); + } +} + +AnsiFileLogSink::~AnsiFileLogSink() +{ + CloseHandle(m_logfile); +} + +void AnsiFileLogSink::log(const std::wstring &message) +{ + auto ansi = common::string::ToAnsi(message); + + ansi.append("\xd\xa"); + + DWORD bytesWritten; + + WriteFile(m_logfile, ansi.c_str(), ansi.size(), &bytesWritten, nullptr); + + if (m_flush) + { + FlushFileBuffers(m_logfile); + } +} + +void Logger::log(const std::wstring &message) +{ + m_logsink->log(Compose(message, Timestamp(), ordinal())); +} + +void Logger::log(const std::wstring &message, const std::vector<std::wstring> &details) +{ + const auto timestamp = this->Timestamp(); + const auto ordinal = this->ordinal(); + + m_logsink->log(Compose(message, timestamp, ordinal)); + + // + // Write details with indentation. + // + for (const auto detail : details) + { + m_logsink->log(Compose(detail, timestamp, ordinal, 4)); + } +} + +// static +std::wstring Logger::Timestamp() +{ + SYSTEMTIME time; + + GetLocalTime(&time); + + std::wstringstream ss; + + ss << L'[' + << std::right << std::setw(2) << std::setfill(L'0') << time.wHour + << L':' + << std::right << std::setw(2) << std::setfill(L'0') << time.wMinute + << L':' + << std::right << std::setw(2) << std::setfill(L'0') << time.wSecond + << L']'; + + return ss.str(); +} + +std::wstring Logger::ordinal() +{ + std::wstringstream ss; + + ss << std::right << std::setw(4) << std::setfill(L' ') << m_ordinal++; + + return ss.str(); +} + +//static +std::wstring Logger::Compose(const std::wstring &message, const std::wstring ×tamp, + const std::wstring &ordinal, size_t indentation) +{ + std::wstringstream ss; + + ss << timestamp << L' ' + << ordinal << L' ' + << std::wstring(indentation, L' ') + << message; + + return ss.str(); +} diff --git a/windows/nsis-plugins/src/log/logger.h b/windows/nsis-plugins/src/log/logger.h new file mode 100644 index 0000000000..71054d0bdc --- /dev/null +++ b/windows/nsis-plugins/src/log/logger.h @@ -0,0 +1,62 @@ +#pragma once + +#include <string> +#include <vector> +#include <memory> +#include <windows.h> + +struct ILogSink +{ + virtual ~ILogSink() = 0 + { + } + + virtual void log(const std::wstring &message) = 0; +}; + +class AnsiFileLogSink : public ILogSink +{ +public: + + AnsiFileLogSink(const std::wstring &file, bool append = true, bool flush = false); + ~AnsiFileLogSink(); + + AnsiFileLogSink(const AnsiFileLogSink &) = delete; + AnsiFileLogSink &operator=(const AnsiFileLogSink &) = delete; + + void log(const std::wstring &message) override; + +private: + + HANDLE m_logfile = INVALID_HANDLE_VALUE; + bool m_flush; +}; + +class Logger +{ +public: + + Logger(std::unique_ptr<ILogSink> &&logsink) + : m_logsink(std::move(logsink)) + { + } + + Logger(const Logger &) = delete; + Logger &operator=(const Logger &) = delete; + + void log(const std::wstring &message); + void log(const std::wstring &message, const std::vector<std::wstring> &details); + +private: + + std::unique_ptr<ILogSink> m_logsink; + + size_t m_ordinal = 1; + + static std::wstring Timestamp(); + + std::wstring ordinal(); + + static std::wstring Compose(const std::wstring &message, const std::wstring ×tamp, + const std::wstring &ordinal, size_t indentation = 0); +}; diff --git a/windows/nsis-plugins/src/log/stdafx.cpp b/windows/nsis-plugins/src/log/stdafx.cpp new file mode 100644 index 0000000000..3b6341d106 --- /dev/null +++ b/windows/nsis-plugins/src/log/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// driverlogic.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" + +// TODO: reference any additional headers you need in STDAFX.H +// and not in this file diff --git a/windows/nsis-plugins/src/log/stdafx.h b/windows/nsis-plugins/src/log/stdafx.h new file mode 100644 index 0000000000..f3a07375c7 --- /dev/null +++ b/windows/nsis-plugins/src/log/stdafx.h @@ -0,0 +1,16 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files: +#include <windows.h> + + + +// TODO: reference additional headers your program requires here diff --git a/windows/nsis-plugins/src/log/targetver.h b/windows/nsis-plugins/src/log/targetver.h new file mode 100644 index 0000000000..ae4a5c032c --- /dev/null +++ b/windows/nsis-plugins/src/log/targetver.h @@ -0,0 +1,12 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include <WinSDKVer.h> + +#define _WIN32_WINNT _WIN32_WINNT_WIN7 + +#include <SDKDDKVer.h> |
