summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOdd Stranne <odd@mullvad.net>2018-09-04 13:08:13 +0200
committerOdd Stranne <odd@mullvad.net>2018-09-04 13:08:13 +0200
commit89361724121530e09d40c9a03cfd6413369cd32b (patch)
tree81f72b0f9ada7e277a4041994c9a3d93ced60f36
parent1d5be836d3aea89ce3e75eabcae8a3e7d5df1c83 (diff)
parent5021eff0588d090dae32d9552b648ee0ce130e2c (diff)
downloadmullvadvpn-89361724121530e09d40c9a03cfd6413369cd32b.tar.xz
mullvadvpn-89361724121530e09d40c9a03cfd6413369cd32b.zip
Merge branch 'installer-log'
-rw-r--r--CHANGELOG.md1
-rw-r--r--dist-assets/windows/installer.nsh51
-rw-r--r--windows/nsis-plugins/nsis-plugins.sln12
-rw-r--r--windows/nsis-plugins/src/driverlogic/context.cpp79
-rw-r--r--windows/nsis-plugins/src/driverlogic/driverlogic.cpp10
-rw-r--r--windows/nsis-plugins/src/driverlogic/driverlogic.vcxproj8
-rw-r--r--windows/nsis-plugins/src/log/dllmain.cpp11
-rw-r--r--windows/nsis-plugins/src/log/log.cpp220
-rw-r--r--windows/nsis-plugins/src/log/log.def10
-rw-r--r--windows/nsis-plugins/src/log/log.h22
-rw-r--r--windows/nsis-plugins/src/log/log.vcxproj125
-rw-r--r--windows/nsis-plugins/src/log/log.vcxproj.filters18
-rw-r--r--windows/nsis-plugins/src/log/logger.cpp111
-rw-r--r--windows/nsis-plugins/src/log/logger.h62
-rw-r--r--windows/nsis-plugins/src/log/stdafx.cpp8
-rw-r--r--windows/nsis-plugins/src/log/stdafx.h16
-rw-r--r--windows/nsis-plugins/src/log/targetver.h12
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 &timestamp,
+ 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 &timestamp,
+ 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>