#include #include #include #include #include #include #include #include #include #include #include "stellar/monitor.h" #include "linenoise/linenoise.h" #include "monitor_cmd_assistant.h" #include "monitor_private.h" #include "monitor/monitor_utils.h" static char g_stellar_cli_prompt[128]; /* prompt pattern: cli@ip:port> */ static const char *g_stellar_monitor_history_file = ".stellar_cli_history.txt"; static const char *g_stellar_monitor_version = "stellar-cli v1.0"; static int g_stm_cli_noninteractive = 0; static const char *g_stm_cli_noninteractive_cmd_line = NULL; static const char *g_stm_cli_ipaddr_str = STM_SERVER_LISTEN_IP; static unsigned short g_stm_cli_port_host = STM_SERVER_LISTEN_PORT; static struct libevent_http_client *g_evh_client; static struct stm_cmd_assistant *g_stm_cli_aide; static int g_stm_cli_connect_timeout = STM_REQUEST_TIMEOUT; static struct monitor_cli_args g_cli_args[4] = { {"-i", "--ip", 1, 0, NULL}, {"-p", "--port", 1, 0, NULL}, {"-t", "--timeout", 1, 0, NULL}, {"-e", "--exec", 1, 1, NULL}, }; struct stm_cmd_parser { sds raw_cmd_line; // need be free int argc; sds *argv; }; struct stm_builtin_cmd_compose { const char *cmd_name; monitor_cmd_cb *cmd_cb; const char *description; }; struct libevent_http_client { struct event_base *base; struct evhttp_connection *conn; struct evhttp_request *req; enum stm_http_response_code response_code; char *response_cstr; }; static int (*g_response_handler)(struct libevent_http_client *evh_client); static void cli_evhttp_free(void) { evhttp_connection_free(g_evh_client->conn); event_base_free(g_evh_client->base); FREE(g_evh_client); } static void stm_cli_args_free(struct stm_cmd_parser *cmd_args) { if (NULL != cmd_args) { sdsfree(cmd_args->raw_cmd_line); sdsfreesplitres(cmd_args->argv, cmd_args->argc); FREE(cmd_args); } } static struct monitor_reply *stm_cli_builtin_exit(UNUSED struct stellar_monitor *monitor, UNUSED int argc, UNUSED char *argv[], void *arg) { for (size_t i = 0; i < sizeof(g_cli_args) / sizeof(struct monitor_cli_args); i++) { sdsfree(g_cli_args[i].value); } stm_cli_args_free((struct stm_cmd_parser *)arg); stm_cmd_assistant_free(g_stm_cli_aide); cli_evhttp_free(); exit(0); return NULL; } static void signal_handler(int signo) { if (signo == SIGINT) { stm_cli_builtin_exit(NULL, 0, NULL, NULL); } } static struct monitor_reply *stm_cli_builtin_clear(UNUSED struct stellar_monitor *monitor, UNUSED int argc, UNUSED char *argv[], UNUSED void *arg) { linenoiseClearScreen(); return NULL; } static struct stm_builtin_cmd_compose g_stm_cli_builtin_commands[] = { {"q", stm_cli_builtin_exit, "cause the shell to exit"}, {"quit", stm_cli_builtin_exit, "cause the shell to exit"}, {"exit", stm_cli_builtin_exit, "cause the shell to exit"}, {"clear", stm_cli_builtin_clear, "clear the terminal screen"}, {NULL, NULL, NULL}}; static void evhttp_conn_close_cb(UNUSED struct evhttp_connection *conn, UNUSED void *arg) { snprintf(g_stellar_cli_prompt, sizeof(g_stellar_cli_prompt), "not connected>"); } static void evhttp_request_error_cb(enum evhttp_request_error errnum, void *arg) { (void)arg; switch (errnum) { case EVREQ_HTTP_TIMEOUT: g_evh_client->response_code = STM_HTTP_408_REQUEST_TIMEOUT; break; case EVREQ_HTTP_INVALID_HEADER: g_evh_client->response_code = STM_HTTP_403_FORBIDDEN; break; case EVREQ_HTTP_DATA_TOO_LONG: g_evh_client->response_code = STM_HTTP_413_PAYLOAD_TOO_LARGE; break; case EVREQ_HTTP_EOF: break; default: break; } } static void evhttp_response_cb(struct evhttp_request *req, void *arg) { (void)arg; if (req == NULL) { return; } struct evbuffer *input_buffer = evhttp_request_get_input_buffer(req); size_t evbuf_len = evbuffer_get_length(input_buffer); if (NULL == input_buffer || 0 == evbuf_len) { return; } g_evh_client->response_cstr = (char *)calloc(1, evbuf_len + 1); evbuffer_remove(input_buffer, g_evh_client->response_cstr, evbuf_len); g_evh_client->response_code = STM_HTTP_200_OK; // terminate event_base_dispatch() event_base_loopbreak(g_evh_client->base); } static struct libevent_http_client *evhttp_client_new(const char *server_ip, unsigned short server_port) { struct libevent_http_client *evh_client = (struct libevent_http_client *)calloc(1, sizeof(struct libevent_http_client)); evh_client->base = event_base_new(); evh_client->conn = evhttp_connection_base_new(evh_client->base, NULL, server_ip, server_port); evhttp_connection_set_timeout(evh_client->conn, g_stm_cli_connect_timeout); return evh_client; } static int evhttp_client_request_new(struct libevent_http_client *evh_client) { evh_client->req = evhttp_request_new(evhttp_response_cb, evh_client); evhttp_request_set_error_cb(evh_client->req, evhttp_request_error_cb); evhttp_connection_set_closecb(evh_client->conn, evhttp_conn_close_cb, evh_client->req); evh_client->response_cstr = NULL; evh_client->response_code = STM_HTTP_204_NO_CONTENT; return 0; } static void evhttp_client_add_header(struct libevent_http_client *evh_client, const char *key, const char *value) { struct evkeyvalq *output_headers = evhttp_request_get_output_headers(evh_client->req); evhttp_add_header(output_headers, key, value); } static void evhttp_client_add_uri(struct libevent_http_client *evh_client, enum evhttp_cmd_type type, const char *uri) { evhttp_make_request(evh_client->conn, evh_client->req, type, uri); } static int default_response_handler(struct libevent_http_client *evh_client) { if (evh_client->response_code != STM_HTTP_200_OK || evh_client->response_cstr == NULL) { snprintf(g_stellar_cli_prompt, sizeof(g_stellar_cli_prompt), "not connected>"); fprintf(stderr, "ERR failed to connect to %s:%u\n", g_stm_cli_ipaddr_str, g_stm_cli_port_host); return -1; } printf("%s", evh_client->response_cstr); fflush(stdout); FREE(evh_client->response_cstr); snprintf(g_stellar_cli_prompt, sizeof(g_stellar_cli_prompt), "cli@%s:%u>", g_stm_cli_ipaddr_str, g_stm_cli_port_host); return 0; } static int command_json_parse_handler(struct libevent_http_client *evh_client) { if (evh_client->response_code != STM_HTTP_200_OK || evh_client->response_cstr == NULL) { snprintf(g_stellar_cli_prompt, sizeof(g_stellar_cli_prompt), "not connected>"); fprintf(stderr, "ERR failed to connect to %s:%u\n", g_stm_cli_ipaddr_str, g_stm_cli_port_host); return -1; } if (stm_cmd_assistant_json_load(g_stm_cli_aide, evh_client->response_cstr) < 0) { fprintf(stderr, "ERR failed to synchronize command info with the monitor server\n"); return -1; } FREE(evh_client->response_cstr); return 0; } static void stm_cli_usage(void) { printf("%s\r\n", g_stellar_monitor_version); printf("Usage:\r\n"); printf(" %s [OPTIONS] [ -e command [arg [arg ...]]]\r\n", "stellar-cli"); printf("\t%s %-6s %s\r\n", "-i", "--ip", "stellar monitor server ip address"); printf("\t%s %-6s %s\r\n", "-p", "--port", "stellar monitor server port"); printf("\t%s %-6s %s\r\n", "-e", "--exec", "non-interactive mode, exit after executing command"); printf("\t%s %-6s %s\r\n", "-t", "--timeout", "maximum time(sec) allowed for connecting to server"); exit(0); } static int stm_cli_exec_builtin_cmd(struct stm_cmd_parser *cmd_args) { const char *cli_cmd_name = cmd_args->argv[0]; size_t raw_cmd_len = strlen(cli_cmd_name); for (int i = 0; g_stm_cli_builtin_commands[i].cmd_name != NULL; i++) { if (stm_strncasecmp_exactly(g_stm_cli_builtin_commands[i].cmd_name, cli_cmd_name, raw_cmd_len) == 0) { g_stm_cli_builtin_commands[i].cmd_cb(NULL, cmd_args->argc, cmd_args->argv, (void *)cmd_args); return 1; } } return 0; } static sds stm_cli_build_RESTful_url(struct stm_cmd_parser *cmd_args) { sds url; char restful_path[256] = {0}; snprintf(restful_path, sizeof(restful_path), "/%s/%s", STM_RESTFUL_VERSION, STM_RESTFUL_RESOURCE); url = sdsempty(); url = sdscatprintf(url, "%s?%s=", restful_path, STM_RESTFUL_URI_CMD_KEY); for (int i = 0; i < cmd_args->argc; i++) { url = sdscat(url, cmd_args->argv[i]); if (i < cmd_args->argc - 1) { url = sdscat(url, " "); // add blank space } } char *encoded_url_str = stm_http_url_encode(url); sds encoded_url = sdsnew(encoded_url_str); sdsfree(url); free(encoded_url_str); return encoded_url; } static int stm_cli_evhttp_run_cmd(struct stm_cmd_parser *cmd_args) { evhttp_client_request_new(g_evh_client); evhttp_client_add_header(g_evh_client, "Connection", "keep-alive"); evhttp_client_add_header(g_evh_client, "Content-Length", "0"); sds url = stm_cli_build_RESTful_url(cmd_args); evhttp_client_add_uri(g_evh_client, EVHTTP_REQ_GET, url); int ret = event_base_dispatch(g_evh_client->base); sdsfree(url); return ret; } /* call remote command use RESTful */ static int stm_cli_exec_rpc_cmd(UNUSED const char *raw_cmd_line, struct stm_cmd_parser *cmd_args) { int ret = stm_cli_evhttp_run_cmd(cmd_args); g_response_handler(g_evh_client); return ret; } static struct stm_cmd_parser *stm_cli_parse_cmd_line(const char *line) { struct stm_cmd_parser *cmd_args = (struct stm_cmd_parser *)calloc(1, sizeof(struct stm_cmd_parser)); cmd_args->raw_cmd_line = sdsnew(line); cmd_args->argv = sdssplitargs(line, &cmd_args->argc); return cmd_args; } static void stm_cli_exec_cmd(const char *raw_line) { struct stm_cmd_parser *cmd_args = stm_cli_parse_cmd_line(raw_line); if (stm_cli_exec_builtin_cmd(cmd_args)) { goto fun_exit; } stm_cli_exec_rpc_cmd(raw_line, cmd_args); fun_exit: stm_cli_args_free(cmd_args); return; } static int stm_cli_builtin_help(const char *line) { int argc = 0; int is_help_cmd = 0; sds *array = sdssplitargs(line, &argc); if (argc != 1) { sdsfreesplitres(array, argc); return 0; } if ((strcasecmp(array[argc - 1], "help") == 0) || (strcasecmp(array[argc - 1], "--help") == 0) || (strcasecmp(array[argc - 1], "-h") == 0) || (strcasecmp(array[argc - 1], "/?") == 0) || (strcasecmp(array[argc - 1], "?") == 0)) { is_help_cmd = 1; } if (is_help_cmd == 1) { stm_cli_exec_cmd("show command brief"); } sdsfreesplitres(array, argc); return is_help_cmd; } static void stm_cli_register_builtin_cmd(void) { for (int i = 0; g_stm_cli_builtin_commands[i].cmd_name != NULL; i++) { stm_cmd_assistant_register_cmd(g_stm_cli_aide, g_stm_cli_builtin_commands[i].cmd_name, g_stm_cli_builtin_commands[i].cmd_cb, NULL, "readonly", "", g_stm_cli_builtin_commands[i].description); } } static void stm_cli_run(void) { char *line; /* Load history from file. The history file is just a plain text file * where entries are separated by newlines. */ linenoiseHistoryLoad(g_stellar_monitor_history_file); /* Load the history at startup */ /* Non-interactive mode */ if (g_stm_cli_noninteractive) { stm_cli_exec_cmd(g_stm_cli_noninteractive_cmd_line); exit(0); } /* Synchronize with the monitor server on boot up */ g_response_handler = command_json_parse_handler; stm_cli_exec_cmd(STM_CLIENT_SERVER_SYNC_CMD); g_response_handler = default_response_handler; /* register builtin command after synchronization */ stm_cli_register_builtin_cmd(); /* Interactive mode */ while (1) { line = linenoise(g_stellar_cli_prompt); if (line && strlen(line) > 0) { if (stm_cli_builtin_help(line) == 0) { stm_cli_exec_cmd(line); } fflush(stdout); linenoiseHistoryAdd(line); } FREE(line); } } static const char *stm_cli_short_options = "he:i:p:t:"; static const struct option stm_cli_long_options[] = { {"help", no_argument, NULL, 'h'}, {"ip", required_argument, NULL, 'i'}, {"port", required_argument, NULL, 'p'}, {"exec", required_argument, NULL, 'e'}, {"timeout", required_argument, NULL, 't'}, }; static int stm_cli_check_args(int argc, char *_argv[]) { int c, ret = 0; char **argv_tmp = CALLOC(char *, argc + 1); for (int i = 0; i < argc; i++) { argv_tmp[i] = _argv[i]; } while (1) { c = getopt_long(argc, argv_tmp, stm_cli_short_options, stm_cli_long_options, NULL); if (c == -1) { ret = 0; break; } switch (c) { case 'h': stm_cli_usage(); break; case 'i': case 'p': case 'e': case 't': break; case '?': /* invalid or unknown option */ ret = -1; break; default: ret = -1; break; } } FREE(argv_tmp); return ret; } static int stm_cli_evhttp_init(void) { g_evh_client = evhttp_client_new(g_stm_cli_ipaddr_str, g_stm_cli_port_host); assert(g_evh_client != NULL); return 0; } static void cli_linenoise_completion_cb(const char *line, linenoiseCompletions *lc) { stm_cmd_assistant_input_line(g_stm_cli_aide, line, (void *)lc); } static char *cli_linenoise_hints_cb(const char *line, int *color, int *bold) { char *hints = (char *)stm_cmd_assistant_input_line_for_hints(g_stm_cli_aide, line); if (NULL == hints) { return NULL; } sds tmp = sdsnew(" "); // add a blank space before hints, easy to input the next command tmp = sdscat(tmp, hints); *color = STM_CLI_CMD_HINTS_COLOR; *bold = STM_CLI_CMD_HINTS_BOLD; return tmp; } static void cli_linenoise_free_hints_cb(void *arg) { sdsfree((sds)arg); } void cli_assistant_completion_cb(void *arg, const char *candidate_completion) { linenoiseCompletions *lc = (linenoiseCompletions *)arg; linenoiseAddCompletion(lc, candidate_completion); } static int stm_assistant_init(void) { g_stm_cli_aide = stm_cmd_assistant_new(); if (NULL == g_stm_cli_aide) { return -1; } /* Set the completion callback. This will be called every time the * user uses the key. */ linenoiseSetCompletionCallback(cli_linenoise_completion_cb); stm_cmd_assistant_set_completion_cb(g_stm_cli_aide, cli_assistant_completion_cb); linenoiseSetHintsCallback(cli_linenoise_hints_cb); linenoiseSetFreeHintsCallback(cli_linenoise_free_hints_cb); return 0; } static int stm_cli_parse_args(int argc, char *argv[]) { if (stm_cli_check_args(argc, argv) < 0) { return -1; } if (monitor_util_parse_cmd_args(argc, (const char **)argv, g_cli_args, sizeof(g_cli_args) / sizeof(struct monitor_cli_args)) < 0) { return -1; } if (g_cli_args[0].value != NULL) { g_stm_cli_ipaddr_str = g_cli_args[0].value; } int tmp_val; if (g_cli_args[1].value != NULL) { tmp_val = atoi(g_cli_args[1].value); if (tmp_val <= 0 || tmp_val > 65535) { fprintf(stderr, "invalid port: %s\n", g_cli_args[1].value); return -1; } g_stm_cli_port_host = (unsigned short)tmp_val; } if (g_cli_args[2].value != NULL) { tmp_val = atoi(g_cli_args[2].value); if (tmp_val <= 0) { fprintf(stderr, "invalid timeout: %s\n", g_cli_args[2].value); return -1; } g_stm_cli_connect_timeout = tmp_val; } if (g_cli_args[3].value != NULL) { g_stm_cli_noninteractive = 1; g_stm_cli_noninteractive_cmd_line = g_cli_args[3].value; } snprintf(g_stellar_cli_prompt, sizeof(g_stellar_cli_prompt), "cli@%s:%u>", g_stm_cli_ipaddr_str, g_stm_cli_port_host); return 0; } int main(int argc, char *argv[]) { if (stm_cli_parse_args(argc, argv) < 0) { return -1; } if (stm_assistant_init() < 0) { return -1; } if (stm_cli_evhttp_init() < 0) { return -1; } g_response_handler = default_response_handler; signal(SIGINT, signal_handler); stm_cli_run(); return 0; }