#include "cli.h" #ifndef _WIN32 #include #endif #include #include #ifndef _WIN32 #include "linenoise.h" #endif #ifndef _WIN32 #include "http_lib.h" #endif #include "json-c/json.h" #include #include #include #define HISTORY_FILE ".biruda_history" #include "coordinator.h" #include "worker.h" #include "system.h" #ifndef _WIN32 typedef enum { COMMAND, // command parsing START_WORKER, // worker commands expect a worker (and STOP_WORKER, // optionally an architecture and os parameter) MESSAGES_WORKER, WAIT // wait state, allow autodisp to work, but wait // for commands for n seconds (handy in batches // and for testing) } command_state_t; typedef enum { WORKER_ILLEGAL, // not in a worker related state WORKER_NAME, // name of the worker WORKER_OS, // in case there is more than one worker with that name WORKER_ARCH, // architecture, if still ambiguos WORKER_EXECUTE // all data, execute worker command now } command_worker_state_t; static char *commands[] = { "help", "quit", "status", "start", "stop", "autodisp", "messages", "wait", NULL }; static bool print_colors = false; static bool is_interactive = false; static command_state_t command_state = COMMAND; static command_worker_state_t command_worker_state = WORKER_ILLEGAL; static char *worker_names[MAX_WORKERS]; static int nof_worker_names = 0; static bool autodisp_toggle = false; static unsigned int wait_time = 0; static void cleanup_worker_names( ) { for( int i = 0; i < nof_worker_names; i++ ) { free( worker_names[i] ); } nof_worker_names = 0; } static void print_error( const char *fmt, ... ); static void get_workers( ) { char *url = "status"; char *data = NULL; int len; http_retcode ret = http_get( url, &data, &len, NULL ); if( ret == 200 ) { cleanup_worker_names( ); char *line = strtok( data, "\n" ); while( line != NULL ) { char *p = strchr( line, ' ' ); if( p == NULL ) { line = strtok( NULL, "\n" ); continue; } if( strncmp( line, "worker", p - line ) != 0 ) { line = strtok( NULL, "\n" ); continue; } char *pp = strchr( p+1, ' ' ); if( pp == NULL ) { line = strtok( NULL, "\n" ); continue; } char s = *pp; *pp = '\0'; if( nof_worker_names >= MAX_WORKERS ) { fprintf( stderr, "ERROR: Too many workers seen, ignoring rest!\n" ); free( data ); return; } bool duplicate = false; for( int j = 0; j < nof_worker_names; j++ ) { if( strcmp( worker_names[j], p + 1 ) == 0 ) { duplicate = true; } } if( !duplicate ) { worker_names[nof_worker_names++] = strdup( p + 1 ); } *pp = s; line = strtok( NULL, "\n" ); } } else { print_error( "ERROR: HTTP error %d", ret ); } free( data ); } static void print_workers( ) { printf( "available workers: " ); for( int i = 0; i < nof_worker_names; i++ ) { if( i != 0 ) printf( " " ); printf( worker_names[i] ); } puts( "" ); } static void completion_func( const char *buf, linenoiseCompletions *lc ) { unsigned int i; size_t len = strlen( buf ); switch( command_state ) { case COMMAND: for (i = 0; commands[i]; ++i) { char *cmd = commands[i]; if( strlen( cmd ) < len ) continue; if( strncasecmp( buf, cmd, len ) == 0 ) { linenoiseAddCompletion( lc, cmd ); } } break; case START_WORKER: case STOP_WORKER: case MESSAGES_WORKER: get_workers( ); for( int i = 0; i < nof_worker_names; i++ ) { linenoiseAddCompletion( lc, worker_names[i] ); } break; case WAIT: // no completion break; } } static void print_help( ) { puts( "\n" " help - show this help page\n" " quit - quit the client\n" " status - status of the biruda network\n" " start - start a worker manually\n" " stop - stop a worker manually\n" " messages - show output of workers\n" " autodisp - automatically show messages (toggle)\n" " wait - wait n seconds before next command\n" ); } static void print_colored( const char *fmt, int color, va_list ap ) { char buf[1024]; (void)vsnprintf( buf, sizeof( buf ), fmt, ap ); if( print_colors ) { printf( "%c[9%dm%s%c[0m\n", 27, color, buf, 27 ); } else { puts( buf ); } } static void print_error( const char *fmt, ... ) { va_list ap; va_start( ap, fmt ); print_colored( fmt, 1, ap ); va_end( ap ); } static void print_answer( const char *fmt, ... ) { va_list ap; va_start( ap, fmt ); print_colored( fmt, 2, ap ); va_end( ap ); } static void print_message( const char *fmt, ... ) { va_list ap; va_start( ap, fmt ); print_colored( fmt, 4, ap ); va_end( ap ); } static void print_status( ) { char *url = "status"; char *data = NULL; int len; http_retcode ret = http_get( url, &data, &len, NULL ); if( ret == 200 ) { if( strlen( data ) > 0 && data[len-1] == '\n' ) { data[len-1] = '\0'; len--; } if( strlen( data ) > 0 && data[len-1] == '\r' ) { data[len-1] = '\0'; } print_answer( data ); } else { print_error( "ERROR: HTTP error %d", ret ); } free( data ); } static void print_worker_output( const char *worker_name ) { char url[128]; snprintf( url, sizeof( url ), "worker?op=output&name=%s", worker_name ); char *data = NULL; int len; http_retcode ret = http_get( url, &data, &len, NULL ); if( ret == 200 ) { if( strlen( data ) > 0 && data[len-1] == '\n' ) { data[len-1] = '\0'; len--; } if( strlen( data ) > 0 && data[len-1] == '\r' ) { data[len-1] = '\0'; len--; } if( len > 0 ) { print_message( "output %s:", worker_name ); print_answer( data ); } } else if( ret == ERRNOLG ) { // ignore empty result, worker has no output currently } else { print_error( "ERROR: HTTP error %d", ret ); } free( data ); } static void print_messages( ) { get_workers( ); for( int i = 0; i < nof_worker_names; i++ ) { print_worker_output( worker_names[i] ); } } static void start_worker( const char *worker_name ) { char url[128]; snprintf( url, sizeof( url ), "worker?op=start&name=%s", worker_name ); http_retcode ret = http_post( url, "", 0, "Content-Type: text/plain" ); if( ret == 200 ) { print_answer( "Request queued" ); } else { print_error( "ERROR: HTTP error %d", ret ); } } static void stop_worker( const char *worker_name ) { char url[128]; snprintf( url, sizeof( url ), "worker?op=stop&name=%s", worker_name ); http_retcode ret = http_post( url, "", 0, "Content-Type: text/plain" ); if( ret == 200 ) { print_answer( "Request queued" ); } else { print_error( "ERROR: HTTP error %d", ret ); } } static void get_worker_data( ) { switch( command_worker_state ) { case WORKER_NAME: get_workers( ); print_workers( ); command_worker_state = WORKER_EXECUTE; break; case WORKER_EXECUTE: case WORKER_ILLEGAL: case WORKER_ARCH: case WORKER_OS: /* later */ print_error( "ERROR: internal worker state error" ); break; } } #endif #ifdef _WIN32 int start_interactive( bool colors, const char *host, unsigned short port, FILE *in ) { fprintf( stderr, "ERROR: Not implemented on Windows!\n" ); return EXIT_FAILURE; } #else int start_interactive( bool colors, const char *host, unsigned short port, FILE *in ) { char history_filename[1024]; // for http_tidy, tell it where to issue requests to http_server = (char *)host; http_port = port; is_interactive = isatty( fileno( in ) ); print_colors = is_interactive ? colors : false; autodisp_toggle = false; if( is_interactive ) { char *home = getenv( "HOME" ); if( home != NULL ) { snprintf( history_filename, sizeof( history_filename ), "%s/%s", home, HISTORY_FILE ); linenoiseHistoryLoad( history_filename ); linenoiseSetCompletionCallback( completion_func ); } } char *context = "biruda"; for( ;; ) { char prompt[128]; char buf[1024]; char *line; switch( command_state ) { case COMMAND: context = "biruda"; break; case START_WORKER: case STOP_WORKER: case MESSAGES_WORKER: context = "worker"; break; case WAIT: context = "wait"; break; } if( autodisp_toggle ) { print_messages( ); } if( command_state == WAIT && wait_time > 0 ) { wait_time--; if( wait_time == 0 ) { command_state = COMMAND; } else { sleep( 1 ); continue; } } if( is_interactive ) { snprintf( prompt, sizeof( prompt ), "%s> ", context ); if( ( line = linenoise( prompt ) ) == NULL ) { switch( command_state ) { case COMMAND: cleanup_worker_names( ); free( line ); return EXIT_SUCCESS; case START_WORKER: case STOP_WORKER: case MESSAGES_WORKER: case WAIT: command_state = COMMAND; command_worker_state = WORKER_ILLEGAL; continue; } break; } strncpy( buf, line, sizeof( buf ) ); free( line ); line = buf; } else { if( fgets( buf, sizeof( buf ), in ) == NULL ) { cleanup_worker_names( ); return EXIT_SUCCESS; } line = buf; } if( strlen( line ) > 1 ) { if( line[strlen( line )-1] == '\n' ) { line[strlen( line )-1] = '\0'; } } if( is_interactive ) { linenoiseHistoryAdd( line ); } switch( command_state ) { case COMMAND: if( strncasecmp( line, "quit", 4 ) == 0 ) { if( is_interactive ) { linenoiseHistorySave( history_filename ); } cleanup_worker_names( ); return EXIT_SUCCESS; } else if( strncasecmp( line, "help", 4 ) == 0 ) { print_help( ); } else if( strncasecmp( line, "status", 6 ) == 0 ) { print_status( ); } else if( strncasecmp( line, "start", 5 ) == 0 ) { command_state = START_WORKER; command_worker_state = WORKER_NAME; get_worker_data( ); } else if( strncasecmp( line, "stop", 5 ) == 0 ) { command_state = STOP_WORKER; command_worker_state = WORKER_NAME; get_worker_data( ); } else if( strncasecmp( line, "messages", 8 ) == 0 ) { command_state = MESSAGES_WORKER; command_worker_state = WORKER_NAME; get_worker_data( ); } else if( strncasecmp( line, "autodisp", 8 ) == 0 ) { if( is_interactive ) { autodisp_toggle = !autodisp_toggle; print_message( "Autodisplay toggle is %s.", autodisp_toggle ? "on" : "off" ); } else { print_error( "'autodisp' makes sense only in interactive mode." ); } } else if( strncasecmp( line, "wait", 4 ) == 0 ) { if( is_interactive ) { puts( "Enter sleep time (in seconds):") ; } command_state = WAIT; } else if( strcmp( line, "" ) == 0 ) { // skip, handy if autodisp is on to show more messages } else { print_error( "Bad command '%s'.", line ); } break; case START_WORKER: if( command_worker_state == WORKER_EXECUTE ) { start_worker( line ); command_state = COMMAND; command_worker_state = WORKER_ILLEGAL; } else { get_worker_data( ); } break; case STOP_WORKER: if( command_worker_state == WORKER_EXECUTE ) { stop_worker( line ); command_state = COMMAND; command_worker_state = WORKER_ILLEGAL; } else { get_worker_data( ); } break; case MESSAGES_WORKER: if( command_worker_state == WORKER_EXECUTE ) { print_messages( ); command_state = COMMAND; command_worker_state = WORKER_ILLEGAL; } else { get_worker_data( ); } break; case WAIT: wait_time = atoi( line ); printf( "Sleeping for %d seconds\n", wait_time ); break; } } return EXIT_SUCCESS; } #endif void print_guessed_env( bool human_readable ) { char hostname[100]; system_hostname( hostname, sizeof( hostname ) ); unsigned int nofCpus = system_available_cpus( ); unsigned int nofPhysMem = system_phys_memory( ); char os_name[100]; system_os( os_name, sizeof( os_name ) ); char machine_arch[100]; system_arch( machine_arch, sizeof( machine_arch ) ); if( human_readable) { printf( "Architecture: %s\n", machine_arch ); printf( "Operating system: %s\n", os_name ); printf( "Hostname: %s\n", hostname ); printf( "Number of CPUs: %d\n", nofCpus ); printf( "Physical memory in kB: %d\n", nofPhysMem ); } else { json_object *obj = json_object_new_object( ); json_object *arch = json_object_new_string( machine_arch ); json_object_object_add( obj, "arch", arch ); json_object *os = json_object_new_string( os_name ); json_object_object_add( obj, "os", os ); json_object *host = json_object_new_string( hostname ); json_object_object_add( obj, "host", host ); json_object *cpus = json_object_new_int( nofCpus ); json_object_object_add( obj, "cpus", cpus ); json_object *physMem = json_object_new_int( nofPhysMem ); json_object_object_add( obj, "physical_memory", physMem ); const char *msg = json_object_to_json_string( obj ); char *res = strdup( msg ); puts( res ); free( res ); json_object_put( obj ); } }