From 7d4ec22375f097bad67386ce364af692fc0477b9 Mon Sep 17 00:00:00 2001 From: Dmitry Gerasimov <dmitriy.gerasimov@demlabs.net> Date: Mon, 22 Aug 2022 17:54:55 +0700 Subject: [PATCH] [+] dap_config_load() for custom config loading [+] dap-sdk/plugin module for universal plugins. By default builtin binary loader [!] CLI server moved from dap_chain_net to the new module dap-sdk/net/cli_server [!] app-cli module moved from cellframe-sdk to dap-sdk [!] s_recv() -> dap_net_recv and also moved from dap_chain_net into the dap_net [*] DARWIN related fixes --- CMakeLists.txt | 8 + core/include/dap_common.h | 46 + core/include/dap_config.h | 1 + core/src/dap_config.c | 339 +++--- io/dap_context.c | 7 +- io/dap_net.c | 56 + io/include/dap_net.h | 2 + net/app-cli/CMakeLists.txt | 11 + net/app-cli/dap_app_cli.c | 221 ++++ net/app-cli/dap_app_cli_net.c | 266 +++++ net/app-cli/dap_app_cli_shell.c | 356 ++++++ net/app-cli/include/dap_app_cli.h | 53 + net/app-cli/include/dap_app_cli_net.h | 54 + net/app-cli/include/dap_app_cli_shell.h | 53 + net/server/CMakeLists.txt | 2 + net/server/cli_server/CMakeLists.txt | 11 + net/server/cli_server/dap_cli_server.c | 1012 +++++++++++++++++ .../cli_server/include/dap_cli_server.h | 67 ++ net/stream/stream/dap_stream.c | 1 - plugin/CMakeLists.txt | 18 + plugin/include/dap_plugin.h | 56 + plugin/include/dap_plugin_binary.h | 27 + plugin/include/dap_plugin_command.h | 37 + plugin/include/dap_plugin_manifest.h | 86 ++ plugin/src/dap_plugin.c | 292 +++++ plugin/src/dap_plugin_binary.c | 128 +++ plugin/src/dap_plugin_command.c | 156 +++ plugin/src/dap_plugin_manifest.c | 289 +++++ 28 files changed, 3487 insertions(+), 168 deletions(-) create mode 100644 net/app-cli/CMakeLists.txt create mode 100644 net/app-cli/dap_app_cli.c create mode 100644 net/app-cli/dap_app_cli_net.c create mode 100644 net/app-cli/dap_app_cli_shell.c create mode 100644 net/app-cli/include/dap_app_cli.h create mode 100644 net/app-cli/include/dap_app_cli_net.h create mode 100644 net/app-cli/include/dap_app_cli_shell.h create mode 100644 net/server/cli_server/CMakeLists.txt create mode 100644 net/server/cli_server/dap_cli_server.c create mode 100644 net/server/cli_server/include/dap_cli_server.h create mode 100644 plugin/CMakeLists.txt create mode 100644 plugin/include/dap_plugin.h create mode 100644 plugin/include/dap_plugin_binary.h create mode 100644 plugin/include/dap_plugin_command.h create mode 100644 plugin/include/dap_plugin_manifest.h create mode 100644 plugin/src/dap_plugin.c create mode 100644 plugin/src/dap_plugin_binary.c create mode 100644 plugin/src/dap_plugin_command.c create mode 100644 plugin/src/dap_plugin_manifest.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 0781b5dec..1a7d3a233 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,14 @@ if (DAPSDK_MODULES MATCHES "crypto") add_subdirectory(crypto) endif() +if (DAPSDK_MODULES MATCHES "app-cli") + add_subdirectory(net/app-cli) +endif() + +if (DAPSDK_MODULES MATCHES "plugin") + add_subdirectory(plugin) +endif() + # I/O subsystem if (DAPSDK_MODULES MATCHES "io") add_subdirectory(io) diff --git a/core/include/dap_common.h b/core/include/dap_common.h index 972a8b990..1269d8eeb 100755 --- a/core/include/dap_common.h +++ b/core/include/dap_common.h @@ -511,6 +511,52 @@ char **dap_parse_items(const char *a_str, char a_delimiter, int *a_count, const unsigned int dap_crc32c(unsigned int crc, const void *buf, size_t buflen); +static inline const char *dap_get_arch() { //Get current architecture, detectx nearly every architecture. Coded by Freak + #if defined(__x86_64__) || defined(_M_X64) + return "x86_64"; + #elif defined(i386) || defined(__i386__) || defined(__i386) || defined(_M_IX86) + return "x86_32"; + #elif defined(__ARM_ARCH_2__) + return "ARM2"; + #elif defined(__ARM_ARCH_3__) || defined(__ARM_ARCH_3M__) + return "ARM3"; + #elif defined(__ARM_ARCH_4T__) || defined(__TARGET_ARM_4T) + return "ARM4T"; + #elif defined(__ARM_ARCH_5_) || defined(__ARM_ARCH_5E_) + return "ARM5" + #elif defined(__ARM_ARCH_6T2_) || defined(__ARM_ARCH_6T2_) + return "ARM6T2"; + #elif defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6ZK__) + return "ARM6"; + #elif defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) + return "ARM7"; + #elif defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) + return "ARM7A"; + #elif defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) || defined(__ARM_ARCH_7S__) + return "ARM7R"; + #elif defined(__ARM_ARCH_7M__) + return "ARM7M"; + #elif defined(__ARM_ARCH_7S__) + return "ARM7S"; + #elif defined(__aarch64__) || defined(_M_ARM64) + return "ARM64"; + #elif defined(mips) || defined(__mips__) || defined(__mips) + return "MIPS"; + #elif defined(__sh__) + return "SUPERH"; + #elif defined(__powerpc) || defined(__powerpc__) || defined(__powerpc64__) || defined(__POWERPC__) || defined(__ppc__) || defined(__PPC__) || defined(_ARCH_PPC) + return "POWERPC"; + #elif defined(__PPC64__) || defined(__ppc64__) || defined(_ARCH_PPC64) + return "POWERPC64"; + #elif defined(__sparc__) || defined(__sparc) + return "SPARC"; + #elif defined(__m68k__) + return "M68K"; + #else + return "UNKNOWN"; + #endif + } + #ifdef __MINGW32__ int exec_silent(const char *a_cmd); #endif diff --git a/core/include/dap_config.h b/core/include/dap_config.h index 806bf6eac..944c32002 100755 --- a/core/include/dap_config.h +++ b/core/include/dap_config.h @@ -40,6 +40,7 @@ typedef struct dap_config{ int dap_config_init(const char * a_configs_path); void dap_config_deinit(); dap_config_t * dap_config_open(const char * a_name); +dap_config_t * dap_config_load(const char * a_file_path); void dap_config_close(dap_config_t * a_config); const char * dap_config_path(); diff --git a/core/src/dap_config.c b/core/src/dap_config.c index 27f0a2e3f..e4961e662 100755 --- a/core/src/dap_config.c +++ b/core/src/dap_config.c @@ -105,194 +105,207 @@ static uint16_t get_array_length(const char* str) { */ dap_config_t * dap_config_open(const char * a_name) { - dap_config_t * ret = NULL; + dap_config_t * l_ret = NULL; if ( a_name ){ log_it(L_DEBUG,"Looking for config name %s...",a_name); size_t l_config_path_size_max = strlen(a_name)+6+strlen(s_configs_path); char *l_config_path = DAP_NEW_SIZE(char,l_config_path_size_max); snprintf(l_config_path,l_config_path_size_max, "%s/%s.cfg",s_configs_path,a_name); - FILE * f = fopen(l_config_path,"r"); - if ( f ){ - fseek(f, 0, SEEK_END); - long buf_len = ftell(f); - char buf[buf_len]; - fseek(f, 0L, SEEK_SET); - log_it(L_DEBUG,"Opened config %s",l_config_path); - ret = DAP_NEW_Z(dap_config_t); - dap_config_internal_t * l_config_internal = DAP_NEW_Z(dap_config_internal_t); - ret->_internal = l_config_internal; - size_t l_global_offset=0; - size_t l_buf_size=0; - size_t l_buf_pos_line_start=0; - size_t l_buf_pos_line_end=0; - dap_config_item_t * l_section_current = NULL ; - bool l_is_space_now = false; - while ( feof(f)==0){ // Break on lines - size_t i; - l_global_offset += (l_buf_size = fread(buf, 1, buf_len, f) ); - for (i=0; i< l_buf_size; i++){ - if( (buf[i] == '\r') || (buf[i] == '\n' ) ){ - if( ! l_is_space_now){ - l_buf_pos_line_end = i; - l_is_space_now = true; - //if(l_buf_pos_line_end) - // l_buf_pos_line_end--; - if(l_buf_pos_line_end != l_buf_pos_line_start ){ // Line detected - char *l_line = NULL; - size_t l_line_length = 0; - size_t j; - - // Trimming spaces and skip the line if commented - for ( j = l_buf_pos_line_start; j < l_buf_pos_line_end; j++ ){ - if ( buf[j] == '#' ) - break; - if (buf[j] != ' ' ){ - l_line_length = (l_buf_pos_line_end - j); - break; - } + l_ret = dap_config_load(l_config_path); + DAP_DELETE(l_config_path); + }else{ + log_it(L_ERROR,"Config name is NULL"); + } + return l_ret; +} + +/** + * @brief Load config from file + * @param a_file_path + * @return + */ +dap_config_t * dap_config_load(const char * a_file_path) +{ + dap_config_t * l_ret = NULL; + + FILE * f = fopen(a_file_path,"r"); + if ( f ){ + fseek(f, 0, SEEK_END); + long buf_len = ftell(f); + char buf[buf_len]; + fseek(f, 0L, SEEK_SET); + log_it(L_DEBUG,"Opened config %s",a_file_path); + l_ret = DAP_NEW_Z(dap_config_t); + dap_config_internal_t * l_config_internal = DAP_NEW_Z(dap_config_internal_t); + l_ret->_internal = l_config_internal; + size_t l_global_offset=0; + size_t l_buf_size=0; + size_t l_buf_pos_line_start=0; + size_t l_buf_pos_line_end=0; + dap_config_item_t * l_section_current = NULL ; + bool l_is_space_now = false; + while ( feof(f)==0){ // Break on lines + size_t i; + l_global_offset += (l_buf_size = fread(buf, 1, buf_len, f) ); + for (i=0; i< l_buf_size; i++){ + if( (buf[i] == '\r') || (buf[i] == '\n' ) ){ + if( ! l_is_space_now){ + l_buf_pos_line_end = i; + l_is_space_now = true; + //if(l_buf_pos_line_end) + // l_buf_pos_line_end--; + if(l_buf_pos_line_end != l_buf_pos_line_start ){ // Line detected + char *l_line = NULL; + size_t l_line_length = 0; + size_t j; + + // Trimming spaces and skip the line if commented + for ( j = l_buf_pos_line_start; j < l_buf_pos_line_end; j++ ){ + if ( buf[j] == '#' ) + break; + if (buf[j] != ' ' ){ + l_line_length = (l_buf_pos_line_end - j); + break; } - if( l_line_length ){ - l_line = DAP_NEW_SIZE(char,l_line_length+1); - memcpy(l_line,buf+j,l_line_length); - l_line[l_line_length] = 0; - - // Process trimmed line - if( (l_line[0] == '[' ) && (l_line[l_line_length-1] == ']' ) ){ // Section detected - //log_it(L_DEBUG, "Raw line '%s'",l_line); - char * l_section_name = strdup(l_line+1); - size_t l_section_name_length = (l_line_length - 2); - l_section_name[l_section_name_length]='\0'; - // log_it(L_DEBUG,"Config section '%s'",l_section_name); - - dap_config_item_t * l_item_section = DAP_NEW_Z(dap_config_item_t); - strncpy(l_item_section->name,l_section_name,sizeof(l_item_section->name)-1); - l_item_section->item_next = l_config_internal->item_root; - l_config_internal->item_root = l_item_section; - free(l_section_name); - - l_section_current = l_item_section; - }else{ // key-value line - //log_it(L_DEBUG,"Read line '%s'",l_line); - char l_param_name[sizeof(l_section_current->name)]; - size_t l_param_name_size=0; - size_t l_param_value_size=0; - char l_param_value[1024]; - l_param_name[0] = 0; - l_param_value[0] = 0; - for ( j = 0; j < l_line_length; j++ ){ // Parse param name - if ( ( l_line[j] == ' ' )|| ( l_line[j] == '=' ) ||( l_line[j] == '\t' ) ){ // Param name - l_param_name_size = j; - if (l_param_name_size > (sizeof(l_param_name) -1) ){ - l_param_name_size = (sizeof(l_param_name) - 1 ); - log_it(L_WARNING,"Too long param name in config, %zu is more than %zu maximum", - j,sizeof(l_param_name) -1); - } - strncpy(l_param_name,l_line,j); - l_param_name[j] = 0; - break; + } + if( l_line_length ){ + l_line = DAP_NEW_SIZE(char,l_line_length+1); + memcpy(l_line,buf+j,l_line_length); + l_line[l_line_length] = 0; + + // Process trimmed line + if( (l_line[0] == '[' ) && (l_line[l_line_length-1] == ']' ) ){ // Section detected + //log_it(L_DEBUG, "Raw line '%s'",l_line); + char * l_section_name = strdup(l_line+1); + size_t l_section_name_length = (l_line_length - 2); + l_section_name[l_section_name_length]='\0'; + // log_it(L_DEBUG,"Config section '%s'",l_section_name); + + dap_config_item_t * l_item_section = DAP_NEW_Z(dap_config_item_t); + strncpy(l_item_section->name,l_section_name,sizeof(l_item_section->name)-1); + l_item_section->item_next = l_config_internal->item_root; + l_config_internal->item_root = l_item_section; + free(l_section_name); + + l_section_current = l_item_section; + }else{ // key-value line + //log_it(L_DEBUG,"Read line '%s'",l_line); + char l_param_name[sizeof(l_section_current->name)]; + size_t l_param_name_size=0; + size_t l_param_value_size=0; + char l_param_value[1024]; + l_param_name[0] = 0; + l_param_value[0] = 0; + for ( j = 0; j < l_line_length; j++ ){ // Parse param name + if ( ( l_line[j] == ' ' )|| ( l_line[j] == '=' ) ||( l_line[j] == '\t' ) ){ // Param name + l_param_name_size = j; + if (l_param_name_size > (sizeof(l_param_name) -1) ){ + l_param_name_size = (sizeof(l_param_name) - 1 ); + log_it(L_WARNING,"Too long param name in config, %zu is more than %zu maximum", + j,sizeof(l_param_name) -1); } - + strncpy(l_param_name,l_line,j); + l_param_name[j] = 0; + break; } - for (; j < l_line_length; j++ ){ // Find beginning of param value - if ( ( l_line[j] != '\t' ) && ( l_line[j] != ' ' ) && ( l_line[j] != '=' ) ){ + } + + for (; j < l_line_length; j++ ){ // Find beginning of param value + if ( ( l_line[j] != '\t' ) && ( l_line[j] != ' ' ) && ( l_line[j] != '=' ) ){ + break; + } + } + l_param_value_size = l_line_length - j; + if (l_param_value_size ){ + if (l_param_value_size > (sizeof(l_param_value) -1) ){ + l_param_value_size = (sizeof(l_param_value) - 1 ); + log_it(L_WARNING,"Too long param value in config, %zu is more than %zu maximum", + l_line_length - j,sizeof(l_param_value) -1); + } + strncpy(l_param_value,l_line +j, l_param_value_size); + l_param_value[l_param_value_size] = '\0'; + for(int j=(int)l_param_value_size-1; j>=0; j--){ + if( (l_param_value[j] ==' ') || (l_param_value[j] =='\t') ){ + l_param_value[j] = '\0'; + }else{ break; } } - l_param_value_size = l_line_length - j; - if (l_param_value_size ){ - if (l_param_value_size > (sizeof(l_param_value) -1) ){ - l_param_value_size = (sizeof(l_param_value) - 1 ); - log_it(L_WARNING,"Too long param value in config, %zu is more than %zu maximum", - l_line_length - j,sizeof(l_param_value) -1); - } - strncpy(l_param_value,l_line +j, l_param_value_size); - l_param_value[l_param_value_size] = '\0'; - for(int j=(int)l_param_value_size-1; j>=0; j--){ - if( (l_param_value[j] ==' ') || (l_param_value[j] =='\t') ){ - l_param_value[j] = '\0'; - }else{ - break; - } + } + // log_it(L_DEBUG," Param '%s' = '%s'", l_param_name, l_param_value); + if (l_section_current){ + + if (l_param_value[0] == '[') { + if(l_param_value[1] == ']') { + //log_it(L_WARNING, "Empty array!"); + DAP_DELETE(l_line); + continue; } - } - // log_it(L_DEBUG," Param '%s' = '%s'", l_param_name, l_param_value); - if (l_section_current){ - - if (l_param_value[0] == '[') { - if(l_param_value[1] == ']') { - //log_it(L_WARNING, "Empty array!"); - DAP_DELETE(l_line); - continue; - } - - // delete '[' and ']' - char* values = l_param_value + 1; - values[l_param_value_size-2] = 0; - - dap_config_item_t * l_item = DAP_NEW_Z(dap_config_item_t); - - strncpy(l_item->name,l_param_name,sizeof(l_item->name)); - l_item->item_next = l_section_current->childs; - l_item->is_array = true; - l_section_current->childs = l_item; - l_item->array_length = get_array_length(l_param_value); - l_item->data_str_array = (char**) malloc (sizeof(char*) * l_item->array_length); - // parsing items in array - int j = 0; - char * l_tmp = NULL; - char *token = strtok_r(values, ",",&l_tmp); - while(token) { - - // trim token whitespace - if (isspace(token[0])) - token = token + 1; - char *closer = strchr(token, ']'); - if (closer) /* last item in array */ - *closer = 0; - - l_item->data_str_array[j] = strdup(token); - - token = strtok_r(NULL, ",",&l_tmp); - j++; - } - l_item->array_length = j; - } else { - dap_config_item_t * l_item = DAP_NEW_Z(dap_config_item_t); - - strncpy(l_item->name,l_param_name,sizeof(l_item->name)); - l_item->item_next = l_section_current->childs; - l_item->data_str = strdup (l_param_value); - - l_section_current->childs = l_item; + + // delete '[' and ']' + char* values = l_param_value + 1; + values[l_param_value_size-2] = 0; + + dap_config_item_t * l_item = DAP_NEW_Z(dap_config_item_t); + + strncpy(l_item->name,l_param_name,sizeof(l_item->name)); + l_item->item_next = l_section_current->childs; + l_item->is_array = true; + l_section_current->childs = l_item; + l_item->array_length = get_array_length(l_param_value); + l_item->data_str_array = (char**) malloc (sizeof(char*) * l_item->array_length); + // parsing items in array + int j = 0; + char * l_tmp = NULL; + char *token = strtok_r(values, ",",&l_tmp); + while(token) { + + // trim token whitespace + if (isspace(token[0])) + token = token + 1; + char *closer = strchr(token, ']'); + if (closer) /* last item in array */ + *closer = 0; + + l_item->data_str_array[j] = strdup(token); + + token = strtok_r(NULL, ",",&l_tmp); + j++; } - }else{ - log_it(L_ERROR,"Can't add param to a tree without current section"); - } + l_item->array_length = j; + } else { + dap_config_item_t * l_item = DAP_NEW_Z(dap_config_item_t); + strncpy(l_item->name,l_param_name,sizeof(l_item->name)); + l_item->item_next = l_section_current->childs; + l_item->data_str = strdup (l_param_value); + + l_section_current->childs = l_item; + } + }else{ + log_it(L_ERROR,"Can't add param to a tree without current section"); } - DAP_DELETE(l_line); + } + DAP_DELETE(l_line); } } - continue; - }else{ - if (l_is_space_now){ - l_is_space_now = false; - l_buf_pos_line_start = i; - } + } + continue; + }else{ + if (l_is_space_now){ + l_is_space_now = false; + l_buf_pos_line_start = i; } } } - fclose(f); - }else{ - log_it(L_ERROR,"Can't open config file '%s' (%s)",l_config_path,strerror(errno)); } - DAP_DELETE(l_config_path); + fclose(f); }else{ - log_it(L_ERROR,"Config name is NULL"); + log_it(L_ERROR,"Can't open config file '%s' (%s)",a_file_path,strerror(errno)); } - return ret; + return l_ret; } /** diff --git a/io/dap_context.c b/io/dap_context.c index 4b0ec46c0..99c1329b3 100644 --- a/io/dap_context.c +++ b/io/dap_context.c @@ -1159,8 +1159,7 @@ int dap_context_poll_update(dap_events_socket_t * a_esocket) u_short l_flags =a_esocket->kqueue_base_flags; u_int l_fflags =a_esocket->kqueue_base_fflags; - int l_kqueue_fd = a_esocket->worker? a_esocket->worker->kqueue_fd : - a_esocket->proc_thread ? a_esocket->proc_thread->kqueue_fd : -1; + int l_kqueue_fd = a_esocket->context->kqueue_fd; if ( l_kqueue_fd == -1 ){ log_it(L_ERROR, "Esocket is not assigned with anything ,exit"); } @@ -1190,8 +1189,8 @@ int dap_context_poll_update(dap_events_socket_t * a_esocket) } } if (l_is_error && l_errno == EBADF){ - log_it(L_ATT,"Poll update: socket %d (%p ) disconnected, rise CLOSE flag to remove from queue, lost %"DAP_UINT64_FORMAT_U":%" DAP_UINT64_FORMAT_U - " bytes",a_esocket->socket,a_esocket,a_esocket->buf_in_size,a_esocket->buf_out_size); + log_it(L_ATT,"Poll update: socket %d (%p ) disconnected, rise CLOSE flag to remove from queue, lost %zd:%zd bytes", + a_esocket->socket,a_esocket,a_esocket->buf_in_size,a_esocket->buf_out_size); a_esocket->flags |= DAP_SOCK_SIGNAL_CLOSE; a_esocket->buf_in_size = a_esocket->buf_out_size = 0; // Reset everything from buffer, we close it now all }else if ( l_is_error && l_errno != EINPROGRESS && l_errno != ENOENT){ diff --git a/io/dap_net.c b/io/dap_net.c index 072388a71..862516695 100644 --- a/io/dap_net.c +++ b/io/dap_net.c @@ -1,3 +1,29 @@ +#ifndef _WIN32 +#include <poll.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <arpa/inet.h> +//#include <unistd.h> // for close +#include <fcntl.h> +//#include <sys/poll.h> +//#include <sys/select.h> +#include <netinet/in.h> +#include <sys/un.h> +#include <sys/stat.h> +//#define closesocket close +//typedef int SOCKET; +//#define SOCKET_ERROR -1 // for win32 = (-1) +//#define INVALID_SOCKET -1 // for win32 = (SOCKET)(~0) +// for Windows +#else +#include <winsock2.h> +#include <windows.h> +#include <mswsock.h> +#include <ws2tcpip.h> +#include <io.h> +#endif + +#include <errno.h> #include <string.h> #include "dap_net.h" @@ -47,3 +73,33 @@ int dap_net_resolve_host(const char *a_host, int ai_family, struct sockaddr *a_a freeaddrinfo(l_res); return -1; } + +/** + * @brief s_recv + * timeout in milliseconds + * return the number of read bytes (-1 err or -2 timeout) + * @param sock + * @param buf + * @param bufsize + * @param timeout + * @return long + */ +long dap_net_recv(SOCKET sd, unsigned char *buf, size_t bufsize, int timeout) +{ +struct pollfd fds = {.fd = sd, .events = POLLIN}; +int res; + + if ( !(res = poll(&fds, 1, timeout)) ) + return -2; + + if ( (res == 1) && !(fds.revents & POLLIN)) + return -1; + + if(res < 1) + return -1; + + if ( 0 >= (res = recv(sd, (char *)buf, bufsize, 0)) ) + printf("[s_recv] recv()->%d, errno: %d\n", res, errno); + + return res; +} diff --git a/io/include/dap_net.h b/io/include/dap_net.h index 2c79b8971..3a5c8350e 100644 --- a/io/include/dap_net.h +++ b/io/include/dap_net.h @@ -43,5 +43,7 @@ #endif #include "dap_common.h" +#include "dap_events_socket.h" int dap_net_resolve_host(const char *a_host, int ai_family, struct sockaddr *a_addr_out); +long dap_net_recv(SOCKET sd, unsigned char *buf, size_t bufsize, int timeout); diff --git a/net/app-cli/CMakeLists.txt b/net/app-cli/CMakeLists.txt new file mode 100644 index 000000000..d3827063a --- /dev/null +++ b/net/app-cli/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.10) +project (dap_app_cli) + +file(GLOB DAP_APP_CLI_SRCS *.c) + +file(GLOB DAP_APP_CLI_HEADERS include/*.h) + +add_library(${PROJECT_NAME} STATIC ${DAP_APP_CLI_SRCS} ${DAP_APP_CLI_HEADERS} ) + +target_link_libraries(${PROJECT_NAME} dap_core dap_io dap_cli_server m) +target_include_directories(${PROJECT_NAME} PUBLIC include/ ) diff --git a/net/app-cli/dap_app_cli.c b/net/app-cli/dap_app_cli.c new file mode 100644 index 000000000..9d29d16ce --- /dev/null +++ b/net/app-cli/dap_app_cli.c @@ -0,0 +1,221 @@ +/* + * Authors: + * Dmitriy A. Gearasimov <kahovski@gmail.com> + * DeM Labs Inc. https://demlabs.net + * DeM Labs Open source community https://github.com/demlabsinc + * Copyright (c) 2017-2019 + * All rights reserved. + + This file is part of DAP (Deus Applications Prototypes) the open source project + + DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DAP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +//#include "dap_client.h" +#include "dap_common.h" +#include "dap_file_utils.h" +#include "dap_strfuncs.h" +#include "dap_cli_server.h" +#include "dap_app_cli.h" +#include "dap_app_cli_net.h" +#include "dap_app_cli_shell.h" + + +#ifdef _WIN32 +#include "registry.h" +#endif + + +/** + * split string to argc and argv + */ +static char** split_word(char *line, int *argc) +{ + if(!line) + { + if(argc) + *argc = 0; + return NULL ; + } + char **argv = calloc(sizeof(char*), strlen(line)); + int n = 0; + char *s, *start = line; + size_t len = strlen(line); + for(s = line; s <= line + len; s++) { + if(whitespace(*s)) { + *s = '\0'; + argv[n] = start; + s++; + // miss spaces + for(; whitespace(*s); s++) + ; + start = s; + n++; + } + } + // last param + if(len) { + argv[n] = start; + n++; + } + if(argc) + *argc = n; + return argv; +} + +/* + * Execute a command line. + */ +int execute_line(dap_app_cli_connect_param_t *cparam, char *line) +{ + register int i; + dap_cli_cmd_t *command; + char *word; + + /* Isolate the command word. */ + i = 0; + while(line[i] && whitespace(line[i])) + i++; + word = line + i; + + int argc = 0; + char **argv = split_word(word, &argc); + + // Call the function + if(argc > 0) { + dap_app_cli_cmd_state_t cmd; + memset(&cmd, 0, sizeof(dap_app_cli_cmd_state_t)); + cmd.cmd_name = (char *) argv[0]; + cmd.cmd_param_count = argc - 1; + if(cmd.cmd_param_count > 0) + cmd.cmd_param = (char**) (argv + 1); + // Send command + int res = dap_app_cli_post_command(cparam, &cmd); + DAP_DELETE(argv); + return res; + }else{ + DAP_DELETE(argv); + fprintf(stderr, "No command\n"); + return -1; + } +} + +/** + * Clear and delete memory of structure cmd_state + */ +void dap_app_cli_free_cmd_state(dap_app_cli_cmd_state_t *cmd) { + if(!cmd->cmd_param) + return; + for(int i = 0; i < cmd->cmd_param_count; i++) + { + DAP_DELETE(cmd->cmd_param[i]); + } + DAP_DELETE(cmd->cmd_res); + DAP_DELETE(cmd); +} + +/** + * Read and execute commands until EOF is reached. This assumes that + * the input source has already been initialized. + */ +int shell_reader_loop(dap_app_cli_connect_param_t *cparam) +{ + char *line, *s; + + rl_initialize(); /* Bind our completer. */ + int done = 0; + // Loop reading and executing lines until the user quits. + for(; done == 0;) { + // Read a line of input + line = rl_readline("> "); + + if(!line) + break; + + /* Remove leading and trailing whitespace from the line. + Then, if there is anything left, add it to the history list + and execute it. */ + s = rl_stripwhite(line); + + if(*s) + { + add_history(s); + execute_line(cparam, s); + } + + DAP_DELETE(line); + } + + return 0; +} + + +/** + * @brief dap_app_cli_main + * @param argc + * @param argv + * @return + */ +int dap_app_cli_main(const char * a_app_name, const char * a_socket_path, int a_argc, char **a_argv) +{ + dap_set_appname(a_app_name); + if (dap_common_init(dap_get_appname(), NULL,NULL) != 0) { + printf("Fatal Error: Can't init common functions module"); + return -2; + } + + dap_log_level_set(L_CRITICAL); +#ifdef _WIN32 + WSADATA wsaData; + WSAStartup(MAKEWORD(2,2), &wsaData); +#endif + // connect to node + dap_app_cli_connect_param_t *cparam = dap_app_cli_connect( a_socket_path ); + if(!cparam) + { + printf("Can't connect to %s on socket %s\n",dap_get_appname(), a_socket_path); + exit(-1); + } + + if(a_argc > 1){ + // Call the function + dap_app_cli_cmd_state_t cmd; + memset(&cmd, 0, sizeof(dap_app_cli_cmd_state_t)); + cmd.cmd_name = strdup(a_argv[1]); + cmd.cmd_param_count = a_argc - 2; + if(cmd.cmd_param_count > 0) + cmd.cmd_param = (char**) (a_argv + 2); + // Send command + int res = dap_app_cli_post_command(cparam, &cmd); + dap_app_cli_disconnect(cparam); +#ifdef _WIN32 + WSACleanup(); +#endif + return res; + }else{ + // command not found, start interactive shell + shell_reader_loop(cparam); + dap_app_cli_disconnect(cparam); + } +#ifdef _WIN32 + WSACleanup(); +#endif + return 0; +} + diff --git a/net/app-cli/dap_app_cli_net.c b/net/app-cli/dap_app_cli_net.c new file mode 100644 index 000000000..a1f37d22c --- /dev/null +++ b/net/app-cli/dap_app_cli_net.c @@ -0,0 +1,266 @@ +/* + * Authors: + * Dmitriy A. Gerasimov <kahovski@gmail.com> + * Alexander Lysikov <alexander.lysikov@demlabs.net> + * DeM Labs Inc. https://demlabs.net + * DeM Labs Open source community https://github.com/demlabsinc + * Copyright (c) 2017-2019 + * All rights reserved. + + This file is part of DAP (Deus Applications Prototypes) the open source project + + DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DAP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. + */ + +//#include <dap_client.h> + +#include <stdlib.h> +#include <sys/types.h> +#include <assert.h> +#include <errno.h> +#include <stdbool.h> + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#include <mswsock.h> +#include <ws2tcpip.h> +#include <io.h> +#else +#include <sys/socket.h> +#include <sys/un.h> +#include <sys/stat.h> +#endif + +#include "dap_common.h" +#include "dap_string.h" +#include "dap_strfuncs.h" +#include "dap_cli_server.h" // for UNIX_SOCKET_FILE +#include "dap_app_cli.h" +#include "dap_app_cli_net.h" +#include "dap_enc_base64.h" + +static int s_status; + +//staic function to receive http data +static void dap_app_cli_http_read(dap_app_cli_connect_param_t *socket, dap_app_cli_cmd_state_t *l_cmd) +{ + ssize_t l_recv_len = recv(*socket, &l_cmd->cmd_res[l_cmd->cmd_res_cur], DAP_CLI_HTTP_RESPONSE_SIZE_MAX, 0); + if (l_recv_len == 0) { + s_status = DAP_CLI_ERROR_INCOMPLETE; + return; + } + if (l_recv_len == -1) { +#ifdef DAP_OS_WINDOWS + int l_errno = WSAGetLastError(); + if (l_errno == WSAEWOULDBLOCK) { +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) { +#endif + s_status = DAP_CLI_ERROR_TIMEOUT; + } else { + s_status = DAP_CLI_ERROR_SOCKET; + } + return; + } + l_cmd->cmd_res_cur +=(size_t) l_recv_len; + switch (s_status) { + case 1: { // Find content length + const char *l_cont_len_str = "Content-Length: "; + char *l_str_ptr = strstr(l_cmd->cmd_res, l_cont_len_str); + if (l_str_ptr && strstr(l_str_ptr, "\r\n")) { + l_cmd->cmd_res_len = atoi(l_str_ptr + strlen(l_cont_len_str)); + if (l_cmd->cmd_res_len == 0) { + s_status = DAP_CLI_ERROR_FORMAT; + break; + } + else { + s_status++; + } + } else { + break; + } + } + case 2: { // Find header end and throw out header + const char *l_head_end_str = "\r\n\r\n"; + char *l_str_ptr = strstr(l_cmd->cmd_res, l_head_end_str); + if (l_str_ptr) { + l_str_ptr += strlen(l_head_end_str); + size_t l_head_size = l_str_ptr - l_cmd->cmd_res; + memmove(l_cmd->cmd_res, l_str_ptr, l_cmd->cmd_res_cur - l_head_size); + l_cmd->cmd_res_cur -= l_head_size; + // read rest of data + if(l_cmd->cmd_res_cur < l_cmd->cmd_res_len) { + l_cmd->cmd_res = DAP_REALLOC(l_cmd->cmd_res, l_cmd->cmd_res_len + 1); + while((l_cmd->cmd_res_len - l_cmd->cmd_res_cur) > 0) { + ssize_t l_recv_len = recv(*socket, &l_cmd->cmd_res[l_cmd->cmd_res_cur], l_cmd->cmd_res_len - l_cmd->cmd_res_cur, 0); + if(l_recv_len <= 0) + break; + l_cmd->cmd_res_cur += l_recv_len; + } + } + s_status++; + } else { + break; + } + } + default: + case 3: { // Complete command reply + if (l_cmd->cmd_res_cur == l_cmd->cmd_res_len) { + l_cmd->cmd_res[l_cmd->cmd_res_cur] = 0; + s_status = 0; + } else { + s_status = DAP_CLI_ERROR_FORMAT; + } + } break; + } +} + +/** + * @brief dap_app_cli_connect + * @details Connect to node unix socket server + * @param a_socket_path + * @return if connect established, else NULL + */ +dap_app_cli_connect_param_t* dap_app_cli_connect(const char *a_socket_path) +{ + // set socket param + int buffsize = DAP_CLI_HTTP_RESPONSE_SIZE_MAX; +#ifdef WIN32 + // TODO connect to the named pipe "\\\\.\\pipe\\node_cli.pipe" + uint16_t l_cli_port = dap_config_get_item_uint16 ( g_config, "conserver", "listen_port_tcp"); + if (!l_cli_port) + return NULL; + SOCKET l_socket = socket(AF_INET, SOCK_STREAM, 0); +#else + if (!a_socket_path) { + return NULL; + } + // create socket + int l_socket = socket(AF_UNIX, SOCK_STREAM, 0); + if (l_socket < 0) { + return NULL; + } + struct timeval l_to = {DAP_CLI_HTTP_TIMEOUT, 0}; +#endif + // connect + int l_addr_len; +#ifdef WIN32 + struct sockaddr_in l_remote_addr; + l_remote_addr.sin_family = AF_INET; + IN_ADDR _in_addr = { { .S_addr = htonl(INADDR_LOOPBACK) } }; + l_remote_addr.sin_addr = _in_addr; + l_remote_addr.sin_port = l_cli_port; + l_addr_len = sizeof(struct sockaddr_in); +#else + struct sockaddr_un l_remote_addr; + l_remote_addr.sun_family = AF_UNIX; + strcpy(l_remote_addr.sun_path, a_socket_path); + l_addr_len = SUN_LEN(&l_remote_addr); +#endif + if (connect(l_socket, (struct sockaddr *)&l_remote_addr, l_addr_len) == SOCKET_ERROR) { +#ifdef __WIN32 + _set_errno(WSAGetLastError()); +#endif + printf("Socket connection err: %d\n", errno); + closesocket(l_socket); + return NULL; + } + dap_app_cli_connect_param_t *l_ret = DAP_NEW(dap_app_cli_connect_param_t); + *l_ret = l_socket; + return l_ret; +} + +/* if cli command argument contains one of the following symbol + argument is going to be encoded to base64 */ +static const char* s_dap_app_cli_forbidden_symbols[] = {"\r\n", ";", ""}; + +bool s_dap_app_cli_cmd_contains_forbidden_symbol(const char * a_cmd_param){ + for(int i = 0; s_dap_app_cli_forbidden_symbols[i][0] != '\0'; i++){ + if(strstr(a_cmd_param, s_dap_app_cli_forbidden_symbols[i])) + return true; + } + return false; +} + +/** + * Send request to kelvin-node + * + * return 0 if OK, else error code + */ +int dap_app_cli_post_command( dap_app_cli_connect_param_t *a_socket, dap_app_cli_cmd_state_t *a_cmd ) +{ + if(!a_socket || !a_cmd || !a_cmd->cmd_name) { + assert(0); + return -1; + } + a_cmd->cmd_res = DAP_NEW_Z_SIZE(char, DAP_CLI_HTTP_RESPONSE_SIZE_MAX); + a_cmd->cmd_res_cur = 0; + dap_string_t *l_cmd_data = dap_string_new(a_cmd->cmd_name); + if (a_cmd->cmd_param) { + for (int i = 0; i < a_cmd->cmd_param_count; i++) { + if (a_cmd->cmd_param[i]) { + dap_string_append(l_cmd_data, "\r\n"); + if(s_dap_app_cli_cmd_contains_forbidden_symbol(a_cmd->cmd_param[i])){ + char * l_cmd_param_base64 = dap_enc_strdup_to_base64(a_cmd->cmd_param[i]); + dap_string_append(l_cmd_data, l_cmd_param_base64); + DAP_DELETE(l_cmd_param_base64); + }else{ + dap_string_append(l_cmd_data, a_cmd->cmd_param[i]); + } + } + } + } + dap_string_append(l_cmd_data, "\r\n\r\n"); + dap_string_t *l_post_data = dap_string_new(""); + dap_string_printf(l_post_data, "POST /connect HTTP/1.1\r\n" + "Host: localhost\r\n" + "Content-Type: text/text\r\n" + "Content-Length: %zu\r\n" + "\r\n" + "%s", l_cmd_data->len, l_cmd_data->str); + send(*a_socket, l_post_data->str, l_post_data->len, 0); + + //wait for command execution + time_t l_start_time = time(NULL); + s_status = 1; + while(s_status > 0) { + dap_app_cli_http_read(a_socket, a_cmd); + if (time(NULL) - l_start_time > DAP_CLI_HTTP_TIMEOUT) + s_status = DAP_CLI_ERROR_TIMEOUT; + } + // process result + if (a_cmd->cmd_res && !s_status) { + char **l_str = dap_strsplit(a_cmd->cmd_res, "\r\n", 1); + int l_cnt = dap_str_countv(l_str); + char *l_str_reply = NULL; + if (l_cnt == 2) { + //long l_err_code = strtol(l_str[0], NULL, 10); + l_str_reply = l_str[1]; + } + printf("%s\n", (l_str_reply) ? l_str_reply : "no response"); + dap_strfreev(l_str); + } + DAP_DELETE(a_cmd->cmd_res); + dap_string_free(l_cmd_data, true); + dap_string_free(l_post_data, true); + return s_status; +} + +int dap_app_cli_disconnect(dap_app_cli_connect_param_t *a_socket) +{ + closesocket(*a_socket); + DAP_DELETE(a_socket); + return 0; +} diff --git a/net/app-cli/dap_app_cli_shell.c b/net/app-cli/dap_app_cli_shell.c new file mode 100644 index 000000000..c1cf0201e --- /dev/null +++ b/net/app-cli/dap_app_cli_shell.c @@ -0,0 +1,356 @@ + +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdint.h> +#include <unistd.h> +#include <sys/types.h> +#include <getopt.h> +#include <signal.h> +#include <string.h> +#include <assert.h> +#include <setjmp.h> +#include <locale.h> + +#ifdef _WIN32 +#include <winsock2.h> +#include <windows.h> +#include <mswsock.h> +#include <ws2tcpip.h> +#include <io.h> +#include <pthread.h> +#include <conio.h> +#else +#include <sys/ttydefaults.h> +#endif + +#include "dap_common.h" +#include "dap_app_cli.h" +#include "dap_app_cli_shell.h" + +//#include "posixjmp.h" + +#ifndef savestring +#define savestring(x) strcpy ((char *)malloc (1 + strlen (x)), (x)) +#endif + +typedef void rl_voidfunc_t(void); +typedef void rl_vintfunc_t(int); + +/* Current prompt. */ +char *rl_prompt = (char *) NULL; +int rl_visible_prompt_length = 0; + +/* Non-zero means we have been called at least once before. */ +static int rl_initialized; + +/* The stuff that gets printed out before the actual text of the line. + This is usually pointing to rl_prompt. */ +char *rl_display_prompt = (char *) NULL; + +/* Non-zero makes this the next keystroke to read. */ +int rl_pending_input = 0; + +/* Make this non-zero to return the current input_line. */ +int rl_done; +/* Non-zero if the previous command was a kill command. */ +int _rl_last_command_was_kill = 0; + +/* Top level environment for readline_internal (). */ +jmp_buf _rl_top_level; + +/* Length of the current input line. */ +int rl_end; + +/* The character that can generate an EOF. Really read from + the terminal driver... just defaulted here. */ +//int _rl_eof_char = CTRL('D'); + +#define NEWLINE '\n' + +/* Input error; can be returned by (*rl_getc_function) if readline is reading + a top-level command (RL_ISSTATE (RL_STATE_READCMD)). */ +#define READERR (-2) + +/* Possible state values for rl_readline_state */ +#define RL_STATE_NONE 0x000000 /* no state; before first call */ + +#define RL_STATE_INITIALIZING 0x0000001 /* initializing */ +#define RL_STATE_INITIALIZED 0x0000002 /* initialization done */ +#define RL_STATE_READCMD 0x0000008 /* reading a command key */ +#define RL_STATE_INPUTPENDING 0x0020000 /* rl_execute_next called */ +#define RL_STATE_TERMPREPPED 0x0000004 /* terminal is prepped */ + +/* Flags word encapsulating the current readline state. */ +unsigned long rl_readline_state = RL_STATE_NONE; + +#define RL_SETSTATE(x) (rl_readline_state |= (x)) +#define RL_UNSETSTATE(x) (rl_readline_state &= ~(x)) +#define RL_ISSTATE(x) (rl_readline_state & (x)) + +/* The names of the streams that we do input and output to. */ +FILE *rl_instream = (FILE *) NULL; +FILE *rl_outstream = (FILE *) NULL; + +/** + * Read one symbol + */ +unsigned char rl_getc(FILE *stream) +{ + int result; + unsigned char c; + + while(1) + { + +#if defined (__MINGW32__) + if (isatty (fileno (stream))) + return (_getch ()); /* "There is no error return." */ +#endif + result = 0; + if(result >= 0) + result = read(fileno(stream), &c, sizeof(unsigned char)); + if(result == sizeof(unsigned char)) + return (c); + + /* If zero characters are returned, then the file that we are + reading from is empty! Return EOF in that case. */ + if(result == 0) + return (EOF); + } +} + +/** + * Set up the prompt and expand it. Called from readline() + */ +int rl_set_prompt(const char *prompt) +{ + free(rl_prompt); + rl_prompt = prompt ? savestring(prompt) : (char *) NULL; + rl_display_prompt = rl_prompt ? rl_prompt : ""; + fprintf(stdout, "%s", prompt); + fflush(stdout); + //rl_visible_prompt_length = rl_expand_prompt (rl_prompt); + return 0; +} + +/** + * Read a line of input. Prompt with PROMPT. An empty PROMPT means none. + * A return value of NULL means that EOF was encountered. + */ +char *rl_readline(const char *prompt) +{ + int value_size = 3, value_len = 0; + char *value = DAP_NEW_Z_SIZE(char, value_size + 1); + + // Set up the prompt + rl_set_prompt(prompt); + + // Read a line of input from the global rl_instream, doing output on the global rl_outstream. + while(1) + { + unsigned char c = rl_getc(rl_instream); + + if((char)c == EOF || c == NEWLINE) + break; + value[value_len] = c; + value_len++; + if(value_len == value_size) { + value_size += 32; + value = realloc(value, value_size + 1); + } + } + return (value); +} + +static char* _rl_get_locale_var(const char *v) +{ + char *lspec; + + lspec = getenv("LC_ALL"); + if(lspec == 0 || *lspec == 0) + lspec = getenv(v); + if(lspec == 0 || *lspec == 0) + lspec = getenv("LANG"); + + return lspec; +} + +/* + * Query the right environment variables and call setlocale() to initialize + * the C library locale settings. + */ +static char* _rl_init_locale(void) +{ + char *ret, *lspec; + + /* Set the LC_CTYPE locale category from environment variables. */ + lspec = _rl_get_locale_var("LC_CTYPE"); + /* Since _rl_get_locale_var queries the right environment variables, + we query the current locale settings with setlocale(), and, if + that doesn't return anything, we set lspec to the empty string to + force the subsequent call to setlocale() to define the `native' + environment. */ + if(lspec == 0 || *lspec == 0) + lspec = setlocale(LC_CTYPE, (char *) NULL); + if(lspec == 0) + lspec = ""; + ret = setlocale(LC_CTYPE, lspec); /* ok, since it does not change locale */ + + //_rl_utf8locale = (ret && *ret) ? utf8locale (ret) : 0; + + return ret; +} + +/* + * Initialize readline (and terminal if not already). + */ +int rl_initialize(void) +{ + /* If we have never been called before, initialize the + terminal and data structures. */ + if(rl_initialized == 0) + { + RL_SETSTATE(RL_STATE_INITIALIZING); + rl_instream = (FILE *) stdin; + rl_outstream = (FILE *) stdout; + RL_UNSETSTATE(RL_STATE_INITIALIZING); + rl_initialized++; + RL_SETSTATE(RL_STATE_INITIALIZED); + } + else + (void) _rl_init_locale(); /* check current locale */ + RL_SETSTATE(RL_STATE_INITIALIZING); + rl_instream = (FILE *) stdin; + rl_outstream = (FILE *) stdout; + RL_UNSETSTATE(RL_STATE_INITIALIZING); + return 0; +} + +int parse_shell_options(char **argv, int arg_start, int arg_end) +{ + int arg_index; + int arg_character, on_or_off, next_arg, i; + char *o_option, *arg_string; + + arg_index = arg_start; + while(arg_index != arg_end && (arg_string = argv[arg_index]) && + (*arg_string == '-' || *arg_string == '+')) + { + /* There are flag arguments, so parse them. */ + next_arg = arg_index + 1; + + /* A single `-' signals the end of options. From the 4.3 BSD sh. + An option `--' means the same thing; this is the standard + getopt(3) meaning. */ + if(arg_string[0] == '-' && + (arg_string[1] == '\0' || + (arg_string[1] == '-' && arg_string[2] == '\0'))) + return (next_arg); + + i = 1; + on_or_off = arg_string[0]; + while( (arg_character = arg_string[i++]) != 0) + { + switch (arg_character) + { + case 'c': + //want_pending_command = 1; + break; + + case 'l': + //make_login_shell = 1; + break; + + case 's': + //read_from_stdin = 1; + break; + + case 'o': + o_option = argv[next_arg]; + if(o_option == 0) + { + //list_minus_o_opts(-1, (on_or_off == '-') ? 0 : 1); + break; + } + //if(set_minus_o_option(on_or_off, o_option) != EXECUTION_SUCCESS) + // exit(EX_BADUSAGE); + next_arg++; + break; + + case 'O': + /* Since some of these can be overridden by the normal + interactive/non-interactive shell initialization or + initializing posix mode, we save the options and process + them after initialization. */ + o_option = argv[next_arg]; + if(o_option == 0) + { + //shopt_listopt(o_option, (on_or_off == '-') ? 0 : 1); + break; + } + //add_shopt_to_alist(o_option, on_or_off); + next_arg++; + break; + + case 'D': + //dump_translatable_strings = 1; + break; + + default: + break; +// if(change_flag(arg_character, on_or_off) == FLAG_ERROR) +// { +// report_error(_("%c%c: invalid option"), on_or_off, arg_character); +// show_shell_usage(stderr, 0); +// exit(EX_BADUSAGE); +// } + } + } + /* Can't do just a simple increment anymore -- what about + "bash -abouo emacs ignoreeof -hP"? */ + arg_index = next_arg; + } + + return (arg_index); +} + +/** + * Strip whitespace from the start and end of STRING. Return a pointer into STRING. + */ +char * rl_stripwhite(char *string) +{ + register char *s, *t; + + for(s = string; whitespace(*s); s++) + ; + + if(*s == 0) + return (s); + + t = s + strlen(s) - 1; + while(t > s && whitespace(*t)) + t--; + *++t = '\0'; + + return s; +} + +/* The structure used to store a history entry. */ +typedef struct _hist_entry { + char *line; + char *timestamp; /* char * rather than time_t for read/write */ + char *data; +} HIST_ENTRY; + +/** + * Place STRING at the end of the history list. + */ +void add_history(const char *string) +{ + HIST_ENTRY *temp; + // The data field is set to NULL + // TODO +} diff --git a/net/app-cli/include/dap_app_cli.h b/net/app-cli/include/dap_app_cli.h new file mode 100644 index 000000000..944bb9c96 --- /dev/null +++ b/net/app-cli/include/dap_app_cli.h @@ -0,0 +1,53 @@ +/* + * Authors: + * Dmitriy A. Gearasimov <kahovski@gmail.com> + * DeM Labs Inc. https://demlabs.net + * DeM Labs Open source community https://github.com/demlabsinc + * Copyright (c) 2017-2019 + * All rights reserved. + + This file is part of DAP (Deus Applications Prototypes) the open source project + + DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DAP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once + +#include <stdint.h> +#include <stddef.h> + +// command description +typedef struct dap_app_cli_cmd_state { + char *cmd_name; + char **cmd_param; + int cmd_param_count; + int ret_code; + // for reply + char *cmd_res; + size_t cmd_res_len; + size_t cmd_res_cur; +} dap_app_cli_cmd_state_t; + +#ifdef __cplusplus +extern "C" { +#endif +/** + * Clear and delete memory of structure cmd_state + */ +void dap_app_cli_free_cmd_state(dap_app_cli_cmd_state_t *cmd); + +int dap_app_cli_main(const char * a_app_name, const char * a_socket_path, int argc, char **argv); + +#ifdef __cplusplus +} +#endif diff --git a/net/app-cli/include/dap_app_cli_net.h b/net/app-cli/include/dap_app_cli_net.h new file mode 100644 index 000000000..c4ffbd06d --- /dev/null +++ b/net/app-cli/include/dap_app_cli_net.h @@ -0,0 +1,54 @@ +/* + * Authors: + * Dmitriy A. Gearasimov <kahovski@gmail.com> + * DeM Labs Inc. https://demlabs.net + * DeM Labs Open source community https://github.com/demlabsinc + * Copyright (c) 2017-2019 + * All rights reserved. + + This file is part of DAP (Deus Applications Prototypes) the open source project + + DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DAP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "dap_app_cli.h" +#include "dap_events_socket.h" + +#define DAP_CLI_HTTP_RESPONSE_SIZE_MAX 65536 +#define DAP_CLI_HTTP_TIMEOUT 10 // seconds +#define DAP_CLI_ERROR_FORMAT -1 +#define DAP_CLI_ERROR_TIMEOUT -2 +#define DAP_CLI_ERROR_SOCKET -3 +#define DAP_CLI_ERROR_INCOMPLETE -4 + +// connection description +typedef uint64_t dap_app_cli_connect_param_t; + +/** + * Connect to node unix socket server + * + * return struct connect_param if connect established, else NULL + */ +dap_app_cli_connect_param_t* dap_app_cli_connect(const char * a_socket_path); + +/** + * Send request to kelvin-node + * + * return 0 if OK, else error code + */ +int dap_app_cli_post_command(dap_app_cli_connect_param_t *socket, dap_app_cli_cmd_state_t *cmd); + +int dap_app_cli_disconnect(dap_app_cli_connect_param_t *socket); diff --git a/net/app-cli/include/dap_app_cli_shell.h b/net/app-cli/include/dap_app_cli_shell.h new file mode 100644 index 000000000..5fdfeb4a5 --- /dev/null +++ b/net/app-cli/include/dap_app_cli_shell.h @@ -0,0 +1,53 @@ +/* + * Authors: + * Dmitriy A. Gearasimov <kahovski@gmail.com> + * DeM Labs Inc. https://demlabs.net + * DeM Labs Open source community https://github.com/demlabsinc + * Copyright (c) 2017-2019 + * All rights reserved. + + This file is part of DAP (Deus Applications Prototypes) the open source project + + DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DAP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#ifndef whitespace +#define whitespace(c) (((c) == ' ') || ((c) == '\t')) +#endif + +/* + * Initialize readline (and terminal if not already). + */ +int rl_initialize(void); + +/** + * Strip whitespace from the start and end of STRING. Return a pointer into STRING. + */ +char * rl_stripwhite(char *string); + +/** + * Read a line of input. Prompt with PROMPT. An empty PROMPT means none. + * A return value of NULL means that EOF was encountered. + */ +char *rl_readline(const char *prompt); + +/** + * Place STRING at the end of the history list. + */ +void add_history(const char *string); + +int parse_shell_options(char **argv, int arg_start, int arg_end); + diff --git a/net/server/CMakeLists.txt b/net/server/CMakeLists.txt index 2ad3a1a3f..37e70acb3 100644 --- a/net/server/CMakeLists.txt +++ b/net/server/CMakeLists.txt @@ -1,11 +1,13 @@ project(libdap-server C) cmake_minimum_required(VERSION 3.10) +add_subdirectory(cli_server) add_subdirectory(notify_server) add_subdirectory(http_server) add_subdirectory(enc_server) add_subdirectory(json_rpc) + if (BUILD_LIB_DAP_SERVER_TESTS) enable_testing() add_subdirectory(test) diff --git a/net/server/cli_server/CMakeLists.txt b/net/server/cli_server/CMakeLists.txt new file mode 100644 index 000000000..b67f12d27 --- /dev/null +++ b/net/server/cli_server/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.10) +project (dap_cli_server C) + +file(GLOB DAP_CLI_SRV_SRCS *.c) + +file(GLOB DAP_CLI_SRV_HEADERS include/*.h) + +add_library(${PROJECT_NAME} STATIC ${DAP_CLI_SRV_SRCS} ${DAP_CLI_SRV_HEADERS} ) + +target_link_libraries(${PROJECT_NAME} dap_core dap_io m) +target_include_directories(${PROJECT_NAME} PUBLIC include/ ) diff --git a/net/server/cli_server/dap_cli_server.c b/net/server/cli_server/dap_cli_server.c new file mode 100644 index 000000000..4ba5f8560 --- /dev/null +++ b/net/server/cli_server/dap_cli_server.c @@ -0,0 +1,1012 @@ +/* + * Authors: + * Dmitriy A. Gerasimov <gerasimov.dmitriy@demlabs.net> + * Alexander Lysikov <alexander.lysikov@demlabs.net> + * DeM Labs Inc. https://demlabs.net + * Cellframe https://cellframe.net + * Copyright (c) 2019-2021 + * All rights reserved. + + This file is part of Cellframe SDK + + Cellframe SDK is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Cellframe SDK is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any Cellframe SDK based project. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +//#include <glib.h> +#include <unistd.h> + +#ifndef _WIN32 +#include <poll.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <arpa/inet.h> +//#include <unistd.h> // for close +#include <fcntl.h> +//#include <sys/poll.h> +//#include <sys/select.h> +#include <netinet/in.h> +#include <sys/un.h> +#include <sys/stat.h> +//#define closesocket close +//typedef int SOCKET; +//#define SOCKET_ERROR -1 // for win32 = (-1) +//#define INVALID_SOCKET -1 // for win32 = (SOCKET)(~0) +// for Windows +#else +#include <winsock2.h> +#include <windows.h> +#include <mswsock.h> +#include <ws2tcpip.h> +#include <io.h> +#endif + +#include <pthread.h> + +#include "dap_common.h" +#include "dap_strfuncs.h" +#include "dap_file_utils.h" +#include "dap_list.h" +#include "dap_net.h" +#include "dap_cli_server.h" + +#define LOG_TAG "dap_cli_server" + +#define MAX_CONSOLE_CLIENTS 16 + +static SOCKET server_sockfd = -1; // network or local unix +static uint32_t l_listen_port = 0; +static bool s_debug_cli = false; + +#ifdef _WIN32 + #define poll WSAPoll +#endif + +static dap_cli_cmd_t * s_commands = NULL; + + +static void* s_thread_one_client_func(void *args); +static void* s_thread_main_func(void *args); +static inline void s_cmd_add_ex(const char * a_name, dap_cli_server_cmd_callback_ex_t a_func, void *a_arg_func, const char *a_doc, const char *a_doc_ex); + + + +/** + * @brief dap_cli_server_init + * @param a_debug_more + * @param a_socket_path_or_address + * @param a_port + * @param a_permissions + * @return + */ +int dap_cli_server_init(bool a_debug_more,const char * a_socket_path_or_address, uint16_t a_port, const char * a_permissions) +{ + s_debug_cli = a_debug_more; +#ifndef _WIN32 + struct sockaddr_un l_server_addr={0}; + l_server_addr.sun_family = AF_UNIX; + snprintf(l_server_addr.sun_path,sizeof(l_server_addr.sun_path), "%s", a_socket_path_or_address); +#else + pthread_t threadId; +#endif + + struct sockaddr_in server_addr; + SOCKET sockfd = -1; + + // create thread for waiting of clients + pthread_t l_thread_id; + + l_listen_port = dap_config_get_item_uint16_default( g_config, "conserver", "listen_port_tcp",0); + + const char * l_listen_unix_socket_path = dap_config_get_item_str( g_config, "conserver", "listen_unix_socket_path"); + + + + const char * l_listen_unix_socket_permissions_str = dap_config_get_item_str( g_config, "conserver", "listen_unix_socket_permissions"); + mode_t l_listen_unix_socket_permissions = 0770; + + if ( l_listen_unix_socket_path && l_listen_unix_socket_permissions ) { + if ( l_listen_unix_socket_permissions_str ) { + uint16_t l_perms; + dap_sscanf(l_listen_unix_socket_permissions_str,"%ho", &l_perms); + l_listen_unix_socket_permissions = l_perms; + } + log_it( L_INFO, "Console interace on path %s (%04o) ", l_listen_unix_socket_path, l_listen_unix_socket_permissions ); + + #ifndef _WIN32 + + if ( server_sockfd >= 0 ) { + dap_cli_server_deinit(); + server_sockfd = 0; + } + + // create socket + sockfd = socket( AF_UNIX, SOCK_STREAM, 0 ); + if( sockfd == INVALID_SOCKET ) + return -1; + + //int gdsg = sizeof(struct sockaddr_un); + + // Creatuing directory if not created + char * l_listen_unix_socket_path_dir = dap_path_get_dirname(l_listen_unix_socket_path); + dap_mkdir_with_parents(l_listen_unix_socket_path_dir); + DAP_DELETE(l_listen_unix_socket_path_dir); + + if ( access( l_listen_unix_socket_path , R_OK) != -1 ) + unlink( l_listen_unix_socket_path ); + + + // connecting the address with a socket + if( bind(sockfd, (const struct sockaddr*) &l_server_addr, sizeof(struct sockaddr_un)) == SOCKET_ERROR) { + // errno = EACCES 13 Permission denied + if ( errno == EACCES ) // EACCES=13 + log_it( L_ERROR, "Server can't start(err=%d). Can't create file=%s [Permission denied]", errno, + l_listen_unix_socket_path ); + else + log_it( L_ERROR, "Server can't start(err=%d). May be problem with file=%s?", errno, l_listen_unix_socket_path ); + closesocket( sockfd ); + return -2; + } + chmod(l_listen_unix_socket_path,l_listen_unix_socket_permissions); + + #else + +// Sleep( 3000 ); + + if( pthread_create(&threadId, NULL, thread_pipe_func, (void*) (intptr_t) sockfd) != 0 ) { + closesocket( sockfd ); + return -7; + } + + return 0; + #endif + + } + else if (l_listen_port ){ + + const char *l_listen_addr_str = dap_config_get_item_str(g_config, "conserver", "listen_address"); + + log_it( L_INFO, "Console interace on addr %s port %u ", l_listen_addr_str, l_listen_port ); + + server_addr.sin_family = AF_INET; +#ifdef _WIN32 + struct in_addr _in_addr = { { .S_addr = htonl(INADDR_LOOPBACK) } }; + server_addr.sin_addr = _in_addr; + server_addr.sin_port = l_listen_port; +#else + inet_pton( AF_INET, l_listen_addr_str, &server_addr.sin_addr ); + server_addr.sin_port = htons( (uint16_t)l_listen_port ); +#endif + // create socket + if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET ) { +#ifdef __WIN32 + _set_errno(WSAGetLastError()); +#endif + log_it( L_ERROR, "Console Server: can't create socket, err %d", errno ); + return -3; + } + + // connecting the address with a socket + if ( bind(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr)) == SOCKET_ERROR ) { +#ifdef __WIN32 + _set_errno(WSAGetLastError()); +#endif + log_it( L_ERROR, "Console Server: can't bind socket, err %d", errno ); + closesocket( sockfd ); + return -4; + } + }else { + log_it (L_INFO, "Not defined console interface"); + return 0; + } + + // turn on reception of connections + if( listen(sockfd, MAX_CONSOLE_CLIENTS) == SOCKET_ERROR ) + return -5; + + if( pthread_create(&l_thread_id, NULL, s_thread_main_func, (void*) (intptr_t) sockfd) != 0 ) { + closesocket( sockfd ); + return -6; + } + + // in order to thread not remain in state "dead" after completion + pthread_detach( l_thread_id ); + server_sockfd = sockfd; + + return 0; +} + +/** + * @brief dap_cli_server_deinit + */ +void dap_cli_server_deinit() +{ + if(server_sockfd != INVALID_SOCKET) + closesocket(server_sockfd); +#ifdef __WIN32 + WSACleanup(); +#endif + +} + +/** + * @brief dap_cli_server_cmd_add + * @param a_name + * @param a_func + * @param a_doc + * @param a_doc_ex + */ +void dap_cli_server_cmd_add(const char * a_name, dap_cli_server_cmd_callback_t a_func, const char *a_doc, const char *a_doc_ex) +{ + s_cmd_add_ex(a_name, (dap_cli_server_cmd_callback_ex_t)(void *)a_func, NULL, a_doc, a_doc_ex); +} + +/** + * @brief s_cmd_add_ex + * @param a_name + * @param a_func + * @param a_arg_func + * @param a_doc + * @param a_doc_ex + */ +static inline void s_cmd_add_ex(const char * a_name, dap_cli_server_cmd_callback_ex_t a_func, void *a_arg_func, const char *a_doc, const char *a_doc_ex) +{ + dap_cli_cmd_t *l_cmd_item = DAP_NEW_Z(dap_cli_cmd_t); + dap_snprintf(l_cmd_item->name,sizeof (l_cmd_item->name),"%s",a_name); + l_cmd_item->doc = strdup( a_doc); + l_cmd_item->doc_ex = strdup( a_doc_ex); + if (a_arg_func) { + l_cmd_item->func_ex = a_func; + l_cmd_item->arg_func = a_arg_func; + } else { + l_cmd_item->func = (dap_cli_server_cmd_callback_t )(void *)a_func; + } + HASH_ADD_STR(s_commands,name,l_cmd_item); + log_it(L_DEBUG,"Added command %s",l_cmd_item->name); +} + +/** + * @brief int s_poll + * Wait for data + * timeout - timeout in ms + * [Specifying a negative value in timeout means an infinite timeout.] + * [Specifying a timeout of zero causes poll() to return immediately, even if no file descriptors are ready.] + * return zero if the time limit expired + * return: >0 if data is present to read + * return: -1 if error + * @param socket + * @param timeout + * @return int + */ +static int s_poll( int sd, int timeout ) +{ +struct pollfd fds = {.fd = sd, .events = POLLIN}; +int res; + + res = poll(&fds, 1, timeout); + + return (res == 1 && !(fds.revents & POLLIN)) ? -1 : res; +} + + +/** + * @brief is_valid_socket + * Check socket for validity + * @param sock + * @return true + * @return false + */ +static int is_valid_socket(SOCKET sd) +{ +struct pollfd fds = {.fd = sd, .events = POLLIN}; +int res; + + if ( 0 > (res = poll(&fds, 1, 0)) ) + return false; + + // event with an error code + if(res > 0) + { + // feature of disconnection under Windows + // under Windows, with socket closed fds.revents=POLLHUP, in Unix fds.events = POLLIN + if(fds.revents & (POLLERR | POLLHUP | POLLNVAL)) + return false; + + // feature of disconnection under Unix (QNX) + // under Windows, with socket closed res = 0, in Unix res = -1 + char buf[2]; + if ( 0 > (res = recv(sd, buf, 1, MSG_PEEK)) ) // MSG_PEEK The data is treated as unread and the next recv() function shall still return this data. + return false; + + // data in the buffer must be(count_desc>0), but read 0 bytes(res=0) + if(!res && (fds.revents & POLLIN)) + return false; + } + + return true; +} + + +/** + * @brief s_get_next_str + * Reading from the socket till arrival the specified string + * + * stop_str - string to which reading will continue + * del_stop_str - удалÑÑ‚ÑŒ ли Ñтроку Ð´Ð»Ñ Ð¿Ð¾Ð¸Ñка в конце + * timeout - in ms + * return: string (if waited for final characters) or NULL, if the string requires deletion + * @param nSocket + * @param dwLen + * @param stop_str + * @param del_stop_str + * @param timeout + * @return char* + */ +char* s_get_next_str( SOCKET nSocket, int *dwLen, const char *stop_str, bool del_stop_str, int timeout ) +{ + bool bSuccess = false; + long nRecv = 0; // count of bytes received + size_t stop_str_len = (stop_str) ? strlen(stop_str) : 0; + // if there is nothing to look for + if(!stop_str_len) + return NULL; + size_t lpszBuffer_len = 256; + char *lpszBuffer = DAP_NEW_Z_SIZE(char, lpszBuffer_len); + // received string will not be larger than MAX_REPLY_LEN + + while(1) //nRecv < MAX_REPLY_LEN) + { + // read one byte + long ret = dap_net_recv(nSocket, (unsigned char *) (lpszBuffer + nRecv), 1, timeout); + //int ret = recv(nSocket,lpszBuffer+nRecv,1, 0); + if(ret <= 0) + { + break; + } + nRecv += ret; + //printf("**debug** socket=%d read %d bytes '%0s'",nSocket, ret, (lpszBuffer + nRecv)); + while((nRecv + 1) >= (long) lpszBuffer_len) + { + lpszBuffer_len *= 2; + lpszBuffer = (char*) realloc(lpszBuffer, lpszBuffer_len); + } + // search for the required string + if(nRecv >= (long) stop_str_len) { + // found the required string + if(!strncasecmp(lpszBuffer + nRecv - stop_str_len, stop_str, stop_str_len)) { + bSuccess = true; + break; + } + } + }; + + // end reading + + if(bSuccess) { + // delete the searched string + if(del_stop_str) { + lpszBuffer[nRecv - (long) stop_str_len] = '\0'; + if(dwLen) + *dwLen =(int) nRecv - (int) stop_str_len; + } + else { + lpszBuffer[nRecv] = '\0'; + if(dwLen) + *dwLen = (int) nRecv; + } + char * l_buf_realloc = DAP_REALLOC(lpszBuffer,(size_t) *dwLen + 1); + if( l_buf_realloc) + lpszBuffer = l_buf_realloc; + return lpszBuffer; + } + + // in case of an error or missing string + + if(dwLen) + *dwLen = 0; + + free(lpszBuffer); + + return NULL; +} + +/** + * threading function for processing a request from a client + */ +static void* s_thread_one_client_func(void *args) +{ +SOCKET newsockfd = (SOCKET) (intptr_t) args; +int str_len, marker = 0, timeout = 5000, argc = 0, is_data; +dap_list_t *cmd_param_list = NULL; +char *str_header; + + if(s_debug_cli) + log_it(L_DEBUG, "new connection sockfd=%"DAP_FORMAT_SOCKET, newsockfd); + + + while ( !(0 > (is_data = s_poll(newsockfd, timeout))) ) // wait data from client + { + if ( !(is_data) ) // timeout + continue; + + if ( !is_valid_socket(newsockfd) ) + break; + + // receiving http header + if ( !(str_header = s_get_next_str(newsockfd, &str_len, "\r\n", true, timeout)) ) + break; // bad format + + if(str_header && !strlen(str_header) ) { + marker++; + if(marker == 1){ + DAP_DELETE(str_header); + continue; + } + } + + // filling parameters of command + if(marker == 1) { + cmd_param_list = dap_list_append(cmd_param_list, str_header); + //printf("g_list_append argc=%d command=%s ", argc, str_header); + argc++; + } + else + DAP_DEL_Z(str_header); + + if(marker == 2 && cmd_param_list) { + dap_list_t *list = cmd_param_list; + // form command + argc = dap_list_length(list); + // command is found + if(argc >= 1) { + int l_verbose = 0; + char *cmd_name = list->data; + list = dap_list_next(list); + // execute command + char *str_cmd = dap_strdup_printf("%s", cmd_name); + dap_cli_cmd_t *l_cmd = dap_cli_server_cmd_find(cmd_name); + int res = -1; + char *str_reply = NULL; + if(l_cmd){ + while(list) { + char *str_cmd_prev = str_cmd; + str_cmd = dap_strdup_printf("%s;%s", str_cmd, list->data); + list = dap_list_next(list); + DAP_DELETE(str_cmd_prev); + } + if(l_cmd->overrides.log_cmd_call) + l_cmd->overrides.log_cmd_call(str_cmd); + else + log_it(L_DEBUG, "execute command=%s", str_cmd); + // exec command + + char **l_argv = dap_strsplit(str_cmd, ";", -1); + // Call the command function + if(l_cmd && l_argv && l_cmd->func) { + if (l_cmd->arg_func) { + res = l_cmd->func_ex(argc, l_argv, l_cmd->arg_func, &str_reply); + } else { + res = l_cmd->func(argc, l_argv, &str_reply); + } + } else if (l_cmd) { + log_it(L_WARNING,"NULL arguments for input for command \"%s\"", str_cmd); + }else { + log_it(L_WARNING,"No function for command \"%s\" but it registred?!", str_cmd); + } + // find '-verbose' command + l_verbose = dap_cli_server_cmd_find_option_val(l_argv, 1, argc, "-verbose", NULL); + dap_strfreev(l_argv); + } else { + str_reply = dap_strdup_printf("can't recognize command=%s", str_cmd); + log_it(L_ERROR,"Reply string: \"%s\"", str_reply); + } + char *reply_body; + if(l_verbose) + reply_body = dap_strdup_printf("%d\r\nret_code: %d\r\n%s\r\n", res, res, (str_reply) ? str_reply : ""); + else + reply_body = dap_strdup_printf("%d\r\n%s\r\n", res, (str_reply) ? str_reply : ""); + // return the result of the command function + char *reply_str = dap_strdup_printf("HTTP/1.1 200 OK\r\n" + "Content-Length: %d\r\n\r\n" + "%s", strlen(reply_body), reply_body); + size_t l_reply_step = 32768; + size_t l_reply_len = strlen(reply_str); + size_t l_reply_rest = l_reply_len; + + while(l_reply_rest) { + size_t l_send_bytes = min(l_reply_step, l_reply_rest); + int ret = send(newsockfd, reply_str + l_reply_len - l_reply_rest, l_send_bytes, 0); + if(ret<=0) + break; + l_reply_rest-=l_send_bytes; + }; + + DAP_DELETE(str_reply); + DAP_DELETE(reply_str); + DAP_DELETE(reply_body); + + DAP_DELETE(str_cmd); + } + dap_list_free_full(cmd_param_list, NULL); + break; + } + } + // close connection + int cs = closesocket(newsockfd); + if (s_debug_cli) + log_it(L_DEBUG, "close connection=%d sockfd=%"DAP_FORMAT_SOCKET, cs, newsockfd); + + return NULL; +} + +#ifdef _WIN32 + +/** + * @brief p_get_next_str + * + * @param hPipe + * @param dwLen + * @param stop_str + * @param del_stop_str + * @param timeout + * @return char* + */ +char *p_get_next_str( HANDLE hPipe, int *dwLen, const char *stop_str, bool del_stop_str, int timeout ) +{ + UNUSED(timeout); + bool bSuccess = false; + long nRecv = 0; // count of bytes received + size_t stop_str_len = (stop_str) ? strlen(stop_str) : 0; + // if there is nothing to look for + + if(!stop_str_len) + return NULL; + + size_t lpszBuffer_len = 256; + char *lpszBuffer = DAP_NEW_Z_SIZE(char, lpszBuffer_len); + // received string will not be larger than MAX_REPLY_LEN + + while( 1 ) //nRecv < MAX_REPLY_LEN) + { + long ret = 0; + // read one byte +// long ret = s_recv( nSocket, (unsigned char *) (lpszBuffer + nRecv), 1, timeout); + + bSuccess = ReadFile( hPipe, lpszBuffer + nRecv, + lpszBuffer_len - nRecv, (LPDWORD)&ret, NULL ); + + //int ret = recv(nSocket,lpszBuffer+nRecv,1, 0); + if ( ret <= 0 || !bSuccess ) + break; + + nRecv += ret; + //printf("**debug** socket=%d read %d bytes '%0s'",nSocket, ret, (lpszBuffer + nRecv)); + + while((nRecv + 1) >= (long) lpszBuffer_len) + { + lpszBuffer_len *= 2; + lpszBuffer = (char*) realloc(lpszBuffer, lpszBuffer_len); + } + + // search for the required string + if(nRecv >= (long) stop_str_len) { + // found the required string + if(!strncasecmp(lpszBuffer + nRecv - stop_str_len, stop_str, stop_str_len)) { + bSuccess = true; + break; + } + } + }; + + // end reading + + if(bSuccess) { + // delete the searched string + if(del_stop_str) { + lpszBuffer[nRecv - (long) stop_str_len] = '\0'; + if(dwLen) + *dwLen =(int) nRecv - (int) stop_str_len; + } + else { + lpszBuffer[nRecv] = '\0'; + if(dwLen) + *dwLen = (int) nRecv; + } + lpszBuffer = DAP_REALLOC(lpszBuffer,(size_t) *dwLen + 1); + return lpszBuffer; + } + + // in case of an error or missing string + + if(dwLen) + *dwLen = 0; + + free(lpszBuffer); + + return NULL; +} + +/** + * @brief thread_pipe_client_func + * threading function for processing a request from a client + * @param args + * @return void* + */ +static void *thread_pipe_client_func( void *args ) +{ + HANDLE hPipe = (HANDLE)args; + +// SOCKET newsockfd = (SOCKET) (intptr_t) args; + if(s_debug_cli) + log_it(L_INFO, "new connection pipe = %p", hPipe); + + int str_len, marker = 0; + int timeout = 5000; // 5 sec + int argc = 0; + + dap_list_t *cmd_param_list = NULL; + + while( 1 ) + { + // wait data from client +// int is_data = s_poll( newsockfd, timeout ); + // timeout +// if(!is_data) +// continue; + // error (may be socket closed) +// if(is_data < 0) +// break; + +// int is_valid = is_valid_socket(newsockfd); +// if(!is_valid) +// { +// break; +// } + + // receiving http header + char *str_header = p_get_next_str( hPipe, &str_len, "\r\n", true, timeout ); + + // bad format + if(!str_header) + break; + + if ( str_header && strlen(str_header) == 0) { + marker++; + if(marker == 1) + continue; + } + + // filling parameters of command + if ( marker == 1 ) { + cmd_param_list = dap_list_append( cmd_param_list, str_header ); + //printf("g_list_append argc=%d command=%s ", argc, str_header); + argc ++; + } + else + free( str_header ); + + if ( marker == 2 ) { + + dap_list_t *list = cmd_param_list; + // form command + + unsigned int argc = dap_list_length( list ); + // command is found + + if ( argc >= 1) { + + int l_verbose = 0; + char *cmd_name = list->data; + list = dap_list_next( list ); + + // execute command + char *str_cmd = dap_strdup_printf( "%s", cmd_name ); + dap_cli_cmd_t *l_cmd = dap_cli_server_cmd_find( cmd_name ); + int res = -1; + char *str_reply = NULL; + + if ( l_cmd ) { + + while( list ) { + str_cmd = dap_strdup_printf( "%s;%s", str_cmd, list->data ); + list = dap_list_next(list); + } + + log_it(L_INFO, "execute command = %s", str_cmd ); + // exec command + + char **l_argv = dap_strsplit( str_cmd, ";", -1 ); + // Call the command function + + if ( l_cmd && l_argv && l_cmd->func ) { + if (l_cmd->arg_func) { + res = l_cmd->func_ex(argc, l_argv, l_cmd->arg_func, &str_reply); + } else { + res = l_cmd->func(argc, l_argv, &str_reply); + } + } + + else if ( l_cmd ) { + log_it(L_WARNING,"NULL arguments for input for command \"%s\"", str_cmd ); + }else { + log_it(L_WARNING,"No function for command \"%s\" but it registred?!", str_cmd ); + } + + // find '-verbose' command + l_verbose = dap_cli_server_cmd_find_option_val( l_argv, 1, argc, "-verbose", NULL ); + dap_strfreev( l_argv ); + + } else { + str_reply = dap_strdup_printf("can't recognize command = %s", str_cmd ); + log_it( L_ERROR, str_reply ); + } + + char *reply_body; + + if(l_verbose) + reply_body = dap_strdup_printf("%d\r\nret_code: %d\r\n%s\r\n", res, res, (str_reply) ? str_reply : ""); + else + reply_body = dap_strdup_printf("%d\r\n%s\r\n", res, (str_reply) ? str_reply : ""); + + // return the result of the command function + char *reply_str = dap_strdup_printf( "HTTP/1.1 200 OK\r\n" + "Content-Length: %d\r\n\r\n" + "%s", + strlen(reply_body), reply_body ); + + int ret;// = send( newsockfd, reply_str, strlen(reply_str) ,0 ); + + WriteFile( hPipe, reply_str, strlen(reply_str), (LPDWORD)&ret, NULL ); + + DAP_DELETE(str_reply); + DAP_DELETE(reply_str); + DAP_DELETE(reply_body); + + DAP_DELETE(str_cmd); + } + dap_list_free_full(cmd_param_list, free); + break; + } + } + + // close connection +// int cs = closesocket(newsockfd); + + log_it( L_INFO, "close connection pipe = %p", hPipe ); + + FlushFileBuffers( hPipe ); + DisconnectNamedPipe( hPipe ); + CloseHandle( hPipe ); + + return NULL; +} + + +/** + * @brief thread_pipe_func + * main threading server function pipe win32 + * @param args + * @return void* + */ +static void* thread_pipe_func( void *args ) +{ + UNUSED(args); + BOOL fConnected = FALSE; + pthread_t threadId; + HANDLE hPipe = INVALID_HANDLE_VALUE; + static const char *cPipeName = "\\\\.\\pipe\\node_cli.pipe"; + + for (;;) + { +/// printf( "\nPipe Server: Main thread awaiting client connection on %s\n", lpszPipename ); + + hPipe = CreateNamedPipe( + cPipeName, // 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 + 4096, // output buffer size + 4096, // input buffer size + 0, // client time-out + NULL ); // default security attribute + + if ( hPipe == INVALID_HANDLE_VALUE ) { + log_it( L_ERROR, "CreateNamedPipe failed, GLE = %lu.\n", GetLastError() ); + return NULL; + } + + fConnected = ConnectNamedPipe( hPipe, NULL ) ? TRUE : ( GetLastError() == ERROR_PIPE_CONNECTED ); + + if ( fConnected ) + { + log_it( L_INFO, "Client %p connected, creating a processing thread.\n", hPipe ); + + pthread_create( &threadId, NULL, thread_pipe_client_func, hPipe ); + pthread_detach( threadId ); + } + else + CloseHandle( hPipe ); + } + + return NULL; +} +#endif + + +/** + * @brief thread_main_func + * main threading server function + * @param args + * @return void* + */ +static void* s_thread_main_func(void *args) +{ + SOCKET sockfd = (SOCKET) (intptr_t) args; + SOCKET newsockfd; + + log_it( L_INFO, "Server start socket = %s", dap_config_get_item_str( g_config, "conserver", "listen_unix_socket_path") ); + // wait of clients + while(1) + { + pthread_t threadId; + struct sockaddr_in peer; + socklen_t size = sizeof(peer); + // received a new connection request + if((newsockfd = accept(sockfd, (struct sockaddr*) &peer, &size)) == (SOCKET) -1) { + log_it(L_ERROR, "new connection break newsockfd=%"DAP_FORMAT_SOCKET, newsockfd); + break; + } + // create child thread for a client connection + pthread_create(&threadId, NULL, s_thread_one_client_func, (void*) (intptr_t) newsockfd); + // in order to thread not remain in state "dead" after completion + pthread_detach(threadId); + }; + // close connection + int cs = closesocket(sockfd); + log_it(L_INFO, "Exit server thread=%d socket=%"DAP_FORMAT_SOCKET, cs, sockfd); + return NULL; +} + + +/** + * @brief dap_chain_node_cli_set_reply_text + * Write text to reply string + * @param str_reply + * @param str + * @param ... + */ +void dap_cli_server_cmd_set_reply_text(char **str_reply, const char *str, ...) +{ + if(str_reply) { + if(*str_reply) { + assert( *str_reply ); + DAP_DELETE(*str_reply); + *str_reply = NULL; + } + va_list args; + va_start(args, str); + *str_reply = dap_strdup_vprintf(str, args); //*str_reply = dap_strdup(str); + va_end(args); + } +} + +/** + * @brief dap_cli_server_cmd_check_option + * @param argv + * @param arg_start + * @param arg_end + * @param opt_name + * @return + */ +int dap_cli_server_cmd_check_option( char** argv, int arg_start, int arg_end, const char *opt_name) +{ + int arg_index = arg_start; + const char *arg_string; + + while(arg_index < arg_end) + { + char * l_argv_cur = argv[arg_index]; + arg_string = l_argv_cur; + // find opt_name + if(arg_string && opt_name && arg_string[0] && opt_name[0] && !strcmp(arg_string, opt_name)) { + return arg_index; + } + arg_index++; + } + return -1; +} + + +/** + * @brief dap_cli_server_cmd_find_option_val + * return index of string in argv, or 0 if not found + * @param argv + * @param arg_start + * @param arg_end + * @param opt_name + * @param opt_value + * @return int + */ +int dap_cli_server_cmd_find_option_val( char** argv, int arg_start, int arg_end, const char *opt_name, const char **opt_value) +{ + assert(argv); + int arg_index = arg_start; + const char *arg_string; + int l_ret_pos = 0; + + while(arg_index < arg_end) + { + char * l_argv_cur = argv[arg_index]; + arg_string = l_argv_cur; + // find opt_name + if(arg_string && opt_name && arg_string[0] && opt_name[0] && !strcmp(arg_string, opt_name)) { + // find opt_value + if(opt_value) { + arg_string = argv[++arg_index]; + if(arg_string) { + *opt_value = arg_string; + return arg_index; + } + // for case if opt_name exist without value + else + l_ret_pos = arg_index; + } + else + // need only opt_name + return arg_index; + } + arg_index++; + } + return l_ret_pos; +} + + +/** + * @brief dap_cli_server_cmd_apply_overrides + * + * @param a_name + * @param a_overrides + */ +void dap_cli_server_cmd_apply_overrides(const char * a_name, const dap_cli_server_cmd_override_t a_overrides) +{ + dap_cli_cmd_t *l_cmd_item = dap_cli_server_cmd_find(a_name); + if(l_cmd_item) + l_cmd_item->overrides = a_overrides; +} + +/** + * @brief dap_cli_server_cmd_get_first + * @return + */ +dap_cli_cmd_t* dap_cli_server_cmd_get_first() +{ + return s_commands; +} + +/** + * @brief dap_cli_server_cmd_find + * @param a_name + * @return + */ +dap_cli_cmd_t* dap_cli_server_cmd_find(const char *a_name) +{ + dap_cli_cmd_t *l_cmd_item = NULL; + HASH_FIND_STR(s_commands,a_name,l_cmd_item); + return l_cmd_item; +} diff --git a/net/server/cli_server/include/dap_cli_server.h b/net/server/cli_server/include/dap_cli_server.h new file mode 100644 index 000000000..856888225 --- /dev/null +++ b/net/server/cli_server/include/dap_cli_server.h @@ -0,0 +1,67 @@ +/* + * Authors: + * Dmitriy A. Gerasimov <gerasimov.dmitriy@demlabs.net> + * Alexander Lysikov <alexander.lysikov@demlabs.net> + * DeM Labs Inc. https://demlabs.net + * Cellframe https://cellframe.net + * Copyright (c) 2019-2022 + * All rights reserved. + + This file is part of Cellframe SDK + + Cellframe SDK is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Cellframe SDK is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any Cellframe SDK based project. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "dap_events_socket.h" +#include "dap_common.h" +#include "dap_config.h" +#include "uthash.h" + +typedef int (*dap_cli_server_cmd_callback_ex_t)(int argc, char ** argv, void *arg_func, char **str_reply); +typedef int (*dap_cli_server_cmd_callback_t)(int argc, char ** argv, char **str_reply); + +typedef void (*dap_cli_server_override_log_cmd_callback_t)(const char*); + +typedef struct dap_cli_server_cmd_override{ + /* use it if you want to prevent logging of some sensetive data */ + dap_cli_server_override_log_cmd_callback_t log_cmd_call; +} dap_cli_server_cmd_override_t; + +typedef struct dap_cli_cmd{ + char name[32]; /* User printable name of the function. */ + union { + dap_cli_server_cmd_callback_t func; /* Function to call to do the job. */ + dap_cli_server_cmd_callback_ex_t func_ex; /* Function with additional arg to call to do the job. */ + }; + void *arg_func; /* additional argument of function*/ + char *doc; /* Documentation for this function. */ + char *doc_ex; /* Full documentation for this function. */ + dap_cli_server_cmd_override_t overrides; /* Used to change default behaviour */ + UT_hash_handle hh; +} dap_cli_cmd_t; + + +int dap_cli_server_init(bool a_debug_more,const char * a_socket_path_or_address, uint16_t a_port, const char * a_permissions) ; +void dap_cli_server_deinit(); + +void dap_cli_server_cmd_add(const char * a_name, dap_cli_server_cmd_callback_t a_func, const char *a_doc, const char *a_doc_ex); +void dap_cli_server_cmd_set_reply_text(char **str_reply, const char *str, ...); +int dap_cli_server_cmd_find_option_val( char** argv, int arg_start, int arg_end, const char *opt_name, const char **opt_value); +int dap_cli_server_cmd_check_option( char** argv, int arg_start, int arg_end, const char *opt_name); +void dap_cli_server_cmd_apply_overrides(const char * a_name, const dap_cli_server_cmd_override_t a_overrides); + +dap_cli_cmd_t* dap_cli_server_cmd_get_first(); +dap_cli_cmd_t* dap_cli_server_cmd_find(const char *a_name); diff --git a/net/stream/stream/dap_stream.c b/net/stream/stream/dap_stream.c index b57af3ccd..a27f3928c 100644 --- a/net/stream/stream/dap_stream.c +++ b/net/stream/stream/dap_stream.c @@ -809,7 +809,6 @@ static void s_stream_proc_pkt_in(dap_stream_t * a_stream, dap_stream_pkt_t *a_pk a_stream->buf_fragments_size_total = 0; a_stream->buf_fragments_size_filled = 0; } - return true; } /** diff --git a/plugin/CMakeLists.txt b/plugin/CMakeLists.txt new file mode 100644 index 000000000..6ef731ee9 --- /dev/null +++ b/plugin/CMakeLists.txt @@ -0,0 +1,18 @@ +cmake_minimum_required(VERSION 3.0) +project(dap_plugin C) + +set(CMAKE_VERBOSE_MAKEFILE ON) +set(CMAKE_COLOR_MAKEFILE ON) +set(CMAKE_C_STANDARD 11) + +file(GLOB DAP_PLUGIN_SRCS src/*.c) +file(GLOB DAP_PLUGIN_HEADERS include/*.h) + +add_library(${PROJECT_NAME} STATIC ${DAP_PLUGIN_SRCS} ${DAP_PLUGIN_HEADERS}) + +target_link_libraries(${PROJECT_NAME}) + +target_link_libraries(${PROJECT_NAME} dap_core dap_cli_server) + +target_include_directories(${PROJECT_NAME} PUBLIC include/ ) + diff --git a/plugin/include/dap_plugin.h b/plugin/include/dap_plugin.h new file mode 100644 index 000000000..e0ac75aa1 --- /dev/null +++ b/plugin/include/dap_plugin.h @@ -0,0 +1,56 @@ +/* +* Authors: +* Alexey V. Stratulat <alexey.stratulat@demlabs.net> +* Dmitriy Gerasimov <dmitriy.gerasimov@demlabs.net +* DeM Labs Inc. https://demlabs.net +* Copyright (c) 2017-2022 +* All rights reserved. + +This file is part of DAP (Deus Applications Prototypes) the open source project + + DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DAP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#ifdef __cplusplus +extern "C"{ +#endif + +#include "dap_config.h" +#include "dap_plugin_manifest.h" + +typedef int (*dap_plugin_type_callback_load_t)(dap_plugin_manifest_t * a_manifest, void ** a_pvt_data, char ** a_error_str ); +typedef int (*dap_plugin_type_callback_unload_t)(dap_plugin_manifest_t * a_manifest, void * a_pvt_data, char ** a_error_str ); + +typedef struct dap_plugin_type_callbacks +{ + dap_plugin_type_callback_load_t load; + dap_plugin_type_callback_unload_t unload; +} dap_plugin_type_callbacks_t; +typedef enum dap_plugin_status{ STATUS_RUNNING, STATUS_STOPPED, STATUS_NONE } dap_plugin_status_t; + +int dap_plugin_init(const char * a_root_path); +void dap_plugin_deinit(); + +int dap_plugin_type_create(const char* a_name, dap_plugin_type_callbacks_t *a_callbacks); +void dap_plugin_start_all(); +void dap_plugin_stop_all(); +dap_plugin_status_t dap_plugin_status(const char * a_name); +int dap_plugin_stop(const char * a_name); +int dap_plugin_start(const char * a_name); + +#ifdef __cplusplus +} +#endif diff --git a/plugin/include/dap_plugin_binary.h b/plugin/include/dap_plugin_binary.h new file mode 100644 index 000000000..48b634a6e --- /dev/null +++ b/plugin/include/dap_plugin_binary.h @@ -0,0 +1,27 @@ +/* + * Authors: + * Dmitriy A. Gerasimov <gerasimov.dmitriy@demlabs.net> + * DeM Labs Inc. https://demlabs.net + * Cellframe https://cellframe.net + * Copyright (c) 2022 + * All rights reserved. + + This file is part of Cellframe SDK + + Cellframe SDK is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Cellframe SDK is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any Cellframe SDK based project. If not, see <http://www.gnu.org/licenses/>. + */ +#pragma once + +int dap_plugin_binary_init(); +void dap_plugin_binary_deinit(); diff --git a/plugin/include/dap_plugin_command.h b/plugin/include/dap_plugin_command.h new file mode 100644 index 000000000..861c65ca5 --- /dev/null +++ b/plugin/include/dap_plugin_command.h @@ -0,0 +1,37 @@ +/* +* Authors: +* Alexey V. Stratulat <alexey.stratulat@demlabs.net> +* DeM Labs Inc. https://demlabs.net +* DeM Labs Open source community https://gitlab.demlabs.net/cellframe/libdap-plugins-python +* Copyright (c) 2017-2020 +* All rights reserved. + +This file is part of DAP (Deus Applications Prototypes) the open source project + + DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DAP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. +*/ +#pragma once + +#define _DAP_CHAIN_PLUGINS_COMMAND_ + +#ifdef __cplusplus +extern "C" { +#endif + +void dap_plugin_command_init(void); +void dap_plugin_command_deinit(void); + +#ifdef __cplusplus +} +#endif diff --git a/plugin/include/dap_plugin_manifest.h b/plugin/include/dap_plugin_manifest.h new file mode 100644 index 000000000..825e86eb3 --- /dev/null +++ b/plugin/include/dap_plugin_manifest.h @@ -0,0 +1,86 @@ +/* +* Authors: +* Alexey V. Stratulat <alexey.stratulat@demlabs.net> +* DeM Labs Inc. https://demlabs.net +* DeM Labs Open source community https://gitlab.demlabs.net/cellframe/libdap-plugins-python +* Copyright (c) 2017-2020 +* All rights reserved. + +This file is part of DAP (Deus Applications Prototypes) the open source project + + DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DAP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#ifdef __cplusplus +extern "C"{ +#endif + +#include <stdbool.h> +#include "uthash.h" +#include "dap_config.h" + +typedef struct dap_plugin_manifest{ + char name[64]; + char *version; + char *author; + char *description; + + + char * type;// Plugin type + char * path;// Path to the directory + dap_config_t * config; // Config file + + // Dependencies + struct dap_plugin_manifest_dependence *dependencies; // Solved dependencies with links on same manifests + char **dependencies_names; // String list of dependencies + size_t dependencies_count; // Number of dependencies; + + // Additional params + size_t params_count; + char ** params; + + // Builtin plugin + bool is_builtin; // Doesn't allow to unload if true + + // uthash handle + UT_hash_handle hh; +}dap_plugin_manifest_t; + +typedef struct dap_plugin_manifest_dependence{ + char name[64]; + dap_plugin_manifest_t * manifest; + UT_hash_handle hh; +}dap_plugin_manifest_dependence_t; + +int dap_plugin_manifest_init(); +void dap_plugin_manifest_deinit(); + +dap_plugin_manifest_t* dap_plugin_manifest_all(void); +dap_plugin_manifest_t *dap_plugin_manifest_find(const char *a_name); + +char* dap_plugin_manifests_get_list_dependencies(dap_plugin_manifest_t *a_element); + +dap_plugin_manifest_t* dap_plugin_manifest_add_from_file(const char *a_file_path); +dap_plugin_manifest_t* dap_plugin_manifest_add_builtin(const char *a_name, const char * a_type, + const char * a_author, const char * a_version, + const char * a_description, char ** a_dependencies_names, + size_t a_dependencies_count, char ** a_params, size_t a_params_count); + +bool dap_plugins_manifest_remove(const char *a_name); + +#ifdef __cplusplus +} +#endif diff --git a/plugin/src/dap_plugin.c b/plugin/src/dap_plugin.c new file mode 100644 index 000000000..2199a7ea1 --- /dev/null +++ b/plugin/src/dap_plugin.c @@ -0,0 +1,292 @@ +/* +* Authors: +* Alexey V. Stratulat <alexey.stratulat@demlabs.net> +* Dmitriy Gerasimov <dmitriy.gerasimov@demlabs.net +* DeM Labs Inc. https://demlabs.net +* Copyright (c) 2017-2022 +* All rights reserved. + +This file is part of DAP (Deus Applications Prototypes) the open source project + + DAP (Deus Applicaions Prototypes) is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DAP is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any DAP based project. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "uthash.h" +#include "dap_config.h" +#include "dap_common.h" +#include "dap_file_utils.h" +#include "dap_plugin_manifest.h" +#include "dap_plugin_command.h" +#include "dap_plugin_binary.h" + +#include "dap_plugin.h" +#include "dap_strfuncs.h" + +#define LOG_TAG "dap_plugin" + +static char *s_plugins_root_path = NULL; + +struct plugin_type{ + char *name; + dap_plugin_type_callbacks_t callbacks; + UT_hash_handle hh; +} * s_types; + + +struct plugin_module{ + char *name; + struct plugin_type *type; + dap_plugin_manifest_t *manifest; + + void * pvt_data; // Here are placed type-related things + UT_hash_handle hh; +}; + +struct plugin_type *s_types = NULL; // List of all registred plugin types +struct plugin_module *s_modules = NULL; // List of all loaded modules +static int s_stop(dap_plugin_manifest_t * a_manifest); +static int s_start(dap_plugin_manifest_t * a_manifest); + +static void s_solve_dependencies(); + + +/** + * @brief dap_plugin_init + * @param a_root_path + * @return + */ +int dap_plugin_init(const char * a_root_path) +{ + s_plugins_root_path = dap_strdup(a_root_path); + + log_it(L_INFO, "Start plugins initialization on root path %s", s_plugins_root_path); + if (!dap_dir_test(s_plugins_root_path)){ + log_it(L_ERROR, "Can't find \"%s\" directory", s_plugins_root_path); + return -1; + } + + //Get list files + dap_list_name_directories_t *l_list_plugins_name = dap_get_subs(s_plugins_root_path); + dap_list_name_directories_t *l_element; + // Register manifests + log_it(L_DEBUG, "Start registration of manifests"); + dap_plugin_manifest_init(); + dap_plugin_command_init(); + + char *l_name_file = NULL; + LL_FOREACH(l_list_plugins_name, l_element){ + log_it(L_NOTICE, "Registration of \"%s\" manifest", l_element->name_directory); + l_name_file = dap_strjoin("",s_plugins_root_path, "/", l_element->name_directory, "/manifest.json", NULL); + if (!dap_plugin_manifest_add_from_file(l_name_file)){ + log_it(L_ERROR, "Registration of \"%s\" manifest is failed", l_element->name_directory); + } + DAP_FREE(l_name_file); + } + s_solve_dependencies(); + return 0; +} + + +void dap_plugin_deinit(){ + log_it(L_NOTICE, "Deinitialize plugins"); + dap_plugin_stop_all(); + dap_plugin_manifest_deinit(); + dap_plugin_command_deinit(); +} + + + +/** + * @brief s_solve_dependencies + */ +static void s_solve_dependencies() +{ + // TODO solving dependencies +} + + +/** + * @brief Create new plugin type. Same name will be new plugin itself to make dependencies from plugin type as from plugin + * @param a_name Plugin type name + * @param a_callbacks Set of callbacks + * @return Returns 0 if success otherwise if not + */ +int dap_plugin_type_create(const char* a_name, dap_plugin_type_callbacks_t* a_callbacks) +{ + if(!a_name){ + log_it(L_CRITICAL, "Can't create plugin type without name!"); + return -1; + } + if(!a_callbacks){ + log_it(L_CRITICAL, "Can't create plugin type without callbacks!"); + return -2; + } + struct plugin_type * l_new = DAP_NEW_Z(struct plugin_type); + if(!l_new){ + log_it(L_CRITICAL, "OOM on new type create"); + return -3; + } + l_new->name = dap_strdup(a_name); + memcpy(&l_new->callbacks,a_callbacks,sizeof(l_new->callbacks)); + return 0; +} + +/** + * @brief dap_plugin_start_all + */ +void dap_plugin_start_all() +{ + dap_plugin_manifest_t * l_manifest, *l_tmp; + HASH_ITER(hh,dap_plugin_manifest_all(),l_manifest,l_tmp ){ + s_start(l_manifest); + } +} + +/** + * @brief dap_plugin_stop_all + */ +void dap_plugin_stop_all() +{ + dap_plugin_manifest_t * l_manifest, *l_tmp; + HASH_ITER(hh,dap_plugin_manifest_all(),l_manifest,l_tmp ){ + s_stop(l_manifest); + } +} + +/** + * @brief dap_plugin_stop + * @param a_name + * @return + */ +int dap_plugin_stop(const char * a_name) +{ + dap_plugin_manifest_t * l_manifest = dap_plugin_manifest_find(a_name); + if(l_manifest) + return s_stop(l_manifest); + else + return -4; // Not found + +} + +/** + * @brief Stop services by manifest + * @param a_manifest + * @return + */ +static int s_stop(dap_plugin_manifest_t * a_manifest) +{ + if(!a_manifest) + return -4; + struct plugin_module * l_module = NULL; + HASH_FIND_STR(s_modules, a_manifest->name , l_module); + if(! l_module){ + log_it(L_ERROR, "Plugin \"%s\" is not loaded", a_manifest->type); + return -5; + } + // unload plugin + char * l_err_str = NULL; + int l_ret = l_module->type->callbacks.unload(a_manifest,l_module->pvt_data, &l_err_str); + if(l_ret){ // Error while unloading + log_it(L_ERROR, "Can't unload plugin \"%s\" because of error \"%s\" (code %d)",a_manifest->name, + l_err_str?l_err_str:"<UNKNOWN>", l_ret); + DAP_DELETE(l_err_str); + }else{ + HASH_DELETE(hh, s_modules,l_module); + DAP_DELETE(l_module); + } + return l_ret; +} + +/** + * @brief dap_plugin_start + * @param a_name + * @return + */ +int dap_plugin_start(const char * a_name) +{ + dap_plugin_manifest_t * l_manifest = dap_plugin_manifest_find(a_name); + if(l_manifest) + return s_start(l_manifest); + else + return -4; // Not found +} + +/** + * @brief l_stop + * @param a_manifest + * @return + */ +static int s_start(dap_plugin_manifest_t * a_manifest) +{ + struct plugin_type * l_type = NULL; + HASH_FIND_STR(s_types, a_manifest->type, l_type); + if(! l_type){ + log_it(L_ERROR, "Plugin type \"%s\" is not recognized", a_manifest->type); + return -1; + } + if (a_manifest->dependencies != NULL){ + log_it(L_NOTICE, "Check for plugin %s dependencies", a_manifest->name); + // Check for dependencies, are they loaded + bool l_is_unsolved = false; + for(size_t i=0; i< a_manifest->dependencies_count; i++){ + dap_plugin_manifest_dependence_t * l_dep = NULL; + HASH_FIND_STR(a_manifest->dependencies, a_manifest->dependencies_names[i], l_dep); + if (!l_dep){ // meet unsolved dependence + log_it(L_ERROR, "Unsolved dependence \"%s\"", a_manifest->dependencies_names[i]); + l_is_unsolved = true; + } + } + if(l_is_unsolved) + return -2; + } + + // load plugin + char * l_err_str = NULL; + void * l_pvt_data = NULL; + int l_ret = l_type->callbacks.load(a_manifest,&l_pvt_data, &l_err_str); + if(l_ret){ // Error while loading + log_it(L_ERROR, "Can't load plugin \"%s\" because of error \"%s\" (code %d)",a_manifest->name, + l_err_str?l_err_str:"<UNKNOWN>", l_ret); + DAP_DELETE(l_err_str); + }else{ // Successfully + struct plugin_module * l_module = DAP_NEW_Z(struct plugin_module); + l_module->pvt_data = l_pvt_data; + strncpy(l_module->name, a_manifest->name, sizeof(l_module->name)-1); + l_module->type = l_type; + l_module->manifest = a_manifest; + HASH_ADD_STR(s_modules,name,l_module); + } + return l_ret; +} + +/** + * @brief dap_plugin_status + * @param a_name + * @return + */ +dap_plugin_status_t dap_plugin_status(const char * a_name) +{ + struct plugin_module * l_module = NULL; + HASH_FIND_STR(s_modules,a_name,l_module); + if(l_module){ + return STATUS_RUNNING; + } + dap_plugin_manifest_t * l_manifest = dap_plugin_manifest_find(a_name); + if(l_manifest) + return STATUS_STOPPED; + return STATUS_NONE; +} + + + diff --git a/plugin/src/dap_plugin_binary.c b/plugin/src/dap_plugin_binary.c new file mode 100644 index 000000000..6bf031d7f --- /dev/null +++ b/plugin/src/dap_plugin_binary.c @@ -0,0 +1,128 @@ +/* + * Authors: + * Dmitriy A. Gerasimov <gerasimov.dmitriy@demlabs.net> + * DeM Labs Inc. https://demlabs.net + * Cellframe https://cellframe.net + * Copyright (c) 2022 + * All rights reserved. + + This file is part of Cellframe SDK + + Cellframe SDK is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Cellframe SDK is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with any Cellframe SDK based project. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "dap_strfuncs.h" +#ifdef DAP_OS_LINUX +#include <dlfcn.h> +#endif + +#include <assert.h> +#include "dap_plugin.h" +#include "dap_plugin_binary.h" +#include "dap_plugin_manifest.h" + +#define LOG_TAG "dap_plugin_binary" + +typedef int (*plugin_init_callback_t)(dap_config_t * a_plugin_config, char ** a_error_str); +typedef void (*plugin_deinit_callback_t)(void); + +static dap_plugin_manifest_t * s_manifest = NULL; // Own manifest + +static int s_type_callback_load(dap_plugin_manifest_t * a_manifest, void ** a_pvt_data, char ** a_error_str ); +static int s_type_callback_unload(dap_plugin_manifest_t * a_manifest, void * a_pvt_data, char ** a_error_str ); + +struct binary_pvt_data{ + void *handle; + plugin_init_callback_t callback_init; + plugin_deinit_callback_t callback_deinit; +}; + +/** + * @brief dap_plugin_binary_init + * @return + */ +int dap_plugin_binary_init() +{ + dap_plugin_type_callbacks_t l_callbacks={}; + l_callbacks.load = s_type_callback_load; + l_callbacks.unload = s_type_callback_unload; + dap_plugin_type_create("bin",&l_callbacks); + s_manifest = dap_plugin_manifest_add_builtin("binary", "binary", "Demlabs Inc", "1.0","Binary shared library loader",NULL,0,NULL,0); + return 0; +} + +/** + * @brief dap_plugin_binary_deinit + */ +void dap_plugin_binary_deinit() +{ + +} + +/** + * @brief s_type_callback_load + * @param a_manifest + * @param a_pvt_data + * @param a_error_str + * @return + */ +static int s_type_callback_load(dap_plugin_manifest_t * a_manifest, void ** a_pvt_data, char ** a_error_str ) +{ + assert(a_pvt_data); + if(a_manifest == s_manifest) // Its our own manifest, do nothing we're already loaded + return 0; + struct binary_pvt_data * l_pvt_data= DAP_NEW_Z(struct binary_pvt_data); + *a_pvt_data = l_pvt_data; + #if defined (DAP_OS_LINUX) && !defined (__ANDROID__) + char * l_path = dap_strdup_printf("%s/%s.linux.common.%s.so",a_manifest->path,a_manifest->name,dap_get_arch()); + l_pvt_data->handle = dlopen(l_path, RTLD_NOW); // Try with specified architecture first + if(l_pvt_data->handle){ + l_pvt_data->callback_init = dlsym(l_pvt_data->handle, "plugin_init"); + l_pvt_data->callback_deinit = dlsym(l_pvt_data->handle, "plugin_deinit"); + }else{ + log_it(L_ERROR,"Can't load %s module: %s (expected path %s)", a_manifest->name, l_path, dlerror()); + *a_error_str = dap_strdup_printf("Can't load %s module: %s (expected path %s)", a_manifest->name, l_path, dlerror()); + return -5; + } + #endif + if( l_pvt_data->callback_init){ + return l_pvt_data->callback_init(a_manifest->config,a_error_str); + }else{ + log_it(L_ERROR,"No \"plugin_init\" entry point in binary plugin") ; + *a_error_str = dap_strdup("No \"plugin_init\" entry point in binary plugin"); + DAP_DELETE(l_pvt_data); + return -5; + } +} + +/** + * @brief s_type_callback_unload + * @param a_manifest + * @param a_pvt_data + * @param a_error_str + * @return + */ +static int s_type_callback_unload(dap_plugin_manifest_t * a_manifest, void * a_pvt_data, char ** a_error_str ) +{ + if(a_manifest == s_manifest) // Its our own manifest, do nothing we're can't be unloaded + return 0; + struct binary_pvt_data * l_pvt_data = (struct binary_pvt_data *) a_pvt_data; + assert(l_pvt_data); + if(l_pvt_data->callback_deinit) + l_pvt_data->callback_deinit(); +#if defined (DAP_OS_LINUX) && !defined (__ANDROID__) + dlclose(l_pvt_data->handle); +#endif + return 0; +} diff --git a/plugin/src/dap_plugin_command.c b/plugin/src/dap_plugin_command.c new file mode 100644 index 000000000..272aa3d1b --- /dev/null +++ b/plugin/src/dap_plugin_command.c @@ -0,0 +1,156 @@ + +#include "dap_common.h" +#include "dap_strfuncs.h" +#include "dap_cli_server.h" +#include "dap_plugin_manifest.h" +#include "dap_plugin.h" +#include "uthash.h" +#include "utlist.h" +#include "dap_plugin_command.h" + +#define LOG_TAG "dap_plugin_command" + +static bool s_l_restart_plugins = false; + +static int s_command_handler(int a_argc, char **a_argv, char **a_str_reply); + + +/** + * @brief dap_chain_plugins_command_create + */ +void dap_plugin_command_init(void) +{ + if (!s_l_restart_plugins){ + dap_cli_server_cmd_add("plugin", s_command_handler, + "Commands for working with plugins:\n", + "plugin list\n" + "\tShow plugins list\n" + "plugin show <plugin name>\n" + "\tShow plugin details\n" + "plugin restart\n" + "\tRestart all plugins\n" + "plugin reload <plugin name>\n" + "\tRestart plugin <plugin name>\n\n"); + s_l_restart_plugins = true; + } +} + +/** + * @brief dap_plugin_command_deinit + */ +void dap_plugin_command_deinit(void) +{ + +} + +/** + * @brief s_command_handler + * @param a_argc + * @param a_argv + * @param a_str_reply + * @return + */ +static int s_command_handler(int a_argc, char **a_argv, char **a_str_reply) +{ + enum { + CMD_NONE, CMD_LIST, CMD_SHOW_NAME, CMD_RESTART, CMD_RELOAD_NAME + }; + int l_arg_index = 1; + int l_cmd_name = CMD_NONE; + dap_plugin_manifest_t *l_manifest = NULL, *l_tmp = NULL; + const char * l_cmd_arg = NULL; + if (dap_cli_server_cmd_find_option_val(a_argv,l_arg_index, a_argc, "list", &l_cmd_arg)) + l_cmd_name = CMD_LIST; + if (dap_cli_server_cmd_find_option_val(a_argv,l_arg_index, a_argc, "show", &l_cmd_arg)) + l_cmd_name = CMD_SHOW_NAME; + if (dap_cli_server_cmd_find_option_val(a_argv,l_arg_index, a_argc, "restart", &l_cmd_arg)) + l_cmd_name = CMD_RESTART; + if (dap_cli_server_cmd_find_option_val(a_argv,l_arg_index, a_argc, "reload", &l_cmd_arg)) + l_cmd_name = CMD_RELOAD_NAME; + switch (l_cmd_name) { + case CMD_LIST:{ + char *l_str = NULL; + l_str = dap_strdup("|\tName plugin\t|\tVersion\t|\tAuthor(s)\t|\n"); + HASH_ITER(hh,dap_plugin_manifest_all(), l_manifest, l_tmp){ + l_str = dap_strjoin(NULL, + l_str, "|\t",l_manifest->name, "\t|\t", l_manifest->version, "\t|\t", l_manifest->author, "\t|\n", NULL); + + } + dap_cli_server_cmd_set_reply_text(a_str_reply, l_str); + }break; + case CMD_SHOW_NAME: + if(!l_cmd_arg){ + dap_cli_server_cmd_set_reply_text(a_str_reply, "Need argument for this command"); + } + HASH_FIND_STR(dap_plugin_manifest_all(), l_cmd_arg, l_manifest); + if(l_manifest){ + char *l_deps = dap_plugin_manifests_get_list_dependencies(l_manifest); + dap_cli_server_cmd_set_reply_text(a_str_reply, " Name: %s\n Version: %s\n Author: %s\n" + " Description: %s\n Dependencies: %s \n\n", + l_manifest->name, l_manifest->version, l_manifest->author, + l_manifest->description, l_deps?l_deps:" "); + if(l_deps) + DAP_DELETE(l_deps); + } else { + dap_cli_server_cmd_set_reply_text(a_str_reply, "Can't find a plugin named %s", l_cmd_arg); + } + break; + case CMD_RESTART: + log_it(L_NOTICE, "Restart python plugin module"); + dap_plugin_stop_all(); + dap_plugin_start_all(); + log_it(L_NOTICE, "Restart completed"); + dap_cli_server_cmd_set_reply_text(a_str_reply, "Restart completed"); + break; + case CMD_RELOAD_NAME:{ + int l_result; + l_result = dap_plugin_stop(l_cmd_arg); + switch (l_result) { + case 0: //All is good + break; + case -4: + dap_cli_server_cmd_set_reply_text(a_str_reply, "A plugin named \"%s\" was not found.", l_cmd_arg); + break; + case -5: + dap_cli_server_cmd_set_reply_text(a_str_reply, "A plugin named \"%s\" is not loaded", l_cmd_arg); + break; + default: + dap_cli_server_cmd_set_reply_text(a_str_reply, "An unforeseen error has occurred."); + break; + } + if(l_result == 0){ + l_result = dap_plugin_start(l_cmd_arg); + switch (l_result) { + case 0: + dap_cli_server_cmd_set_reply_text(a_str_reply, "Restart \"%s\" plugin is completed successfully.", l_cmd_arg); + break; + case -1: + dap_cli_server_cmd_set_reply_text(a_str_reply, "Plugin \"%s\" has unsupported type, pls check manifest file", l_cmd_arg); + break; + case -2: + dap_cli_server_cmd_set_reply_text(a_str_reply, + "\"%s\" plugin has unresolved dependencies. Restart all plugins.", + l_cmd_arg); + break; + case -3: + dap_cli_server_cmd_set_reply_text(a_str_reply, "Registration \"%s\" manifest for \"%s\" plugin is failed.", l_cmd_arg); + break; + case -4: + dap_cli_server_cmd_set_reply_text(a_str_reply, "Plugin \"%s\" was not found.", l_cmd_arg); + break; + case -5: + dap_cli_server_cmd_set_reply_text(a_str_reply, "Plugin \"%s\" can't load", l_cmd_arg); + break; + default: + dap_cli_server_cmd_set_reply_text(a_str_reply, "An unforeseen error has occurred."); + break; + } + } + }break; + default: + dap_cli_server_cmd_set_reply_text(a_str_reply, "Arguments are incorrect."); + break; + + } + return 0; +} diff --git a/plugin/src/dap_plugin_manifest.c b/plugin/src/dap_plugin_manifest.c new file mode 100644 index 000000000..32a511185 --- /dev/null +++ b/plugin/src/dap_plugin_manifest.c @@ -0,0 +1,289 @@ +#include "dap_common.h" +#include "dap_config.h" +#include "dap_strfuncs.h" +#include "dap_file_utils.h" +#include "json-c/json_object.h" +#include "json-c/json_tokener.h" + +#include "dap_plugin_manifest.h" +#include "uthash.h" +#include <string.h> + +#define LOG_TAG "dap_plugin_manifest" + +dap_plugin_manifest_t* s_manifests = NULL; + +static void s_manifest_delete(dap_plugin_manifest_t *a_manifest); + +/** + * @brief dap_plugin_manifest_init + * @return + */ +int dap_plugin_manifest_init() +{ + return 0; +} + +/** + * @brief dap_plugin_manifest_deinit + */ +void dap_plugin_manifest_deinit() +{ + dap_plugin_manifest_t *l_manifest, * l_tmp; + HASH_ITER(hh,s_manifests,l_manifest,l_tmp){ + HASH_DELETE(hh, s_manifests, l_manifest); + s_manifest_delete(l_manifest); + } +} + + +/** + * @brief dap_plugin_manifest_add_from_scratch + * @param a_name + * @param a_type + * @param a_author + * @param a_version + * @param a_description + * @param a_dependencies_names + * @param a_dependencies_count + * @param a_params + * @param a_params_count + * @return + */ +dap_plugin_manifest_t* dap_plugin_manifest_add_builtin(const char *a_name, const char * a_type, + const char * a_author, const char * a_version, + const char * a_description, char ** a_dependencies_names, + size_t a_dependencies_count, char ** a_params, size_t a_params_count) +{ + dap_plugin_manifest_t *l_manifest = NULL; + HASH_FIND_STR(s_manifests, a_name, l_manifest); + if(l_manifest){ + log_it(L_ERROR, "Plugin name \"%s\" is already present", a_name); + return NULL; + } + + l_manifest = DAP_NEW_Z(dap_plugin_manifest_t); + strncpy(l_manifest->name,a_name, sizeof(l_manifest->name)-1); + l_manifest->type = dap_strdup(a_type); + l_manifest->is_builtin = true; + l_manifest->author = dap_strdup(a_author); + l_manifest->version = dap_strdup(a_version); + l_manifest->description = dap_strdup(a_description); + l_manifest->dependencies_names = DAP_NEW_Z_SIZE(char *, sizeof(char*)* a_dependencies_count); + for(size_t i = 0; i < a_dependencies_count; i++){ + l_manifest->dependencies_names[i] = dap_strdup (a_dependencies_names[i]); + } + l_manifest->dependencies_count = a_dependencies_count; + + l_manifest->params_count = a_params_count; + l_manifest->params = DAP_NEW_Z_SIZE(char *, sizeof(char*)* a_params_count); + for(size_t i = 0; i < a_params_count; i++){ + l_manifest->params[i] = dap_strdup (a_params[i]); + } + HASH_ADD_STR(s_manifests,name,l_manifest); + return l_manifest; +} + +/** + * @brief dap_plugin_manifest_add_from_file + * @param file_path + * @return + */ +dap_plugin_manifest_t* dap_plugin_manifest_add_from_file(const char *a_file_path) +{ + //READ File in char + log_it(L_INFO, "Parse JSON file"); + FILE *l_json_file = fopen(a_file_path, "rt"); + if (l_json_file == NULL){ + log_it(L_ERROR, "Can't open manifest file on path: %s", a_file_path); + return NULL; + } + fseek(l_json_file, 0, SEEK_END); + size_t size_file = (size_t)ftell(l_json_file); + char *l_json_data = DAP_NEW_SIZE(char, size_file); + rewind(l_json_file); + fread(l_json_data, sizeof(char), size_file, l_json_file); + fclose(l_json_file); + //Parse JSON + json_object *l_json_obj = json_tokener_parse(l_json_data); + json_object *l_json_name = NULL; + json_object *l_json_version = NULL; + json_object *l_json_dependencies = NULL; + json_object *l_json_author = NULL; + json_object *l_json_description = NULL; + json_object *l_json_path = NULL; + json_object *l_json_params = NULL; + json_object *l_json_type = NULL; + + if (!json_object_object_get_ex(l_json_obj, "name", &l_json_name)) + return NULL; + if (!json_object_object_get_ex(l_json_obj, "type", &l_json_type)) + return NULL; + if (!json_object_object_get_ex(l_json_obj, "version", &l_json_version)) + return NULL; + if (!json_object_object_get_ex(l_json_obj, "dependencies", &l_json_dependencies)) + return NULL; + if (!json_object_object_get_ex(l_json_obj, "author", &l_json_author)) + return NULL; + if (!json_object_object_get_ex(l_json_obj, "description", &l_json_description)) + return NULL; + json_object_object_get_ex(l_json_obj, "description", &l_json_params); + json_object_object_get_ex(l_json_obj, "path", &l_json_path); + + const char *l_name, *l_type, *l_version, *l_author, *l_description; + size_t l_dependencies_count, l_params_count; + char ** l_dependencies_names = NULL, **l_params = NULL; + l_name = json_object_get_string(l_json_name); + + dap_plugin_manifest_t *l_manifest = NULL; + HASH_FIND_STR(s_manifests, l_name, l_manifest); + if(l_manifest){ + log_it(L_ERROR, "Plugin name \"%s\" is already present", l_name); + DAP_DELETE(l_json_data); + return NULL; + } + + + l_type = json_object_get_string(l_json_type); + l_version = json_object_get_string(l_json_version); + l_author = json_object_get_string(l_json_author); + l_description = json_object_get_string(l_json_description); + l_dependencies_count = (size_t)json_object_array_length(l_json_dependencies); + l_params_count = (size_t)json_object_array_length(l_json_params); + + // Read dependencies; + if(l_dependencies_count){ + l_dependencies_names = DAP_NEW_SIZE(char*, sizeof(char*)* l_dependencies_count ); + for (size_t i = 0; i < l_dependencies_count; i++){ + l_dependencies_names[i] = dap_strdup(json_object_get_string(json_object_array_get_idx(l_json_dependencies, i))); + } + } + + // Read additional params + if(l_params_count){ + l_params = DAP_NEW_SIZE(char*, sizeof(char*)* l_params_count ); + for (size_t i = 0; i < l_params_count; i++){ + l_params[i] = dap_strdup(json_object_get_string(json_object_array_get_idx(l_json_params, i))); + } + } + + // Create manifest itself + l_manifest = DAP_NEW_Z(dap_plugin_manifest_t); + strncpy(l_manifest->name,l_name, sizeof(l_manifest->name)-1); + l_manifest->type = dap_strdup(l_type); + l_manifest->author = dap_strdup(l_author); + l_manifest->version = dap_strdup(l_version); + l_manifest->description = dap_strdup(l_description); + l_manifest->dependencies_names = l_dependencies_names; + l_manifest->dependencies_count = l_dependencies_count; + l_manifest->params_count = l_params_count; + l_manifest->params = l_params; + if(l_json_path){ // If targeted manualy plugin's path + l_manifest->path = dap_strdup(json_object_get_string(l_json_path)); + }else{ // Compose it from plugin root path + l_manifest->path = dap_path_get_dirname(a_file_path); + } + + char * l_config_path = dap_strdup_printf("%s/%s.cfg", l_manifest->path,l_manifest->name ); + if(dap_file_test(l_config_path)) // If present custom config + l_manifest->config = dap_config_load(l_config_path); + DAP_DELETE(l_config_path); + + HASH_ADD_STR(s_manifests,name,l_manifest); + + json_object_put(l_json_dependencies); + json_object_put(l_json_description); + json_object_put(l_json_author); + json_object_put(l_json_version); + json_object_put(l_json_name); + DAP_FREE(l_json_obj); + DAP_FREE(l_json_data); + return l_manifest; +} + +/** + * @brief Returns all the manifests declared in system + * @return + */ +dap_plugin_manifest_t* dap_plugin_manifest_all() +{ + return s_manifests; +} + +/** + * @brief Find plugin manifest by its unique name + * @param a_name Plugin name + * @return Pointer to manifest object if found or NULL if not + */ +dap_plugin_manifest_t *dap_plugin_manifest_find(const char *a_name) +{ + dap_plugin_manifest_t *l_ret; + HASH_FIND_STR(s_manifests,a_name,l_ret); + return l_ret; +} + +/** + * @brief Create string with list of dependencies, breaking by "," + * @param a_element + * @return + */ +char* dap_plugin_manifests_get_list_dependencies(dap_plugin_manifest_t *a_element) +{ + if (a_element->dependencies == NULL) { + return NULL; + } else { + char *l_result = ""; + dap_plugin_manifest_dependence_t * l_dep, *l_tmp; + HASH_ITER(hh,a_element->dependencies,l_dep,l_tmp){ + dap_plugin_manifest_t * l_dep_manifest = l_dep->manifest; + if (l_dep->hh.hh_next ) + l_result = dap_strjoin(NULL, l_result, l_dep_manifest->name, ", ", NULL); + else + l_result = dap_strjoin(NULL, l_result, l_dep_manifest->name, NULL); + } + return l_result; + } +} + +/** + * @brief s_manifest_delete + * @param a_manifest + */ +static void s_manifest_delete(dap_plugin_manifest_t *a_manifest) +{ + DAP_DELETE(a_manifest->name); + DAP_DELETE(a_manifest->version); + DAP_DELETE(a_manifest->author); + DAP_DELETE(a_manifest->description); + if(a_manifest->dependencies_names){ + for(size_t i = 0; i< a_manifest->dependencies_count; i++) + DAP_DELETE(a_manifest->dependencies_names[i]); + DAP_DELETE(a_manifest->dependencies_names); + } + dap_plugin_manifest_dependence_t * l_dep, *l_tmp; + HASH_ITER(hh,a_manifest->dependencies,l_dep,l_tmp){ + HASH_DELETE(hh, a_manifest->dependencies, l_dep); + DAP_DELETE(l_dep); + } + DAP_DELETE(a_manifest); + +} + +/** + * @brief dap_plugins_manifest_remove + * @param a_name + * @return + */ +bool dap_plugins_manifest_remove(const char *a_name) +{ + dap_plugin_manifest_t *l_manifest; + HASH_FIND_STR(s_manifests, a_name,l_manifest); + if(l_manifest) + HASH_DEL(s_manifests, l_manifest); + else + return false; + + s_manifest_delete(l_manifest); + return true; +} + -- GitLab