mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
Merge pull request #1222 from mattias-symphony/SDA-3148
SDA-3148 Implementation and tests for IPC client/server module
This commit is contained in:
commit
e6e5119a13
@ -8,6 +8,7 @@
|
||||
#include <stdlib.h>
|
||||
#include "ipc.h"
|
||||
|
||||
#define PIPE_NAME "symphony_sda_auto_update_ipc"
|
||||
|
||||
int main( int argc, char** argv ) {
|
||||
if( argc < 3 ) {
|
||||
@ -16,7 +17,7 @@ int main( int argc, char** argv ) {
|
||||
}
|
||||
char const* installer_filename = argv[ 1 ];
|
||||
char const* application_filename = argv[ 2 ];
|
||||
ipc_client_t* client = ipc_client_connect();
|
||||
ipc_client_t* client = ipc_client_connect( PIPE_NAME );
|
||||
ipc_client_send( client, installer_filename );
|
||||
char response[ 256 ];
|
||||
int size = 0;
|
||||
@ -29,7 +30,7 @@ int main( int argc, char** argv ) {
|
||||
}
|
||||
response[ size ] = '\0';
|
||||
printf( "%s\n", response );
|
||||
ipc_client_connect();
|
||||
ipc_client_connect( PIPE_NAME );
|
||||
|
||||
int result = (int)(uintptr_t) ShellExecute( NULL, NULL, application_filename,
|
||||
NULL, NULL, SW_SHOWNORMAL );
|
||||
@ -45,3 +46,7 @@ int main( int argc, char** argv ) {
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
#define IPC_IMPLEMENTATION
|
||||
#include "ipc.h"
|
||||
|
||||
|
@ -70,7 +70,18 @@
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup />
|
||||
<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>
|
||||
|
@ -10,6 +10,8 @@
|
||||
#pragma comment(lib, "advapi32.lib")
|
||||
#pragma comment(lib, "Shell32.lib")
|
||||
|
||||
#define PIPE_NAME "symphony_sda_auto_update_ipc"
|
||||
|
||||
#define SVCNAME TEXT("SDA Auto Update")
|
||||
|
||||
SERVICE_STATUS gSvcStatus;
|
||||
@ -168,6 +170,35 @@ VOID WINAPI SvcMain( DWORD dwArgc, LPTSTR *lpszArgv )
|
||||
}
|
||||
|
||||
|
||||
// This routine is a simple function to print the client request to the console
|
||||
// and populate the reply buffer with a default data string. This is where you
|
||||
// would put the actual client request processing code that runs in the context
|
||||
// of an instance thread. Keep in mind the main thread will continue to wait for
|
||||
// and receive other client connections while the instance thread is working.
|
||||
void run_installer( char const* request, void* user_data, char* response, size_t capacity ) {
|
||||
printf( "Client Request String:\"%s\"\n", request );
|
||||
|
||||
char command[ 512 ];
|
||||
sprintf( command, "/i %s /q", request );
|
||||
|
||||
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;
|
||||
ShellExecuteEx( &ShExecInfo );
|
||||
WaitForSingleObject( ShExecInfo.hProcess, INFINITE );
|
||||
CloseHandle( ShExecInfo.hProcess );
|
||||
|
||||
strcpy( response, "OK" );
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Purpose:
|
||||
// The service code
|
||||
@ -183,7 +214,7 @@ VOID WINAPI SvcMain( DWORD dwArgc, LPTSTR *lpszArgv )
|
||||
//
|
||||
VOID SvcInit( DWORD dwArgc, LPTSTR *lpszArgv)
|
||||
{
|
||||
ipc_server_t* server = ipc_server_start();
|
||||
ipc_server_t* server = ipc_server_start( PIPE_NAME, run_installer, NULL );
|
||||
|
||||
// TO_DO: Declare and set any required variables.
|
||||
// Be sure to periodically call ReportSvcStatus() with
|
||||
@ -296,3 +327,6 @@ VOID WINAPI SvcCtrlHandler( DWORD dwCtrl )
|
||||
|
||||
}
|
||||
|
||||
#define IPC_IMPLEMENTATION
|
||||
#include "ipc.h"
|
||||
|
||||
|
@ -69,7 +69,18 @@
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup />
|
||||
<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>
|
||||
@ -125,6 +136,9 @@
|
||||
<ItemGroup>
|
||||
<ClCompile Include="auto_update_service.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ipc.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
|
@ -1,7 +1,66 @@
|
||||
#ifndef ipc_h
|
||||
#define ipc_h
|
||||
|
||||
#define IPC_MESSAGE_MAX_LENGTH 512
|
||||
|
||||
// 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")
|
||||
|
||||
// TODO: placeholder until implementing logging
|
||||
#define IPC_LOG printf
|
||||
|
||||
|
||||
// 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 ) {
|
||||
WIN32_FIND_DATA data;
|
||||
WIN32_FIND_DATAA data;
|
||||
memset( &data, 0, sizeof( data ) );
|
||||
|
||||
HANDLE hfind = FindFirstFileA( "\\\\.\\pipe\\*", &data );
|
||||
@ -19,97 +78,129 @@ bool pipe_exists( const char* pipe_name ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#define PIPE_NAME "\\\\.\\pipe\\symphony_sda_auto_update_ipc"
|
||||
#define BUFSIZE 512
|
||||
|
||||
// This holds data related to a single client instance
|
||||
struct ipc_client_t {
|
||||
HANDLE pipe;
|
||||
HANDLE pipe; // The named pipe to communicate over
|
||||
};
|
||||
|
||||
|
||||
ipc_client_t* ipc_client_connect() {
|
||||
if( !pipe_exists( PIPE_NAME ) ) {
|
||||
printf( "Named pipe does not exits\n" );
|
||||
// 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 ) {
|
||||
|
||||
// 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
|
||||
Sleep( 1000 );
|
||||
if( !pipe_exists( pipe_name ) ) {
|
||||
IPC_LOG( "Named pipe does not exist\n" );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Expand the pipe name to the valid form eg. "\\.\pipe\name"
|
||||
char expanded_pipe_name[ MAX_PATH ];
|
||||
if( !expand_pipe_name( pipe_name, expanded_pipe_name, sizeof( expanded_pipe_name ) ) ) {
|
||||
IPC_LOG( "Pipe name too long\n" );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 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( ; ; ) { // Keep trying to connect while pipe is busy
|
||||
pipe = CreateFile(
|
||||
PIPE_NAME, // pipe name
|
||||
GENERIC_READ | // read and write access
|
||||
for( ; ; ) { // This loop will typically not run more than two iterations, due to multiple exit points
|
||||
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
|
||||
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.
|
||||
// Break if the pipe handle is valid - a connection is now established
|
||||
if( pipe != INVALID_HANDLE_VALUE ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Exit if an error other than ERROR_PIPE_BUSY occurs.
|
||||
|
||||
// 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 ) {
|
||||
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 ) {
|
||||
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 ) {
|
||||
printf( "Could not open pipe. LastError=%d\n", GetLastError() );
|
||||
IPC_LOG( "Could not open pipe. LastError=%d\n", GetLastError() );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// All pipe instances are busy, so wait for 20 seconds.
|
||||
if( !WaitNamedPipe( PIPE_NAME, 20000 ) ) {
|
||||
printf( "Could not open pipe: 20 second wait timed out." );
|
||||
return NULL;
|
||||
if( !WaitNamedPipeA( expanded_pipe_name, 20000 ) ) {
|
||||
// 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
|
||||
Sleep(1000);
|
||||
if( !WaitNamedPipeA( expanded_pipe_name, 20000 ) ) {
|
||||
IPC_LOG( "Could not open pipe on second attempt: 20 second wait timed out. LastError=%d\n", GetLastError() );
|
||||
return NULL;
|
||||
}
|
||||
} else {
|
||||
IPC_LOG( "Could not open pipe: 20 second wait timed out. LastError=%d\n", GetLastError() );
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipc_client_t* connection = new ipc_client_t();
|
||||
// A fully working connection has been set up, return it to the caller
|
||||
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 ) {
|
||||
FlushFileBuffers( connection->pipe );
|
||||
DisconnectNamedPipe( connection->pipe );
|
||||
CloseHandle( connection->pipe );
|
||||
delete connection;
|
||||
free( connection );
|
||||
}
|
||||
|
||||
|
||||
bool ipc_client_send( ipc_client_t* connection, char const* message ) {
|
||||
DWORD mode = PIPE_READMODE_MESSAGE;
|
||||
BOOL success = SetNamedPipeHandleState(
|
||||
connection->pipe, // pipe handle
|
||||
&mode, // new pipe mode
|
||||
NULL, // don't set maximum bytes
|
||||
NULL ); // don't set maximum time
|
||||
if( !success ) {
|
||||
printf( "SetNamedPipeHandleState failed. LastError=%d\n", GetLastError() );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Send a message to the pipe server.
|
||||
DWORD written = 0;
|
||||
success = WriteFile(
|
||||
connection->pipe, // pipe handle
|
||||
message, // message
|
||||
(DWORD) strlen( message ) + 1, // message length
|
||||
&written, // bytes written
|
||||
NULL ); // not overlapped
|
||||
|
||||
if( !success ) {
|
||||
printf( "WriteFile to pipe failed. LastError=%d\n", GetLastError() );
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
enum ipc_receive_status_t {
|
||||
IPC_RECEIVE_STATUS_DONE,
|
||||
IPC_RECEIVE_STATUS_MORE_DATA,
|
||||
IPC_RECEIVE_STATUS_ERROR,
|
||||
};
|
||||
|
||||
// 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 ) {
|
||||
DWORD size_read = 0;
|
||||
BOOL success = ReadFile(
|
||||
@ -120,7 +211,7 @@ ipc_receive_status_t ipc_client_receive( ipc_client_t* connection, char* output,
|
||||
NULL ); // not overlapped
|
||||
|
||||
if( !success && GetLastError() != ERROR_MORE_DATA ) {
|
||||
printf( "ReadFile from pipe failed. LastError=%d\n", GetLastError() );
|
||||
IPC_LOG( "ReadFile from pipe failed. LastError=%d\n", GetLastError() );
|
||||
return IPC_RECEIVE_STATUS_ERROR;
|
||||
}
|
||||
|
||||
@ -136,163 +227,201 @@ ipc_receive_status_t ipc_client_receive( ipc_client_t* connection, char* output,
|
||||
}
|
||||
|
||||
|
||||
// This routine is a simple function to print the client request to the console
|
||||
// and populate the reply buffer with a default data string. This is where you
|
||||
// would put the actual client request processing code that runs in the context
|
||||
// of an instance thread. Keep in mind the main thread will continue to wait for
|
||||
// and receive other client connections while the instance thread is working.
|
||||
VOID run_installer( LPSTR pchRequest, LPSTR pchReply, LPDWORD pchBytes ) {
|
||||
printf( "Client Request String:\"%s\"\n", pchRequest );
|
||||
// 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.
|
||||
DWORD written = 0;
|
||||
BOOL success = WriteFile(
|
||||
connection->pipe, // pipe handle
|
||||
message, // message
|
||||
(DWORD) strlen( message ) + 1, // message length
|
||||
&written, // bytes written
|
||||
NULL ); // not overlapped
|
||||
|
||||
char command[ 512 ];
|
||||
sprintf( command, "/i %s /q", pchRequest );
|
||||
if( !success ) {
|
||||
IPC_LOG( "WriteFile to pipe failed. LastError=%d\n", GetLastError() );
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
ShellExecuteEx( &ShExecInfo );
|
||||
WaitForSingleObject( ShExecInfo.hProcess, INFINITE );
|
||||
CloseHandle( ShExecInfo.hProcess );
|
||||
|
||||
strcpy( pchReply, "OK" );
|
||||
*pchBytes = (DWORD)( ( strlen( pchReply ) + 1 ) * sizeof( CHAR ) );
|
||||
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 {
|
||||
HANDLE thread;
|
||||
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)
|
||||
};
|
||||
|
||||
|
||||
// This routine is a thread processing function to read from and reply to a client
|
||||
// via the open pipe connection passed from the main loop. Note this allows
|
||||
// the main loop to continue executing, potentially creating more threads of
|
||||
// this procedure to run concurrently, depending on the number of incoming
|
||||
// client connections.
|
||||
// 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 ) {
|
||||
HANDLE hHeap = GetProcessHeap();
|
||||
CHAR* pchRequest = (CHAR*) HeapAlloc( hHeap, 0, BUFSIZE * sizeof( CHAR ) );
|
||||
CHAR* pchReply = (CHAR*) HeapAlloc( hHeap, 0, BUFSIZE * sizeof( CHAR ) );
|
||||
|
||||
ipc_client_thread_t* context = (ipc_client_thread_t*) param;
|
||||
|
||||
DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0;
|
||||
// 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
|
||||
);
|
||||
|
||||
// Do some extra error checking since the app will keep running even if this
|
||||
// thread fails.
|
||||
// Main request-response loop. Will run until exit requested or an eror occurs
|
||||
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;
|
||||
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
|
||||
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
|
||||
|
||||
// Check if the Read operation is in progress (ReadFile returns FALSE and the error is ERROR_IO_PENDING )
|
||||
if( !success && GetLastError() == ERROR_IO_PENDING ) {
|
||||
// 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
|
||||
if( WaitForSingleObject( io_event, 500 ) == WAIT_TIMEOUT ) {
|
||||
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)
|
||||
success = GetOverlappedResult(
|
||||
context->pipe, // handle to pipe
|
||||
&context->io, // OVERLAPPED structure
|
||||
&bytes_read, // bytes transferred
|
||||
FALSE ); // don't wait
|
||||
}
|
||||
|
||||
if( param == NULL ) {
|
||||
printf( "\nERROR - Pipe Server Failure:\n" );
|
||||
printf( " ipc_client_thread got an unexpected NULL value in lpvParam.\n" );
|
||||
printf( " ipc_client_thread exitting.\n" );
|
||||
if( pchReply != NULL ) {
|
||||
HeapFree( hHeap, 0, pchReply );
|
||||
// The read have completed (successfully or not) so exit the read loop
|
||||
read_pending = false;
|
||||
}
|
||||
if( pchRequest != NULL ) {
|
||||
HeapFree( hHeap, 0, pchRequest );
|
||||
}
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if( pchRequest == NULL ) {
|
||||
printf( "\nERROR - Pipe Server Failure:\n" );
|
||||
printf( " ipc_client_thread got an unexpected NULL heap allocation.\n" );
|
||||
printf( " ipc_client_thread exitting.\n" );
|
||||
if( pchReply != NULL ) {
|
||||
HeapFree( hHeap, 0, pchReply );
|
||||
}
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if( pchReply == NULL ) {
|
||||
printf( "\nERROR - Pipe Server Failure:\n");
|
||||
printf( " ipc_client_thread got an unexpected NULL heap allocation.\n" );
|
||||
printf( " ipc_client_thread exitting.\n");
|
||||
if( pchRequest != NULL ) {
|
||||
HeapFree( hHeap, 0, pchRequest );
|
||||
}
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Print verbose messages. In production code, this should be for debugging only.
|
||||
printf( "ipc_client_thread created, receiving and processing messages.\n" );
|
||||
|
||||
// The thread's parameter is a handle to a pipe object instance.
|
||||
HANDLE hPipe = (HANDLE) param;
|
||||
|
||||
// Loop until done reading
|
||||
for( ; ; ) {
|
||||
// Read client requests from the pipe. This simplistic code only allows messages
|
||||
// up to BUFSIZE characters in length.
|
||||
BOOL fSuccess = ReadFile(
|
||||
hPipe, // handle to pipe
|
||||
pchRequest, // buffer to receive data
|
||||
BUFSIZE * sizeof( CHAR ), // size of buffer
|
||||
&cbBytesRead, // number of bytes read
|
||||
NULL ); // not overlapped I/O
|
||||
|
||||
if( !fSuccess || cbBytesRead == 0 ) {
|
||||
|
||||
// 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) {
|
||||
printf( "ipc_client_thread: client disconnected.\n" );
|
||||
//IPC_LOG( "ipc_client_thread: client disconnected.\n" );
|
||||
} else {
|
||||
printf( "ipc_client_thread ReadFile failed, LastError=%d.\n", GetLastError() );
|
||||
IPC_LOG( "ipc_client_thread ReadFile failed, LastError=%d.\n", GetLastError() );
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Process the incoming message.
|
||||
run_installer( pchRequest, pchReply, &cbReplyBytes );
|
||||
|
||||
// Write the reply to the pipe.
|
||||
fSuccess = WriteFile(
|
||||
hPipe, // handle to pipe
|
||||
pchReply, // buffer to write from
|
||||
cbReplyBytes, // number of bytes to write
|
||||
&cbWritten, // number of bytes written
|
||||
NULL ); // not overlapped I/O
|
||||
// Check if a server shutdown have requested this thread to be terminated, and exit if that's the case
|
||||
if( context->exit_flag ) {
|
||||
break;
|
||||
}
|
||||
|
||||
if( !fSuccess || cbReplyBytes != cbWritten ) {
|
||||
printf( ("ipc_client_thread WriteFile failed, LastError=%d.\n"), GetLastError());
|
||||
// Process the incoming message by calling the user-supplied request handler function
|
||||
char response[ IPC_MESSAGE_MAX_LENGTH ];
|
||||
memset( response, 0, sizeof( response ) );
|
||||
context->request_handler( request, context->user_data, response, sizeof( response ) );
|
||||
response[ sizeof( response ) - 1 ] = '\0'; // Force zero termination (truncate string)
|
||||
// TODO: Do we need to handle this better? Log it?
|
||||
DWORD response_length = (DWORD)strlen( response ) + 1;
|
||||
|
||||
// Write the reply to the pipe
|
||||
DWORD bytes_written = 0;
|
||||
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
|
||||
|
||||
// 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 ) {
|
||||
success = GetOverlappedResult(
|
||||
context->pipe, // handle to pipe
|
||||
&context->io, // OVERLAPPED structure
|
||||
&bytes_written, // bytes transferred
|
||||
TRUE ); // wait
|
||||
}
|
||||
|
||||
// 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( ("ipc_client_thread WriteFile failed, LastError=%d.\n"), GetLastError());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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.
|
||||
|
||||
FlushFileBuffers( hPipe );
|
||||
DisconnectNamedPipe( hPipe );
|
||||
CloseHandle( hPipe );
|
||||
|
||||
HeapFree( hHeap, 0, pchRequest );
|
||||
HeapFree( hHeap, 0, pchReply );
|
||||
|
||||
printf( "ipc_client_thread exiting.\n" );
|
||||
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;
|
||||
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_server_t* server = (ipc_server_t*) param;
|
||||
|
||||
// TODO: logging
|
||||
//fp = fopen( "C:\\auto_update_poc\\log.txt", "w" );
|
||||
//setvbuf(fp, NULL, _IONBF, 0);
|
||||
|
||||
// Create security attribs
|
||||
// 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 ) ) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
EXPLICIT_ACCESS access = { 0 };
|
||||
access.grfAccessPermissions = FILE_ALL_ACCESS;
|
||||
access.grfAccessMode = SET_ACCESS;
|
||||
@ -300,126 +429,264 @@ DWORD WINAPI ipc_server_thread( LPVOID param ) {
|
||||
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 ) {
|
||||
FreeSid(sid);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR)LocalAlloc( LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH );
|
||||
if( !sd ) {
|
||||
FreeSid( sid );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if( !InitializeSecurityDescriptor( sd, SECURITY_DESCRIPTOR_REVISION ) ) {
|
||||
LocalFree(sd);
|
||||
FreeSid(sid);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if( !SetSecurityDescriptorDacl( sd, TRUE, acl, FALSE ) ) {
|
||||
LocalFree( sd );
|
||||
FreeSid( sid );
|
||||
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. It is an infinite loop.
|
||||
|
||||
for( ; ; ) {
|
||||
printf( "\nPipe Server: Main thread awaiting client connection on %s\n", PIPE_NAME );
|
||||
HANDLE hPipe = CreateNamedPipe(
|
||||
PIPE_NAME, // pipe name
|
||||
PIPE_ACCESS_DUPLEX, // read/write access
|
||||
// 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
|
||||
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
|
||||
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
|
||||
PIPE_UNLIMITED_INSTANCES, // max. instances
|
||||
BUFSIZE, // output buffer size
|
||||
BUFSIZE, // input buffer size
|
||||
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( hPipe == INVALID_HANDLE_VALUE ) {
|
||||
printf( "CreateNamedPipe failed, LastError=%d.\n", GetLastError() );
|
||||
// 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 ) {
|
||||
IPC_LOG( "CreateNamedPipe failed, LastError=%d.\n", GetLastError() );
|
||||
LocalFree( acl );
|
||||
LocalFree( sd );
|
||||
FreeSid( sid );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Wait for the client to connect; if it succeeds,
|
||||
// the function returns a nonzero value. If the function
|
||||
// returns zero, GetLastError returns ERROR_PIPE_CONNECTED.
|
||||
|
||||
BOOL fConnected = ConnectNamedPipe( hPipe, NULL ) ?
|
||||
TRUE : ( GetLastError() == ERROR_PIPE_CONNECTED );
|
||||
|
||||
if( fConnected ) {
|
||||
printf( "Client connected, creating a processing thread.\n" );
|
||||
|
||||
// Create a thread for this client.
|
||||
DWORD dwThreadId = 0;
|
||||
HANDLE hThread = CreateThread(
|
||||
NULL, // no security attribute
|
||||
0, // default stack size
|
||||
ipc_client_thread, // thread proc
|
||||
(LPVOID) hPipe, // thread parameter
|
||||
0, // not suspended
|
||||
&dwThreadId ); // returns thread ID
|
||||
// Signal to `ipc_server_start` that the server thread is now fully up and
|
||||
// running and accepting connections
|
||||
if( !event_raised ) {
|
||||
SetEvent( server->thread_started_event );
|
||||
event_raised = true; // Make sure we don't signal the event again
|
||||
}
|
||||
|
||||
if( hThread == NULL ) {
|
||||
printf( "CreateThread failed, LastError=%d.\n", GetLastError() );
|
||||
// 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;
|
||||
ConnectNamedPipe( server->pipe, &server->io );
|
||||
if( GetLastError() == ERROR_IO_PENDING ) {
|
||||
for( ; ; ) {
|
||||
if( WaitForSingleObject( server->io.hEvent, 100 ) == WAIT_OBJECT_0 ) {
|
||||
break;
|
||||
}
|
||||
if( server->exit_flag ) {
|
||||
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( "Connection failed. LastError=%d\n",GetLastError() );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if a server shutdown have requested this thread to be terminated, and exit if that's the case
|
||||
if( server->exit_flag ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// 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 ) {
|
||||
if( server->client_threads_count < MAX_CLIENT_CONNECTIONS ) {
|
||||
context = &server->client_threads[ server->client_threads_count++ ];
|
||||
} else {
|
||||
// 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( "Too many connections\n" );
|
||||
LocalFree( acl );
|
||||
LocalFree( sd );
|
||||
FreeSid( sid );
|
||||
if( server->pipe != INVALID_HANDLE_VALUE ) {
|
||||
CloseHandle( server->pipe );
|
||||
server->pipe = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
CloseHandle( io_event );
|
||||
return EXIT_FAILURE;
|
||||
} else {
|
||||
CloseHandle( hThread );
|
||||
}
|
||||
} else {
|
||||
// The client could not connect, so close the pipe.
|
||||
CloseHandle( hPipe );
|
||||
}
|
||||
|
||||
// Initialize the client slot
|
||||
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
|
||||
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( "CreateThread failed, LastError=%d.\n", GetLastError() );
|
||||
LocalFree( acl );
|
||||
LocalFree( sd );
|
||||
FreeSid( sid );
|
||||
if( server->pipe != INVALID_HANDLE_VALUE ) {
|
||||
CloseHandle( server->pipe );
|
||||
server->pipe = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
CloseHandle( io_event );
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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 );
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
ipc_server_t* ipc_server_start() {
|
||||
DWORD threadId = 0;
|
||||
HANDLE thread = CreateThread(
|
||||
// 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 ) {
|
||||
|
||||
// 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"
|
||||
if( !expand_pipe_name( pipe_name, server->expanded_pipe_name, sizeof( server->expanded_pipe_name ) ) ) {
|
||||
IPC_LOG( "Pipe name too long\n" );
|
||||
free( server );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 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
|
||||
server->thread = CreateThread(
|
||||
NULL, // default security attributes
|
||||
0, // use default stack size
|
||||
ipc_server_thread, // thread function name
|
||||
NULL, // argument to thread function
|
||||
server, // argument to thread function
|
||||
0, // use default creation flags
|
||||
&threadId ); // returns the thread identifier
|
||||
NULL ); // returns the thread identifier
|
||||
|
||||
ipc_server_t* server = new ipc_server_t;
|
||||
server->thread = thread;
|
||||
// If thread creation failed, return error
|
||||
if( server->thread == NULL ) {
|
||||
IPC_LOG( "Failed to create server thread\n" );
|
||||
CloseHandle( server->thread_started_event );
|
||||
free( server );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Wait for the server thread to be up and running and accepting connections
|
||||
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( "Timeout waiting for client thread to start\n" );
|
||||
CloseHandle( server->thread_started_event );
|
||||
TerminateThread( server->thread, EXIT_FAILURE );
|
||||
free( server );
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// 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 ) {
|
||||
TerminateThread( server->thread, EXIT_SUCCESS );
|
||||
WaitForSingleObject( server->thread, INFINITE );
|
||||
delete 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
|
||||
}
|
||||
WaitForSingleObject( server->thread, INFINITE ); // Wait for server thread to exit
|
||||
|
||||
// Loop over all clients and terminate each one
|
||||
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 );
|
||||
}
|
||||
|
||||
#endif /* IPC_IMPLEMENTATION */
|
||||
|
17
auto_update/run_loop.bat
Normal file
17
auto_update/run_loop.bat
Normal file
@ -0,0 +1,17 @@
|
||||
@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
|
@ -3,7 +3,7 @@
|
||||
Licensing information can be found at the end of the file.
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
testfw.h - v1.0 - Basic test framwework for C/C++.
|
||||
testfw.h - v1.1 - Basic test framwework for C/C++.
|
||||
|
||||
Do this:
|
||||
#define TESTFW_IMPLEMENTATION
|
||||
@ -15,8 +15,14 @@ before you include this file in *one* C/C++ file to create the implementation.
|
||||
|
||||
#define TESTFW_INIT() testfw_init()
|
||||
#define TESTFW_SUMMARY() testfw_summary( __FILE__, __func__, __LINE__ )
|
||||
#define TESTFW_TEST_BEGIN( desc ) testfw_test_begin( desc, __FILE__, __func__, __LINE__ );
|
||||
#define TESTFW_TEST_END() testfw_test_end( __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();
|
||||
@ -28,6 +34,9 @@ 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 */
|
||||
@ -340,6 +349,98 @@ void testfw_test_end( char const* filename, char const* funcname, int line )
|
||||
}
|
||||
|
||||
|
||||
#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;
|
||||
|
@ -5,7 +5,13 @@
|
||||
#endif
|
||||
#include "testfw.h"
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <windows.h>
|
||||
|
||||
#include "ipc.h"
|
||||
|
||||
bool pipe_exists( const char* pipe_name );
|
||||
|
||||
void test_fw_ok() {
|
||||
TESTFW_TEST_BEGIN( "Checking that test framework is ok" );
|
||||
@ -14,15 +20,214 @@ void test_fw_ok() {
|
||||
}
|
||||
|
||||
|
||||
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 ) {
|
||||
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 ) {
|
||||
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 ) {
|
||||
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 ) {
|
||||
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"
|
||||
|
||||
|
@ -72,21 +72,25 @@
|
||||
<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>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@ -100,7 +104,7 @@
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@ -114,7 +118,7 @@
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@ -128,7 +132,7 @@
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>_CRTDBG_MAP_ALLOC;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
@ -139,6 +143,7 @@
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="ipc.h" />
|
||||
<ClInclude Include="testfw.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
Loading…
Reference in New Issue
Block a user