mirror of
https://github.com/finos/SymphonyElectron.git
synced 2024-11-21 08:34:40 -06:00
SDA-3589 (Auto update for mac and windows) (#1447)
* SDA-3588 - Auto update for macOS and Windows * SDA-3588 - bump version to 19.0.1 * SDA-3588 - fix run-script-os version * SDA-3588 - Remove old auto update and update setAppUserModelId * SDA-3588 - Update app ID to com.symphony.electron_desktop * SDA-3588 - Update app ID to com.symphony.electron_desktop in Symphony.cs * SDA-3588 - Add autoUpdateUrl field in config file * SDA-3588 - Validate url * SDA-3588 - Include installer nsis script * SDA-3589 - Remove os path from update url and rename artifacts * SDA-3589 - set empty value for autoUpdateUrl * SDA-3589 - Add validation for autoUpdateUrl field * SDA-3589 - change auto update manual workflow
This commit is contained in:
parent
b94712685c
commit
094a68f99a
@ -1,51 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.31112.23
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "auto_update_helper", "auto_update_helper.vcxproj", "{9A61CB06-1A97-440C-A931-508C9019F0FB}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "auto_update_service", "auto_update_service.vcxproj", "{7E157BD0-86F0-4325-848C-BA1C87D9C99C}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tests", "tests.vcxproj", "{98A68237-C5F6-487D-8D49-346D6991F2C5}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{9A61CB06-1A97-440C-A931-508C9019F0FB}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{9A61CB06-1A97-440C-A931-508C9019F0FB}.Debug|x64.Build.0 = Debug|x64
|
||||
{9A61CB06-1A97-440C-A931-508C9019F0FB}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{9A61CB06-1A97-440C-A931-508C9019F0FB}.Debug|x86.Build.0 = Debug|Win32
|
||||
{9A61CB06-1A97-440C-A931-508C9019F0FB}.Release|x64.ActiveCfg = Release|x64
|
||||
{9A61CB06-1A97-440C-A931-508C9019F0FB}.Release|x64.Build.0 = Release|x64
|
||||
{9A61CB06-1A97-440C-A931-508C9019F0FB}.Release|x86.ActiveCfg = Release|Win32
|
||||
{9A61CB06-1A97-440C-A931-508C9019F0FB}.Release|x86.Build.0 = Release|Win32
|
||||
{7E157BD0-86F0-4325-848C-BA1C87D9C99C}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{7E157BD0-86F0-4325-848C-BA1C87D9C99C}.Debug|x64.Build.0 = Debug|x64
|
||||
{7E157BD0-86F0-4325-848C-BA1C87D9C99C}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{7E157BD0-86F0-4325-848C-BA1C87D9C99C}.Debug|x86.Build.0 = Debug|Win32
|
||||
{7E157BD0-86F0-4325-848C-BA1C87D9C99C}.Release|x64.ActiveCfg = Release|x64
|
||||
{7E157BD0-86F0-4325-848C-BA1C87D9C99C}.Release|x64.Build.0 = Release|x64
|
||||
{7E157BD0-86F0-4325-848C-BA1C87D9C99C}.Release|x86.ActiveCfg = Release|Win32
|
||||
{7E157BD0-86F0-4325-848C-BA1C87D9C99C}.Release|x86.Build.0 = Release|Win32
|
||||
{98A68237-C5F6-487D-8D49-346D6991F2C5}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{98A68237-C5F6-487D-8D49-346D6991F2C5}.Debug|x64.Build.0 = Debug|x64
|
||||
{98A68237-C5F6-487D-8D49-346D6991F2C5}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{98A68237-C5F6-487D-8D49-346D6991F2C5}.Debug|x86.Build.0 = Debug|Win32
|
||||
{98A68237-C5F6-487D-8D49-346D6991F2C5}.Release|x64.ActiveCfg = Release|x64
|
||||
{98A68237-C5F6-487D-8D49-346D6991F2C5}.Release|x64.Build.0 = Release|x64
|
||||
{98A68237-C5F6-487D-8D49-346D6991F2C5}.Release|x86.ActiveCfg = Release|Win32
|
||||
{98A68237-C5F6-487D-8D49-346D6991F2C5}.Release|x86.Build.0 = Release|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {ACFD70FC-B270-4021-9FB7-2FB6C2EB1C43}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
@ -1,302 +0,0 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_NONSTDC_NO_WARNINGS
|
||||
|
||||
//#define LOG_TO_CONSOLE_FOR_DEBUGGING
|
||||
|
||||
#include <windows.h>
|
||||
#include <aclapi.h>
|
||||
#pragma comment(lib, "advapi32.lib")
|
||||
#pragma comment( lib, "shell32.lib" )
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
|
||||
#define IPC_LOG_INFO LOG_INFO
|
||||
#define IPC_LOG_ERROR LOG_ERROR
|
||||
#define IPC_LOG_LAST_ERROR LOG_LAST_ERROR
|
||||
#include "ipc.h"
|
||||
|
||||
#define PIPE_NAME "symphony_sda_auto_update_ipc"
|
||||
|
||||
|
||||
struct log_t {
|
||||
CRITICAL_SECTION mutex;
|
||||
FILE* file;
|
||||
int time_offset;
|
||||
} g_log;
|
||||
|
||||
|
||||
void internal_log( char const* file, int line, char const* func, char const* level, char const* format, ... ) {
|
||||
EnterCriticalSection( &g_log.mutex );
|
||||
|
||||
if( g_log.file ) {
|
||||
char const* lastbackslash = strrchr( file, '\\' );
|
||||
if( lastbackslash ) {
|
||||
file = lastbackslash + 1;
|
||||
}
|
||||
|
||||
time_t rawtime;
|
||||
struct tm* info;
|
||||
time( &rawtime );
|
||||
info = localtime( &rawtime );
|
||||
int offset = g_log.time_offset;
|
||||
int offs_s = offset % 60;
|
||||
offset -= offs_s;
|
||||
int offs_m = ( offset % (60 * 60) ) / 60;
|
||||
offset -= offs_m * 60;
|
||||
int offs_h = offset / ( 60 * 60 );
|
||||
|
||||
fprintf( g_log.file, "%d-%02d-%02d %02d:%02d:%02d:025 %+02d:%02d | %s | %s(%d) | %s: ", info->tm_year + 1900, info->tm_mon + 1,
|
||||
info->tm_mday, info->tm_hour, info->tm_min, info->tm_sec, offs_h, offs_m, level, file, line, func );
|
||||
va_list args;
|
||||
va_start( args, format );
|
||||
vfprintf( g_log.file, format, args );
|
||||
va_end( args );
|
||||
fflush( g_log.file );
|
||||
}
|
||||
|
||||
LeaveCriticalSection( &g_log.mutex );
|
||||
}
|
||||
|
||||
|
||||
void internal_log_last_error( char const* file, int line, char const* func, char const* level, char const* message ) {
|
||||
EnterCriticalSection( &g_log.mutex );
|
||||
|
||||
DWORD error = GetLastError();
|
||||
|
||||
LPSTR buffer = NULL;
|
||||
size_t size = FormatMessageA(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), (LPSTR)&buffer, 0, NULL);
|
||||
|
||||
|
||||
internal_log( file, line, func, level, "%s: LastError == %u \"%s\"", message, error, buffer ? buffer : "" );
|
||||
|
||||
if( buffer ) {
|
||||
LocalFree( buffer );
|
||||
}
|
||||
|
||||
LeaveCriticalSection( &g_log.mutex );
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define LOG_INFO( format, ... ) internal_log( __FILE__, __LINE__, __func__, "info", format "\n", __VA_ARGS__ )
|
||||
#define LOG_ERROR( format, ... ) internal_log( __FILE__, __LINE__, __func__, "error", format "\n", __VA_ARGS__ )
|
||||
#define LOG_LAST_ERROR( message ) internal_log_last_error( __FILE__, __LINE__, __func__, "error", message )
|
||||
|
||||
|
||||
void log_init( char const* filename ) {
|
||||
InitializeCriticalSection( &g_log.mutex );
|
||||
|
||||
if( filename ) {
|
||||
#ifndef LOG_TO_CONSOLE_FOR_DEBUGGING
|
||||
g_log.file = fopen( filename, "w" );
|
||||
#else
|
||||
g_log.file = stdout;
|
||||
#endif
|
||||
|
||||
time_t rawtime = time( NULL );
|
||||
struct tm* ptm = gmtime( &rawtime );
|
||||
time_t gmt = mktime( ptm );
|
||||
g_log.time_offset = (int)difftime( rawtime, gmt );
|
||||
LOG_INFO( "Log file created: %s", filename );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main( int argc, char* argv[] ) {
|
||||
// Find argument ending with .log, if any
|
||||
char const* log_filename = NULL;
|
||||
for( int i = 0; i < argc; ++i ) {
|
||||
char const* ext = strrchr( argv[ i ], '.' );
|
||||
if( stricmp( ext, ".log" ) == 0 ) {
|
||||
log_filename = argv[ i ];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( !log_filename ) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Initialize logging
|
||||
log_init( log_filename );
|
||||
|
||||
LOG_INFO( "argc: %d", argc );
|
||||
for( int i = 0; i < argc; ++i ) {
|
||||
LOG_INFO( "argv[%d]: %s", i, argv[ i ] );
|
||||
}
|
||||
|
||||
if( argc != 4 ) {
|
||||
LOG_ERROR( "Not enough arguments: %d provided, expected 4", argc );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
char const* installer_filename = argv[ 1 ];
|
||||
char const* application_filename = argv[ 2 ];
|
||||
LOG_INFO( "installer_filename: %s", installer_filename );
|
||||
LOG_INFO( "application_filename: %s", application_filename );
|
||||
|
||||
BOOL installation_successful = FALSE;
|
||||
LOG_INFO( "Connecting to IPC server" );
|
||||
ipc_client_t* client = ipc_client_connect( PIPE_NAME );
|
||||
if( client ) {
|
||||
BOOL installation_in_progress = FALSE;
|
||||
LOG_INFO( "Connected" );
|
||||
char command[ 512 ];
|
||||
sprintf( command, "msi %s", installer_filename );
|
||||
LOG_INFO( "Sending command: \"%s\"", command );
|
||||
if( ipc_client_send( client, command ) ) {
|
||||
LOG_INFO( "Command sent" );
|
||||
char response[ 256 ] = { 0 };
|
||||
int size = 0;
|
||||
int temp_size = 0;
|
||||
LOG_INFO( "Receiving response" );
|
||||
ipc_receive_status_t status = IPC_RECEIVE_STATUS_MORE_DATA;
|
||||
while( size < sizeof( response ) - 1 && status == IPC_RECEIVE_STATUS_MORE_DATA ) {
|
||||
status = ipc_client_receive( client, response + size,
|
||||
sizeof( response ) - size - 1, &temp_size );
|
||||
if( status == IPC_RECEIVE_STATUS_ERROR ) {
|
||||
LOG_ERROR( "Receiving response failed" );
|
||||
break;
|
||||
}
|
||||
size += temp_size;
|
||||
}
|
||||
response[ size ] = '\0';
|
||||
LOG_INFO( "Response received: \"%s\"", response );
|
||||
if( strcmp( response, "OK" ) == 0 ) {
|
||||
LOG_INFO( "Installation in progress" );
|
||||
installation_in_progress = TRUE;
|
||||
} else {
|
||||
LOG_ERROR( "Installation failed" );
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR( "Failed to send command" );
|
||||
}
|
||||
|
||||
// Service will shut down while installation is in progress, so we need to reconnect
|
||||
if( installation_in_progress ) {
|
||||
ipc_client_disconnect( client );
|
||||
client = NULL;
|
||||
LOG_INFO( "Attempting to reconnect" );
|
||||
int time_elapsed_ms = 0;
|
||||
while( !client ) {
|
||||
Sleep( 5000 );
|
||||
client = ipc_client_connect( PIPE_NAME );
|
||||
if( !client ) {
|
||||
time_elapsed_ms += 5000;
|
||||
if( time_elapsed_ms > 10*60*1000 ) {
|
||||
LOG_ERROR( "Unable to reconnect to service after 10 minutes, things have gone very wrong" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( client ) {
|
||||
// retrieve status
|
||||
LOG_INFO( "Retrieving installation status from service" );
|
||||
LOG_INFO( "Sending command: \"status\"" );
|
||||
if( ipc_client_send( client, "status" ) ) {
|
||||
LOG_INFO( "Command sent" );
|
||||
char response[ 256 ] = { 0 };
|
||||
int size = 0;
|
||||
int temp_size = 0;
|
||||
LOG_INFO( "Receiving response" );
|
||||
ipc_receive_status_t status = IPC_RECEIVE_STATUS_MORE_DATA;
|
||||
while( size < sizeof( response ) - 1 && status == IPC_RECEIVE_STATUS_MORE_DATA ) {
|
||||
status = ipc_client_receive( client, response + size,
|
||||
sizeof( response ) - size - 1, &temp_size );
|
||||
if( status == IPC_RECEIVE_STATUS_ERROR ) {
|
||||
LOG_ERROR( "Receiving response failed" );
|
||||
break;
|
||||
}
|
||||
size += temp_size;
|
||||
}
|
||||
response[ size ] = '\0';
|
||||
if( *response ) {
|
||||
if( stricmp( response, "FINISHED" ) == 0 ) {
|
||||
LOG_INFO( "INSTALLATION SUCCESSFUL" );
|
||||
installation_successful = TRUE;
|
||||
} else if( stricmp( response, "FAILED" ) == 0 ) {
|
||||
LOG_ERROR( "FAILED TO LAUNCH INSTALLER" );
|
||||
} else if( stricmp( response, "INVALID" ) == 0 ) {
|
||||
LOG_ERROR( "NO RECENT INSTALLATION" );
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR( "Failed to get a valid status response" );
|
||||
ipc_client_disconnect( client );
|
||||
client = NULL;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR( "Failed to send command, disconnecting and aborting. Logs not collected." );
|
||||
ipc_client_disconnect( client );
|
||||
client = NULL;
|
||||
}
|
||||
|
||||
// retrieve logs
|
||||
if( client ) {
|
||||
LOG_INFO( "Retrieving logs from service" );
|
||||
bool logs_finished = false;
|
||||
while( !logs_finished ) {
|
||||
LOG_INFO( "Sending command: \"log\"" );
|
||||
if( ipc_client_send( client, "log" ) ) {
|
||||
LOG_INFO( "Command sent" );
|
||||
char response[ 4096 ] = { 0 };
|
||||
int size = 0;
|
||||
int temp_size = 0;
|
||||
LOG_INFO( "Receiving response" );
|
||||
ipc_receive_status_t status = IPC_RECEIVE_STATUS_MORE_DATA;
|
||||
while( size < sizeof( response ) - 1 && status == IPC_RECEIVE_STATUS_MORE_DATA ) {
|
||||
status = ipc_client_receive( client, response + size,
|
||||
sizeof( response ) - size - 1, &temp_size );
|
||||
if( status == IPC_RECEIVE_STATUS_ERROR ) {
|
||||
LOG_ERROR( "Receiving response failed" );
|
||||
break;
|
||||
}
|
||||
size += temp_size;
|
||||
}
|
||||
response[ size ] = '\0';
|
||||
if( *response ) {
|
||||
size_t len = strlen( response );
|
||||
if( len > 0 && response[ len - 1 ] == '\n' ) {
|
||||
response[ len - 1 ] = '\0';
|
||||
}
|
||||
LOG_INFO( "SERVER LOG | %s", response );
|
||||
} else {
|
||||
LOG_INFO( "All logs retrieved" );
|
||||
logs_finished = true;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR( "Failed to send command, disconnecting and aborting. Logs not collected." );
|
||||
ipc_client_disconnect( client );
|
||||
client = NULL;
|
||||
}
|
||||
}
|
||||
LOG_INFO( "All done, disconnecting from client" );
|
||||
ipc_client_disconnect( client );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LOG_INFO( "Launching SDA after installation: \"%s\"", application_filename );
|
||||
int result = (int)(uintptr_t) ShellExecute( NULL, NULL, application_filename,
|
||||
NULL, NULL, SW_SHOWNORMAL );
|
||||
|
||||
if( result <= 32 ) {
|
||||
LOG_LAST_ERROR( "Failed to launch SDA after installation" );
|
||||
}
|
||||
|
||||
if( !installation_successful ) {
|
||||
LOG_ERROR( "Installation failed." );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
LOG_INFO( "Installation successful." );
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
#define IPC_IMPLEMENTATION
|
||||
#include "ipc.h"
|
||||
|
@ -1,34 +0,0 @@
|
||||
#define IDR_ICON 101
|
||||
IDR_ICON ICON "icon.ico"
|
||||
|
||||
#define VER_MAJOR 1
|
||||
#define VER_MINOR 0
|
||||
#define VER_REVISION 0
|
||||
|
||||
|
||||
|
||||
#define INNER_TO_STR(x) #x
|
||||
#define TO_STR(x) INNER_TO_STR(x)
|
||||
#define VER_STRING TO_STR(VER_MAJOR) "." TO_STR(VER_MINOR) "." TO_STR(VER_REVISION) ".0"
|
||||
|
||||
#define VS_VERSION_INFO 0x1L
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VER_MAJOR,VER_MINOR,VER_REVISION
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904E4"
|
||||
BEGIN
|
||||
VALUE "FileDescription", "Symphony Auto Update Helper"
|
||||
VALUE "ProductName", "Symphony Auto Update Helper"
|
||||
VALUE "ProductVersion", VER_STRING
|
||||
VALUE "LegalCopyright", "Copyright 2021 Symphony OSS"
|
||||
END
|
||||
END
|
||||
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1252
|
||||
END
|
||||
END
|
@ -1,146 +0,0 @@
|
||||
<?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>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{9A61CB06-1A97-440C-A931-508C9019F0FB}</ProjectGuid>
|
||||
<RootNamespace>autoupdate</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>auto_update_helper</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</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>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<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|x64'">
|
||||
<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'">
|
||||
<IntDir>$(Platform)\$(Configuration)\auto_update_helper\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<IntDir>$(Platform)\$(Configuration)\auto_update_helper\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<IntDir>$(Platform)\$(Configuration)\auto_update_helper\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<IntDir>$(Platform)\$(Configuration)\auto_update_helper\</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="auto_update_helper.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ipc.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
@ -1,625 +0,0 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_NONSTDC_NO_WARNINGS
|
||||
|
||||
#define SERVICE_LOG_INFO LOG_INFO
|
||||
#define SERVICE_LOG_ERROR LOG_ERROR
|
||||
#define SERVICE_LOG_LAST_ERROR LOG_LAST_ERROR
|
||||
#include "service.h"
|
||||
|
||||
#define IPC_LOG_INFO LOG_INFO
|
||||
#define IPC_LOG_ERROR LOG_ERROR
|
||||
#define IPC_LOG_LAST_ERROR LOG_LAST_ERROR
|
||||
#include "ipc.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
#include <windows.h>
|
||||
#include <wincrypt.h>
|
||||
#include <wintrust.h>
|
||||
#include <Softpub.h>
|
||||
|
||||
#pragma comment(lib, "Shell32.lib")
|
||||
#pragma comment (lib, "wintrust.lib")
|
||||
#pragma comment(lib, "crypt32.lib")
|
||||
|
||||
#define SERVICE_NAME "symphony_sda_auto_update_service"
|
||||
#define PIPE_NAME "symphony_sda_auto_update_ipc"
|
||||
|
||||
|
||||
// The digital certificate of an installation package is validate before it is installed
|
||||
// This is a list of certificate thumbprints for current and past Symphony certificates
|
||||
// Whenever a new certificate is taken into use, its thumbprint needs to be added here
|
||||
// or the installation packages signed with it will be rejected.
|
||||
|
||||
struct { BYTE hash[ 20 ]; } thumbprints[] = {
|
||||
/* e846d3fb2a93007e921c3affcd7032f0186f116a */
|
||||
"\xe8\x46\xd3\xfb\x2a\x93\x00\x7e\x92\x1c\x3a\xff\xcd\x70\x32\xf0\x18\x6f\x11\x6a",
|
||||
|
||||
/* 99b3333ac4457a4e21a527cc11040b28c15c1d3f */
|
||||
"\x99\xb3\x33\x3a\xc4\x45\x7a\x4e\x21\xa5\x27\xcc\x11\x04\x0b\x28\xc1\x5c\x1d\x3f",
|
||||
};
|
||||
|
||||
|
||||
enum install_status_t {
|
||||
INSTALL_STATUS_NONE,
|
||||
INSTALL_STATUS_FINISHED,
|
||||
INSTALL_STATUS_FAILED,
|
||||
};
|
||||
|
||||
enum install_status_t g_status_for_last_install = INSTALL_STATUS_NONE;
|
||||
|
||||
|
||||
// Logging
|
||||
|
||||
struct log_entry_t {
|
||||
time_t timestamp;
|
||||
char* text;
|
||||
};
|
||||
|
||||
struct log_t {
|
||||
CRITICAL_SECTION mutex;
|
||||
char filename[ MAX_PATH ];
|
||||
FILE* file;
|
||||
int time_offset;
|
||||
int count;
|
||||
int capacity;
|
||||
struct log_entry_t* entries;
|
||||
clock_t session_end;
|
||||
} g_log;
|
||||
|
||||
void internal_log( char const* file, int line, char const* func, char const* level, char const* format, ... ) {
|
||||
EnterCriticalSection( &g_log.mutex );
|
||||
|
||||
char const* lastbackslash = strrchr( file, '\\' );
|
||||
if( lastbackslash ) {
|
||||
file = lastbackslash + 1;
|
||||
}
|
||||
|
||||
time_t rawtime;
|
||||
struct tm* info;
|
||||
time( &rawtime );
|
||||
info = localtime( &rawtime );
|
||||
int offset = g_log.time_offset;
|
||||
int offs_s = offset % 60;
|
||||
offset -= offs_s;
|
||||
int offs_m = ( offset % (60 * 60) ) / 60;
|
||||
offset -= offs_m * 60;
|
||||
int offs_h = offset / ( 60 * 60 );
|
||||
|
||||
if( g_log.file ) {
|
||||
fprintf( g_log.file, "%d-%02d-%02d %02d:%02d:%02d:025 %+02d:%02d | %s | %s(%d) | %s: ", info->tm_year + 1900, info->tm_mon + 1,
|
||||
info->tm_mday, info->tm_hour, info->tm_min, info->tm_sec, offs_h, offs_m, level, file, line, func );
|
||||
va_list args;
|
||||
va_start( args, format );
|
||||
vfprintf( g_log.file, format, args );
|
||||
va_end( args );
|
||||
fflush( g_log.file );
|
||||
}
|
||||
|
||||
size_t len = IPC_MESSAGE_MAX_LENGTH;
|
||||
char* buffer = (char*) malloc( len + 1 );
|
||||
size_t count = snprintf( buffer, len, "%d-%02d-%02d %02d:%02d:%02d:025 %+02d:%02d | %s | %s(%d) | %s: ", info->tm_year + 1900, info->tm_mon + 1,
|
||||
info->tm_mday, info->tm_hour, info->tm_min, info->tm_sec, offs_h, offs_m, level, file, line, func );
|
||||
buffer[ count ] = '\0';
|
||||
va_list args;
|
||||
va_start( args, format );
|
||||
count += vsnprintf( buffer + count, len - count, format, args );
|
||||
buffer[ count ] = '\0';
|
||||
va_end( args );
|
||||
|
||||
if( g_log.count >= g_log.capacity ) {
|
||||
g_log.capacity *= 2;
|
||||
g_log.entries = (struct log_entry_t*) realloc( g_log.entries, g_log.capacity * sizeof( struct log_entry_t ) );
|
||||
}
|
||||
|
||||
g_log.entries[ g_log.count ].timestamp = clock();
|
||||
g_log.entries[ g_log.count ].text = buffer;
|
||||
++g_log.count;
|
||||
|
||||
LeaveCriticalSection( &g_log.mutex );
|
||||
}
|
||||
|
||||
|
||||
void internal_log_last_error( char const* file, int line, char const* func, char const* level, char const* message ) {
|
||||
EnterCriticalSection( &g_log.mutex );
|
||||
|
||||
DWORD error = GetLastError();
|
||||
|
||||
LPSTR buffer = NULL;
|
||||
size_t size = FormatMessageA(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), (LPSTR)&buffer, 0, NULL);
|
||||
|
||||
|
||||
internal_log( file, line, func, level, "%s: LastError == %u \"%s\"", message, error, buffer ? buffer : "" );
|
||||
|
||||
if( buffer ) {
|
||||
LocalFree( buffer );
|
||||
}
|
||||
|
||||
LeaveCriticalSection( &g_log.mutex );
|
||||
}
|
||||
|
||||
|
||||
|
||||
#define LOG_INFO( format, ... ) internal_log( __FILE__, __LINE__, __func__, "info", format "\n", __VA_ARGS__ )
|
||||
#define LOG_ERROR( format, ... ) internal_log( __FILE__, __LINE__, __func__, "error", format "\n", __VA_ARGS__ )
|
||||
#define LOG_LAST_ERROR( message ) internal_log_last_error( __FILE__, __LINE__, __func__, "error", message )
|
||||
|
||||
|
||||
void log_init( bool usestdout ) {
|
||||
InitializeCriticalSection( &g_log.mutex );
|
||||
|
||||
if( usestdout ) {
|
||||
g_log.file = stdout;
|
||||
} else {
|
||||
char path[ MAX_PATH ];
|
||||
ExpandEnvironmentStringsA( "%LOCALAPPDATA%\\SdaAutoUpdate", path, MAX_PATH );
|
||||
CreateDirectory( path, NULL );
|
||||
sprintf( g_log.filename, "%s\\saus_%d.log", path, (int) time( NULL ) );
|
||||
|
||||
g_log.file = fopen( g_log.filename, "w" );
|
||||
}
|
||||
|
||||
time_t rawtime = time( NULL );
|
||||
struct tm* ptm = gmtime( &rawtime );
|
||||
time_t gmt = mktime( ptm );
|
||||
g_log.time_offset = (int)difftime( rawtime, gmt );
|
||||
|
||||
g_log.count = 0;
|
||||
g_log.capacity = 256;
|
||||
g_log.entries = (struct log_entry_t*) malloc( g_log.capacity * sizeof( struct log_entry_t ) );
|
||||
LOG_INFO( "Log file created" );
|
||||
}
|
||||
|
||||
|
||||
void retrieve_status( char* response, size_t capacity ) {
|
||||
if( g_status_for_last_install == INSTALL_STATUS_FINISHED ) {
|
||||
strcpy( response, "FINISHED" );
|
||||
} else if( g_status_for_last_install == INSTALL_STATUS_FAILED ) {
|
||||
strcpy( response, "FAILED" );
|
||||
} else {
|
||||
strcpy( response, "INVALID" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void retrieve_buffered_log_line( char* response, size_t capacity ) {
|
||||
EnterCriticalSection( &g_log.mutex );
|
||||
if( g_log.session_end == 0 ) {
|
||||
g_log.session_end = clock();
|
||||
}
|
||||
if( g_log.count > 0 && g_log.entries[ 0 ].timestamp <= g_log.session_end ) {
|
||||
strncpy( response, g_log.entries[ 0 ].text, capacity );
|
||||
free( g_log.entries[ 0 ].text );
|
||||
--g_log.count;
|
||||
memmove( g_log.entries, g_log.entries + 1, g_log.count * sizeof( *g_log.entries ) );
|
||||
} else {
|
||||
g_log.session_end = 0;
|
||||
strcpy( response, "" );
|
||||
}
|
||||
LeaveCriticalSection( &g_log.mutex );
|
||||
}
|
||||
|
||||
|
||||
// This is Microsofts code for verifying the digital signature of a file, taken from here:
|
||||
// https://docs.microsoft.com/en-us/windows/win32/seccrypto/example-c-program--verifying-the-signature-of-a-pe-file
|
||||
|
||||
BOOL VerifyEmbeddedSignature( LPCWSTR pwszSourceFile ) {
|
||||
LONG lStatus;
|
||||
DWORD dwLastError;
|
||||
|
||||
// Initialize the WINTRUST_FILE_INFO structure.
|
||||
|
||||
WINTRUST_FILE_INFO FileData;
|
||||
memset( &FileData, 0, sizeof( FileData ) );
|
||||
FileData.cbStruct = sizeof( WINTRUST_FILE_INFO );
|
||||
FileData.pcwszFilePath = pwszSourceFile;
|
||||
FileData.hFile = NULL;
|
||||
FileData.pgKnownSubject = NULL;
|
||||
|
||||
/*
|
||||
WVTPolicyGUID specifies the policy to apply on the file
|
||||
WINTRUST_ACTION_GENERIC_VERIFY_V2 policy checks:
|
||||
|
||||
1) The certificate used to sign the file chains up to a root
|
||||
certificate located in the trusted root certificate store. This
|
||||
implies that the identity of the publisher has been verified by
|
||||
a certification authority.
|
||||
|
||||
2) In cases where user interface is displayed (which this example
|
||||
does not do), WinVerifyTrust will check for whether the
|
||||
end entity certificate is stored in the trusted publisher store,
|
||||
implying that the user trusts content from this publisher.
|
||||
|
||||
3) The end entity certificate has sufficient permission to sign
|
||||
code, as indicated by the presence of a code signing EKU or no
|
||||
EKU.
|
||||
*/
|
||||
|
||||
GUID WVTPolicyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
|
||||
WINTRUST_DATA WinTrustData;
|
||||
|
||||
// Initialize the WinVerifyTrust input data structure.
|
||||
|
||||
// Default all fields to 0.
|
||||
memset( &WinTrustData, 0, sizeof( WinTrustData ) );
|
||||
|
||||
WinTrustData.cbStruct = sizeof( WinTrustData );
|
||||
|
||||
// Use default code signing EKU.
|
||||
WinTrustData.pPolicyCallbackData = NULL;
|
||||
|
||||
// No data to pass to SIP.
|
||||
WinTrustData.pSIPClientData = NULL;
|
||||
|
||||
// Disable WVT UI.
|
||||
WinTrustData.dwUIChoice = WTD_UI_NONE;
|
||||
|
||||
// No revocation checking.
|
||||
WinTrustData.fdwRevocationChecks = WTD_REVOKE_NONE;
|
||||
|
||||
// Verify an embedded signature on a file.
|
||||
WinTrustData.dwUnionChoice = WTD_CHOICE_FILE;
|
||||
|
||||
// Verify action.
|
||||
WinTrustData.dwStateAction = WTD_STATEACTION_VERIFY;
|
||||
|
||||
// Verification sets this value.
|
||||
WinTrustData.hWVTStateData = NULL;
|
||||
|
||||
// Not used.
|
||||
WinTrustData.pwszURLReference = NULL;
|
||||
|
||||
// This is not applicable if there is no UI because it changes
|
||||
// the UI to accommodate running applications instead of
|
||||
// installing applications.
|
||||
WinTrustData.dwUIContext = 0;
|
||||
|
||||
// Set pFile.
|
||||
WinTrustData.pFile = &FileData;
|
||||
|
||||
// WinVerifyTrust verifies signatures as specified by the GUID
|
||||
// and Wintrust_Data.
|
||||
lStatus = WinVerifyTrust( NULL, &WVTPolicyGUID, &WinTrustData );
|
||||
|
||||
BOOL result = FALSE;
|
||||
switch( lStatus ) {
|
||||
case ERROR_SUCCESS:
|
||||
/*
|
||||
Signed file:
|
||||
- Hash that represents the subject is trusted.
|
||||
|
||||
- Trusted publisher without any verification errors.
|
||||
|
||||
- UI was disabled in dwUIChoice. No publisher or
|
||||
time stamp chain errors.
|
||||
|
||||
- UI was enabled in dwUIChoice and the user clicked
|
||||
"Yes" when asked to install and run the signed
|
||||
subject.
|
||||
*/
|
||||
LOG_INFO( "The file is signed and the signature was verified." );
|
||||
result = TRUE;
|
||||
break;
|
||||
|
||||
case TRUST_E_NOSIGNATURE:
|
||||
// The file was not signed or had a signature
|
||||
// that was not valid.
|
||||
|
||||
// Get the reason for no signature.
|
||||
dwLastError = GetLastError();
|
||||
if( TRUST_E_NOSIGNATURE == dwLastError || TRUST_E_SUBJECT_FORM_UNKNOWN == dwLastError || TRUST_E_PROVIDER_UNKNOWN == dwLastError ) {
|
||||
// The file was not signed.
|
||||
LOG_ERROR( "The file is not signed." );
|
||||
} else {
|
||||
// The signature was not valid or there was an error
|
||||
// opening the file.
|
||||
LOG_LAST_ERROR( "An unknown error occurred trying to verify the signature of the file." );
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case TRUST_E_EXPLICIT_DISTRUST:
|
||||
// The hash that represents the subject or the publisher
|
||||
// is not allowed by the admin or user.
|
||||
LOG_ERROR( "The signature is present, but specifically disallowed." );
|
||||
break;
|
||||
|
||||
case TRUST_E_SUBJECT_NOT_TRUSTED:
|
||||
// The user clicked "No" when asked to install and run.
|
||||
LOG_ERROR( "The signature is present, but not trusted." );
|
||||
break;
|
||||
|
||||
case CRYPT_E_SECURITY_SETTINGS:
|
||||
/*
|
||||
The hash that represents the subject or the publisher
|
||||
was not explicitly trusted by the admin and the
|
||||
admin policy has disabled user trust. No signature,
|
||||
publisher or time stamp errors.
|
||||
*/
|
||||
LOG_ERROR( "CRYPT_E_SECURITY_SETTINGS - The hash "
|
||||
"representing the subject or the publisher wasn't "
|
||||
"explicitly trusted by the admin and admin policy "
|
||||
"has disabled user trust. No signature, publisher "
|
||||
"or timestamp errors.");
|
||||
break;
|
||||
|
||||
default:
|
||||
// The UI was disabled in dwUIChoice or the admin policy
|
||||
// has disabled user trust. lStatus contains the
|
||||
// publisher or time stamp chain error.
|
||||
LOG_ERROR( "Error is: 0x%x.", lStatus );
|
||||
break;
|
||||
}
|
||||
|
||||
// Any hWVTStateData must be released by a call with close.
|
||||
WinTrustData.dwStateAction = WTD_STATEACTION_CLOSE;
|
||||
lStatus = WinVerifyTrust( NULL, &WVTPolicyGUID, &WinTrustData);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Checks that the embedded signature is valid, and also checks that it is specifically
|
||||
// a known Symphony certificate, by looking for any of the certificate thumbprints listed
|
||||
// in the `thumbprints` array defined at the top of this file.
|
||||
|
||||
BOOL validate_installer( char const* filename ) {
|
||||
size_t length = 0;
|
||||
mbstowcs_s( &length, NULL, 0, filename, _TRUNCATE );
|
||||
wchar_t* wfilename = (wchar_t*) malloc( sizeof( wchar_t* ) * ( length + 1 ) );
|
||||
mbstowcs_s( &length, wfilename, length, filename, strlen( filename ) );
|
||||
BOOL signature_valid = VerifyEmbeddedSignature( wfilename );
|
||||
if( !signature_valid ) {
|
||||
LOG_ERROR( "The installer was not signed with a valid certificate" );
|
||||
free( wfilename );
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DWORD dwEncoding, dwContentType, dwFormatType;
|
||||
HCERTSTORE hStore = NULL;
|
||||
HCRYPTMSG hMsg = NULL;
|
||||
BOOL result = CryptQueryObject( CERT_QUERY_OBJECT_FILE,
|
||||
wfilename,
|
||||
CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
|
||||
CERT_QUERY_FORMAT_FLAG_BINARY,
|
||||
0,
|
||||
&dwEncoding,
|
||||
&dwContentType,
|
||||
&dwFormatType,
|
||||
&hStore,
|
||||
&hMsg,
|
||||
NULL );
|
||||
free( wfilename );
|
||||
|
||||
if( !result ) {
|
||||
LOG_LAST_ERROR( "CryptQueryObject failed" );
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL found = FALSE;
|
||||
for( int i = 0; i < sizeof( thumbprints ) / sizeof( *thumbprints ); ++i ) {
|
||||
CRYPT_HASH_BLOB hash_blob = {
|
||||
sizeof( thumbprints[ i ].hash ) / sizeof( *(thumbprints[ i ].hash) ),
|
||||
thumbprints[ i ].hash
|
||||
};
|
||||
CERT_CONTEXT const* cert_context = CertFindCertificateInStore(
|
||||
hStore,
|
||||
(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING),
|
||||
0,
|
||||
CERT_FIND_HASH,
|
||||
&hash_blob,
|
||||
NULL );
|
||||
|
||||
if( cert_context ) {
|
||||
found = TRUE;
|
||||
CertFreeCertificateContext( cert_context );
|
||||
}
|
||||
}
|
||||
if( !found ) {
|
||||
LOG_ERROR( "The installer was not signed with a known Symphony certificate" );
|
||||
}
|
||||
CryptMsgClose( hMsg );
|
||||
CertCloseStore( hStore, CERT_CLOSE_STORE_FORCE_FLAG );
|
||||
return found;
|
||||
}
|
||||
|
||||
|
||||
void merge_msiexec_log( char const* logfile ) {
|
||||
wchar_t wlogfile[ MAX_PATH ];
|
||||
MultiByteToWideChar( CP_ACP, 0, logfile, -1, wlogfile, sizeof( wlogfile ) / sizeof( *wlogfile ) );
|
||||
FILE* fp = _wfopen( wlogfile, L"r, ccs=UTF-16LE");
|
||||
if( !fp ) {
|
||||
LOG_ERROR( "Failed to read msiexec log file" );
|
||||
}
|
||||
fseek( fp, 2, SEEK_SET );
|
||||
static wchar_t wline[ 4096 ] = { 0 };
|
||||
static char line[ 4096 ] = { 0 };
|
||||
while( fgetws( wline, sizeof( wline ) / sizeof( *wline ), fp ) != NULL ) {
|
||||
WideCharToMultiByte( CP_ACP, 0, (wchar_t*) wline, -1, line, sizeof( line ), NULL, NULL );
|
||||
size_t len = strlen( line );
|
||||
if( len > 0 ) {
|
||||
char* end = ( line + len ) - 1;
|
||||
if( *end == '\n' ) *end = '\0';
|
||||
}
|
||||
LOG_INFO( "MSIEXEC: %s", line );
|
||||
memset( wline, 0, sizeof( wline ) );
|
||||
memset( line, 0, sizeof( line ) );
|
||||
}
|
||||
fclose( fp );
|
||||
}
|
||||
|
||||
|
||||
// Runs msiexec with the supplied filename
|
||||
bool run_installer( char const* filename ) {
|
||||
// Reject installers which are not signed with a Symphony certificate
|
||||
if( !validate_installer( filename ) ) {
|
||||
LOG_ERROR( "The signature of %s could is not a valid Symphony signature" );
|
||||
return false;
|
||||
}
|
||||
LOG_INFO( "Signature of installer successfully validated" );
|
||||
|
||||
char logfile[ MAX_PATH ];
|
||||
ExpandEnvironmentStringsA( "%LOCALAPPDATA%\\SdaAutoUpdate\\msiexec.log", logfile, MAX_PATH );
|
||||
DeleteFileA( logfile );
|
||||
|
||||
char command[ 512 ];
|
||||
sprintf( command, "/i %s /q LAUNCH_ON_INSTALL=\"false\" /l*v \"%s\" ", filename, logfile );
|
||||
LOG_INFO( "MSI command: %s", command );
|
||||
|
||||
SHELLEXECUTEINFO ShExecInfo = { 0 };
|
||||
ShExecInfo.cbSize = sizeof( SHELLEXECUTEINFO );
|
||||
ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
|
||||
ShExecInfo.hwnd = NULL;
|
||||
ShExecInfo.lpVerb = "open";
|
||||
ShExecInfo.lpFile = "msiexec";
|
||||
ShExecInfo.lpParameters = command;
|
||||
ShExecInfo.lpDirectory = NULL;
|
||||
ShExecInfo.nShow = SW_SHOW;
|
||||
ShExecInfo.hInstApp = NULL;
|
||||
|
||||
char statusfile[ MAX_PATH ];
|
||||
ExpandEnvironmentStringsA( "%LOCALAPPDATA%\\SdaAutoUpdate\\status.sau", statusfile, MAX_PATH );
|
||||
FILE* fp = fopen( statusfile, "w" );
|
||||
fprintf( fp, "PENDING" );
|
||||
fclose( fp );
|
||||
if( ShellExecuteEx( &ShExecInfo ) ) {
|
||||
fp = fopen( statusfile, "w" );
|
||||
fprintf( fp, "SUCCESS" );
|
||||
fclose( fp );
|
||||
LOG_INFO( "ShellExecuteEx successful, waiting to finish" );
|
||||
WaitForSingleObject( ShExecInfo.hProcess, INFINITE );
|
||||
LOG_INFO( "ShellExecuteEx finished" );
|
||||
DWORD exitCode = 0;
|
||||
GetExitCodeProcess( ShExecInfo.hProcess, &exitCode );
|
||||
LOG_INFO( "ShellExecuteEx exit code: %d", exitCode );
|
||||
CloseHandle( ShExecInfo.hProcess );
|
||||
merge_msiexec_log( logfile );
|
||||
return exitCode == 0 ? true : false;
|
||||
} else {
|
||||
DeleteFileA( statusfile );
|
||||
g_status_for_last_install = INSTALL_STATUS_FAILED;
|
||||
LOG_LAST_ERROR( "Failed to run installer" );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Called by IPC server when a request comes in. Installs the package and returns a resul
|
||||
// Also detects disconnects
|
||||
void ipc_handler( char const* request, void* user_data, char* response, size_t capacity ) {
|
||||
LOG_INFO( "IPC handler invoked for request: %s", request );
|
||||
bool* is_connected = (bool*) user_data;
|
||||
if( !request ) {
|
||||
LOG_INFO( "Empty request, disconnection requested" );
|
||||
*is_connected = false;
|
||||
service_cancel_sleep();
|
||||
return;
|
||||
}
|
||||
|
||||
// identify command
|
||||
if( strlen( request ) > 5 && strnicmp( request, "msi ", 4 ) == 0 ) {
|
||||
// "msi" - run installer
|
||||
LOG_INFO( "MSI command, running installer" );
|
||||
if( run_installer( request + 4 ) ) {
|
||||
LOG_INFO( "Response: OK" );
|
||||
strcpy( response, "OK" );
|
||||
} else {
|
||||
LOG_INFO( "Response: ERROR" );
|
||||
strcpy( response, "ERROR" );
|
||||
}
|
||||
} else if( strlen( request ) == 6 && stricmp( request, "status" ) == 0 ) {
|
||||
// "status" - return result of last install
|
||||
LOG_INFO( "STATUS command, reading status file" );
|
||||
retrieve_status( response, capacity );
|
||||
LOG_INFO( "Status: %s", response );
|
||||
} else if( strlen( request ) == 3 && stricmp( request, "log" ) == 0 ) {
|
||||
// "log" - send log line
|
||||
LOG_INFO( "LOG command, returning next log line" );
|
||||
retrieve_buffered_log_line( response, capacity );
|
||||
} else {
|
||||
LOG_INFO( "Unknown command \"%s\", ignored", request );
|
||||
strcpy( response, "ERROR" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Service main function. Keeps an IPC server running - if it gets disconnected it starts it
|
||||
// up again. Install requests are handled by ipc_handler above
|
||||
void service_main( bool* service_is_running ) {
|
||||
LOG_INFO( "Service main function running" );
|
||||
|
||||
char statusfile[ MAX_PATH ];
|
||||
ExpandEnvironmentStringsA( "%LOCALAPPDATA%\\SdaAutoUpdate\\status.sau", statusfile, MAX_PATH );
|
||||
char status[ 32 ] = { 0 };
|
||||
FILE* fp = fopen( statusfile, "r" );
|
||||
if( fp ) {
|
||||
fgets( status, sizeof( status ), fp );
|
||||
fclose( fp );
|
||||
}
|
||||
if( stricmp( status, "PENDING" ) == 0 || stricmp( status, "SUCCESS" ) == 0 ) {
|
||||
g_status_for_last_install = INSTALL_STATUS_FINISHED;
|
||||
}
|
||||
|
||||
while( *service_is_running ) {
|
||||
bool is_connected = true;
|
||||
LOG_INFO( "Starting IPC server" );
|
||||
ipc_server_t* server = ipc_server_start( PIPE_NAME, ipc_handler, &is_connected );
|
||||
while( is_connected && *service_is_running ) {
|
||||
service_sleep();
|
||||
}
|
||||
LOG_INFO( "IPC server disconnected" );
|
||||
ipc_server_stop( server );
|
||||
}
|
||||
LOG_INFO( "Leaving service main function" );
|
||||
}
|
||||
|
||||
|
||||
int main( int argc, char** argv ) {
|
||||
if( argc >= 2 && stricmp( argv[ 1 ], "test" ) == 0 ) {
|
||||
log_init( true );
|
||||
} else {
|
||||
log_init( false );
|
||||
}
|
||||
|
||||
// Debug helpers for install/uninstall
|
||||
if( argc >= 2 && stricmp( argv[ 1 ], "install" ) == 0 ) {
|
||||
if( service_install( SERVICE_NAME ) ) {
|
||||
printf("Service installed successfully\n");
|
||||
return EXIT_SUCCESS;
|
||||
} else {
|
||||
printf("Service failed to install\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
if( argc >= 2 && stricmp( argv[ 1 ], "uninstall" ) == 0 ) {
|
||||
if( service_uninstall( SERVICE_NAME ) ) {
|
||||
printf("Service uninstalled successfully\n");
|
||||
return EXIT_SUCCESS;
|
||||
} else {
|
||||
printf("Service failed to uninstall\n");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
if( argc >= 2 && stricmp( argv[ 1 ], "test" ) == 0 ) {
|
||||
// run the ipc server as a normal exe, not as an installed service, for faster turnaround and debugging when testing
|
||||
bool running = true;
|
||||
return service_main( &running );
|
||||
}
|
||||
|
||||
// Run the service - called by the Windows Services system
|
||||
LOG_INFO( "Starting service" );
|
||||
service_run( SERVICE_NAME, service_main );
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
#define SERVICE_IMPLEMENTATION
|
||||
#include "service.h"
|
||||
|
||||
#define IPC_IMPLEMENTATION
|
||||
#include "ipc.h"
|
@ -1,34 +0,0 @@
|
||||
#define IDR_ICON 101
|
||||
IDR_ICON ICON "icon.ico"
|
||||
|
||||
#define VER_MAJOR 1
|
||||
#define VER_MINOR 0
|
||||
#define VER_REVISION 0
|
||||
|
||||
|
||||
|
||||
#define INNER_TO_STR(x) #x
|
||||
#define TO_STR(x) INNER_TO_STR(x)
|
||||
#define VER_STRING TO_STR(VER_MAJOR) "." TO_STR(VER_MINOR) "." TO_STR(VER_REVISION) ".0"
|
||||
|
||||
#define VS_VERSION_INFO 0x1L
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION VER_MAJOR,VER_MINOR,VER_REVISION
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK "040904E4"
|
||||
BEGIN
|
||||
VALUE "FileDescription", "Symphony Auto Update Service"
|
||||
VALUE "ProductName", "Symphony Auto Update Service"
|
||||
VALUE "ProductVersion", VER_STRING
|
||||
VALUE "LegalCopyright", "Copyright 2021 Symphony OSS"
|
||||
END
|
||||
END
|
||||
|
||||
BLOCK "VarFileInfo"
|
||||
BEGIN
|
||||
VALUE "Translation", 0x409, 1252
|
||||
END
|
||||
END
|
@ -1,146 +0,0 @@
|
||||
<?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>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{7E157BD0-86F0-4325-848C-BA1C87D9C99C}</ProjectGuid>
|
||||
<RootNamespace>autoupdateservice</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>MultiByte</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>MultiByte</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>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<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|x64'">
|
||||
<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'">
|
||||
<IntDir>$(Platform)\$(Configuration)\auto_update_service\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<IntDir>$(Platform)\$(Configuration)\auto_update_service\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<IntDir>$(Platform)\$(Configuration)\auto_update_service\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<IntDir>$(Platform)\$(Configuration)\auto_update_service\</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="auto_update_service.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ipc.h" />
|
||||
<ClInclude Include="service.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
Binary file not shown.
Before Width: | Height: | Size: 302 KiB |
@ -1,821 +0,0 @@
|
||||
#ifndef ipc_h
|
||||
#define ipc_h
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define IPC_MESSAGE_MAX_LENGTH 4096
|
||||
|
||||
// client
|
||||
|
||||
typedef struct ipc_client_t ipc_client_t;
|
||||
|
||||
ipc_client_t* ipc_client_connect( char const* pipe_name );
|
||||
|
||||
void ipc_client_disconnect( ipc_client_t* connection );
|
||||
|
||||
typedef enum ipc_receive_status_t {
|
||||
IPC_RECEIVE_STATUS_DONE,
|
||||
IPC_RECEIVE_STATUS_MORE_DATA,
|
||||
IPC_RECEIVE_STATUS_ERROR,
|
||||
} ipc_receive_status_t;
|
||||
|
||||
ipc_receive_status_t ipc_client_receive( ipc_client_t* connection, char* output, int output_size, int* received_size );
|
||||
|
||||
bool ipc_client_send( ipc_client_t* connection, char const* message );
|
||||
|
||||
|
||||
// server
|
||||
|
||||
typedef struct ipc_server_t ipc_server_t;
|
||||
|
||||
typedef void (*ipc_request_handler_t)( char const* request, void* user_data, char* response, size_t capacity );
|
||||
|
||||
ipc_server_t* ipc_server_start( char const* pipe_name, ipc_request_handler_t request_handler, void* user_data );
|
||||
|
||||
void ipc_server_stop( ipc_server_t* server );
|
||||
|
||||
|
||||
|
||||
#endif /* ipc_h */
|
||||
|
||||
|
||||
|
||||
#ifdef IPC_IMPLEMENTATION
|
||||
#undef IPC_IMPLEMENTATION
|
||||
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
#include <aclapi.h>
|
||||
|
||||
#pragma comment(lib, "advapi32.lib")
|
||||
|
||||
#ifndef IPC_LOG_INFO
|
||||
#define IPC_LOG_INFO printf( "\n" ), printf
|
||||
#endif
|
||||
|
||||
#ifndef IPC_LOG_ERROR
|
||||
#define IPC_LOG_ERROR printf( "\n" ), printf
|
||||
#endif
|
||||
|
||||
#ifndef IPC_LOG_LAST_ERROR
|
||||
#define IPC_LOG_LAST_ERROR printf( "\nLastError=%d : ", GetLastError() ), printf
|
||||
#endif
|
||||
|
||||
// Named pipes are on the form "\\.\pipe\name" but we don't want the user to have
|
||||
// to specify all that, so we expand what they pass in from "name" to "\\.\pipe\name"
|
||||
bool expand_pipe_name( char const* pipe_name, char* buffer, size_t capacity ) {
|
||||
int result = snprintf( buffer, capacity, "\\\\.\\pipe\\%s", pipe_name );
|
||||
return result >= 0 && result < (int) capacity;
|
||||
}
|
||||
|
||||
// Returns true if a pipe of the specified name exists, false if none exists
|
||||
bool pipe_exists( const char* pipe_name ) {
|
||||
IPC_LOG_INFO( "Checking if pipe exists: %s", pipe_name );
|
||||
WIN32_FIND_DATAA data;
|
||||
memset( &data, 0, sizeof( data ) );
|
||||
|
||||
HANDLE hfind = FindFirstFileA( "\\\\.\\pipe\\*", &data );
|
||||
if( hfind != INVALID_HANDLE_VALUE ) {
|
||||
do {
|
||||
char const* filename = data.cFileName;
|
||||
if( _stricmp( filename, pipe_name ) == 0 ) {
|
||||
FindClose( hfind );
|
||||
IPC_LOG_INFO( "Pipe found: %s", filename );
|
||||
return true;
|
||||
}
|
||||
} while( FindNextFileA( hfind, &data ) );
|
||||
FindClose( hfind );
|
||||
}
|
||||
|
||||
IPC_LOG_ERROR( "Pipe not found" );
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// This holds data related to a single client instance
|
||||
struct ipc_client_t {
|
||||
HANDLE pipe; // The named pipe to communicate over
|
||||
};
|
||||
|
||||
|
||||
// Establishes a connection to the specified named pipe
|
||||
// Returns NULL if a connection could not be established
|
||||
ipc_client_t* ipc_client_connect( char const* pipe_name ) {
|
||||
IPC_LOG_INFO( "Connecting to named pipe: %s", pipe_name );
|
||||
// Make sure a pipe with the specified name exists
|
||||
if( !pipe_exists( pipe_name ) ) {
|
||||
// Retry once if pipe was not found - this would be very rare, but will make it more robust
|
||||
IPC_LOG_INFO( "Pipe was not found, waiting a little and trying again" );
|
||||
Sleep( 1000 );
|
||||
if( !pipe_exists( pipe_name ) ) {
|
||||
IPC_LOG_INFO( "Pipe was still not found after waiting" );
|
||||
IPC_LOG_ERROR( "Named pipe does not exist" );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Expand the pipe name to the valid form eg. "\\.\pipe\name"
|
||||
char expanded_pipe_name[ MAX_PATH ];
|
||||
IPC_LOG_INFO( "Expanding to fully qualified pipe name: %s", pipe_name );
|
||||
if( !expand_pipe_name( pipe_name, expanded_pipe_name, sizeof( expanded_pipe_name ) ) ) {
|
||||
IPC_LOG_ERROR( "Pipe name too long" );
|
||||
return NULL;
|
||||
}
|
||||
IPC_LOG_INFO( "Expanded pipe name: %s", expanded_pipe_name );
|
||||
|
||||
// A named pipe has a maximum number of connections. When a client disconnect, it
|
||||
// can take a while for the disconnect to register on the server side, so we need
|
||||
// to handle the case where the pipe is busy. In practice, this should be rare,
|
||||
// but for robustness we handle it anyway.
|
||||
HANDLE pipe = NULL;
|
||||
for( ; ; ) { // This loop will typically not run more than two iterations, due to multiple exit points
|
||||
IPC_LOG_INFO( "Try to create connection" );
|
||||
pipe = CreateFileA(
|
||||
expanded_pipe_name, // pipe name
|
||||
GENERIC_READ | // read and write access
|
||||
GENERIC_WRITE,
|
||||
0, // no sharing
|
||||
NULL, // default security attributes
|
||||
OPEN_EXISTING, // opens existing pipe
|
||||
0, // default attributes
|
||||
NULL ); // no template file
|
||||
|
||||
// Break if the pipe handle is valid - a connection is now established
|
||||
if( pipe != INVALID_HANDLE_VALUE ) {
|
||||
IPC_LOG_INFO( "Connection attempt succeeded" );
|
||||
break;
|
||||
}
|
||||
|
||||
// Retry once if pipe was not found. Very rare that this would happen, but we're going for stability
|
||||
if( GetLastError() == ERROR_FILE_NOT_FOUND ) {
|
||||
IPC_LOG_INFO( "The pipe was not found, which was unexpected at this point, so we wait a little and try again" );
|
||||
Sleep( 1000 );
|
||||
pipe = CreateFileA(
|
||||
expanded_pipe_name, // pipe name
|
||||
GENERIC_READ | // read and write access
|
||||
GENERIC_WRITE,
|
||||
0, // no sharing
|
||||
NULL, // default security attributes
|
||||
OPEN_EXISTING, // opens existing pipe
|
||||
0, // default attributes
|
||||
NULL ); // no template file
|
||||
|
||||
// Break if the pipe handle is valid - a connection is now established
|
||||
if( pipe != INVALID_HANDLE_VALUE ) {
|
||||
IPC_LOG_INFO( "Second connection attempt succeeded" );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If we get an error other than ERROR_PIPE_BUSY, we fail to establish a connection.
|
||||
// In the case of ERROR_PIPE_BUSY we will wait for the pipe not to be busy (see below)
|
||||
if( GetLastError() != ERROR_PIPE_BUSY ) {
|
||||
IPC_LOG_LAST_ERROR( "Could not open pipe: " );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// All pipe instances are busy, so wait for 20 seconds.
|
||||
IPC_LOG_INFO( "All pipe instances are busy, so we wait for 20 seconds and then try again" );
|
||||
if( !WaitNamedPipeA( expanded_pipe_name, 20000 ) ) {
|
||||
IPC_LOG_INFO( "Wait failed" );
|
||||
// In the specific case of getting an ERROR_FILE_NOT_FOUND, we try doing the
|
||||
// wait one more time. The reason this would happen is if the server was just restarting
|
||||
// at the start of the call to ipc_client_connect, and thus the check if the pipe exist
|
||||
// passed, but when we got to the wait, the pipe was closed down and not yet started up
|
||||
// again. Waiting briefly and then trying again will ensure that we handle this rare case
|
||||
// of the server being restarted, but it will be very very rare.
|
||||
if( GetLastError() == ERROR_FILE_NOT_FOUND ) {
|
||||
// retry once just in case pipe was not created yet
|
||||
IPC_LOG_INFO( "Try the wait again after a short pause, in case it was just being created" );
|
||||
Sleep(1000);
|
||||
if( !WaitNamedPipeA( expanded_pipe_name, 20000 ) ) {
|
||||
IPC_LOG_LAST_ERROR( "Could not open pipe on second attempt: 20 second wait timed out: " );
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
IPC_LOG_LAST_ERROR( "Could not open pipe: 20 second wait timed out: " );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A fully working connection has been set up, return it to the caller
|
||||
IPC_LOG_INFO( "Connection successful" );
|
||||
ipc_client_t* connection = (ipc_client_t*) malloc( sizeof( ipc_client_t ) );
|
||||
connection->pipe = pipe;
|
||||
return connection;
|
||||
}
|
||||
|
||||
|
||||
// Disconnect the client from the server, and release the resources used by it
|
||||
// This will allow the server to eventually recycle and reuse that connection slot,
|
||||
// but in some cases it can take a brief period of time for that to happen
|
||||
void ipc_client_disconnect( ipc_client_t* connection ) {
|
||||
IPC_LOG_INFO( "Disconnecting client" );
|
||||
FlushFileBuffers( connection->pipe );
|
||||
DisconnectNamedPipe( connection->pipe );
|
||||
CloseHandle( connection->pipe );
|
||||
free( connection );
|
||||
IPC_LOG_INFO( "Disconnection complete" );
|
||||
}
|
||||
|
||||
|
||||
// Wait for data to be available on the named pipe, and once it is, read it into the
|
||||
// provided buffer. Returns a status enum for success or failure, or for the case
|
||||
// where more data was cued up on the server side than could be received in one call,
|
||||
// in which case the ipc_client_receive function should be called again to complete
|
||||
// the retrieval of the message. The function will wait indefinitely, until either
|
||||
// a message is available, or the pipe is closed.
|
||||
// TODO: consider a timeout for the wait, to allow for more robust client implementations
|
||||
ipc_receive_status_t ipc_client_receive( ipc_client_t* connection, char* output, int output_size, int* received_size ) {
|
||||
IPC_LOG_INFO( "Reading data" );
|
||||
DWORD size_read = 0;
|
||||
BOOL success = ReadFile(
|
||||
connection->pipe, // pipe handle
|
||||
output, // buffer to receive reply
|
||||
output_size, // size of buffer
|
||||
&size_read, // number of bytes read
|
||||
NULL ); // not overlapped
|
||||
|
||||
IPC_LOG_INFO( "Read returned %s", success ? "true" : "false" );
|
||||
if( !success && GetLastError() != ERROR_MORE_DATA ) {
|
||||
IPC_LOG_LAST_ERROR( "ReadFile from pipe failed: " );
|
||||
return IPC_RECEIVE_STATUS_ERROR;
|
||||
}
|
||||
|
||||
IPC_LOG_INFO( "Data size received: %u", size_read );
|
||||
if( received_size ) {
|
||||
*received_size = size_read;
|
||||
}
|
||||
|
||||
if( success ) {
|
||||
IPC_LOG_INFO( "Read done" );
|
||||
return IPC_RECEIVE_STATUS_DONE;
|
||||
} else {
|
||||
IPC_LOG_INFO( "More data to be read" );
|
||||
return IPC_RECEIVE_STATUS_MORE_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Sends the specified message (as a zero-terminated string) to the server
|
||||
// Will wait for the server to receive the message, and how long that wait
|
||||
// is will depend on if the server is busy when the message is sent.
|
||||
// TODO: consider a timeout for the wait, to allow for more robust client implementations
|
||||
bool ipc_client_send( ipc_client_t* connection, char const* message ) {
|
||||
// Send a message to the pipe server.
|
||||
IPC_LOG_INFO( "Sending data" );
|
||||
DWORD written = 0;
|
||||
BOOL success = WriteFile(
|
||||
connection->pipe, // pipe handle
|
||||
message, // message
|
||||
(DWORD) strlen( message ) + 1, // message length
|
||||
&written, // bytes written
|
||||
NULL ); // not overlapped
|
||||
|
||||
IPC_LOG_INFO( "Write returned %s", success ? "true" : "false" );
|
||||
if( !success ) {
|
||||
IPC_LOG_LAST_ERROR( "WriteFile to pipe failed: " );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// This holds the data for a single server-side client thread
|
||||
typedef struct ipc_client_thread_t {
|
||||
BOOL recycle; // When a client disconnect, this flag is set to TRUE so the slot can be reused
|
||||
ipc_request_handler_t request_handler; // When a request is recieved from a client, the server calls this handler
|
||||
void* user_data; // When the request_handler is called, this user_data field is passed along with it
|
||||
int exit_flag; // Set by the server to signal that the client thread should exit
|
||||
HANDLE thread; // Handle to this client thread, used by server to wait for it to exit (on server shutdown)
|
||||
HANDLE pipe; // The named pipe instance allocated to this client
|
||||
OVERLAPPED io; // We are using non-blocking I/O so the server can cancel pending read/write operations on shutdown
|
||||
} ipc_client_thread_t;
|
||||
|
||||
|
||||
// Typically, we should only ever have one connections, so this is probably overkill, but
|
||||
// it doesn't hurt
|
||||
#define MAX_CLIENT_CONNECTIONS 32
|
||||
|
||||
|
||||
// This holds the data for an ipc server instance
|
||||
struct ipc_server_t {
|
||||
char expanded_pipe_name[ MAX_PATH ]; // Holds the result of expanding from the "name" form to the "\\.\pipe\name" form
|
||||
HANDLE thread; // Handle to the main server thread, used to wait for thread exit on server shutdown
|
||||
HANDLE thread_started_event; // When the main server thread is started, ipc_server_start needs to wait until it is ready to accept connections before returning to the caller
|
||||
HANDLE pipe; // The server pipe instance currently used to listen for connections, will be handed to client thread when connection is made
|
||||
OVERLAPPED io; // We are using non-blocking I/O so the server can cancel pending ConnectNamedPipe operations on shutdown
|
||||
int exit_flag; // Set by the ipc_server_stop to signal that the main server thread should exit
|
||||
ipc_request_handler_t request_handler; // When a request is recieved from a client, the server calls this handler
|
||||
void* user_data; // When the request_handler is called, this user_data field is passed along with it
|
||||
ipc_client_thread_t client_threads[ MAX_CLIENT_CONNECTIONS ]; // Array of client instances, an instance is only in use if its `recycle` flag is FALSE
|
||||
int client_threads_count; // Number of slots used on the `client_threads` array (but a slot may or may not be in use depending on its `recycle` flag)
|
||||
};
|
||||
|
||||
|
||||
// When a client connects to the server, the server creates a new thread to handle that connection,
|
||||
// and this is the function running on that thread. It basically just sits in a loop, doing a Read
|
||||
// from the pipe and waiting until it gets a message. Then it will call the user supplied request
|
||||
// handler, and then it does a Write on the pipe to send the response it got from the request handler
|
||||
// to the pipe.
|
||||
DWORD WINAPI ipc_client_thread( LPVOID param ) {
|
||||
IPC_LOG_INFO( "[%u] Client thread started", GetCurrentThreadId() );
|
||||
|
||||
ipc_client_thread_t* context = (ipc_client_thread_t*) param;
|
||||
|
||||
// Create the event used to wait for Read/Write operations to complete
|
||||
HANDLE io_event = CreateEvent(
|
||||
NULL, // default security attributes
|
||||
TRUE, // manual-reset event
|
||||
FALSE, // initial state is nonsignaled
|
||||
NULL // object name
|
||||
);
|
||||
|
||||
// Main request-response loop. Will run until exit requested or an eror occurs
|
||||
IPC_LOG_INFO( "[%u] Enter client thread main loop", GetCurrentThreadId() );
|
||||
while( !context->exit_flag ) {
|
||||
// Read loop, keeps trying to read until data arrives or an error occurs (including a shutdown
|
||||
// cancelling the read operation)
|
||||
char request[ IPC_MESSAGE_MAX_LENGTH ]; // buffer to hold incoming data
|
||||
DWORD bytes_read = 0;
|
||||
BOOL success = FALSE;
|
||||
bool read_pending = true;
|
||||
IPC_LOG_INFO( "[%u] Starting read loop", GetCurrentThreadId() );
|
||||
while( read_pending ) {
|
||||
// Set up non-blocking I/O
|
||||
memset( &context->io, 0, sizeof( context->io ) );
|
||||
ResetEvent( io_event );
|
||||
context->io.hEvent = io_event;
|
||||
// Read client requests from the pipe in a non-blocking call
|
||||
IPC_LOG_INFO( "[%u] Reading from pipe", GetCurrentThreadId() );
|
||||
success = ReadFile(
|
||||
context->pipe, // handle to pipe
|
||||
request, // buffer to receive data
|
||||
IPC_MESSAGE_MAX_LENGTH, // size of buffer
|
||||
&bytes_read, // number of bytes read
|
||||
&context->io ); // overlapped I/O
|
||||
|
||||
IPC_LOG_INFO( "[%u] Read returned: %s", GetCurrentThreadId(), success ? "true" : "false" );
|
||||
// Check if the Read operation is in progress (ReadFile returns FALSE and the error is ERROR_IO_PENDING )
|
||||
if( !success && GetLastError() == ERROR_IO_PENDING ) {
|
||||
IPC_LOG_INFO( "[%u] Pipe is in IO_PENDING state, read is in progress", GetCurrentThreadId() );
|
||||
// Wait for the event to be triggered, but timeout after half a second and re-issue the Read
|
||||
// This is so the re-issued Read can detect if the pipe have been closed, and thus exit the thread
|
||||
IPC_LOG_INFO( "[%u] Wait for read to complete", GetCurrentThreadId() );
|
||||
if( WaitForSingleObject( io_event, 500 ) == WAIT_TIMEOUT ) {
|
||||
IPC_LOG_INFO( "[%u] Read timed out, try again", GetCurrentThreadId() );
|
||||
CancelIoEx( context->pipe, &context->io );
|
||||
continue; // Make another Read call
|
||||
}
|
||||
|
||||
// The wait did not timeout, so the Read operation should now be completed (or failed)
|
||||
IPC_LOG_INFO( "[%u] Read completed, checking result", GetCurrentThreadId() );
|
||||
success = GetOverlappedResult(
|
||||
context->pipe, // handle to pipe
|
||||
&context->io, // OVERLAPPED structure
|
||||
&bytes_read, // bytes transferred
|
||||
FALSE ); // don't wait
|
||||
IPC_LOG_INFO( "[%u] Read was %s", GetCurrentThreadId(), success ? "successful" : "unsuccessful" );
|
||||
IPC_LOG_INFO( "[%u] Bytes read %u", GetCurrentThreadId(), bytes_read );
|
||||
}
|
||||
|
||||
// The read have completed (successfully or not) so exit the read loop
|
||||
read_pending = false;
|
||||
}
|
||||
IPC_LOG_INFO( "[%u] Finished read loop, result was %s", GetCurrentThreadId(), success ? "success" : "failure" );
|
||||
|
||||
|
||||
// If the Read was unsuccessful (or read no data), log the error and exit the thread
|
||||
// TODO: consider if there are better ways to deal with the error. There might not be,
|
||||
// but then the user-code calling client send/receive might need some robust retry code
|
||||
if( !success || bytes_read == 0 ) {
|
||||
if( GetLastError() == ERROR_BROKEN_PIPE ) {
|
||||
IPC_LOG_INFO( "[%u] Client disconnected", GetCurrentThreadId() );
|
||||
} else {
|
||||
IPC_LOG_LAST_ERROR( "ReadFile failed: " );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if a server shutdown have requested this thread to be terminated, and exit if that's the case
|
||||
if( context->exit_flag ) {
|
||||
IPC_LOG_INFO( "[%u] Server shutdown requested, terminating thread", GetCurrentThreadId() );
|
||||
break;
|
||||
}
|
||||
|
||||
IPC_LOG_INFO( "[%u] Incoming message: %s", GetCurrentThreadId(), request );
|
||||
|
||||
// Process the incoming message by calling the user-supplied request handler function
|
||||
char response[ IPC_MESSAGE_MAX_LENGTH ];
|
||||
memset( response, 0, sizeof( response ) );
|
||||
IPC_LOG_INFO( "[%u] Processing message", GetCurrentThreadId() );
|
||||
context->request_handler( request, context->user_data, response, sizeof( response ) );
|
||||
response[ sizeof( response ) - 1 ] = '\0'; // Force zero termination (truncate string)
|
||||
IPC_LOG_INFO( "[%u] Outgoing response: \"%.32s%s\"", GetCurrentThreadId(), response, strlen( response ) > 32 ? "..." : "" );
|
||||
DWORD response_length = (DWORD)strlen( response ) + 1;
|
||||
IPC_LOG_INFO( "[%u] Response length: %u", GetCurrentThreadId(), response_length );
|
||||
|
||||
// Write the reply to the pipe
|
||||
DWORD bytes_written = 0;
|
||||
IPC_LOG_INFO( "[%u] Sending response", GetCurrentThreadId() );
|
||||
success = WriteFile(
|
||||
context->pipe, // handle to pipe
|
||||
response, // buffer to write from
|
||||
response_length, // number of bytes to write
|
||||
&bytes_written, // number of bytes written
|
||||
&context->io ); // overlapped I/O
|
||||
|
||||
IPC_LOG_INFO( "[%u] Write returned: %s", GetCurrentThreadId(), success ? "true" : "false" );
|
||||
|
||||
// If the write operation is in progress, we wait until it is done, or aborted due to server shutdown
|
||||
if( success || GetLastError() == ERROR_IO_PENDING ) {
|
||||
IPC_LOG_INFO( "[%u] Pipe is in IO_PENDING state, write is in progress", GetCurrentThreadId() );
|
||||
success = GetOverlappedResult(
|
||||
context->pipe, // handle to pipe
|
||||
&context->io, // OVERLAPPED structure
|
||||
&bytes_written, // bytes transferred
|
||||
TRUE ); // wait
|
||||
IPC_LOG_INFO( "[%u] Write was %s", GetCurrentThreadId(), success ? "successful" : "unsuccessful" );
|
||||
IPC_LOG_INFO( "[%u] Bytes written %u", GetCurrentThreadId(), bytes_written );
|
||||
}
|
||||
|
||||
// If the Write was unsuccessful (or didn't manage to write the whole buffer), log the error and exit the thread
|
||||
if( !success || bytes_written != response_length ) {
|
||||
IPC_LOG_LAST_ERROR( "WriteFile failed: " );
|
||||
break;
|
||||
}
|
||||
}
|
||||
IPC_LOG_INFO( "[%u] Finished client thread main loop", GetCurrentThreadId() );
|
||||
|
||||
// Signal that a disconnect has happened
|
||||
context->request_handler( NULL, context->user_data, NULL, 0 );
|
||||
|
||||
// Flush the pipe to allow the client to read the pipe's contents
|
||||
// before disconnecting. Then disconnect the pipe, and close the
|
||||
// handle to this pipe instance.
|
||||
CloseHandle( io_event );
|
||||
FlushFileBuffers( context->pipe );
|
||||
DisconnectNamedPipe( context->pipe );
|
||||
CloseHandle( context->pipe );
|
||||
|
||||
// Mark this client slot for recycling for new connections
|
||||
context->pipe = INVALID_HANDLE_VALUE;
|
||||
context->recycle = TRUE;
|
||||
IPC_LOG_INFO( "[%u] Client thread terminated", GetCurrentThreadId() );
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
// When the `ipc_server_start` is called, it creates this thread which sits in a loop
|
||||
// and listens for new client connections, until exit is requested by a call to
|
||||
// `ipc_server_stop`. When a new connection is made, it will start another thread to
|
||||
// handle the I/O for that specific client. Then it will open a new listening pipe
|
||||
// instance for further connections.
|
||||
DWORD WINAPI ipc_server_thread( LPVOID param ) {
|
||||
IPC_LOG_INFO( "[%u] Server thread started", GetCurrentThreadId() );
|
||||
ipc_server_t* server = (ipc_server_t*) param;
|
||||
|
||||
// Create security attribs, we need this so the server can run in session 0
|
||||
// while client runs as a normal user session
|
||||
SID_IDENTIFIER_AUTHORITY auth = { SECURITY_WORLD_SID_AUTHORITY };
|
||||
PSID sid;
|
||||
if( !AllocateAndInitializeSid( &auth, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &sid ) ) {
|
||||
IPC_LOG_LAST_ERROR( "AllocateAndInitializeSid failed: " );
|
||||
IPC_LOG_INFO( "[%u] Server thread terminated", GetCurrentThreadId() );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
EXPLICIT_ACCESS access = { 0 };
|
||||
access.grfAccessPermissions = FILE_ALL_ACCESS;
|
||||
access.grfAccessMode = SET_ACCESS;
|
||||
access.grfInheritance = NO_INHERITANCE;
|
||||
access.Trustee.TrusteeForm = TRUSTEE_IS_SID;
|
||||
access.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
|
||||
access.Trustee.ptstrName = (LPTSTR)sid;
|
||||
PACL acl;
|
||||
if( SetEntriesInAcl(1, &access, NULL, &acl) != ERROR_SUCCESS ) {
|
||||
IPC_LOG_LAST_ERROR( "SetEntriesInAcl failed: " );
|
||||
FreeSid(sid);
|
||||
IPC_LOG_INFO( "[%u] Server thread terminated", GetCurrentThreadId() );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR)LocalAlloc( LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH );
|
||||
if( !sd ) {
|
||||
IPC_LOG_ERROR( "LocalAlloc failed" );
|
||||
FreeSid( sid );
|
||||
IPC_LOG_ERROR( "[%u] Server thread terminated", GetCurrentThreadId() );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if( !InitializeSecurityDescriptor( sd, SECURITY_DESCRIPTOR_REVISION ) ) {
|
||||
IPC_LOG_LAST_ERROR( "InitializeSecurityDescriptor failed: " );
|
||||
LocalFree(sd);
|
||||
FreeSid(sid);
|
||||
IPC_LOG_ERROR( "[%u] Server thread terminated", GetCurrentThreadId() );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if( !SetSecurityDescriptorDacl( sd, TRUE, acl, FALSE ) ) {
|
||||
IPC_LOG_LAST_ERROR( "SetSecurityDescriptorDacl failed: " );
|
||||
LocalFree( sd );
|
||||
FreeSid( sid );
|
||||
IPC_LOG_ERROR( "[%u] Server thread terminated", GetCurrentThreadId() );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
SECURITY_ATTRIBUTES attribs;
|
||||
attribs.nLength = sizeof( SECURITY_ATTRIBUTES );
|
||||
attribs.lpSecurityDescriptor = sd;
|
||||
attribs.bInheritHandle = -1;
|
||||
|
||||
// Create the event used to wait for ConnectNamedPipe operations to complete
|
||||
HANDLE io_event = CreateEvent(
|
||||
NULL, // default security attributes
|
||||
TRUE, // manual-reset event
|
||||
FALSE, // initial state is nonsignaled
|
||||
NULL // object name
|
||||
);
|
||||
|
||||
// The main loop creates an instance of the named pipe and then waits for a client
|
||||
// to connect to it. When the client connects, a thread is created to handle
|
||||
// communications with that client, and this loop is free to wait for the next client
|
||||
// connect request
|
||||
IPC_LOG_INFO( "[%u] Enter server thread main loop", GetCurrentThreadId() );
|
||||
bool event_raised = false; // We make sure to only raise the server-thread-is-ready event once
|
||||
while( !server->exit_flag ) {
|
||||
// Create a pipe instance to listen for connections
|
||||
IPC_LOG_INFO( "[%u] Creating named pipe listening for connections", GetCurrentThreadId() );
|
||||
server->pipe = CreateNamedPipeA(
|
||||
server->expanded_pipe_name,// pipe name
|
||||
PIPE_ACCESS_DUPLEX | // read/write access
|
||||
FILE_FLAG_OVERLAPPED, // we use async I/O so that we can cancel ConnectNamedPipe operations
|
||||
PIPE_TYPE_MESSAGE | // message type pipe
|
||||
PIPE_READMODE_MESSAGE | // message-read mode
|
||||
PIPE_WAIT, // blocking mode
|
||||
MAX_CLIENT_CONNECTIONS, // max. instances
|
||||
IPC_MESSAGE_MAX_LENGTH, // output buffer size
|
||||
IPC_MESSAGE_MAX_LENGTH, // input buffer size
|
||||
0, // client time-out
|
||||
&attribs ); // default security attribute
|
||||
|
||||
// If we failed to create the pipe, we log the error and exit
|
||||
// TODO: Should we handle this some other way? perhaps report the error back to user
|
||||
if( server->pipe == INVALID_HANDLE_VALUE ) {
|
||||
|
||||
// If the failure was due to pipe busy, try again in a bit
|
||||
if( GetLastError() == ERROR_PIPE_BUSY ) {
|
||||
IPC_LOG_LAST_ERROR( "CreateNamedPipe failed: " );
|
||||
IPC_LOG_INFO( "[%u] Pipe was busy, waiting a bit then trying once more.", GetCurrentThreadId() );
|
||||
Sleep(1000);
|
||||
|
||||
server->pipe = CreateNamedPipeA(
|
||||
server->expanded_pipe_name,// pipe name
|
||||
PIPE_ACCESS_DUPLEX | // read/write access
|
||||
FILE_FLAG_OVERLAPPED, // we use async I/O so that we can cancel ConnectNamedPipe operations
|
||||
PIPE_TYPE_MESSAGE | // message type pipe
|
||||
PIPE_READMODE_MESSAGE | // message-read mode
|
||||
PIPE_WAIT, // blocking mode
|
||||
MAX_CLIENT_CONNECTIONS, // max. instances
|
||||
IPC_MESSAGE_MAX_LENGTH, // output buffer size
|
||||
IPC_MESSAGE_MAX_LENGTH, // input buffer size
|
||||
0, // client time-out
|
||||
&attribs ); // default security attribute
|
||||
}
|
||||
|
||||
if( server->pipe == INVALID_HANDLE_VALUE ) {
|
||||
IPC_LOG_LAST_ERROR( "CreateNamedPipe failed: " );
|
||||
|
||||
LocalFree( acl );
|
||||
LocalFree( sd );
|
||||
FreeSid( sid );
|
||||
IPC_LOG_ERROR( "[%u] Server thread terminated", GetCurrentThreadId() );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Signal to `ipc_server_start` that the server thread is now fully up and
|
||||
// running and accepting connections
|
||||
if( !event_raised ) {
|
||||
IPC_LOG_INFO( "[%u] Signaling to ipc_server_start that init is complete and we are now listening", GetCurrentThreadId() );
|
||||
SetEvent( server->thread_started_event );
|
||||
event_raised = true; // Make sure we don't signal the event again
|
||||
}
|
||||
|
||||
// Wait for the client to connect, using async I/O operation, so ConnectNamedPipe returns immediately
|
||||
memset( &server->io, 0, sizeof( server->io ) );
|
||||
server->io.hEvent = io_event;
|
||||
IPC_LOG_INFO( "[%u] Wait for client to connect", GetCurrentThreadId() );
|
||||
ConnectNamedPipe( server->pipe, &server->io );
|
||||
if( GetLastError() == ERROR_IO_PENDING ) {
|
||||
for( ; ; ) {
|
||||
if( WaitForSingleObject( server->io.hEvent, 100 ) == WAIT_OBJECT_0 ) {
|
||||
IPC_LOG_INFO( "[%u] Connection completed", GetCurrentThreadId() );
|
||||
break;
|
||||
}
|
||||
if( server->exit_flag ) {
|
||||
IPC_LOG_INFO( "[%u] Server shutdown requested", GetCurrentThreadId() );
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if( GetLastError() != ERROR_PIPE_CONNECTED ) {
|
||||
if( GetLastError() != ERROR_OPERATION_ABORTED || server->exit_flag == 0 ) {
|
||||
// The client could not connect, so close the pipe.
|
||||
IPC_LOG_LAST_ERROR( "Connection failed: " );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a server shutdown have requested this thread to be terminated, and exit if that's the case
|
||||
if( server->exit_flag ) {
|
||||
IPC_LOG_INFO( "[%u] Server shutdown requested, breaking main loop", GetCurrentThreadId() );
|
||||
break;
|
||||
}
|
||||
|
||||
IPC_LOG_INFO( "[%u] Client connected, setting up client thread to handle it", GetCurrentThreadId() );
|
||||
|
||||
// Find a free client slot to recycle for this new client connection
|
||||
ipc_client_thread_t* context = NULL;
|
||||
for( int i = 0; i < server->client_threads_count; ++i ) {
|
||||
if( server->client_threads[ i ].recycle ) {
|
||||
context = &server->client_threads[ i ];
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no free slot to recycle, use a new slot if available
|
||||
if( !context ) {
|
||||
IPC_LOG_INFO( "[%u] No free slot to recycle, allocating a new slot", GetCurrentThreadId() );
|
||||
if( server->client_threads_count < MAX_CLIENT_CONNECTIONS ) {
|
||||
context = &server->client_threads[ server->client_threads_count++ ];
|
||||
} else {
|
||||
IPC_LOG_ERROR( "[%u] Maximum number of connectsions reached - client should have been held in wait state, and this error should never have been triggered", GetCurrentThreadId() );
|
||||
// If we already reached the maximum number of connections, we have to bail out
|
||||
// This shouldn't really happen though, as the client should be kept in the wait
|
||||
// state by the pipe itself which is specified to accept only the same number of
|
||||
// connections
|
||||
// TODO: Perhaps better to just silently refuse the connection but stay alive?
|
||||
// or maybe kill the connection that has been idle for the longest time?
|
||||
IPC_LOG_INFO( "[%u] Too many connections", GetCurrentThreadId() );
|
||||
LocalFree( acl );
|
||||
LocalFree( sd );
|
||||
FreeSid( sid );
|
||||
if( server->pipe != INVALID_HANDLE_VALUE ) {
|
||||
CloseHandle( server->pipe );
|
||||
server->pipe = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
CloseHandle( io_event );
|
||||
IPC_LOG_ERROR( "[%u] Server thread terminated", GetCurrentThreadId() );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the client slot
|
||||
IPC_LOG_INFO( "[%u] Initializing client slot", GetCurrentThreadId() );
|
||||
memset( context, 0, sizeof( *context ) );
|
||||
context->request_handler = server->request_handler;
|
||||
context->user_data = server->user_data;
|
||||
context->pipe = server->pipe;
|
||||
|
||||
// We are handing the pipe over to the client thread, but will be creating a new one on
|
||||
// the next iteration through the loop
|
||||
server->pipe = INVALID_HANDLE_VALUE;
|
||||
|
||||
// Create a dedicated thread to handle this connection
|
||||
IPC_LOG_INFO( "[%u] Creating the client thread", GetCurrentThreadId() );
|
||||
context->thread = CreateThread(
|
||||
NULL, // no security attribute
|
||||
0, // default stack size
|
||||
ipc_client_thread, // thread proc
|
||||
(LPVOID) context, // thread parameter
|
||||
0, // not suspended
|
||||
NULL ); // returns thread ID
|
||||
|
||||
// If we failed to create thread, something's gone very wrong, so we need to bail
|
||||
if( context->thread == NULL ) {
|
||||
IPC_LOG_LAST_ERROR( "CreateThread failed: " );
|
||||
LocalFree( acl );
|
||||
LocalFree( sd );
|
||||
FreeSid( sid );
|
||||
if( server->pipe != INVALID_HANDLE_VALUE ) {
|
||||
CloseHandle( server->pipe );
|
||||
server->pipe = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
CloseHandle( io_event );
|
||||
IPC_LOG_ERROR( "[%u] Server thread terminated", GetCurrentThreadId() );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
IPC_LOG_INFO( "[%u] Finished server thread main loop", GetCurrentThreadId() );
|
||||
|
||||
|
||||
// Cleanup thread resources before we exit
|
||||
LocalFree( acl );
|
||||
LocalFree( sd );
|
||||
FreeSid( sid );
|
||||
|
||||
if( server->pipe != INVALID_HANDLE_VALUE ) {
|
||||
CloseHandle( server->pipe );
|
||||
server->pipe = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
CloseHandle( io_event );
|
||||
IPC_LOG_INFO( "[%u] Server thread terminated by request", GetCurrentThreadId() );
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
// Starts a named pipe server with the specified pipe name, and starts listening for
|
||||
// client connections on a separate thread, so will return immediately. The server
|
||||
// thread will keep listening for connections until `ipc_server_stop` is called.
|
||||
ipc_server_t* ipc_server_start( char const* pipe_name, ipc_request_handler_t request_handler, void* user_data ) {
|
||||
IPC_LOG_INFO( "Starting named pipe server: %s", pipe_name );
|
||||
|
||||
// Allocate the server instance and initialize it
|
||||
ipc_server_t* server = (ipc_server_t*) malloc( sizeof( ipc_server_t ) );
|
||||
memset( server, 0, sizeof( ipc_server_t ) );
|
||||
server->pipe = INVALID_HANDLE_VALUE;
|
||||
server->request_handler = request_handler;
|
||||
server->user_data = user_data;
|
||||
|
||||
// Expand the pipe name to the valid form eg. "\\.\pipe\name"
|
||||
IPC_LOG_INFO( "Expanding to fully qualified pipe name: %s", pipe_name );
|
||||
if( !expand_pipe_name( pipe_name, server->expanded_pipe_name, sizeof( server->expanded_pipe_name ) ) ) {
|
||||
IPC_LOG_ERROR( "Pipe name too long" );
|
||||
free( server );
|
||||
return NULL;
|
||||
}
|
||||
IPC_LOG_INFO( "Expanded pipe name: %s", server->expanded_pipe_name );
|
||||
|
||||
// Create the event used by the server thread to signal that it is up and running and accepting connections
|
||||
server->thread_started_event = CreateEvent(
|
||||
NULL, // default security attributes
|
||||
TRUE, // manual-reset event
|
||||
FALSE, // initial state is nonsignaled
|
||||
NULL // object name
|
||||
);
|
||||
|
||||
// Start the server thread which accepts connections and starts dedicated client threads for each new connection
|
||||
IPC_LOG_INFO( "Starting server thread" );
|
||||
server->thread = CreateThread(
|
||||
NULL, // default security attributes
|
||||
0, // use default stack size
|
||||
ipc_server_thread, // thread function name
|
||||
server, // argument to thread function
|
||||
0, // use default creation flags
|
||||
NULL ); // returns the thread identifier
|
||||
|
||||
// If thread creation failed, return error
|
||||
if( server->thread == NULL ) {
|
||||
IPC_LOG_LAST_ERROR( "Failed to create server thread: " );
|
||||
CloseHandle( server->thread_started_event );
|
||||
free( server );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Wait for the server thread to be up and running and accepting connections
|
||||
IPC_LOG_INFO( "Waiting for server thread to be initialized" );
|
||||
if( WaitForSingleObject( server->thread_started_event, 10000 ) != WAIT_OBJECT_0 ) {
|
||||
// If it takes more than 10 seconds for the server thread to start up, something
|
||||
// has gone very wrong so we abort and return an error
|
||||
IPC_LOG_LAST_ERROR( "Timeout waiting for client thread to start: " );
|
||||
CloseHandle( server->thread_started_event );
|
||||
TerminateThread( server->thread, EXIT_FAILURE );
|
||||
free( server );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
IPC_LOG_INFO( "Server up and running" );
|
||||
// Return the fully set up and ready server instance
|
||||
return server;
|
||||
}
|
||||
|
||||
|
||||
// Signals the server thread to stop, cancels all pending I/O operations on all
|
||||
// client threads, and release the resources used by the server
|
||||
void ipc_server_stop( ipc_server_t* server ) {
|
||||
IPC_LOG_INFO( "Stopping named pipe server" );
|
||||
server->exit_flag = 1; // Signal server thread top stop
|
||||
if( server->pipe != INVALID_HANDLE_VALUE ) {
|
||||
CancelIoEx( server->pipe, &server->io ); // Cancel pending ConnectNamedPipe operatios, if any
|
||||
}
|
||||
IPC_LOG_INFO( "Waiting for server thread to exit" );
|
||||
WaitForSingleObject( server->thread, INFINITE ); // Wait for server thread to exit
|
||||
IPC_LOG_INFO( "Server thread stopped" );
|
||||
|
||||
// Loop over all clients and terminate each one
|
||||
IPC_LOG_INFO( "Terminating client connections" );
|
||||
for( int i = 0; i < server->client_threads_count; ++i ) {
|
||||
ipc_client_thread_t* client = &server->client_threads[ i ];
|
||||
if( !client->recycle ) { // A slot is only valid if `recycle` is FALSE
|
||||
client->exit_flag = 1; // Tell client thread to exit
|
||||
CancelIoEx( client->pipe, &client->io ); // Cancel any pending Read/Write operation
|
||||
WaitForSingleObject( client->thread, INFINITE ); // Wait for client thread to exit
|
||||
}
|
||||
}
|
||||
|
||||
// Free server resources
|
||||
CloseHandle( server->thread_started_event );
|
||||
free( server );
|
||||
IPC_LOG_INFO( "Server stopped and terminated" );
|
||||
}
|
||||
|
||||
#endif /* IPC_IMPLEMENTATION */
|
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
@ -1,10 +0,0 @@
|
||||
{
|
||||
"name": "auto_update",
|
||||
"version": "0.0.1",
|
||||
"description": "SDA Auto Update",
|
||||
"scripts": {
|
||||
"test": "cl tests.cpp /O2 /MTd /D_DEBUG /D_CRTDBG_MAP_ALLOC /nologo /link /SUBSYSTEM:CONSOLE && tests.exe",
|
||||
"preinstall": "npm run test && npm run build",
|
||||
"build": "rc.exe /nologo auto_update_helper.rc & cl auto_update_helper.c auto_update_helper.res /O2 /MT /nologo /link /SUBSYSTEM:CONSOLE /MANIFESTINPUT:manifest.xml /MANIFEST:EMBED & rc.exe /nologo auto_update_service.rc & cl auto_update_service.c auto_update_service.res /O2 /MT /nologo /link /SUBSYSTEM:CONSOLE /MANIFESTINPUT:manifest.xml /MANIFEST:EMBED"
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
@echo off
|
||||
echo runs the tests in an infinite loop
|
||||
echo intended to be run manually and left running for a long time,
|
||||
echo as a final sanity check to make sure the tests are stable
|
||||
echo will exit if any tests fail
|
||||
set started=%date% %time%
|
||||
:loop
|
||||
echo INITIATED AT %started%
|
||||
echo CURRENTLY AT %date% %time%
|
||||
call npm run test
|
||||
if %ERRORLEVEL% NEQ 0 (
|
||||
echo.
|
||||
echo INITIATED AT %started%
|
||||
echo TERMINATED AT %date% %time%
|
||||
goto :eof
|
||||
)
|
||||
goto :loop
|
@ -1,323 +0,0 @@
|
||||
#ifndef service_h
|
||||
#define service_h
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
bool service_install( char const* service_name );
|
||||
bool service_uninstall( char const* service_name );
|
||||
|
||||
typedef void (*service_main_func_t)( bool* is_running );
|
||||
|
||||
void service_run( char const* service_name, service_main_func_t main_func );
|
||||
|
||||
void service_sleep( void );
|
||||
void service_cancel_sleep( void );
|
||||
|
||||
#endif /* service_h */
|
||||
|
||||
|
||||
#ifdef SERVICE_IMPLEMENTATION
|
||||
#undef SERVICE_IMPLEMENTATION
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// Get an errror description for the last error from Windows, for logging purposes
|
||||
// TODO: thread safe return value
|
||||
char const* error_message( void ) {
|
||||
DWORD error = GetLastError();
|
||||
if( error == 0 ) {
|
||||
return "";
|
||||
}
|
||||
|
||||
LPSTR buffer = NULL;
|
||||
size_t size = FormatMessageA(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, error, MAKELANGID( LANG_NEUTRAL, SUBLANG_DEFAULT ), (LPSTR)&buffer, 0, NULL);
|
||||
|
||||
static char message[ 1024 ] = "";
|
||||
if( buffer ) {
|
||||
strncpy( message, buffer, sizeof( message ) );
|
||||
message[ sizeof( message ) - 1 ] = '\0';
|
||||
LocalFree(buffer);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
|
||||
// Helper function to install the service, used for debugging (for production, service is set up by installer).
|
||||
bool service_install( char const* service_name ) {
|
||||
// Get the path to the running executable
|
||||
TCHAR path[ MAX_PATH ];
|
||||
if( !GetModuleFileName( NULL, path, MAX_PATH ) ) {
|
||||
printf("Cannot install service: LastError = %d %s\n", GetLastError(), error_message() );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get a handle to the SCM database.
|
||||
SC_HANDLE scm = OpenSCManager(
|
||||
NULL, // local computer
|
||||
NULL, // ServicesActive database
|
||||
SC_MANAGER_ALL_ACCESS ); // full access rights
|
||||
if( !scm ) {
|
||||
printf( "OpenSCManager failed: LastError = %d %s\n", GetLastError(), error_message() );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the service
|
||||
SC_HANDLE service = CreateService(
|
||||
scm, // SCM database
|
||||
service_name, // name of service
|
||||
service_name, // service name to display
|
||||
SERVICE_ALL_ACCESS, // desired access
|
||||
SERVICE_WIN32_OWN_PROCESS, // service type
|
||||
SERVICE_DEMAND_START, // start type
|
||||
SERVICE_ERROR_NORMAL, // error control type
|
||||
path, // path to service's binary
|
||||
NULL, // no load ordering group
|
||||
NULL, // no tag identifier
|
||||
NULL, // no dependencies
|
||||
NULL, // LocalSystem account
|
||||
NULL); // no password
|
||||
if( !service ) {
|
||||
printf( "CreateService failed: LastError = %d %s\n", GetLastError(), error_message() );
|
||||
CloseServiceHandle( scm );
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseServiceHandle( service );
|
||||
CloseServiceHandle( scm );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Helper function to uninstall the service, used for debugging (for production, service is set up by installer).
|
||||
bool service_uninstall( char const* service_name ) {
|
||||
// Get a handle to the SCM database.
|
||||
SC_HANDLE scm = OpenSCManager(
|
||||
NULL, // local computer
|
||||
NULL, // ServicesActive database
|
||||
SC_MANAGER_ALL_ACCESS ); // full access rights
|
||||
if( !scm ) {
|
||||
printf( "OpenSCManager failed: LastError = %d %s\n", GetLastError(), error_message() );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get a handle to the service.
|
||||
SC_HANDLE service = OpenService(
|
||||
scm, // SCM database
|
||||
service_name, // name of service
|
||||
DELETE); // need delete access
|
||||
if( !service ) {
|
||||
printf( "OpenService failed: LastError = %d %s\n", GetLastError(), error_message() );
|
||||
CloseServiceHandle( scm );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete the service.
|
||||
if( !DeleteService( service ) ) {
|
||||
printf( "DeleteService failed: LastError = %d %s\n", GetLastError(), error_message() );
|
||||
CloseServiceHandle( service );
|
||||
CloseServiceHandle( scm );
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseServiceHandle( service );
|
||||
CloseServiceHandle( scm );
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Global state for the service
|
||||
struct {
|
||||
char const* service_name;
|
||||
service_main_func_t main_func;
|
||||
bool is_running;
|
||||
HANDLE stop_event;
|
||||
SERVICE_STATUS service_status;
|
||||
SERVICE_STATUS_HANDLE status_handle;
|
||||
HANDLE sleep_event;
|
||||
} g_service = { FALSE };
|
||||
|
||||
|
||||
// Thread proc for service. Just calls the user-provided main func
|
||||
DWORD WINAPI service_main_thread( LPVOID param ) {
|
||||
SERVICE_LOG_INFO( "Starting service main thread" );
|
||||
g_service.main_func( (bool*) param );
|
||||
SERVICE_LOG_INFO( "Terminating service main thread" );
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
// Helper function to report the status of the service. The service needs to
|
||||
// notify the windows service manager of its status correctly, otherwise it
|
||||
// will be considerered unresponsive and closed
|
||||
VOID report_service_status( DWORD current_state, DWORD exit_code, DWORD wait_hint ) {
|
||||
SERVICE_LOG_INFO( "Reporting service status, current_state=%u, exit_code=%u, wait_hint=%u", current_state, exit_code, wait_hint );
|
||||
static DWORD check_point = 1;
|
||||
|
||||
g_service.service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
||||
g_service.service_status.dwServiceSpecificExitCode = 0;
|
||||
g_service.service_status.dwCurrentState = current_state;
|
||||
g_service.service_status.dwWin32ExitCode = exit_code;
|
||||
g_service.service_status.dwWaitHint = wait_hint;
|
||||
|
||||
if( current_state == SERVICE_START_PENDING ) {
|
||||
g_service.service_status.dwControlsAccepted = 0;
|
||||
} else {
|
||||
g_service.service_status.dwControlsAccepted = SERVICE_ACCEPT_STOP;
|
||||
}
|
||||
|
||||
if( current_state == SERVICE_RUNNING || current_state == SERVICE_STOPPED ) {
|
||||
g_service.service_status.dwCheckPoint = 0;
|
||||
} else {
|
||||
g_service.service_status.dwCheckPoint = check_point++;
|
||||
}
|
||||
|
||||
// Report the status of the service to the SCM.
|
||||
SetServiceStatus( g_service.status_handle, &g_service.service_status );
|
||||
SERVICE_LOG_INFO( "Service statua reported" );
|
||||
}
|
||||
|
||||
|
||||
// Service control handler, used to signal the service to stop (if the user stops it
|
||||
// in the Services control panel)
|
||||
VOID WINAPI service_ctrl_handler( DWORD ctrl ) {
|
||||
SERVICE_LOG_INFO( "Service control handler, ctrl=%u", ctrl );
|
||||
switch( ctrl ) {
|
||||
case SERVICE_CONTROL_STOP: {
|
||||
SERVICE_LOG_INFO( "SERVICE_CONTROL_STOP" );
|
||||
SERVICE_LOG_INFO( "Reporting service status SERVICE_STOP_PENDING" );
|
||||
report_service_status( SERVICE_STOP_PENDING, NO_ERROR, 0 );
|
||||
|
||||
// Signal the service to stop.
|
||||
SERVICE_LOG_INFO( "Signaling service to stop" );
|
||||
SetEvent( g_service.stop_event );
|
||||
|
||||
SERVICE_LOG_INFO( "Reporting service status %u", g_service.service_status.dwCurrentState );
|
||||
report_service_status(g_service.service_status.dwCurrentState, NO_ERROR, 0);
|
||||
return;
|
||||
} break;
|
||||
|
||||
case SERVICE_CONTROL_INTERROGATE: {
|
||||
SERVICE_LOG_INFO( "SERVICE_CONTROL_INTERROGATE" );
|
||||
SERVICE_LOG_INFO( "Reporting service status SERVICE_STOP_PENDING" );
|
||||
report_service_status( g_service.service_status.dwCurrentState, NO_ERROR, 0 );
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// TODO: better logging once we have a final solution for logging (another jira ticket)
|
||||
|
||||
// Main function of the service. Starts a thread to run the user-provided service function
|
||||
VOID WINAPI service_proc( DWORD argc, LPSTR *argv ) {
|
||||
SERVICE_LOG_INFO( "Starting service proc" );
|
||||
|
||||
// Register the handler function for the service
|
||||
g_service.status_handle = RegisterServiceCtrlHandler( g_service.service_name, service_ctrl_handler );
|
||||
if( !g_service.status_handle ) {
|
||||
SERVICE_LOG_ERROR( "Failed to register the service control handler" );
|
||||
return;
|
||||
}
|
||||
|
||||
// Report initial status to the SCM
|
||||
report_service_status( SERVICE_START_PENDING, NO_ERROR, 3000 );
|
||||
|
||||
// Create the event used to signal the service to stop
|
||||
g_service.stop_event = CreateEvent(
|
||||
NULL, // default security attributes
|
||||
TRUE, // manual reset event
|
||||
FALSE, // not signaled
|
||||
NULL); // no name
|
||||
if ( g_service.stop_event == NULL ) {
|
||||
SERVICE_LOG_ERROR( "No event" );
|
||||
report_service_status( SERVICE_STOPPED, NO_ERROR, 0 );
|
||||
return;
|
||||
}
|
||||
|
||||
// Start the thread which runs the user-provided main function
|
||||
SERVICE_LOG_INFO( "Creating service main thread" );
|
||||
HANDLE thread = CreateThread(
|
||||
NULL, // no security attribute
|
||||
0, // default stack size
|
||||
service_main_thread, // thread proc
|
||||
&g_service.is_running, // thread parameter
|
||||
0, // not suspended
|
||||
NULL ); // returns thread ID
|
||||
|
||||
// Report to the SCM that the service is now up and running
|
||||
report_service_status( SERVICE_RUNNING, NO_ERROR, 0 );
|
||||
SERVICE_LOG_INFO( "Service started" );
|
||||
|
||||
// Wait until the service is stopped
|
||||
WaitForSingleObject( g_service.stop_event, INFINITE );
|
||||
SERVICE_LOG_INFO( "Service stop requested" );
|
||||
|
||||
// Flag the user-provided main func to exit
|
||||
g_service.is_running = false;
|
||||
SetEvent( g_service.sleep_event );
|
||||
|
||||
// Wait until user-provided main func has exited
|
||||
SERVICE_LOG_INFO( "Wait for service main thread to exit" );
|
||||
WaitForSingleObject( thread, INFINITE );
|
||||
SERVICE_LOG_INFO( "Service main thread exited" );
|
||||
|
||||
// Report to the SCM that the service has stopped
|
||||
report_service_status( SERVICE_STOPPED, NO_ERROR, 0 );
|
||||
|
||||
SERVICE_LOG_INFO( "Service proc terminated" );
|
||||
}
|
||||
|
||||
|
||||
// Run the service with the specified name, and invoke the main_func function
|
||||
// The main_func provided should exit if is_running becomes false
|
||||
void service_run( char const* service_name, service_main_func_t main_func ) {
|
||||
SetLastError( 0 );
|
||||
SERVICE_LOG_INFO( "Starting service" );
|
||||
|
||||
// Initialize global state
|
||||
g_service.service_name = service_name;
|
||||
g_service.main_func = main_func;
|
||||
g_service.is_running = true;
|
||||
g_service.sleep_event = CreateEvent(
|
||||
NULL, // default security attributes
|
||||
TRUE, // manual reset event
|
||||
FALSE, // not signaled
|
||||
NULL); // no name
|
||||
|
||||
// Launch service
|
||||
SERVICE_TABLE_ENTRY dispatch_table[] = {
|
||||
{ (LPSTR) service_name, (LPSERVICE_MAIN_FUNCTION) service_proc },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
if( !StartServiceCtrlDispatcher( dispatch_table ) ) {
|
||||
SERVICE_LOG_LAST_ERROR( "StartServiceCtrlDispatcher" );
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
CloseHandle( g_service.sleep_event );
|
||||
SERVICE_LOG_INFO( "Service stopped" );
|
||||
}
|
||||
|
||||
|
||||
// Sleeps until service_cancel_sleep is called, either manually by the user or
|
||||
// because the serice received a stop request
|
||||
void service_sleep( void ) {
|
||||
ResetEvent( g_service.sleep_event );
|
||||
WaitForSingleObject( g_service.sleep_event, INFINITE );
|
||||
}
|
||||
|
||||
|
||||
// Can be used to cancel a pending service sleep
|
||||
void service_cancel_sleep( void ) {
|
||||
SetEvent( g_service.sleep_event );
|
||||
}
|
||||
|
||||
|
||||
|
||||
#endif /* SERVICE_IMPLEMENTATION */
|
@ -1,546 +0,0 @@
|
||||
/*
|
||||
------------------------------------------------------------------------------
|
||||
Licensing information can be found at the end of the file.
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
testfw.h - v1.1 - Basic test framwework for C/C++.
|
||||
|
||||
Do this:
|
||||
#define TESTFW_IMPLEMENTATION
|
||||
before you include this file in *one* C/C++ file to create the implementation.
|
||||
*/
|
||||
|
||||
#ifndef testfw_h
|
||||
#define testfw_h
|
||||
|
||||
#define TESTFW_INIT() testfw_init()
|
||||
#define TESTFW_SUMMARY() testfw_summary( __FILE__, __func__, __LINE__ )
|
||||
#if defined( _WIN32 ) && !defined( TESTFW_NO_SEH )
|
||||
#define TESTFW_TEST_BEGIN( desc ) testfw_test_begin( desc, __FILE__, __func__, __LINE__ ); __try {
|
||||
#define TESTFW_TEST_END() } __except( EXCEPTION_EXECUTE_HANDLER ) { testfw_exception( GetExceptionCode() ); } \
|
||||
testfw_test_end( __FILE__, __func__, __LINE__ )
|
||||
#else
|
||||
#define TESTFW_TEST_BEGIN( desc ) testfw_test_begin( desc, __FILE__, __func__, __LINE__ )
|
||||
#define TESTFW_TEST_END() testfw_test_end( __FILE__, __func__, __LINE__ )
|
||||
#endif
|
||||
#define TESTFW_EXPECTED( expression ) testfw_expected( (expression) ? 1 : 0, #expression, __FILE__, __func__, __LINE__ )
|
||||
|
||||
void testfw_init();
|
||||
int testfw_summary( char const* filename, char const* funcname, int line );
|
||||
void testfw_test_begin( char const* desc, char const* filename, char const* funcname, int line );
|
||||
void testfw_test_end( char const* filename, char const* funcname, int line );
|
||||
void testfw_expected( int expression, char const* expression_str, char const* filename, char const* funcname, int line );
|
||||
void testfw_print_test_desc();
|
||||
void testfw_print_failure( char const* filename, int line );
|
||||
void testfw_assertion_count_inc();
|
||||
void testfw_current_test_assertion_failed();
|
||||
#if defined( _WIN32 ) && !defined( TESTFW_NO_SEH )
|
||||
void testfw_exception( unsigned int exception_code );
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* testfw_h */
|
||||
|
||||
|
||||
|
||||
#ifdef TESTFW_IMPLEMENTATION
|
||||
#undef TESTFW_IMPLEMENTATION
|
||||
|
||||
#define _CRT_NONSTDC_NO_DEPRECATE
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning( push )
|
||||
#pragma warning( disable: 4619 ) // pragma warning : there is no warning number 'number'
|
||||
#pragma warning( disable: 4668 ) // 'symbol' is not defined as a preprocessor macro, replacing with '0'
|
||||
#include <crtdbg.h>
|
||||
#pragma warning( pop )
|
||||
#endif /* _WIN32 */
|
||||
|
||||
#ifndef TESTFW_PRINTF
|
||||
#define _CRT_NONSTDC_NO_DEPRECATE
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#include <stdio.h>
|
||||
|
||||
#define TESTFW_PRINTF printf
|
||||
#endif
|
||||
|
||||
#ifdef TESTFW_NO_ANSI
|
||||
#define TESTFW_ANSI_BLACK ""
|
||||
#define TESTFW_ANSI_BLUE ""
|
||||
#define TESTFW_ANSI_GREEN ""
|
||||
#define TESTFW_ANSI_CYAN ""
|
||||
#define TESTFW_ANSI_RED ""
|
||||
#define TESTFW_ANSI_MAGENTA ""
|
||||
#define TESTFW_ANSI_BROWN ""
|
||||
#define TESTFW_ANSI_LIGHT_GREY ""
|
||||
#define TESTFW_ANSI_GREY ""
|
||||
#define TESTFW_ANSI_LIGHT_BLUE ""
|
||||
#define TESTFW_ANSI_LIGHT_GREEN ""
|
||||
#define TESTFW_ANSI_LIGHT_CYAN ""
|
||||
#define TESTFW_ANSI_LIGHT_RED ""
|
||||
#define TESTFW_ANSI_LIGHT_MAGENTA ""
|
||||
#define TESTFW_ANSI_YELLOW ""
|
||||
#define TESTFW_ANSI_WHITE ""
|
||||
#define TESTFW_ANSI_RESET ""
|
||||
#else
|
||||
#ifndef TESTFW_ANSI_BLACK
|
||||
#define TESTFW_ANSI_BLACK "\x1b[30m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_BLUE
|
||||
#define TESTFW_ANSI_BLUE "\x1b[34m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_GREEN
|
||||
#define TESTFW_ANSI_GREEN "\x1b[32m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_CYAN
|
||||
#define TESTFW_ANSI_CYAN "\x1b[36m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_RED
|
||||
#define TESTFW_ANSI_RED "\x1b[31m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_MAGENTA
|
||||
#define TESTFW_ANSI_MAGENTA "\x1b[35m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_BROWN
|
||||
#define TESTFW_ANSI_BROWN "\x1b[33m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_LIGHT_GREY
|
||||
#define TESTFW_ANSI_LIGHT_GREY "\x1b[37m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_GREY
|
||||
#define TESTFW_ANSI_GREY "\x1b[30;1m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_LIGHT_BLUE
|
||||
#define TESTFW_ANSI_LIGHT_BLUE "\x1b[34;1m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_LIGHT_GREEN
|
||||
#define TESTFW_ANSI_LIGHT_GREEN "\x1b[32;1m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_LIGHT_CYAN
|
||||
#define TESTFW_ANSI_LIGHT_CYAN "\x1b[36;1m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_LIGHT_RED
|
||||
#define TESTFW_ANSI_LIGHT_RED "\x1b[31;1m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_LIGHT_MAGENTA
|
||||
#define TESTFW_ANSI_LIGHT_MAGENTA "\x1b[35;1m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_YELLOW
|
||||
#define TESTFW_ANSI_YELLOW "\x1b[33;1m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_WHITE
|
||||
#define TESTFW_ANSI_WHITE "\x1b[37;1m"
|
||||
#endif
|
||||
#ifndef TESTFW_ANSI_RESET
|
||||
#define TESTFW_ANSI_RESET "\x1b[0m"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
struct testfw_internal_current_test_state_t
|
||||
{
|
||||
int is_active;
|
||||
char const* file;
|
||||
char const* func;
|
||||
int line;
|
||||
char const* desc;
|
||||
int desc_printed;
|
||||
int counted_as_failed;
|
||||
};
|
||||
|
||||
|
||||
static struct
|
||||
{
|
||||
int tests_total;
|
||||
int tests_failed;
|
||||
int assertions_total;
|
||||
int assertions_failed;
|
||||
testfw_internal_current_test_state_t current_test;
|
||||
#if defined( _WIN32 ) && defined( _DEBUG )
|
||||
int total_leaks;
|
||||
#endif
|
||||
} testfw_internal_state = {};
|
||||
|
||||
|
||||
static void testfw_internal_print_progress_divider( char ch, int fail, int total )
|
||||
{
|
||||
int width = 79;
|
||||
int first = (int)( ( width * fail ) / total );
|
||||
int second = width - first;
|
||||
if( fail > 0 && first == 0 )
|
||||
{
|
||||
++first;
|
||||
--second;
|
||||
}
|
||||
TESTFW_PRINTF( "%s", TESTFW_ANSI_LIGHT_RED );
|
||||
for( int i = 0; i < first; ++i ) TESTFW_PRINTF( "%c", ch );
|
||||
TESTFW_PRINTF( "%s", TESTFW_ANSI_LIGHT_GREEN );
|
||||
for( int i = 0; i < second; ++i ) TESTFW_PRINTF( "%c", ch );
|
||||
TESTFW_PRINTF( "%s", TESTFW_ANSI_RESET );
|
||||
TESTFW_PRINTF( "\n" );
|
||||
}
|
||||
|
||||
|
||||
#if defined( _WIN32 ) && defined( _DEBUG )
|
||||
|
||||
static int testfw_internal_debug_report_hook( int report_type, char* message, int* return_value )
|
||||
{
|
||||
*return_value = 0; // Don't break to debugger
|
||||
|
||||
if( stricmp( message, "Detected memory leaks!\n" ) == 0 )
|
||||
{
|
||||
TESTFW_PRINTF( "-------------------------------------------------------------------------------\n" );
|
||||
TESTFW_PRINTF( "%sMEMORY CHECKS FAILED:%s Detected memory leaks\n", TESTFW_ANSI_LIGHT_RED,
|
||||
TESTFW_ANSI_RESET );
|
||||
return 1; // Tell CRT not to print the message
|
||||
}
|
||||
else if( stricmp( message, "Dumping objects ->\n" ) == 0 )
|
||||
{
|
||||
TESTFW_PRINTF( "-------------------------------------------------------------------------------\n" );
|
||||
TESTFW_PRINTF( "\n" );
|
||||
return 1; // Tell CRT not to print the message
|
||||
}
|
||||
else if( stricmp( message, "Object dump complete.\n" ) == 0 )
|
||||
{
|
||||
TESTFW_PRINTF( "\n" );
|
||||
_CrtMemState state;
|
||||
_CrtMemCheckpoint( &state );
|
||||
|
||||
static char const* const block_use_names[ 5 ] = { "Free", "Normal", "CRT", "Ignore", "Client", };
|
||||
for( unsigned int i = 0; i < 5; ++i )
|
||||
{
|
||||
if( i != 2 && state.lCounts[ i ] )
|
||||
TESTFW_PRINTF( "%lld bytes in %lld %hs Blocks.\n", (long long) state.lSizes[ i ],
|
||||
(long long) state.lCounts[ i ], block_use_names[ i ] );
|
||||
}
|
||||
|
||||
TESTFW_PRINTF( "\n" );
|
||||
TESTFW_PRINTF( "High water mark: %lld bytes.\n", (long long)state.lHighWaterCount);
|
||||
TESTFW_PRINTF( "All allocations: %lld bytes.\n", (long long)state.lTotalCount);
|
||||
TESTFW_PRINTF( "\n" );
|
||||
TESTFW_PRINTF( "===============================================================================\n" );
|
||||
TESTFW_PRINTF( "%sMEMORY LEAKS: %d%s\n\n", TESTFW_ANSI_LIGHT_RED, testfw_internal_state.total_leaks, TESTFW_ANSI_RESET );
|
||||
return 1; // Tell CRT not to print the message
|
||||
}
|
||||
else if( strnicmp( message, " Data: <", 8 ) == 0 )
|
||||
{
|
||||
++testfw_internal_state.total_leaks;
|
||||
return 1; // Tell CRT not to print the message
|
||||
}
|
||||
|
||||
return 0; // Tell CRT it can print the message, as we didn't handle it
|
||||
}
|
||||
|
||||
#endif /* _WIN32 && _DEBUG */
|
||||
|
||||
|
||||
void testfw_init()
|
||||
{
|
||||
memset( &testfw_internal_state, 0, sizeof( testfw_internal_state ) );
|
||||
|
||||
#ifdef _WIN32
|
||||
int flag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG ); // Get current flag
|
||||
flag ^= _CRTDBG_LEAK_CHECK_DF; // Turn on leak-checking bit
|
||||
_CrtSetDbgFlag( flag ); // Set flag to the new value
|
||||
_CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE );
|
||||
_CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDOUT );
|
||||
_CrtSetReportHook2( _CRT_RPTHOOK_INSTALL, testfw_internal_debug_report_hook );
|
||||
#endif /* _WIN32 */
|
||||
}
|
||||
|
||||
|
||||
static int testfw_internal_must_be_in_test()
|
||||
{
|
||||
if( !testfw_internal_state.current_test.is_active )
|
||||
TESTFW_PRINTF( "\n\n%sEXPECTED TO BE IN AN ACTIVE TEST, BUT NO TEST IS CURRENTLY ACTIVE.%s\n\n",
|
||||
TESTFW_ANSI_LIGHT_RED, TESTFW_ANSI_RESET );
|
||||
return !testfw_internal_state.current_test.is_active;
|
||||
}
|
||||
|
||||
|
||||
static int testfw_internal_can_not_be_in_test()
|
||||
{
|
||||
if( testfw_internal_state.current_test.is_active )
|
||||
TESTFW_PRINTF( "\n\n%sEXPECTED TO NOT BE IN AN ACTIVE TEST, BUT A TEST IS CURRENTLY ACTIVE.%s\n\n",
|
||||
TESTFW_ANSI_LIGHT_RED, TESTFW_ANSI_RESET );
|
||||
return testfw_internal_state.current_test.is_active;
|
||||
}
|
||||
|
||||
|
||||
void testfw_print_test_desc()
|
||||
{
|
||||
if( testfw_internal_must_be_in_test() ) return;
|
||||
|
||||
if( !testfw_internal_state.current_test.desc_printed )
|
||||
{
|
||||
testfw_internal_state.current_test.desc_printed = 1;
|
||||
|
||||
TESTFW_PRINTF( "\n" );
|
||||
TESTFW_PRINTF( "-------------------------------------------------------------------------------\n" );
|
||||
TESTFW_PRINTF( "%s\n", testfw_internal_state.current_test.desc ? testfw_internal_state.current_test.desc :
|
||||
"<NO DESCRIPTION>" );
|
||||
TESTFW_PRINTF( "-------------------------------------------------------------------------------\n" );
|
||||
TESTFW_PRINTF( "%s%s(%d): %s%s\n", TESTFW_ANSI_GREY, testfw_internal_state.current_test.file,
|
||||
testfw_internal_state.current_test.line, TESTFW_ANSI_RESET, testfw_internal_state.current_test.func );
|
||||
|
||||
TESTFW_PRINTF( "%s", TESTFW_ANSI_GREY );
|
||||
TESTFW_PRINTF( "...............................................................................\n" );
|
||||
TESTFW_PRINTF( "%s", TESTFW_ANSI_RESET );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int testfw_summary( char const* filename, char const* funcname, int line )
|
||||
{
|
||||
if( testfw_internal_can_not_be_in_test() ) return -1;
|
||||
|
||||
TESTFW_PRINTF( "\n" );
|
||||
testfw_internal_print_progress_divider( '=', testfw_internal_state.tests_failed, testfw_internal_state.tests_total );
|
||||
if( testfw_internal_state.tests_failed == 0 && testfw_internal_state.assertions_failed == 0 )
|
||||
{
|
||||
TESTFW_PRINTF( "%sAll tests passed%s (%d assertions in %d test cases)\n", TESTFW_ANSI_LIGHT_GREEN,
|
||||
TESTFW_ANSI_RESET, testfw_internal_state.assertions_total, testfw_internal_state.tests_total );
|
||||
}
|
||||
else
|
||||
{
|
||||
TESTFW_PRINTF( "test cases: %4d |%s %4d passed %s|%s %4d failed%s\n", testfw_internal_state.tests_total,
|
||||
TESTFW_ANSI_LIGHT_GREEN, testfw_internal_state.tests_total - testfw_internal_state.tests_failed,
|
||||
TESTFW_ANSI_RESET, TESTFW_ANSI_LIGHT_RED, testfw_internal_state.tests_failed, TESTFW_ANSI_RESET );
|
||||
TESTFW_PRINTF( "assertions: %4d |%s %4d passed %s|%s %4d failed%s\n", testfw_internal_state.assertions_total,
|
||||
TESTFW_ANSI_LIGHT_GREEN, testfw_internal_state.assertions_total - testfw_internal_state.assertions_failed,
|
||||
TESTFW_ANSI_RESET, TESTFW_ANSI_LIGHT_RED, testfw_internal_state.assertions_failed, TESTFW_ANSI_RESET );
|
||||
}
|
||||
|
||||
TESTFW_PRINTF( "\n\n" );
|
||||
|
||||
#if defined( _WIN32 ) && defined( _DEBUG )
|
||||
int result = _CrtDumpMemoryLeaks();
|
||||
testfw_internal_state.tests_failed += result ? 1 : 0;
|
||||
#endif
|
||||
|
||||
return testfw_internal_state.tests_failed;
|
||||
}
|
||||
|
||||
|
||||
void testfw_test_begin( char const* desc, char const* filename, char const* funcname, int line )
|
||||
{
|
||||
if( testfw_internal_can_not_be_in_test() ) return;
|
||||
|
||||
memset( &testfw_internal_state.current_test, 0, sizeof( testfw_internal_state.current_test ) );
|
||||
testfw_internal_state.current_test.is_active = 1;
|
||||
testfw_internal_state.current_test.file = filename;
|
||||
testfw_internal_state.current_test.func = funcname;
|
||||
testfw_internal_state.current_test.line = line;
|
||||
testfw_internal_state.current_test.desc = desc;
|
||||
testfw_internal_state.current_test.desc_printed = 0;
|
||||
testfw_internal_state.current_test.counted_as_failed = 0;
|
||||
|
||||
++testfw_internal_state.tests_total;
|
||||
}
|
||||
|
||||
|
||||
void testfw_test_end( char const* filename, char const* funcname, int line )
|
||||
{
|
||||
if( testfw_internal_must_be_in_test() ) return;
|
||||
|
||||
memset( &testfw_internal_state.current_test, 0, sizeof( testfw_internal_state.current_test ) );
|
||||
}
|
||||
|
||||
|
||||
#if defined( _WIN32 ) && !defined( TESTFW_NO_SEH )
|
||||
void testfw_exception( unsigned int exception_code )
|
||||
{
|
||||
if( testfw_internal_must_be_in_test() ) return;
|
||||
|
||||
if( !testfw_internal_state.current_test.counted_as_failed )
|
||||
{
|
||||
testfw_internal_state.current_test.counted_as_failed = 1;
|
||||
++testfw_internal_state.tests_failed;
|
||||
}
|
||||
|
||||
char exception_str[ 64 ];
|
||||
switch( exception_code )
|
||||
{
|
||||
case EXCEPTION_ACCESS_VIOLATION:
|
||||
strcpy( exception_str, "EXCEPTION_ACCESS_VIOLATION" );
|
||||
break;
|
||||
case EXCEPTION_DATATYPE_MISALIGNMENT:
|
||||
strcpy( exception_str, "EXCEPTION_DATATYPE_MISALIGNMENT" );
|
||||
break;
|
||||
case EXCEPTION_BREAKPOINT:
|
||||
strcpy( exception_str, "EXCEPTION_BREAKPOINT" );
|
||||
break;
|
||||
case EXCEPTION_SINGLE_STEP:
|
||||
strcpy( exception_str, "EXCEPTION_SINGLE_STEP" );
|
||||
break;
|
||||
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
|
||||
strcpy( exception_str, "EXCEPTION_ARRAY_BOUNDS_EXCEEDED" );
|
||||
break;
|
||||
case EXCEPTION_FLT_DENORMAL_OPERAND:
|
||||
strcpy( exception_str, "EXCEPTION_FLT_DENORMAL_OPERAND" );
|
||||
break;
|
||||
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
|
||||
strcpy( exception_str, "EXCEPTION_FLT_DIVIDE_BY_ZERO" );
|
||||
break;
|
||||
case EXCEPTION_FLT_INEXACT_RESULT:
|
||||
strcpy( exception_str, "EXCEPTION_FLT_INEXACT_RESULT" );
|
||||
break;
|
||||
case EXCEPTION_FLT_INVALID_OPERATION:
|
||||
strcpy( exception_str, "EXCEPTION_FLT_INVALID_OPERATION" );
|
||||
break;
|
||||
case EXCEPTION_FLT_OVERFLOW:
|
||||
strcpy( exception_str, "EXCEPTION_FLT_OVERFLOW" );
|
||||
break;
|
||||
case EXCEPTION_FLT_STACK_CHECK:
|
||||
strcpy( exception_str, "EXCEPTION_FLT_STACK_CHECK" );
|
||||
break;
|
||||
case EXCEPTION_FLT_UNDERFLOW:
|
||||
strcpy( exception_str, "EXCEPTION_FLT_UNDERFLOW" );
|
||||
break;
|
||||
case EXCEPTION_INT_DIVIDE_BY_ZERO:
|
||||
strcpy( exception_str, "EXCEPTION_INT_DIVIDE_BY_ZERO" );
|
||||
break;
|
||||
case EXCEPTION_INT_OVERFLOW:
|
||||
strcpy( exception_str, "EXCEPTION_INT_OVERFLOW" );
|
||||
break;
|
||||
case EXCEPTION_PRIV_INSTRUCTION:
|
||||
strcpy( exception_str, "EXCEPTION_PRIV_INSTRUCTION" );
|
||||
break;
|
||||
case EXCEPTION_IN_PAGE_ERROR:
|
||||
strcpy( exception_str, "EXCEPTION_IN_PAGE_ERROR" );
|
||||
break;
|
||||
case EXCEPTION_ILLEGAL_INSTRUCTION:
|
||||
strcpy( exception_str, "EXCEPTION_ILLEGAL_INSTRUCTION" );
|
||||
break;
|
||||
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
|
||||
strcpy( exception_str, "EXCEPTION_NONCONTINUABLE_EXCEPTION" );
|
||||
break;
|
||||
case EXCEPTION_STACK_OVERFLOW:
|
||||
strcpy( exception_str, "EXCEPTION_STACK_OVERFLOW" );
|
||||
break;
|
||||
case EXCEPTION_INVALID_DISPOSITION:
|
||||
strcpy( exception_str, "EXCEPTION_INVALID_DISPOSITION" );
|
||||
break;
|
||||
case EXCEPTION_GUARD_PAGE:
|
||||
strcpy( exception_str, "EXCEPTION_GUARD_PAGE" );
|
||||
break;
|
||||
case EXCEPTION_INVALID_HANDLE:
|
||||
strcpy( exception_str, "EXCEPTION_INVALID_HANDLE" );
|
||||
break;
|
||||
default:
|
||||
sprintf( exception_str, "%X", exception_code );
|
||||
}
|
||||
|
||||
testfw_print_test_desc();
|
||||
TESTFW_PRINTF( "\n%s%s(%d): %sFAILED:%s\n", TESTFW_ANSI_GREY, testfw_internal_state.current_test.file, testfw_internal_state.current_test.line, TESTFW_ANSI_LIGHT_RED,
|
||||
TESTFW_ANSI_RESET );
|
||||
TESTFW_PRINTF( "\n %sEXCEPTION( %s%s%s )%s\n", TESTFW_ANSI_CYAN, TESTFW_ANSI_WHITE, exception_str,
|
||||
TESTFW_ANSI_CYAN, TESTFW_ANSI_RESET );
|
||||
}
|
||||
#endif
|
||||
|
||||
void testfw_current_test_assertion_failed()
|
||||
{
|
||||
if( testfw_internal_must_be_in_test() ) return;
|
||||
|
||||
++testfw_internal_state.assertions_failed;
|
||||
|
||||
if( !testfw_internal_state.current_test.counted_as_failed )
|
||||
{
|
||||
testfw_internal_state.current_test.counted_as_failed = 1;
|
||||
++testfw_internal_state.tests_failed;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void testfw_assertion_count_inc()
|
||||
{
|
||||
if( testfw_internal_must_be_in_test() ) return;
|
||||
|
||||
++testfw_internal_state.assertions_total;
|
||||
}
|
||||
|
||||
|
||||
void testfw_print_failure( char const* filename, int line )
|
||||
{
|
||||
TESTFW_PRINTF( "\n%s%s(%d): %sFAILED:%s\n", TESTFW_ANSI_GREY, filename, line, TESTFW_ANSI_LIGHT_RED,
|
||||
TESTFW_ANSI_RESET );
|
||||
}
|
||||
|
||||
|
||||
void testfw_expected( int expression, char const* expression_str, char const* filename,
|
||||
char const* funcname, int line )
|
||||
{
|
||||
if( testfw_internal_must_be_in_test() ) return;
|
||||
|
||||
testfw_assertion_count_inc();
|
||||
|
||||
if( !expression )
|
||||
{
|
||||
testfw_current_test_assertion_failed();
|
||||
testfw_print_test_desc();
|
||||
testfw_print_failure( filename, line );
|
||||
TESTFW_PRINTF( "\n %sTESTFW_EXPECTED( %s%s%s )%s\n", TESTFW_ANSI_CYAN, TESTFW_ANSI_WHITE, expression_str,
|
||||
TESTFW_ANSI_CYAN, TESTFW_ANSI_RESET );
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* TESTFW_IMPLEMENTATION */
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
This software is available under 2 licenses - you may choose the one you like.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
ALTERNATIVE A - MIT License
|
||||
|
||||
Copyright (c) 2019 Mattias Gustavsson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
ALTERNATIVE B - Public Domain (www.unlicense.org)
|
||||
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
||||
software, either in source code form or as a compiled binary, for any purpose,
|
||||
commercial or non-commercial, and by any means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this
|
||||
software dedicate any and all copyright interest in the software to the public
|
||||
domain. We make this dedication for the benefit of the public at large and to
|
||||
the detriment of our heirs and successors. We intend this dedication to be an
|
||||
overt act of relinquishment in perpetuity of all present and future rights to
|
||||
this software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
@ -1,241 +0,0 @@
|
||||
#define _CRT_SECURE_NO_WARNINGS
|
||||
#define _CRT_NONSTDC_NO_WARNINGS
|
||||
#if defined( _WIN32 ) && defined( _DEBUG )
|
||||
#include <crtdbg.h>
|
||||
#endif
|
||||
#include "testfw.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <windows.h>
|
||||
|
||||
void disable_log( char const*, ... ) { };
|
||||
#define IPC_LOG_INFO disable_log
|
||||
#define IPC_LOG_ERROR disable_log
|
||||
#define IPC_LOG_LAST_ERROR disable_log
|
||||
#include "ipc.h"
|
||||
|
||||
bool pipe_exists( const char* pipe_name );
|
||||
|
||||
void test_fw_ok() {
|
||||
TESTFW_TEST_BEGIN( "Checking that test framework is ok" );
|
||||
TESTFW_EXPECTED( true );
|
||||
TESTFW_TEST_END();
|
||||
}
|
||||
|
||||
|
||||
void ipc_tests() {
|
||||
{
|
||||
TESTFW_TEST_BEGIN( "Check that IPC server is not already running" );
|
||||
TESTFW_EXPECTED( !pipe_exists( "test_pipe" ) );
|
||||
TESTFW_TEST_END();
|
||||
}
|
||||
|
||||
{
|
||||
TESTFW_TEST_BEGIN( "Can start IPC server" );
|
||||
ipc_server_t* server = ipc_server_start( "test_pipe",
|
||||
[]( char const*, void*, char*, size_t ) { }, NULL );
|
||||
TESTFW_EXPECTED( server != NULL );
|
||||
ipc_server_stop( server );
|
||||
TESTFW_TEST_END();
|
||||
}
|
||||
|
||||
{
|
||||
TESTFW_TEST_BEGIN( "Can stop IPC server" );
|
||||
ipc_server_t* server = ipc_server_start( "test_pipe",
|
||||
[]( char const*, void*, char*, size_t ) { }, NULL );
|
||||
TESTFW_EXPECTED( server != NULL );
|
||||
ipc_server_stop( server );
|
||||
TESTFW_EXPECTED( !pipe_exists( "test_pipe" ) );
|
||||
TESTFW_TEST_END();
|
||||
}
|
||||
|
||||
{
|
||||
TESTFW_TEST_BEGIN( "Can connect multiple IPC clients" );
|
||||
ipc_server_t* server = ipc_server_start( "test_pipe",
|
||||
[]( char const*, void*, char*, size_t ) { }, NULL );
|
||||
TESTFW_EXPECTED( server != NULL );
|
||||
ipc_client_t* clients[ 32 ];
|
||||
for( int i = 0; i < sizeof( clients ) / sizeof( *clients ); ++i ) {
|
||||
clients[ i ] = ipc_client_connect( "test_pipe" );
|
||||
TESTFW_EXPECTED( clients[ i ] );
|
||||
}
|
||||
for( int i = 0; i < sizeof( clients ) / sizeof( *clients ); ++i ) {
|
||||
ipc_client_disconnect( clients[ i ] );
|
||||
}
|
||||
ipc_server_stop( server );
|
||||
TESTFW_TEST_END();
|
||||
}
|
||||
|
||||
{
|
||||
TESTFW_TEST_BEGIN( "Can connect multiple IPC clients multiple times" );
|
||||
ipc_server_t* server = ipc_server_start( "test_pipe",
|
||||
[]( char const*, void*, char*, size_t ) { }, NULL );
|
||||
TESTFW_EXPECTED( server != NULL );
|
||||
for( int j = 0; j < 10; ++j ) {
|
||||
ipc_client_t* clients[ 32 ];
|
||||
for( int i = 0; i < sizeof( clients ) / sizeof( *clients ); ++i ) {
|
||||
clients[ i ] = ipc_client_connect( "test_pipe" );
|
||||
TESTFW_EXPECTED( clients[ i ] );
|
||||
}
|
||||
for( int i = 0; i < sizeof( clients ) / sizeof( *clients ); ++i ) {
|
||||
ipc_client_disconnect( clients[ i ] );
|
||||
}
|
||||
}
|
||||
ipc_server_stop( server );
|
||||
TESTFW_TEST_END();
|
||||
}
|
||||
|
||||
{
|
||||
TESTFW_TEST_BEGIN( "Can connect IPC client" );
|
||||
ipc_server_t* server = ipc_server_start( "test_pipe",
|
||||
[]( char const*, void*, char*, size_t ) { }, NULL );
|
||||
TESTFW_EXPECTED( server != NULL );
|
||||
ipc_client_t* client = ipc_client_connect( "test_pipe" );
|
||||
TESTFW_EXPECTED( client != NULL );
|
||||
ipc_client_disconnect( client );
|
||||
ipc_server_stop( server );
|
||||
TESTFW_TEST_END();
|
||||
}
|
||||
|
||||
{
|
||||
TESTFW_TEST_BEGIN( "Can send IPC message from client to server" );
|
||||
bool message_received = false;
|
||||
ipc_server_t* server = ipc_server_start( "test_pipe",
|
||||
[]( char const* message, void* user_data, char*, size_t ) {
|
||||
if( !message ) return; // client disconnect
|
||||
bool* message_received = (bool*) user_data;
|
||||
*message_received = true;
|
||||
TESTFW_EXPECTED( strcmp( message, "Test message" ) == 0 );
|
||||
}, &message_received );
|
||||
TESTFW_EXPECTED( server != NULL );
|
||||
ipc_client_t* client = ipc_client_connect( "test_pipe" );
|
||||
TESTFW_EXPECTED( client != NULL );
|
||||
TESTFW_EXPECTED( ipc_client_send( client, "Test message" ) == true );
|
||||
char temp[ IPC_MESSAGE_MAX_LENGTH ];
|
||||
int size = 0;
|
||||
ipc_client_receive( client, temp, sizeof( temp ), &size );
|
||||
TESTFW_EXPECTED( message_received == true );
|
||||
|
||||
ipc_client_disconnect( client );
|
||||
ipc_server_stop( server );
|
||||
TESTFW_TEST_END();
|
||||
}
|
||||
|
||||
{
|
||||
TESTFW_TEST_BEGIN( "Can receive IPC response from server" );
|
||||
ipc_server_t* server = ipc_server_start( "test_pipe",
|
||||
[]( char const* message, void* user_data, char* response, size_t ) {
|
||||
if( !message ) return; // client disconnect
|
||||
strcpy( response, "Test response" );
|
||||
}, NULL );
|
||||
TESTFW_EXPECTED( server != NULL );
|
||||
ipc_client_t* client = ipc_client_connect( "test_pipe" );
|
||||
TESTFW_EXPECTED( client != NULL );
|
||||
TESTFW_EXPECTED( ipc_client_send( client, "Test message" ) == true );
|
||||
char response[ IPC_MESSAGE_MAX_LENGTH ];
|
||||
int size = 0;
|
||||
TESTFW_EXPECTED( ipc_client_receive( client, response, sizeof( response ), &size ) == IPC_RECEIVE_STATUS_DONE );
|
||||
TESTFW_EXPECTED( size == strlen( "Test response" ) + 1 );
|
||||
TESTFW_EXPECTED( strcmp( response, "Test response" ) == 0 );
|
||||
ipc_client_disconnect( client );
|
||||
ipc_server_stop( server );
|
||||
TESTFW_TEST_END();
|
||||
}
|
||||
|
||||
{
|
||||
TESTFW_TEST_BEGIN( "Can send and receive long IPC messages" );
|
||||
ipc_server_t* server = ipc_server_start( "test_pipe",
|
||||
[]( char const* message, void* user_data, char* response, size_t capacity ) {
|
||||
if( !message ) return; // client disconnect
|
||||
char expected_message[ IPC_MESSAGE_MAX_LENGTH ];
|
||||
for( int i = 0; i < IPC_MESSAGE_MAX_LENGTH - 1; ++i ) {
|
||||
expected_message[ i ] = 'A' + ( i % ( 'Z' - 'A' + 1 ) );
|
||||
}
|
||||
expected_message[ IPC_MESSAGE_MAX_LENGTH - 1 ] = '\0';
|
||||
TESTFW_EXPECTED( strcmp( message, expected_message ) == 0 );
|
||||
for( int i = 0; i < (int) capacity - 1; ++i ) {
|
||||
response[ i ] = 'a' + ( i % ( 'z' - 'a' + 1 ) );
|
||||
}
|
||||
response[ capacity - 1 ] = '\0';
|
||||
}, NULL );
|
||||
TESTFW_EXPECTED( server != NULL );
|
||||
ipc_client_t* client = ipc_client_connect( "test_pipe" );
|
||||
TESTFW_EXPECTED( client != NULL );
|
||||
char message[ IPC_MESSAGE_MAX_LENGTH ];
|
||||
for( int i = 0; i < IPC_MESSAGE_MAX_LENGTH - 1; ++i ) {
|
||||
message[ i ] = 'A' + ( i % ( 'Z' - 'A' + 1 ) );
|
||||
}
|
||||
message[ IPC_MESSAGE_MAX_LENGTH - 1 ] = '\0';
|
||||
TESTFW_EXPECTED( ipc_client_send( client, message ) == true );
|
||||
char response[ IPC_MESSAGE_MAX_LENGTH ];
|
||||
int size = 0;
|
||||
TESTFW_EXPECTED( ipc_client_receive( client, response, sizeof( response ), &size ) == IPC_RECEIVE_STATUS_DONE );
|
||||
char expected_response[ IPC_MESSAGE_MAX_LENGTH ];
|
||||
for( int i = 0; i < IPC_MESSAGE_MAX_LENGTH - 1; ++i ) {
|
||||
expected_response[ i ] = 'a' + ( i % ( 'z' - 'a' + 1 ) );
|
||||
}
|
||||
expected_response[ IPC_MESSAGE_MAX_LENGTH - 1 ] = '\0';
|
||||
TESTFW_EXPECTED( size == IPC_MESSAGE_MAX_LENGTH );
|
||||
TESTFW_EXPECTED( strcmp( response, expected_response ) == 0 );
|
||||
|
||||
ipc_client_disconnect( client );
|
||||
ipc_server_stop( server );
|
||||
TESTFW_TEST_END();
|
||||
}
|
||||
|
||||
{
|
||||
TESTFW_TEST_BEGIN( "Can send and receive multiple IPC messages" );
|
||||
int received_count = 0;
|
||||
ipc_server_t* server = ipc_server_start( "test_pipe",
|
||||
[]( char const* message, void* user_data, char* response, size_t ) {
|
||||
if( !message ) return; // client disconnect
|
||||
int* received_count = (int*) user_data;
|
||||
char expected_message[ IPC_MESSAGE_MAX_LENGTH ];
|
||||
sprintf( expected_message, "Test message %d", *received_count );
|
||||
TESTFW_EXPECTED( strcmp( message, expected_message ) == 0 );
|
||||
sprintf( response, "Test response %d", *received_count );
|
||||
*received_count = *received_count + 1;
|
||||
}, &received_count );
|
||||
TESTFW_EXPECTED( server != NULL );
|
||||
ipc_client_t* client = ipc_client_connect( "test_pipe" );
|
||||
TESTFW_EXPECTED( client != NULL );
|
||||
for( int i = 0; i < 64; ++i ) {
|
||||
char message[ IPC_MESSAGE_MAX_LENGTH ];
|
||||
sprintf( message, "Test message %d", i );
|
||||
TESTFW_EXPECTED( ipc_client_send( client, message ) == true );
|
||||
char response[ IPC_MESSAGE_MAX_LENGTH ];
|
||||
int size = 0;
|
||||
TESTFW_EXPECTED( ipc_client_receive( client, response, sizeof( response ), &size ) == IPC_RECEIVE_STATUS_DONE );
|
||||
char expected_response[ IPC_MESSAGE_MAX_LENGTH ];
|
||||
sprintf( expected_response, "Test response %d", i );
|
||||
TESTFW_EXPECTED( size == strlen( expected_response ) + 1 );
|
||||
TESTFW_EXPECTED( strcmp( response, expected_response ) == 0 );
|
||||
}
|
||||
TESTFW_EXPECTED( received_count == 64 );
|
||||
|
||||
ipc_client_disconnect( client );
|
||||
ipc_server_stop( server );
|
||||
TESTFW_TEST_END();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main( int argc, char** argv ) {
|
||||
TESTFW_INIT();
|
||||
|
||||
test_fw_ok();
|
||||
ipc_tests();
|
||||
|
||||
int result = TESTFW_SUMMARY();
|
||||
return result == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
||||
#define IPC_IMPLEMENTATION
|
||||
#include "ipc.h"
|
||||
|
||||
#define TESTFW_IMPLEMENTATION
|
||||
#define TESTFW_NO_ANSI
|
||||
#include "testfw.h"
|
||||
|
@ -1,155 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" 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>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>16.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{98a68237-c5f6-487d-8d49-346d6991f2c5}</ProjectGuid>
|
||||
<RootNamespace>tests</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</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>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<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|x64'">
|
||||
<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>
|
||||
<IntDir>$(Platform)\$(Configuration)\tests\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IntDir>$(Platform)\$(Configuration)\tests\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IntDir>$(Platform)\$(Configuration)\tests\</IntDir>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IntDir>$(Platform)\$(Configuration)\tests\</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ipc.h" />
|
||||
<ClInclude Include="testfw.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="tests.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
26
build/installer.nsh
Normal file
26
build/installer.nsh
Normal file
@ -0,0 +1,26 @@
|
||||
!include LogicLib.nsh
|
||||
|
||||
; Uninstall existing Symphony before installing a new version
|
||||
!macro uninstallSymphony
|
||||
StrCpy $0 0
|
||||
SetRegView 64
|
||||
loop:
|
||||
EnumRegKey $1 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" $0
|
||||
StrCmp $1 "" done
|
||||
ReadRegStr $2 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "DisplayName"
|
||||
${If} $2 == "Symphony"
|
||||
ReadRegStr $3 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$1" "UninstallString"
|
||||
ExecWait '$3 /qn'
|
||||
${EndIf}
|
||||
IntOp $0 $0 + 1
|
||||
Goto loop
|
||||
done:
|
||||
!macroend
|
||||
|
||||
; Preinstall script
|
||||
!macro preInit
|
||||
SetRegView 64
|
||||
!insertmacro uninstallSymphony
|
||||
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "$PROGRAMFILES64\Symphony\Symphony"
|
||||
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "$PROGRAMFILES64\Symphony\Symphony"
|
||||
!macroend
|
@ -1,5 +1,9 @@
|
||||
{
|
||||
"url":"https://my.symphony.com",
|
||||
"autoUpdateUrl": "",
|
||||
"autoUpdateChannel": "general",
|
||||
"isAutoUpdateEnabled": true,
|
||||
"autoUpdateCheckInterval": "30",
|
||||
"overrideUserAgent": false,
|
||||
"minimizeOnClose" : "ENABLED",
|
||||
"launchOnStartup" : "ENABLED",
|
||||
|
@ -29,32 +29,6 @@ class Script
|
||||
|
||||
var userDataPathArgument = "--userDataPath=\"[USER_DATA_PATH]\"";
|
||||
|
||||
//Disabling auto-update service
|
||||
|
||||
// File updateService = new File(@"..\..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\auto-update\auto_update_service.exe");
|
||||
// updateService.ServiceInstaller = new ServiceInstaller
|
||||
// {
|
||||
// Name = "auto_update_service",
|
||||
// DisplayName = "SDA Auto Update Service",
|
||||
// Description = "Symphony Desktop Application Auto Update Service",
|
||||
// FirstFailureActionType = FailureActionType.restart,
|
||||
// SecondFailureActionType = FailureActionType.restart,
|
||||
// ThirdFailureActionType = FailureActionType.restart,
|
||||
// RestartServiceDelayInSeconds = 60,
|
||||
// ResetPeriodInDays = 1,
|
||||
// ServiceSid = ServiceSid.none,
|
||||
// ConfigureServiceTrigger = ConfigureServiceTrigger.Install,
|
||||
// Type = SvcType.ownProcess,
|
||||
// Vital = true,
|
||||
// ErrorControl = SvcErrorControl.normal,
|
||||
// Start = SvcStartType.auto,
|
||||
// EraseDescription = false,
|
||||
// Interactive = false,
|
||||
// StartOn = SvcEvent.Install,
|
||||
// StopOn = SvcEvent.InstallUninstall_Wait,
|
||||
// RemoveOn = SvcEvent.Uninstall_Wait,
|
||||
// };
|
||||
|
||||
// Create a wixsharp project instance and assign the project name to it, and a hierarchy of all files to include
|
||||
// Files are taken from multiple locations, and not all files in each location should be included, which is why
|
||||
// the file list is rather long and explicit. At some point we might make the `dist` folder match exactly the
|
||||
@ -75,7 +49,6 @@ class Script
|
||||
}
|
||||
),
|
||||
// updateService,
|
||||
// new File(@"..\..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\auto-update\auto_update_helper.exe"),
|
||||
new File(@"..\..\..\dist\win-unpacked\chrome_100_percent.pak"),
|
||||
new File(@"..\..\..\dist\win-unpacked\chrome_200_percent.pak"),
|
||||
new File(@"..\..\..\dist\win-unpacked\d3dcompiler_47.dll"),
|
||||
@ -514,6 +487,7 @@ public class CustomActions
|
||||
{
|
||||
key.DeleteValue("Symphony", false);
|
||||
key.DeleteValue("com.symphony.electron-desktop", false);
|
||||
key.DeleteValue("com.symphony.electron_desktop", false);
|
||||
key.DeleteValue("electron.app.Symphony", false);
|
||||
}
|
||||
}
|
||||
|
209
package-lock.json
generated
209
package-lock.json
generated
@ -17,6 +17,7 @@
|
||||
"electron-dl": "3.0.0",
|
||||
"electron-fetch": "1.4.0",
|
||||
"electron-log": "4.0.7",
|
||||
"electron-updater": "^5.0.1",
|
||||
"ffi-napi": "^4.0.3",
|
||||
"filesize": "6.1.0",
|
||||
"lazy-brush": "^1.0.1",
|
||||
@ -2641,6 +2642,12 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
"version": "7.3.9",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/semver/-/semver-7.3.9.tgz",
|
||||
"integrity": "sha1-FSxsIKdojDC5Z+wYQdMazlaYY/w=",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/sinon": {
|
||||
"version": "7.5.2",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/sinon/-/sinon-7.5.2.tgz",
|
||||
@ -9387,6 +9394,103 @@
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/electron-updater": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/electron-updater/-/electron-updater-5.0.1.tgz",
|
||||
"integrity": "sha1-MnkV8bLlc9rxzh2BAryrk/SIDBo=",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/semver": "^7.3.6",
|
||||
"builder-util-runtime": "9.0.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lazy-val": "^1.0.5",
|
||||
"lodash.escaperegexp": "^4.1.2",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"semver": "^7.3.5"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha1-JG9Q88p4oyQPbJl+ipvR6sSeSzg=",
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/builder-util-runtime": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/builder-util-runtime/-/builder-util-runtime-9.0.0.tgz",
|
||||
"integrity": "sha1-OkC6c4JxLM2yRHFWf5HXwWfgCDA=",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.2",
|
||||
"sax": "^1.2.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/fs-extra": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
"integrity": "sha1-Aoc8+8QITd4SfqpfmQXu8jJdGr8=",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha1-wftl+PUBeQHN0slRhkuhhFihBgI=",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha1-vFWyY0eTxnnsZAMJTrE2mKbsCq4=",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"graceful-fs": "^4.1.6"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/semver": {
|
||||
"version": "7.3.7",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha1-EsW2Sa/b+QSXB3luIqQCiBTOUj8=",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-updater/node_modules/universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha1-daSYTv7cSwiXXFrrc/Uw0C3yVxc=",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron/node_modules/@types/node": {
|
||||
"version": "16.11.33",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/node/-/node-16.11.33.tgz",
|
||||
@ -15835,7 +15939,6 @@
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/lazy-val/-/lazy-val-1.0.5.tgz",
|
||||
"integrity": "sha1-bPO59bwxzufuPjacCDK3WD3Nkj0=",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lazystream": {
|
||||
@ -16160,6 +16263,12 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.escaperegexp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
|
||||
"integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c=",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.flatten": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||
@ -16184,7 +16293,6 @@
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.islength": {
|
||||
@ -16356,7 +16464,6 @@
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha1-bW/mVw69lqr5D8rR2vo7JWbbOpQ=",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
@ -20824,7 +20931,6 @@
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/saxes": {
|
||||
@ -25039,7 +25145,6 @@
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI=",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
@ -27134,6 +27239,11 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/semver": {
|
||||
"version": "7.3.9",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/semver/-/semver-7.3.9.tgz",
|
||||
"integrity": "sha1-FSxsIKdojDC5Z+wYQdMazlaYY/w="
|
||||
},
|
||||
"@types/sinon": {
|
||||
"version": "7.5.2",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/@types/sinon/-/sinon-7.5.2.tgz",
|
||||
@ -32256,6 +32366,77 @@
|
||||
"integrity": "sha1-TQBHkFWnKCzdGxnK7Antd3lSlkA=",
|
||||
"dev": true
|
||||
},
|
||||
"electron-updater": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/electron-updater/-/electron-updater-5.0.1.tgz",
|
||||
"integrity": "sha1-MnkV8bLlc9rxzh2BAryrk/SIDBo=",
|
||||
"requires": {
|
||||
"@types/semver": "^7.3.6",
|
||||
"builder-util-runtime": "9.0.0",
|
||||
"fs-extra": "^10.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"lazy-val": "^1.0.5",
|
||||
"lodash.escaperegexp": "^4.1.2",
|
||||
"lodash.isequal": "^4.5.0",
|
||||
"semver": "^7.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha1-JG9Q88p4oyQPbJl+ipvR6sSeSzg="
|
||||
},
|
||||
"builder-util-runtime": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/builder-util-runtime/-/builder-util-runtime-9.0.0.tgz",
|
||||
"integrity": "sha1-OkC6c4JxLM2yRHFWf5HXwWfgCDA=",
|
||||
"requires": {
|
||||
"debug": "^4.3.2",
|
||||
"sax": "^1.2.4"
|
||||
}
|
||||
},
|
||||
"fs-extra": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
"integrity": "sha1-Aoc8+8QITd4SfqpfmQXu8jJdGr8=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha1-wftl+PUBeQHN0slRhkuhhFihBgI=",
|
||||
"requires": {
|
||||
"argparse": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"jsonfile": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/jsonfile/-/jsonfile-6.1.0.tgz",
|
||||
"integrity": "sha1-vFWyY0eTxnnsZAMJTrE2mKbsCq4=",
|
||||
"requires": {
|
||||
"graceful-fs": "^4.1.6",
|
||||
"universalify": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.7",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha1-EsW2Sa/b+QSXB3luIqQCiBTOUj8=",
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha1-daSYTv7cSwiXXFrrc/Uw0C3yVxc="
|
||||
}
|
||||
}
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.4",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/elliptic/-/elliptic-6.5.4.tgz",
|
||||
@ -36953,8 +37134,7 @@
|
||||
"lazy-val": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/lazy-val/-/lazy-val-1.0.5.tgz",
|
||||
"integrity": "sha1-bPO59bwxzufuPjacCDK3WD3Nkj0=",
|
||||
"dev": true
|
||||
"integrity": "sha1-bPO59bwxzufuPjacCDK3WD3Nkj0="
|
||||
},
|
||||
"lazystream": {
|
||||
"version": "1.0.0",
|
||||
@ -37206,6 +37386,11 @@
|
||||
"integrity": "sha1-yQRGkMIeBClL6qUXcS/e0fqI3pg=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.escaperegexp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
|
||||
"integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c="
|
||||
},
|
||||
"lodash.flatten": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
|
||||
@ -37226,8 +37411,7 @@
|
||||
"lodash.isequal": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
|
||||
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
|
||||
"dev": true
|
||||
"integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA="
|
||||
},
|
||||
"lodash.islength": {
|
||||
"version": "4.0.1",
|
||||
@ -37357,7 +37541,6 @@
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha1-bW/mVw69lqr5D8rR2vo7JWbbOpQ=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
@ -40655,8 +40838,7 @@
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/sax/-/sax-1.2.4.tgz",
|
||||
"integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk=",
|
||||
"dev": true
|
||||
"integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk="
|
||||
},
|
||||
"saxes": {
|
||||
"version": "3.1.11",
|
||||
@ -43760,8 +43942,7 @@
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://repo.symphony.com/artifactory/api/npm/npm-virtual-dev/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI=",
|
||||
"dev": true
|
||||
"integrity": "sha1-m7knkNnA7/7GO+c1GeEaNQGaOnI="
|
||||
},
|
||||
"yaml": {
|
||||
"version": "1.10.2",
|
||||
|
24
package.json
24
package.json
@ -36,13 +36,20 @@
|
||||
"copy": "run-os",
|
||||
"copy:darwin": "ncp config 'node_modules/electron/dist/Electron.app/Contents/config'",
|
||||
"copy:win32": "ncp config node_modules\\electron\\dist\\config",
|
||||
"unpacked-mac": "npm run prebuild && npm run test && ./node_modules/.bin/electron-builder build --mac --dir",
|
||||
"unpacked-win": "npm run prebuild && npm run test && node_modules\\.bin\\electron-builder build --win --x64 --dir"
|
||||
"unpacked-mac": "npm run prebuild && npm run test && ./node_modules/.bin/electron-builder build --mac",
|
||||
"unpacked-win": "npm run prebuild && npm run test && node_modules\\.bin\\electron-builder build --win --x64"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.symphony.electron-desktop",
|
||||
"appId": "com.symphony.electron_desktop",
|
||||
"uninstallDisplayName": "${productName}",
|
||||
"artifactName": "${productName}-${version}-${os}.${ext}",
|
||||
"asar": true,
|
||||
"asarUnpack": "**/*.node",
|
||||
"nsis": {
|
||||
"perMachine": true,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"include": "build/installer.nsh"
|
||||
},
|
||||
"files": [
|
||||
"!coverage/*",
|
||||
"!installer/*",
|
||||
@ -77,12 +84,19 @@
|
||||
"library/libsymphonysearch.dylib",
|
||||
"library/cryptoLib.dylib",
|
||||
"library/dictionary"
|
||||
],
|
||||
"target": [
|
||||
"dir",
|
||||
"dmg",
|
||||
"zip"
|
||||
]
|
||||
},
|
||||
"win": {
|
||||
"icon": "images/icon.ico",
|
||||
"target": [
|
||||
"squirrel"
|
||||
"dir",
|
||||
"nsis",
|
||||
"zip"
|
||||
]
|
||||
},
|
||||
"linux": {
|
||||
@ -159,6 +173,7 @@
|
||||
"electron-dl": "3.0.0",
|
||||
"electron-fetch": "1.4.0",
|
||||
"electron-log": "4.0.7",
|
||||
"electron-updater": "^5.0.1",
|
||||
"ffi-napi": "^4.0.3",
|
||||
"filesize": "6.1.0",
|
||||
"lazy-brush": "^1.0.1",
|
||||
@ -171,7 +186,6 @@
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@symphony/symphony-c9-shell": "3.14.99-37",
|
||||
"auto-update": "file:auto_update",
|
||||
"screen-share-indicator-frame": "git+https://github.com/symphonyoss/ScreenShareIndicatorFrame.git#v1.4.13",
|
||||
"screen-snippet": "git+https://github.com/symphonyoss/ScreenSnippet2.git#9.2.2",
|
||||
"winreg": "^1.2.4"
|
||||
|
@ -122,18 +122,6 @@ IF %errorlevel% neq 0 (
|
||||
exit /b -1
|
||||
)
|
||||
|
||||
@REM call %SIGNING_FILE_PATH% ..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\auto-update\auto_update_service.exe
|
||||
@REM IF %errorlevel% neq 0 (
|
||||
@REM echo "Signing failed"
|
||||
@REM exit /b -1
|
||||
@REM )
|
||||
|
||||
@REM call %SIGNING_FILE_PATH% ..\..\dist\win-unpacked\resources\app.asar.unpacked\node_modules\auto-update\auto_update_helper.exe
|
||||
@REM IF %errorlevel% neq 0 (
|
||||
@REM echo "Signing failed"
|
||||
@REM exit /b -1
|
||||
@REM )
|
||||
|
||||
call %SIGNING_FILE_PATH% ..\..\dist\win-unpacked\Symphony.exe
|
||||
IF %errorlevel% neq 0 (
|
||||
echo "Signing failed"
|
||||
|
@ -38,6 +38,10 @@ jest.mock('../src/app/auto-launch-controller', () => {
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../src/app/auto-update-handler', () => {
|
||||
return {};
|
||||
});
|
||||
|
||||
jest.mock('../src/app/config-handler', () => {
|
||||
return {
|
||||
CloudConfigDataTypes: {
|
||||
|
@ -5,6 +5,12 @@ import {
|
||||
import { windowHandler } from '../src/app/window-handler';
|
||||
import { BrowserWindow, dialog, ipcRenderer } from './__mocks__/electron';
|
||||
|
||||
jest.mock('../src/app/auto-update-handler', () => {
|
||||
return {
|
||||
updateAndRestart: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('fs', () => ({
|
||||
writeFileSync: jest.fn(),
|
||||
existsSync: jest.fn(() => true),
|
||||
|
@ -21,6 +21,12 @@ jest.mock('../src/app/protocol-handler', () => {
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../src/app/auto-update-handler', () => {
|
||||
return {
|
||||
updateAndRestart: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../src/app/screen-snippet-handler', () => {
|
||||
return {
|
||||
screenSnippet: {
|
||||
@ -99,6 +105,16 @@ jest.mock('../src/app/config-handler', () => {
|
||||
devToolsEnabled: true,
|
||||
};
|
||||
}),
|
||||
getUserConfigFields: jest.fn(() => {
|
||||
return {
|
||||
url: 'https://symphony.com',
|
||||
};
|
||||
}),
|
||||
getGlobalConfigFields: jest.fn(() => {
|
||||
return {
|
||||
url: 'https://symphony.com',
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
});
|
||||
@ -495,14 +511,14 @@ describe('main api handler', () => {
|
||||
cmd: apiCmds.getNativeWindowHandle,
|
||||
windowName: 'main',
|
||||
});
|
||||
expect(windows['main'].getNativeWindowHandle).toBeCalledTimes(1);
|
||||
expect(windows.main.getNativeWindowHandle).toBeCalledTimes(1);
|
||||
|
||||
ipcMain.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.getNativeWindowHandle,
|
||||
windowName: 'popout1',
|
||||
});
|
||||
expect(windows['popout1'].getNativeWindowHandle).toBeCalledTimes(1);
|
||||
expect(windows['popout2'].getNativeWindowHandle).toBeCalledTimes(0);
|
||||
expect(windows.popout1.getNativeWindowHandle).toBeCalledTimes(1);
|
||||
expect(windows.popout2.getNativeWindowHandle).toBeCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should call `connectC9Pipe` correctly', () => {
|
||||
|
@ -1,10 +1,11 @@
|
||||
import {
|
||||
calculatePercentage,
|
||||
compareVersions,
|
||||
formatString,
|
||||
getCommandLineArgs,
|
||||
getGuid,
|
||||
isUrl,
|
||||
throttle,
|
||||
calculatePercentage,
|
||||
} from '../src/common/utils';
|
||||
|
||||
describe('utils', () => {
|
||||
@ -172,4 +173,22 @@ describe('utils', () => {
|
||||
expect(calculatePercentage(500, 50)).toBe(250);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isURL', () => {
|
||||
it('should return true for URL string', () => {
|
||||
expect(isUrl('https://corporate.symphony.com')).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for http URLs string', () => {
|
||||
expect(isUrl('http://corporate.symphony.com')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for URL string without https', () => {
|
||||
expect(isUrl('corporate.symphony.com')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for not a URL string', () => {
|
||||
expect(isUrl('/general')).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -34,6 +34,8 @@ import {
|
||||
zoomOut,
|
||||
} from './window-utils';
|
||||
|
||||
import { autoUpdate } from './auto-update-handler';
|
||||
|
||||
export const menuSections = {
|
||||
about: 'about',
|
||||
edit: 'edit',
|
||||
@ -280,6 +282,12 @@ export class AppMenu {
|
||||
windowHandler.createAboutAppWindow(windowName);
|
||||
},
|
||||
},
|
||||
{
|
||||
click: (_item) => {
|
||||
autoUpdate.checkUpdates();
|
||||
},
|
||||
label: i18n.t('Check for updates')(),
|
||||
},
|
||||
this.buildSeparator(),
|
||||
{ label: i18n.t('Services')(), role: 'services' },
|
||||
this.buildSeparator(),
|
||||
|
@ -1,67 +1,151 @@
|
||||
import { app } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import electronLog from 'electron-log';
|
||||
import { MacUpdater, NsisUpdater } from 'electron-updater';
|
||||
|
||||
import { spawn } from 'child_process';
|
||||
import { isDevEnv, isElectronQA, isWindowsOS } from '../common/env';
|
||||
import { isMac, isWindowsOS } from '../common/env';
|
||||
import { logger } from '../common/logger';
|
||||
import { isUrl } from '../common/utils';
|
||||
import { whitelistHandler } from '../common/whitelist-handler';
|
||||
import { config } from './config-handler';
|
||||
import { windowHandler } from './window-handler';
|
||||
|
||||
class AutoUpdate {
|
||||
private readonly tempDir: string;
|
||||
private filename: string;
|
||||
private logFilePath: string;
|
||||
private updateUtil: string;
|
||||
private updateUtilArgs: ReadonlyArray<string>;
|
||||
export class AutoUpdate {
|
||||
public isUpdateAvailable: boolean = false;
|
||||
public autoUpdater: MacUpdater | NsisUpdater | undefined = undefined;
|
||||
|
||||
constructor() {
|
||||
if (isElectronQA) {
|
||||
this.tempDir = os.tmpdir();
|
||||
} else {
|
||||
this.tempDir = path.join(app.getPath('userData'), 'temp');
|
||||
if (!fs.existsSync(this.tempDir)) {
|
||||
fs.mkdirSync(this.tempDir);
|
||||
}
|
||||
if (isMac) {
|
||||
this.autoUpdater = new MacUpdater(this.getUpdateUrl());
|
||||
} else if (isWindowsOS) {
|
||||
this.autoUpdater = new NsisUpdater(this.getUpdateUrl());
|
||||
}
|
||||
|
||||
this.filename = '';
|
||||
this.logFilePath = path.join(this.tempDir, 'auto_update.log');
|
||||
if (this.autoUpdater) {
|
||||
this.autoUpdater.logger = electronLog;
|
||||
this.autoUpdater.autoDownload = false;
|
||||
this.autoUpdater.autoInstallOnAppQuit = true;
|
||||
|
||||
this.updateUtil = isDevEnv
|
||||
? path.join(
|
||||
__dirname,
|
||||
'../../../node_modules/auto-update/auto_update_helper.exe',
|
||||
)
|
||||
: path.join(path.dirname(app.getPath('exe')), 'auto_update_helper.exe');
|
||||
this.autoUpdater.on('update-not-available', () => {
|
||||
const mainWebContents = windowHandler.mainWebContents;
|
||||
// Display client banner
|
||||
if (mainWebContents && !mainWebContents.isDestroyed()) {
|
||||
mainWebContents.send('display-client-banner', {
|
||||
reason: 'autoUpdate',
|
||||
action: 'update-not-available',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.updateUtilArgs = [];
|
||||
this.autoUpdater.on('update-available', (info) => {
|
||||
const mainWebContents = windowHandler.mainWebContents;
|
||||
// Display client banner
|
||||
if (mainWebContents && !mainWebContents.isDestroyed()) {
|
||||
mainWebContents.send('display-client-banner', {
|
||||
reason: 'autoUpdate',
|
||||
action: 'update-available',
|
||||
data: info,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.autoUpdater.on('download-progress', (info) => {
|
||||
const mainWebContents = windowHandler.mainWebContents;
|
||||
// Display client banner
|
||||
if (mainWebContents && !mainWebContents.isDestroyed()) {
|
||||
mainWebContents.send('display-client-banner', {
|
||||
reason: 'autoUpdate',
|
||||
action: 'download-progress',
|
||||
data: info,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.autoUpdater.on('update-downloaded', (info) => {
|
||||
this.isUpdateAvailable = true;
|
||||
const mainWebContents = windowHandler.mainWebContents;
|
||||
// Display client banner
|
||||
if (mainWebContents && !mainWebContents.isDestroyed()) {
|
||||
mainWebContents.send('display-client-banner', {
|
||||
reason: 'autoUpdate',
|
||||
action: 'update-downloaded',
|
||||
data: info,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the auto update helper
|
||||
* Installs the latest update quits and relaunches application
|
||||
*/
|
||||
public async update(filename: string) {
|
||||
logger.info(`auto-update-handler: Starting auto update!`);
|
||||
this.filename = filename;
|
||||
if (isWindowsOS) {
|
||||
this.updateUtilArgs = [
|
||||
this.filename,
|
||||
app.getPath('exe'),
|
||||
this.logFilePath,
|
||||
];
|
||||
logger.info(
|
||||
`auto-update-handler: Running update helper${this.updateUtil} with args ${this.updateUtilArgs}!`,
|
||||
);
|
||||
|
||||
try {
|
||||
spawn(this.updateUtil, this.updateUtilArgs);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`auto-update-handler: auto update failed. Error: ${error}!`,
|
||||
);
|
||||
}
|
||||
public updateAndRestart = async (): Promise<void> => {
|
||||
if (!this.isUpdateAvailable) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Handle update and restart for macOS
|
||||
if (isMac) {
|
||||
windowHandler.setIsAutoUpdating(true);
|
||||
}
|
||||
setImmediate(() => {
|
||||
if (this.autoUpdater) {
|
||||
this.autoUpdater.quitAndInstall();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks for the latest updates
|
||||
* @return void
|
||||
*/
|
||||
public checkUpdates = async (): Promise<void> => {
|
||||
logger.info('auto-update-handler: Checking for updates');
|
||||
if (this.autoUpdater) {
|
||||
const updateCheckResult = await this.autoUpdater.checkForUpdates();
|
||||
logger.info('auto-update-handler: ', updateCheckResult);
|
||||
}
|
||||
logger.info('auto-update-handler: After checking auto update');
|
||||
};
|
||||
|
||||
/**
|
||||
* Downloads the latest update
|
||||
* @return void
|
||||
*/
|
||||
public downloadUpdate = async (): Promise<void> => {
|
||||
logger.info('auto-update-handler: download update');
|
||||
if (this.autoUpdater) {
|
||||
await this.autoUpdater.downloadUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructs the SDA auto update end point url
|
||||
*
|
||||
* @return string
|
||||
* @example https://corporate.symphony.com/macos/general
|
||||
*/
|
||||
public getUpdateUrl = (): string => {
|
||||
const { url: userConfigURL } = config.getUserConfigFields(['url']);
|
||||
const { url: globalConfigURL } = config.getGlobalConfigFields(['url']);
|
||||
const { autoUpdateUrl, autoUpdateChannel } = config.getConfigFields([
|
||||
'autoUpdateChannel',
|
||||
'autoUpdateUrl',
|
||||
]);
|
||||
|
||||
if (autoUpdateUrl && isUrl(autoUpdateUrl)) {
|
||||
logger.info(
|
||||
`auto-update-handler: autoUpdateUrl exists so, using it`,
|
||||
autoUpdateUrl,
|
||||
);
|
||||
return autoUpdateUrl;
|
||||
}
|
||||
|
||||
const url = userConfigURL ? userConfigURL : globalConfigURL;
|
||||
|
||||
const { subdomain, domain, tld } = whitelistHandler.parseDomain(url);
|
||||
const updateUrl = `https://${subdomain}.${domain}.${tld}/${autoUpdateChannel}`;
|
||||
logger.info(`auto-update-handler: using generic pod url`, updateUrl);
|
||||
|
||||
return updateUrl;
|
||||
};
|
||||
}
|
||||
|
||||
const autoUpdate = new AutoUpdate();
|
||||
|
@ -28,6 +28,11 @@ export const ConfigFieldsToRestart = new Set([
|
||||
|
||||
export interface IConfig {
|
||||
url: string;
|
||||
autoUpdateChannel: string;
|
||||
autoUpdateUrl: string;
|
||||
isAutoUpdateEnabled: boolean;
|
||||
autoUpdateCheckInterval: string;
|
||||
lastAutoUpdateCheckDate: string;
|
||||
minimizeOnClose: CloudConfigDataTypes;
|
||||
launchOnStartup: CloudConfigDataTypes;
|
||||
alwaysOnTop: CloudConfigDataTypes;
|
||||
@ -76,6 +81,8 @@ export interface IPodLevelEntitlements {
|
||||
bringToFront: CloudConfigDataTypes;
|
||||
disableThrottling: CloudConfigDataTypes;
|
||||
launchOnStartup: CloudConfigDataTypes;
|
||||
isAutoUpdateEnabled: boolean;
|
||||
autoUpdateCheckInterval: string;
|
||||
memoryThreshold: string;
|
||||
ctWhitelist: string[];
|
||||
podWhitelist: string[];
|
||||
|
@ -22,7 +22,7 @@ app.enableSandbox();
|
||||
|
||||
// need to set this explicitly if using Squirrel
|
||||
// https://www.electron.build/configuration/configuration#Configuration-squirrelWindows
|
||||
app.setAppUserModelId('com.symphony.electron-desktop');
|
||||
app.setAppUserModelId('com.symphony.electron_desktop');
|
||||
|
||||
// Set user data path before app ready event
|
||||
if (isDevEnv) {
|
||||
|
@ -18,6 +18,7 @@ import { logger } from '../common/logger';
|
||||
import { activityDetection } from './activity-detection';
|
||||
import { analytics } from './analytics-handler';
|
||||
import appStateHandler from './app-state-handler';
|
||||
import { autoUpdate } from './auto-update-handler';
|
||||
import { closeC9Pipe, connectC9Pipe, writeC9Pipe } from './c9-pipe-handler';
|
||||
import { loadC9Shell } from './c9-shell-handler';
|
||||
import { getCitrixMediaRedirectionStatus } from './citrix-handler';
|
||||
@ -298,9 +299,6 @@ ipcMain.on(
|
||||
}
|
||||
}
|
||||
break;
|
||||
// case apiCmds.autoUpdate:
|
||||
// autoUpdate.update(arg.filename);
|
||||
// break;
|
||||
case apiCmds.aboutAppClipBoardData:
|
||||
if (arg.clipboard && arg.clipboardType) {
|
||||
clipboard.write(
|
||||
@ -370,6 +368,12 @@ ipcMain.on(
|
||||
case apiCmds.launchCloud9:
|
||||
loadC9Shell(event.sender);
|
||||
break;
|
||||
case apiCmds.updateAndRestart:
|
||||
autoUpdate.updateAndRestart();
|
||||
break;
|
||||
case apiCmds.downloadUpdate:
|
||||
autoUpdate.downloadUpdate();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -143,6 +143,7 @@ export class WindowHandler {
|
||||
public isCustomTitleBar: boolean;
|
||||
public isWebPageLoading: boolean = true;
|
||||
public isLoggedIn: boolean = false;
|
||||
public isAutoUpdating: boolean = false;
|
||||
public screenShareIndicatorFrameUtil: string;
|
||||
private readonly defaultPodUrl: string = 'https://[POD].symphony.com';
|
||||
private readonly contextIsolation: boolean;
|
||||
@ -631,13 +632,16 @@ export class WindowHandler {
|
||||
}
|
||||
|
||||
const { minimizeOnClose } = config.getConfigFields(['minimizeOnClose']);
|
||||
if (minimizeOnClose === CloudConfigDataTypes.ENABLED) {
|
||||
if (
|
||||
minimizeOnClose === CloudConfigDataTypes.ENABLED &&
|
||||
!this.isAutoUpdating
|
||||
) {
|
||||
event.preventDefault();
|
||||
this.mainWindow.minimize();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isMac) {
|
||||
if (isMac && !this.isAutoUpdating) {
|
||||
event.preventDefault();
|
||||
this.mainWindow.hide();
|
||||
return;
|
||||
@ -887,6 +891,15 @@ export class WindowHandler {
|
||||
this.titleBarView = titleBarView;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the application is Auto Updating
|
||||
*
|
||||
* @param isAutoUpdating
|
||||
*/
|
||||
public setIsAutoUpdating(isAutoUpdating: boolean): void {
|
||||
this.isAutoUpdating = isAutoUpdating;
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the window from an event emitted by the render processes
|
||||
*
|
||||
|
@ -20,7 +20,7 @@ import { apiName } from '../common/api-interface';
|
||||
import { isDevEnv, isLinux, isMac, isWindowsOS } from '../common/env';
|
||||
import { i18n, LocaleType } from '../common/i18n';
|
||||
import { logger } from '../common/logger';
|
||||
import { getGuid } from '../common/utils';
|
||||
import { getDifferenceInDays, getGuid, getRandomTime } from '../common/utils';
|
||||
import { whitelistHandler } from '../common/whitelist-handler';
|
||||
import { autoLaunchInstance } from './auto-launch-controller';
|
||||
import {
|
||||
@ -48,7 +48,9 @@ import {
|
||||
} from './window-handler';
|
||||
|
||||
import { notification } from '../renderer/notification';
|
||||
import { autoUpdate } from './auto-update-handler';
|
||||
import { mainEvents } from './main-event-handler';
|
||||
|
||||
interface IStyles {
|
||||
name: styleNames;
|
||||
content: string;
|
||||
@ -66,6 +68,11 @@ const { ctWhitelist } = config.getConfigFields(['ctWhitelist']);
|
||||
// Network status check variables
|
||||
const networkStatusCheckInterval = 10 * 1000;
|
||||
let networkStatusCheckIntervalId;
|
||||
|
||||
const MAX_AUTO_UPDATE_CHECK_INTERVAL = 4 * 60 * 60 * 1000; // 4hrs
|
||||
const MIN_AUTO_UPDATE_CHECK_INTERVAL = 2 * 60 * 60 * 1000; // 2hrs
|
||||
let autoUpdateIntervalId;
|
||||
|
||||
let isNetworkMonitorInitialized = false;
|
||||
|
||||
const styles: IStyles[] = [];
|
||||
@ -1024,11 +1031,15 @@ export const updateFeaturesForCloudConfig = async (
|
||||
launchOnStartup,
|
||||
memoryRefresh,
|
||||
memoryThreshold,
|
||||
isAutoUpdateEnabled,
|
||||
autoUpdateCheckInterval,
|
||||
} = config.getConfigFields([
|
||||
'launchOnStartup',
|
||||
'alwaysOnTop',
|
||||
'memoryRefresh',
|
||||
'memoryThreshold',
|
||||
'isAutoUpdateEnabled',
|
||||
'autoUpdateCheckInterval',
|
||||
]) as IConfig;
|
||||
|
||||
const mainWebContents = windowHandler.getMainWebContents();
|
||||
@ -1077,6 +1088,47 @@ export const updateFeaturesForCloudConfig = async (
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SDA auto updater
|
||||
logger.info(`window-utils: initiate auto update?`, isAutoUpdateEnabled);
|
||||
if (isAutoUpdateEnabled) {
|
||||
if (!autoUpdateIntervalId) {
|
||||
// randomised to avoid having all users getting SDA update at the same time
|
||||
autoUpdateIntervalId = setInterval(async () => {
|
||||
const { lastAutoUpdateCheckDate } = config.getUserConfigFields([
|
||||
'lastAutoUpdateCheckDate',
|
||||
]);
|
||||
if (!lastAutoUpdateCheckDate || lastAutoUpdateCheckDate === '') {
|
||||
logger.info(
|
||||
`window-utils: lastAutoUpdateCheckDate is not set in user config file so checking for updates`,
|
||||
lastAutoUpdateCheckDate,
|
||||
autoUpdateCheckInterval,
|
||||
);
|
||||
await config.updateUserConfig({
|
||||
lastAutoUpdateCheckDate: new Date().toISOString(),
|
||||
});
|
||||
autoUpdate.checkUpdates();
|
||||
return;
|
||||
}
|
||||
logger.info(
|
||||
`window-utils: is last check date > auto update check interval?`,
|
||||
lastAutoUpdateCheckDate,
|
||||
autoUpdateCheckInterval,
|
||||
);
|
||||
// Compare the current date and user config last auto update checked date
|
||||
// and if it is greater that autoUpdateCheckInterval we check for new updates
|
||||
if (
|
||||
getDifferenceInDays(new Date(), new Date(lastAutoUpdateCheckDate)) >
|
||||
Number(autoUpdateCheckInterval)
|
||||
) {
|
||||
await config.updateUserConfig({
|
||||
lastAutoUpdateCheckDate: new Date().toISOString(),
|
||||
});
|
||||
autoUpdate.checkUpdates();
|
||||
}
|
||||
}, getRandomTime(MIN_AUTO_UPDATE_CHECK_INTERVAL, MAX_AUTO_UPDATE_CHECK_INTERVAL));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -48,7 +48,6 @@ export enum apiCmds {
|
||||
showNotification = 'show-notification',
|
||||
closeAllWrapperWindows = 'close-all-windows',
|
||||
setZoomLevel = 'set-zoom-level',
|
||||
// autoUpdate = 'auto-update',
|
||||
aboutAppClipBoardData = 'about-app-clip-board-data',
|
||||
closeMainWindow = 'close-main-window',
|
||||
minimizeMainWindow = 'minimize-main-window',
|
||||
@ -69,6 +68,8 @@ export enum apiCmds {
|
||||
connectCloud9Pipe = 'connect-cloud9-pipe',
|
||||
writeCloud9Pipe = 'write-cloud9-pipe',
|
||||
closeCloud9Pipe = 'close-cloud9-pipe',
|
||||
updateAndRestart = 'update-and-restart',
|
||||
downloadUpdate = 'download-update',
|
||||
}
|
||||
|
||||
export enum apiName {
|
||||
|
@ -275,3 +275,40 @@ export const arrayEquals = (a: string[], b: string[]) => {
|
||||
a.every((val, index) => val === b[index])
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a random number that is between (min - max)
|
||||
* if min is 4hrs and max is 12hrs then the
|
||||
* returned value will be a random b/w 4 - 12 hrs
|
||||
*
|
||||
* @param min {number} - millisecond
|
||||
* @param max {number} - millisecond
|
||||
*/
|
||||
export const getRandomTime = (min: number, max: number): number => {
|
||||
min = Math.ceil(min);
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the difference between 2 Dates in Days
|
||||
*
|
||||
* @param startDate
|
||||
* @param endDate
|
||||
*
|
||||
* @return number
|
||||
*/
|
||||
export const getDifferenceInDays = (startDate: Date, endDate: Date): number => {
|
||||
const msInDay = 24 * 60 * 60 * 1000;
|
||||
return Math.round(
|
||||
Math.abs(Number(endDate.getTime()) - Number(startDate.getTime())) / msInDay,
|
||||
);
|
||||
};
|
||||
|
||||
export const isUrl = (str: string): boolean => {
|
||||
try {
|
||||
return Boolean(new URL(str).protocol === 'https:');
|
||||
} catch (_e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
@ -271,12 +271,6 @@
|
||||
</table>
|
||||
<input type="button" id="restart-app" value="Restart App" />
|
||||
|
||||
<!-- <hr />
|
||||
<h1>Auto-update Test</h1>
|
||||
Full path to MSI <input type="text" id="update-file" />
|
||||
<br />
|
||||
<button id="run-update">Update</button> -->
|
||||
|
||||
<hr />
|
||||
<p>Native Window Handle:</p>
|
||||
<button id="get-window-handle">
|
||||
@ -337,7 +331,6 @@
|
||||
getCPUUsage: 'get-cpu-usage',
|
||||
checkMediaPermission: 'check-media-permission',
|
||||
restartApp: 'restart-app',
|
||||
// autoUpdate: 'auto-update',
|
||||
getNativeWindowHandle: 'get-native-window-handle',
|
||||
getCitrixMediaRedirectionStatus: 'get-citrix-media-redirection-status',
|
||||
};
|
||||
|
@ -31,6 +31,7 @@
|
||||
"Build expired": "Build expired",
|
||||
"Cancel": "Cancel",
|
||||
"Certificate Error": "Certificate Error",
|
||||
"Check for updates": "Check for updates",
|
||||
"Clear cache and Reload": "Clear cache and Reload",
|
||||
"Close": "Close",
|
||||
"ContextMenu": {
|
||||
|
@ -31,6 +31,7 @@
|
||||
"Build expired": "Build expired",
|
||||
"Cancel": "Cancel",
|
||||
"Certificate Error": "Certificate Error",
|
||||
"Check for updates": "Check for updates",
|
||||
"Clear cache and Reload": "Clear cache and Reload",
|
||||
"Close": "Close",
|
||||
"ContextMenu": {
|
||||
|
@ -31,6 +31,7 @@
|
||||
"Build expired": "Construit expiré",
|
||||
"Cancel": "Annuler",
|
||||
"Certificate Error": "Erreur de certificat",
|
||||
"Check for updates": "Vérifier les mises à jour",
|
||||
"Clear cache and Reload": "Vider le cache et rafraîchir Symphony",
|
||||
"Close": "Fermer",
|
||||
"ContextMenu": {
|
||||
|
@ -31,6 +31,7 @@
|
||||
"Build expired": "Construit expiré",
|
||||
"Cancel": "Annuler",
|
||||
"Certificate Error": "Erreur de certificat",
|
||||
"Check for updates": "Vérifier les mises à jour",
|
||||
"Clear cache and Reload": "Vider le cache et rafraîchir Symphony",
|
||||
"Close": "Fermer",
|
||||
"ContextMenu": {
|
||||
|
@ -31,6 +31,7 @@
|
||||
"Build expired": "期限切れのビルド",
|
||||
"Cancel": "キャンセル",
|
||||
"Certificate Error": "証明書のエラー",
|
||||
"Check for updates": "アップデートを確認",
|
||||
"Clear cache and Reload": "キャッシュをクリアしてリロードする",
|
||||
"Close": "閉じる",
|
||||
"ContextMenu": {
|
||||
|
@ -31,6 +31,7 @@
|
||||
"Build expired": "期限切れのビルド",
|
||||
"Cancel": "キャンセル",
|
||||
"Certificate Error": "証明書のエラー",
|
||||
"Check for updates": "アップデートを確認",
|
||||
"Clear cache and Reload": "キャッシュをクリアしてリロードする",
|
||||
"Close": "閉じる",
|
||||
"ContextMenu": {
|
||||
|
@ -94,6 +94,8 @@ if (ssfWindow.ssf) {
|
||||
registerClientBanner: ssfWindow.ssf.registerClientBanner,
|
||||
launchCloud9: ssfWindow.ssf.launchCloud9,
|
||||
connectCloud9Pipe: ssfWindow.ssf.connectCloud9Pipe,
|
||||
updateAndRestartSDA: ssfWindow.ssf.updateAndRestart,
|
||||
downloadUpdate: ssfWindow.ssf.downloadUpdate,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -69,7 +69,7 @@ export interface ILocalObject {
|
||||
analyticsEventHandler?: (arg: any) => void;
|
||||
restartFloater?: (arg: IRestartFloaterData) => void;
|
||||
showClientBannerCallback?: Array<
|
||||
(reason: string, action: ConfigUpdateType) => void
|
||||
(reason: string, action: ConfigUpdateType, data?: object) => void
|
||||
>;
|
||||
c9PipeEventCallback?: (event: string, arg?: any) => void;
|
||||
c9MessageCallback?: (status: IShellStatus) => void;
|
||||
@ -854,6 +854,24 @@ export class SSFApi {
|
||||
cmd: apiCmds.launchCloud9,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows JS to install new update and restart SDA
|
||||
*/
|
||||
public updateAndRestart(): void {
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.updateAndRestart,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows JS to download the latest SDA updates
|
||||
*/
|
||||
public downloadUpdate(): void {
|
||||
ipcRenderer.send(apiName.symphonyApi, {
|
||||
cmd: apiCmds.downloadUpdate,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1091,6 +1109,10 @@ local.ipcRenderer.on('notification-actions', (_event, args) => {
|
||||
local.ipcRenderer.on('display-client-banner', (_event, args) => {
|
||||
if (local.showClientBannerCallback) {
|
||||
for (const callback of local.showClientBannerCallback) {
|
||||
if (args.data) {
|
||||
callback(args.reason, args.action, args.data);
|
||||
return;
|
||||
}
|
||||
callback(args.reason, args.action);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user