diff --git a/auto_update/auto_update_helper.cpp b/auto_update/auto_update_helper.cpp index 812ee4b6..b0ca7438 100644 --- a/auto_update/auto_update_helper.cpp +++ b/auto_update/auto_update_helper.cpp @@ -8,6 +8,7 @@ #include #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" + diff --git a/auto_update/auto_update_helper.vcxproj b/auto_update/auto_update_helper.vcxproj index ac5fb195..cb4e21c1 100644 --- a/auto_update/auto_update_helper.vcxproj +++ b/auto_update/auto_update_helper.vcxproj @@ -70,7 +70,18 @@ - + + $(Platform)\$(Configuration)\auto_update_helper\ + + + $(Platform)\$(Configuration)\auto_update_helper\ + + + $(Platform)\$(Configuration)\auto_update_helper\ + + + $(Platform)\$(Configuration)\auto_update_helper\ + Level3 diff --git a/auto_update/auto_update_service.cpp b/auto_update/auto_update_service.cpp index 44e7feee..17ff0892 100644 --- a/auto_update/auto_update_service.cpp +++ b/auto_update/auto_update_service.cpp @@ -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" + diff --git a/auto_update/auto_update_service.vcxproj b/auto_update/auto_update_service.vcxproj index 1bfe9c8e..a1060d6f 100644 --- a/auto_update/auto_update_service.vcxproj +++ b/auto_update/auto_update_service.vcxproj @@ -69,7 +69,18 @@ - + + $(Platform)\$(Configuration)\auto_update_service\ + + + $(Platform)\$(Configuration)\auto_update_service\ + + + $(Platform)\$(Configuration)\auto_update_service\ + + + $(Platform)\$(Configuration)\auto_update_service\ + Level3 @@ -125,6 +136,9 @@ + + + diff --git a/auto_update/ipc.h b/auto_update/ipc.h index b208868f..8a04de39 100644 --- a/auto_update/ipc.h +++ b/auto_update/ipc.h @@ -1,7 +1,60 @@ +#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 +#include +#include + +#pragma comment(lib, "advapi32.lib") + +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; +} 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,37 +72,65 @@ bool pipe_exists( const char* pipe_name ) { return false; } -#define PIPE_NAME "\\\\.\\pipe\\symphony_sda_auto_update_ipc" -#define BUFSIZE 512 struct ipc_client_t { HANDLE pipe; }; -ipc_client_t* ipc_client_connect() { - if( !pipe_exists( PIPE_NAME ) ) { - printf( "Named pipe does not exits\n" ); +ipc_client_t* ipc_client_connect( char const* pipe_name ) { + if( !pipe_exists( pipe_name ) ) { + // Retry once if pipe was not found + Sleep( 1000 ); + if( !pipe_exists( pipe_name ) ) { + printf( "Named pipe does not exist\n" ); + return NULL; + } + } + + char expanded_pipe_name[ MAX_PATH ]; + if( !expand_pipe_name( pipe_name, expanded_pipe_name, sizeof( expanded_pipe_name ) ) ) { + printf( "Pipe name too long\n" ); return NULL; } HANDLE pipe = NULL; for( ; ; ) { // Keep trying to connect while pipe is busy - pipe = CreateFile( - PIPE_NAME, // pipe name - GENERIC_READ | // read and write access + 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. if( pipe != INVALID_HANDLE_VALUE ) { break; } - + + // Retry once if pipe was not found + if( GetLastError() == ERROR_FILE_NOT_FOUND ) { + Sleep( 1000 ); + printf( "retry\n" ); + 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. + if( pipe != INVALID_HANDLE_VALUE ) { + break; + } + } + + // Exit if an error other than ERROR_PIPE_BUSY occurs. if( GetLastError() != ERROR_PIPE_BUSY ) { printf( "Could not open pipe. LastError=%d\n", GetLastError() ); @@ -57,21 +138,57 @@ ipc_client_t* ipc_client_connect() { } // 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 ) ) { + if( GetLastError() == ERROR_FILE_NOT_FOUND ) { + // retry once just in case pipe was not created yet + Sleep(1000); + if( !WaitNamedPipeA( expanded_pipe_name, 20000 ) ) { + printf( "Could not open pipe on second attempt: 20 second wait timed out. LastError=%d\n", GetLastError() ); + return NULL; + } + } else { + printf( "Could not open pipe: 20 second wait timed out. LastError=%d\n", GetLastError() ); + return NULL; + } } } - - ipc_client_t* connection = new ipc_client_t(); + ipc_client_t* connection = (ipc_client_t*) malloc( sizeof( ipc_client_t ) ); connection->pipe = pipe; return connection; } void ipc_client_disconnect( ipc_client_t* connection ) { + FlushFileBuffers( connection->pipe ); + DisconnectNamedPipe( connection->pipe ); CloseHandle( connection->pipe ); - delete connection; + free( connection ); +} + + +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( + connection->pipe, // pipe handle + output, // buffer to receive reply + output_size, // size of buffer + &size_read, // number of bytes read + NULL ); // not overlapped + + if( !success && GetLastError() != ERROR_MORE_DATA ) { + printf( "ReadFile from pipe failed. LastError=%d\n", GetLastError() ); + return IPC_RECEIVE_STATUS_ERROR; + } + + if( received_size ) { + *received_size = size_read; + } + + if( success ) { + return IPC_RECEIVE_STATUS_DONE; + } else { + return IPC_RECEIVE_STATUS_MORE_DATA; + } } @@ -104,71 +221,33 @@ bool ipc_client_send( ipc_client_t* connection, char const* message ) { return true; } -enum ipc_receive_status_t { - IPC_RECEIVE_STATUS_DONE, - IPC_RECEIVE_STATUS_MORE_DATA, - IPC_RECEIVE_STATUS_ERROR, -}; - -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( - connection->pipe, // pipe handle - output, // buffer to receive reply - output_size, // size of buffer - &size_read, // number of bytes read - NULL ); // not overlapped - - if( !success && GetLastError() != ERROR_MORE_DATA ) { - printf( "ReadFile from pipe failed. LastError=%d\n", GetLastError() ); - return IPC_RECEIVE_STATUS_ERROR; - } - - if( received_size ) { - *received_size = size_read; - } - - if( success ) { - return IPC_RECEIVE_STATUS_DONE; - } else { - return IPC_RECEIVE_STATUS_MORE_DATA; - } -} - - -// 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 ); - - char command[ 512 ]; - sprintf( command, "/i %s /q", pchRequest ); - - 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 ) ); -} +typedef struct ipc_client_thread_t { + BOOL recycle; + ipc_server_t* server; + HANDLE thread; + HANDLE thread_started_event; + HANDLE pipe; + OVERLAPPED io; + int exit_flag; +} ipc_client_thread_t; +#define MAX_CLIENT_CONNECTIONS 32 struct ipc_server_t { + char expanded_pipe_name[ MAX_PATH ]; + PSID sid; + PACL acl; + PSECURITY_DESCRIPTOR sd; HANDLE thread; + HANDLE thread_started_event; + HANDLE pipe; + OVERLAPPED io; + int exit_flag; + ipc_request_handler_t request_handler; + void* user_data; + ipc_client_thread_t client_threads[ MAX_CLIENT_CONNECTIONS ]; + int client_threads_count; }; @@ -178,106 +257,127 @@ struct ipc_server_t { // this procedure to run concurrently, depending on the number of incoming // client connections. 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 ) ); - - DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0; - - // Do some extra error checking since the app will keep running even if this - // thread fails. - - 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 ); - } - 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" ); + //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; + ipc_client_thread_t* context= (ipc_client_thread_t*) param; + ipc_server_t* server = context->server; + HANDLE hPipe = context->pipe; + HANDLE hEvent = CreateEvent( + NULL, // default security attributes + TRUE, // manual-reset event + FALSE, // initial state is nonsignaled + NULL // object name + ); + //printf( "Thread started\n" ); + SetEvent( context->thread_started_event ); // 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 + CHAR pchRequest[ IPC_MESSAGE_MAX_LENGTH ]; + for( ; ; ) { + // Read client requests from the pipe. This simplistic code only allows messages + // up to IPC_MESSAGE_MAX_LENGTH characters in length. + //printf( "ipc_client_thread: reading.\n" ); + DWORD cbBytesRead = 0; + memset( &context->io, 0, sizeof( context->io ) ); + ResetEvent( hEvent ); + context->io.hEvent = hEvent; + BOOL fSuccess = ReadFile( + hPipe, // handle to pipe + pchRequest, // buffer to receive data + IPC_MESSAGE_MAX_LENGTH * sizeof( CHAR ), // size of buffer + &cbBytesRead, // number of bytes read + &context->io ); // overlapped I/O + //printf( "ipc_client_thread: read.\n" ); - if( !fSuccess || cbBytesRead == 0 ) { - if (GetLastError() == ERROR_BROKEN_PIPE) { - printf( "ipc_client_thread: client disconnected.\n" ); - } else { - printf( "ipc_client_thread ReadFile failed, LastError=%d.\n", GetLastError() ); + if( !fSuccess && GetLastError() == ERROR_IO_PENDING ) { + if( WaitForSingleObject( hEvent, 500 ) == WAIT_TIMEOUT ) { + CancelIoEx( context->pipe, &context->io ); + printf( "\n\nRETRY\n\n"); + continue; + } + fSuccess = GetOverlappedResult( + hPipe, // handle to pipe + &context->io, // OVERLAPPED structure + &cbBytesRead, // bytes transferred + FALSE ); // wait } + + if( !fSuccess || cbBytesRead == 0 ) { + if (GetLastError() == ERROR_BROKEN_PIPE) { + //printf( "ipc_client_thread: client disconnected.\n" ); + } else { + printf( "ipc_client_thread ReadFile failed, LastError=%d.\n", GetLastError() ); + } + goto exit_client_thread; + } + + if( context->exit_flag ) { + goto exit_client_thread; + } + break; } // Process the incoming message. - run_installer( pchRequest, pchReply, &cbReplyBytes ); + char pchReply[ IPC_MESSAGE_MAX_LENGTH ]; + memset( pchReply, 0, sizeof( pchReply ) ); + server->request_handler( pchRequest, server->user_data, pchReply, sizeof( pchReply ) ); + pchReply[ IPC_MESSAGE_MAX_LENGTH - 1 ] = '\0'; // Force zero termination (truncate string) + DWORD cbReplyBytes = (DWORD)strlen(pchReply) + 1; // Write the reply to the pipe. - fSuccess = WriteFile( + //printf( "ipc_client_thread: writing.\n" ); + DWORD cbWritten = 0; + BOOL 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 + &context->io ); // overlapped I/O + //printf( "ipc_client_thread: writ.\n" ); + if( fSuccess || GetLastError() == ERROR_IO_PENDING ) { + fSuccess = GetOverlappedResult( + hPipe, // handle to pipe + &context->io, // OVERLAPPED structure + &cbWritten, // bytes transferred + FALSE); // wait + } + if( !fSuccess || cbReplyBytes != cbWritten ) { printf( ("ipc_client_thread WriteFile failed, LastError=%d.\n"), GetLastError()); break; } - } + if( context->exit_flag ) { + break; + } + } +exit_client_thread: + //printf( "ipc_client_thread cleanup.\n" ); + // 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( hEvent ); FlushFileBuffers( hPipe ); DisconnectNamedPipe( hPipe ); CloseHandle( hPipe ); - - HeapFree( hHeap, 0, pchRequest ); - HeapFree( hHeap, 0, pchReply ); - - printf( "ipc_client_thread exiting.\n" ); + context->pipe = INVALID_HANDLE_VALUE; + context->recycle = TRUE; + //printf( "ipc_client_thread exiting.\n" ); return EXIT_SUCCESS; } +void ipc_stop_client_thread( ipc_client_thread_t* thread_context ) { + thread_context->exit_flag = 1; + CancelIoEx( thread_context->pipe, &thread_context->io ); + WaitForSingleObject( thread_context->thread, INFINITE ); +} DWORD WINAPI ipc_server_thread( LPVOID param ) { ipc_server_t* server = (ipc_server_t*) param; @@ -286,11 +386,147 @@ DWORD WINAPI ipc_server_thread( LPVOID param ) { //fp = fopen( "C:\\auto_update_poc\\log.txt", "w" ); //setvbuf(fp, NULL, _IONBF, 0); + SECURITY_ATTRIBUTES attribs; + attribs.nLength = sizeof( SECURITY_ATTRIBUTES ); + attribs.lpSecurityDescriptor = server->sd; + attribs.bInheritHandle = -1; + + // 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. + bool event_raised = false; + while( !server->exit_flag ) { + //printf( "\nPipe Server: Main thread awaiting client connection on %s\n", server->expanded_pipe_name ); + server->pipe = CreateNamedPipeA( + server->expanded_pipe_name,// pipe name + PIPE_ACCESS_DUPLEX | // read/write access + FILE_FLAG_OVERLAPPED, + 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 ) { + printf( "CreateNamedPipe failed, LastError=%d.\n", GetLastError() ); + return EXIT_FAILURE; + } + + if( !event_raised ) { + SetEvent( server->thread_started_event ); + event_raised = true; + } + + // 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. + memset( &server->io, 0, sizeof( server->io ) ); + server->io.hEvent = CreateEvent( + NULL, // default security attributes + TRUE, // manual-reset event + FALSE, // initial state is nonsignaled + NULL // object name + ); + 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. + printf( "Connection failed. LastError=%d\n",GetLastError() ); + CloseHandle( server->io.hEvent ); + break; + } + } + CloseHandle( server->io.hEvent ); + if( server->exit_flag ) { + break; + } + + //printf( "Client connected, creating a processing thread.\n" ); + // Create a thread for this client. + 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( !context ) { + if( server->client_threads_count == MAX_CLIENT_CONNECTIONS ) { + printf( "Too many connections\n" ); + if( server->pipe != INVALID_HANDLE_VALUE ) { + CloseHandle( server->pipe ); + server->pipe = INVALID_HANDLE_VALUE; + } + + return EXIT_FAILURE; + } else { + context = &server->client_threads[ server->client_threads_count++ ]; + } + } + memset( context, 0, sizeof( *context ) ); + context->server = server; + context->pipe = server->pipe; + server->pipe = INVALID_HANDLE_VALUE; + context->thread_started_event = CreateEvent( + NULL, // default security attributes + TRUE, // manual-reset event + FALSE, // initial state is nonsignaled + NULL // object name + ); + + 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( context->thread == NULL ) { + printf( "CreateThread failed, LastError=%d.\n", GetLastError() ); + return EXIT_FAILURE; + } + //printf( "Waiting for thread\n" ); + WaitForSingleObject( context->thread_started_event, INFINITE ); + } + + if( server->pipe != INVALID_HANDLE_VALUE ) { + CloseHandle( server->pipe ); + server->pipe = INVALID_HANDLE_VALUE; + } + + return EXIT_SUCCESS; +} + + +ipc_server_t* ipc_server_start( char const* pipe_name, ipc_request_handler_t request_handler, void* user_data ) { + ipc_server_t* server = (ipc_server_t*) malloc( sizeof( ipc_server_t ) ); + memset( server, 0, sizeof( ipc_server_t ) ); + server->pipe = INVALID_HANDLE_VALUE; + if( !expand_pipe_name( pipe_name, server->expanded_pipe_name, sizeof( server->expanded_pipe_name ) ) ) { + printf( "Pipe name too long\n" ); + free( server ); + return NULL; + } + // Create security attribs 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; + if( !AllocateAndInitializeSid( &auth, 1, SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, &server->sid ) ) { + free( server ); + return NULL; } EXPLICIT_ACCESS access = { 0 }; @@ -299,127 +535,87 @@ DWORD WINAPI ipc_server_thread( LPVOID param ) { access.grfInheritance = NO_INHERITANCE; access.Trustee.TrusteeForm = TRUSTEE_IS_SID; access.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; - access.Trustee.ptstrName = (LPTSTR)sid; + access.Trustee.ptstrName = (LPTSTR)server->sid; - PACL acl; - if( SetEntriesInAcl(1, &access, NULL, &acl) != ERROR_SUCCESS ) { - FreeSid(sid); - return EXIT_FAILURE; + if( SetEntriesInAcl(1, &access, NULL, &server->acl) != ERROR_SUCCESS ) { + FreeSid( server->sid ); + free( server ); + return NULL; } - PSECURITY_DESCRIPTOR sd = (PSECURITY_DESCRIPTOR)LocalAlloc( LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH ); - if( !sd ) { - FreeSid( sid ); - return EXIT_FAILURE; + server->sd = (PSECURITY_DESCRIPTOR)LocalAlloc( LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH ); + if( !server->sd ) { + FreeSid( server->sid ); + free( server ); + return NULL; } - if( !InitializeSecurityDescriptor( sd, SECURITY_DESCRIPTOR_REVISION ) ) { - LocalFree(sd); - FreeSid(sid); - return EXIT_FAILURE; + if( !InitializeSecurityDescriptor( server->sd, SECURITY_DESCRIPTOR_REVISION ) ) { + LocalFree(server->sd); + FreeSid( server->sid ); + free( server ); + return NULL; } - if( !SetSecurityDescriptorDacl( sd, TRUE, acl, FALSE ) ) { - LocalFree( sd ); - FreeSid( sid ); - return EXIT_FAILURE; + if( !SetSecurityDescriptorDacl( server->sd, TRUE, server->acl, FALSE ) ) { + LocalFree( server->sd ); + FreeSid( server->sid ); + free( server ); + return NULL; } - SECURITY_ATTRIBUTES attribs; - attribs.nLength = sizeof( SECURITY_ATTRIBUTES ); - attribs.lpSecurityDescriptor = sd; - attribs.bInheritHandle = -1; - - - // 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 - 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 - 0, // client time-out - &attribs ); // default security attribute - - if( hPipe == INVALID_HANDLE_VALUE ) { - printf( "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 - - if( hThread == NULL ) { - printf( "CreateThread failed, LastError=%d.\n", GetLastError() ); - LocalFree( acl ); - LocalFree( sd ); - FreeSid( sid ); - return EXIT_FAILURE; - } else { - CloseHandle( hThread ); - } - } else { - // The client could not connect, so close the pipe. - CloseHandle( hPipe ); - } - } - - - LocalFree( acl ); - LocalFree( sd ); - FreeSid( sid ); - return EXIT_SUCCESS; -} - - -ipc_server_t* ipc_server_start() { - DWORD threadId = 0; - HANDLE thread = CreateThread( + server->request_handler = request_handler; + server->user_data = user_data; + server->thread_started_event = CreateEvent( + NULL, // default security attributes + TRUE, // manual-reset event + FALSE, // initial state is nonsignaled + NULL // object name + ); + DWORD threadId = 0; + 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 + if( server->thread == NULL ) { + printf( "Failed to create server thread\n" ); + LocalFree( server->acl ); + LocalFree( server->sd ); + FreeSid( server->sid ); + free( server ); + return NULL; + } - ipc_server_t* server = new ipc_server_t; - server->thread = thread; + if( WaitForSingleObject( server->thread_started_event, 10000 ) != WAIT_OBJECT_0 ) { + printf( "Timeout waiting for client thread to start\n" ); + LocalFree( server->acl ); + LocalFree( server->sd ); + FreeSid( server->sid ); + free( server ); + return NULL; + } return server; } void ipc_server_stop( ipc_server_t* server ) { - TerminateThread( server->thread, EXIT_SUCCESS ); + server->exit_flag = 1; + if( server->pipe != INVALID_HANDLE_VALUE ) { + CancelIoEx( server->pipe, &server->io ); + } WaitForSingleObject( server->thread, INFINITE ); - delete server; + LocalFree( server->acl ); + LocalFree( server->sd ); + FreeSid( server->sid ); + for( int i = 0; i < server->client_threads_count; ++i ) { + if( !server->client_threads[ i ].recycle ) { + ipc_stop_client_thread( &server->client_threads[ i ] ); + } + } + free( server ); } + +#endif /* IPC_IMPLEMENTATION */ diff --git a/auto_update/run_loop.bat b/auto_update/run_loop.bat new file mode 100644 index 00000000..ba203905 --- /dev/null +++ b/auto_update/run_loop.bat @@ -0,0 +1,8 @@ +REM runs the tests in an infinite loop +REM intended to be run manually and left running for a long time, +REM as a final sanity check to make sure the tests are stable +REM will exit if any tests fail +:loop + call npm run test + if %ERRORLEVEL% NEQ 0 goto :eof +goto :loop \ No newline at end of file diff --git a/auto_update/tests.cpp b/auto_update/tests.cpp index be3d4ce5..def19ffe 100644 --- a/auto_update/tests.cpp +++ b/auto_update/tests.cpp @@ -5,7 +5,13 @@ #endif #include "testfw.h" #include +#include +#include +#include +#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" + diff --git a/auto_update/tests.vcxproj b/auto_update/tests.vcxproj index edb11841..551b247b 100644 --- a/auto_update/tests.vcxproj +++ b/auto_update/tests.vcxproj @@ -72,21 +72,25 @@ true + $(Platform)\$(Configuration)\tests\ false + $(Platform)\$(Configuration)\tests\ true + $(Platform)\$(Configuration)\tests\ false + $(Platform)\$(Configuration)\tests\ Level3 true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true @@ -100,7 +104,7 @@ true true true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true @@ -114,7 +118,7 @@ Level3 true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true @@ -128,7 +132,7 @@ true true true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + _CRTDBG_MAP_ALLOC;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true @@ -139,6 +143,7 @@ +