#include #include #include #include #include #include #include "console/console.h" #include "console/prefix_match.h" #include "console/utils.h" // library includes #include "argtable3.h" #include "console_linenoise.h" #include "queue.h" #include "console_split_argv.h" #if CONSOLE_USE_TERMIOS && CONSOLE_USE_FILE_IO_STREAMS static int enableRawMode(console_ctx_t *ctx, int fd); static void disableRawMode(console_ctx_t *ctx, int fd); #endif static int cmd_exit(console_ctx_t *ctx, cmd_signature_t *reg); /** * Console errors - return codes */ static const char *err_names[_CONSOLE_ERR_MAX] = { [CONSOLE_OK] = "OK", [CONSOLE_ERROR] = "Unknown Error", [CONSOLE_ERR_NO_MEM] = "Out of memory", [CONSOLE_ERR_BAD_CALL] = "Illegal function call", [CONSOLE_ERR_INVALID_ARG] = "Invalid argument(s)", [CONSOLE_ERR_UNKNOWN_CMD] = "Unknown command", [CONSOLE_ERR_TIMEOUT] = "Timed out", [CONSOLE_ERR_IO] = "IO error", [CONSOLE_ERR_NOT_POSSIBLE] = "Not Possible", }; void console_err_print_ctx(struct console_ctx *ctx, int e) { console_printf_ctx(ctx, COLOR_RESET, "Err(%d)", e); if (e >= CONSOLE_OK && e < _CONSOLE_ERR_MAX) { console_printf_ctx(ctx, COLOR_RESET, " - %s", err_names[e]); } } /** * Empty argtable used internally by commands with no arguments */ static struct { struct arg_end *end; } s_empty_argtable; /** * Entry in the internal commands table. */ typedef struct cmd_item_ { struct cmd_signature sig; //!< Command signature const char* group; //!< Command group (the first word, if multi-part) const char* alias_of; //!< Aliased command name console_command_t func; //!< Command function pointer STAILQ_ENTRY(cmd_item_) next; } cmd_item_t; /** * Commands table */ static STAILQ_HEAD(cmd_list_, cmd_item_) s_cmd_list = {}; typedef struct cmd_groups_item_ { const char *group; const char *description; unsigned int num_commands; STAILQ_ENTRY(cmd_groups_item_) next; } cmd_groups_item_t; static STAILQ_HEAD(cmd_groups_, cmd_groups_item_) s_cmd_groups = {}; enum fuzzymatch_cmd_result { FUZZY_CMD_NO_MATCH = 0, FUZZY_CMD_AMBIGUOUS, FUZZY_CMD_PREFIX_MATCH, FUZZY_CMD_EXACT_MATCH, }; /** * Recognize an entered command, allowing abbreviations and detecting ambiguity * * @param[in] name - entered command name * @param[out] status - status of the recognition; NULL = don't care * @param[out] pLongestNWords - word count of the longest matched command * (may be used in case of ambiguity to show all possible alternatives of the same length) * @return the detected command, if any */ static const cmd_item_t *fuzzy_recognize_command( char *name, enum fuzzymatch_cmd_result *status, size_t *pLongestNWords ); #define CMDLIST_SHOW_GROUPS 1 //!< Show groups in the command list, also hide commands that are in a group. #define CMDLIST_SHOW_CMDS 2 //!< Show individual commands in the command list #define CMDLIST_SHOW_ALIASES 4 //!< Include aliases in the command list #define CMDLIST_DETAILED 8 //!< Use detailed listing format with descriptions #define CMDLIST_GROUPCONTENT 16 //!< Showing what's inside a group /** * The "body" of the "ls" command */ static console_err_t do_list_commands(console_ctx_t *ctx, const char *filter_group, uint16_t flags); /** Line buffer used for command parsing */ static char s_line_buf[CONSOLE_LINE_BUF_LEN]; /** Argv array, holds pointers to s_line_buf. */ static char *s_argv[CONSOLE_MAX_NUM_ARGS]; /** Command eval lock, guarding shared state */ #if CONSOLE_USE_FREERTOS static console_mutex_t s_console_eval_mutex; #endif /** Flag that console was already inited */ static volatile bool console_inited = false; /** Console config (local copy) */ static console_config_t s_config = CONSOLE_CONFIG_DEFAULTS(); /** Global pointer to console context, valid within a command handler only */ struct console_ctx * console_active_ctx = NULL; /** * Register default console commands (help, ls, exit) */ static void register_default_commands(void); static const cmd_item_t *find_command_by_name(const char *name); static const cmd_item_t *find_command_by_handler(console_command_t handler); /** Append to a buffer with a maximal capacity */ static char *strbufcat(char *dest, size_t bufcap, const char*src) { size_t available = bufcap - strlen(dest) - 1; if (available == 0) return dest; return strncat(dest, src, available); } /** Append at most num chars to a buffer with a maximal capacity */ static char *strnbufcat(char *dest, size_t bufcap, const char*src, size_t num) { size_t available = bufcap - strlen(dest) - 1; if (num < available) { available = num; } return strncat(dest, src, available); } /** Append to a buffer with a maximal capacity */ static inline char *strbufcat1(char *dest, size_t bufcap, char src) { return strnbufcat(dest, bufcap, &src, 1); } /** * @brief Callback which provides command completion for linenoise library * * When using linenoise for line editing, command completion support * can be enabled like this: * * linenoiseSetCompletionCallback(&console_get_completion); * * @param buf the string typed by the user * @param lc linenoiseCompletions to be filled in */ static void console_get_completion(const char *buf, linenoiseCompletions *lc); /** * @brief Callback which provides command hints for linenoise library * * When using linenoise for line editing, hints support can be enabled as * follows: * * linenoiseSetHintsCallback((linenoiseHintsCallback*) &console_get_hint); * * The extra cast is needed because linenoiseHintsCallback is defined as * returning a char* instead of const char*. * * @param[in] buf line typed by the user * @param[out] color ANSI color code to be used when displaying the hint * @param[out] bold set to 1 if hint has to be displayed in bold * @return string containing the hint text. This string is persistent and should * not be freed (i.e. linenoiseSetFreeHintsCallback should not be used). */ static const char *console_get_hint(const char *buf, int *color, int *bold); void __attribute__((weak)) console_internal_error_print(const char *msg) { printf("\x1b[31m%s\x1b[m\n", msg); } /** * Fill defaults, preserve `__internal_heap_allocated` and set MAGIC */ static void console_ctx_defaults(struct console_ctx *ctx) { // clear, preserving the HA flag bool ha = ctx->__internal_heap_allocated; bzero(ctx, sizeof(console_ctx_t)); ctx->__internal_heap_allocated = ha; ctx->__internal_magic = CONSOLE_CTX_MAGIC; ctx->exit_allowed = true; ctx->interactive = true; ctx->use_colors = true; strcpy(ctx->prompt, "> "); } console_err_t console_init(const console_config_t *config) { if (console_inited) { return CONSOLE_OK; // no-op } if (config) { memcpy(&s_config, config, sizeof(struct console_config)); } s_empty_argtable.end = arg_end(1); STAILQ_INIT(&s_cmd_list); STAILQ_INIT(&s_cmd_groups); #if CONSOLE_USE_FREERTOS s_console_eval_mutex = xSemaphoreCreateMutex(); if (!s_console_eval_mutex) { return CONSOLE_ERR_NO_MEM; } #elif CONSOLE_USE_PTHREADS if (pthread_mutex_init(&s_console_eval_mutex, NULL) != 0) { printf("\n mutex init has failed\n"); return 1; } #endif // 'console_inited' must be set before calling 'register_default_commands()' // because there is a check in the register function. console_inited = true; register_default_commands(); #if CONSOLE_TESTING_ALLOC_FUNCS // test malloc char *buf = console_malloc(11); assert(buf); strcpy(buf, "0123456789"); assert(0 == strcmp(buf, "0123456789")); // no-op does not change the pointer char *reallocated = console_realloc(buf, 11, 11); assert(reallocated == buf); buf = reallocated; reallocated = NULL; // growing does reallocated = console_realloc(buf, 11, 20); assert(reallocated != buf); assert(0 == strcmp(reallocated, "0123456789")); buf = reallocated; reallocated = NULL; // test that we can write into strcat(buf, "banana"); assert(0 == strcmp(buf, "0123456789banana")); char *copy = console_strdup(buf); assert(copy != buf); assert(0 == strcmp(buf, "0123456789banana")); assert(0 == strcmp(copy, "0123456789banana")); assert(0 == copy[16]); char *copy2 = console_strndup(buf, 5); assert(0 == strcmp(copy2, "01234")); assert(0 == copy2[5]); assert(copy2 != buf); uint8_t *calloced = console_calloc(100,1); for(int i=0;i<100;i++) { assert(calloced[i] == 0); } console_free(calloced); console_free(copy); console_free(copy2); console_free(buf); console_free(NULL); #endif return CONSOLE_OK; } /** * Extract "command group" from a command name. * * Single-word commands have NULL group. * * @param[in] name - command signature * @return the first word of the command, or NULL */ static const char *command_name_to_group(const char *name) { if (pm_count_words(name, " ") <= 1) { return NULL; } const char *end = pm_skip_words(name, " ", 1); size_t len = end - name; // Look if we already have the group defined - avoids a needless strdup struct cmd_groups_item_ *it = NULL; STAILQ_FOREACH(it, &s_cmd_groups, next) { if (strncmp(name, it->group, len) == 0 && it->group[len] == 0) { return it->group; } } return console_strndup(name, len); } static console_err_t do_add_group(const char *name, const char *descr, bool increment_cmds) { if (!name) return CONSOLE_ERR_INVALID_ARG; // iterate groups and look if we already know this one cmd_groups_item_t *it = NULL; bool known = false; STAILQ_FOREACH(it, &s_cmd_groups, next) { /* Check if command starts with buf */ if (strcmp(name, it->group) == 0) { known = true; if (descr) { // maybe it was already spawned by a command. it->description = descr; } if (increment_cmds) { it->num_commands++; } break; } } if (!known) { // add new group to the group list cmd_groups_item_t *grp = console_calloc(1, sizeof(cmd_groups_item_t)); if (!grp) return CONSOLE_ERR_NO_MEM; grp->group = name; // borrow it forever grp->description = descr; // borrow it forever if (increment_cmds) { grp->num_commands++; } STAILQ_INSERT_TAIL(&s_cmd_groups, grp, next); } return 0; } console_err_t console_group_add(const char *name, const char *descr) { return do_add_group(name, descr, false); } static console_err_t add_command_to_list(cmd_item_t *cmd) { if (cmd->group/* && !cmd->alias_of*/) { console_err_t rv = do_add_group(cmd->group, NULL, true); if (rv != CONSOLE_OK) { return rv; } } STAILQ_INSERT_TAIL(&s_cmd_list, cmd, next); return 0; } static console_err_t add_alias(const cmd_item_t *original, const char *alias) { // check for duplicate const cmd_item_t *existing = find_command_by_name(alias); if (existing) { fprintf(stderr, "Console: Command already exists: %s\n", alias); return CONSOLE_ERR_UNKNOWN_CMD; } cmd_item_t *item2 = (cmd_item_t *) console_calloc(1, sizeof(cmd_item_t)); if (!item2) return CONSOLE_ERR_NO_MEM; memcpy(item2, original, sizeof(cmd_item_t)); item2->alias_of = original->sig.command; item2->sig.command = alias; item2->group = command_name_to_group(alias); item2->next.stqe_next = NULL; add_command_to_list(item2); return CONSOLE_OK; } console_err_t console_cmd_add_alias(const char *original, const char *alias) { // find command to copy const cmd_item_t *item = find_command_by_name(original); if (!item) { fprintf(stderr, "Console: Unknown command to alias: %s\n", original); return CONSOLE_ERR_UNKNOWN_CMD; } return add_alias(item, alias); } console_err_t console_cmd_add_alias_fn(console_command_t handler, const char *alias) { // find command to copy const cmd_item_t *item = find_command_by_handler(handler); if (!item) { fprintf(stderr, "Console: Unknown command to alias: %p\n", handler); return CONSOLE_ERR_UNKNOWN_CMD; } return add_alias(item, alias); } static void print_arg_hint_to_buffer(char *argbuf, size_t argbuf_cap, const struct arg_hdr *arg) { const bool have_value = (arg->flag & ARG_HASVALUE); const bool have_short = arg->shortopts; const bool have_long = arg->longopts; const bool optional = arg->mincount==0; strbufcat1(argbuf, argbuf_cap, ' '); if (optional) { strbufcat1(argbuf, argbuf_cap, '['); } if (have_short) { strbufcat1(argbuf, argbuf_cap, '-'); strnbufcat(argbuf, argbuf_cap, arg->shortopts, 1); } if (have_long) { strbufcat(argbuf, argbuf_cap, have_short?"|--":"--"); strbufcat(argbuf, argbuf_cap, arg->longopts); } if (have_value) { if (have_short || have_long) { strbufcat1(argbuf, argbuf_cap, have_short? ' ' : '='); } strbufcat(argbuf, argbuf_cap, arg->datatype); } if (optional) { strbufcat1(argbuf, argbuf_cap, ']'); } } console_err_t console_cmd_register(console_command_t handler, const char *name) { if (!console_inited) { console_internal_error_print("console_cmd_register: not inited!"); return CONSOLE_ERR_BAD_CALL; } if (!handler) { console_internal_error_print("console_cmd_register: NULL handler!"); return CONSOLE_ERR_INVALID_ARG; } if (!name || name[0]==0) { console_internal_error_print("console_cmd_register: empty name!"); return CONSOLE_ERR_INVALID_ARG; } const cmd_item_t *existing = NULL; // // If the command is already registered, create an alias. // const cmd_item_t *existing = find_command_by_handler(handler); // if (existing) { // return add_alias(existing, name); // } // check for duplicate existing = find_command_by_name(name); if (existing) { fprintf(stderr, "Console: Command already exists: %s\n", name); return CONSOLE_ERR_UNKNOWN_CMD; } cmd_item_t *item = (cmd_item_t *) console_calloc(1, sizeof(cmd_item_t)); if (item == NULL) { return CONSOLE_ERR_NO_MEM; } item->func = handler; item->sig.command = name; handler(NULL, &item->sig); item->sig.command = name; // discard possible changes made inside the function item->group = command_name_to_group(name); if (item->sig.argtable) { if (NULL == item->sig.hint) { /* Generate hint based on cmd->argtable */ #if CONSOLE_USE_MEMSTREAM char *buf = NULL; size_t buf_size = 0; FILE *f = open_memstream(&buf, &buf_size); if (f != NULL) { arg_print_syntax(f, item->sig.argtable, NULL); fclose(f); } item->sig.hint = buf; // hint stays on heap #else // this is a fallback replacement for the argtable version struct arg_hdr **table = item->sig.argtable; int tabindex = 0; const size_t argbuf_cap = CONSOLE_LINE_BUF_LEN; char *argbuf = s_line_buf; // we can use 's_line_buf' as scratch here, certainly no command is running yet *argbuf = 0; for(tabindex = 0; table[tabindex] && !(table[tabindex]->flag & ARG_TERMINATOR) && (tabindex < CONSOLE_MAX_NUM_ARGS); tabindex++) { print_arg_hint_to_buffer(argbuf, argbuf_cap, table[tabindex]); } size_t real_len = strlen(argbuf); if (real_len > 0) { char *copy = console_malloc(real_len + 1); if (copy) { memcpy(copy, argbuf, real_len + 1); item->sig.hint = copy; // stays on the heap } } #endif } } else { item->sig.argtable = &s_empty_argtable; // hint is NULL } // Add the new entry to the list add_command_to_list(item); return CONSOLE_OK; } size_t console_count_commands(void) { assert(console_inited); size_t count = 0; cmd_item_t *it = NULL; STAILQ_FOREACH(it, &s_cmd_list, next) { count++; } return count; } static void console_get_completion(const char *buf, linenoiseCompletions *lc) { assert(console_inited); assert(NULL != buf); assert(NULL != lc); size_t len = strlen(buf); if (len == 0) { return; } cmd_item_t *it = NULL; STAILQ_FOREACH(it, &s_cmd_list, next) { /* Check if command starts with buf */ if (strncmp(buf, it->sig.command, len) == 0) { consLnAddCompletion((linenoiseCompletions *) lc, it->sig.command); } } } static const char *console_get_hint(const char *buf, int *color, int *bold) { assert(console_inited); assert(NULL != buf); assert(NULL != color); assert(NULL != bold); size_t len = strlen(buf); cmd_item_t *it = NULL; STAILQ_FOREACH(it, &s_cmd_list, next) { if (strlen(it->sig.command) == len && strncmp(buf, it->sig.command, len) == 0) { *color = 35; // purple *bold = false; return it->sig.hint ? it->sig.hint : ""; } } return NULL; } static const cmd_item_t *find_command_by_name(const char *name) { assert(console_inited); assert(NULL != name); const cmd_item_t *cmd = NULL; cmd_item_t *it = NULL; STAILQ_FOREACH(it, &s_cmd_list, next) { if (prefix_multipart_test(name, it->sig.command, " ", PREFIXMATCH_NOABBREV) == PM_TEST_MATCH) { cmd = it; break; } } return cmd; } static const cmd_item_t *find_command_by_handler(console_command_t handler) { assert(console_inited); assert(NULL != handler); const cmd_item_t *cmd = NULL; cmd_item_t *it = NULL; STAILQ_FOREACH(it, &s_cmd_list, next) { if (it->func == handler) { cmd = it; break; } } return cmd; } /** * Print help for one command. Must be called in a command context. * * @param it - the command * @param with_args - to show args * @return success */ static console_err_t print_help_onecmd(const cmd_item_t *cmd, bool with_args); static void print_command_suggestions(char *cmdline) { cmd_item_t *it = NULL; STAILQ_FOREACH(it, &s_cmd_list, next) { // if a command is multi-part size_t full_nwords = pm_count_words(it->sig.command, " "); for (size_t nwords = full_nwords; nwords >= 1; nwords--) { char *end = (char *) pm_skip_words(cmdline, " ", nwords); if (!end) break; char old_end = *end; // backup the old byte at the end position *end = 0; // terminate the string there if (0 != prefix_multipart_test(cmdline, it->sig.command, " ", PREFIXMATCH_MULTI_PARTIAL)) { console_color_printf(COLOR_YELLOW, "-> %s\n", it->sig.command); break; // use the longest match } *end = old_end; } } } static void print_ambiguous_command_suggestions(char *cmdline, size_t longest_nwords) { cmd_item_t *it = NULL; STAILQ_FOREACH(it, &s_cmd_list, next) { // if a command is multi-part size_t nwords = pm_count_words(it->sig.command, " "); char *end = (char *) pm_skip_words(cmdline, " ", nwords); if (!end) continue; char old_end = *end; // backup the old byte at the end position *end = 0; // terminate the string there if (1 == prefix_multipart_test(cmdline, it->sig.command, " ", 0)) { // use the longest matching command if (nwords == longest_nwords) { console_color_printf(COLOR_YELLOW, "-> %s\n", it->sig.command); } } *end = old_end; } } /** * Show command group info, called from the handle function. * * s_line_buf must contain the raw unprocessed command. * * @param ctx * @param g * @return */ static console_err_t print_group_info(console_ctx_t *ctx, const cmd_groups_item_t *g) { console_err_t rv; const char *descr = g->description ? g->description : "Command group:"; if (ctx->use_colors) { console_color_printf(COLOR_GREEN, "%s\n", descr); } else { console_printf("%s\n", descr); } // catch attempts to show details bool detailed = (strstr(s_line_buf, "-h") != NULL) || (strstr(s_line_buf, "-d") != NULL) || (strstr(s_line_buf, "-v") != NULL); // remove the -h etc char *first_hyphen = strchr(s_line_buf, '-'); if (first_hyphen) { *first_hyphen = 0; } rv = do_list_commands(ctx, s_line_buf, (detailed ? CMDLIST_DETAILED : 0) | CMDLIST_SHOW_CMDS | CMDLIST_GROUPCONTENT); if (!detailed) { console_printf("Use -h to show command descriptions.\n"); } return rv; } console_err_t console_handle_cmd(console_ctx_t *ctx, const char *cmdline, int *pRetval, const struct cmd_signature **pCommandSig) { if (!console_inited) { console_internal_error_print("console_handle_cmd: not inited!"); return CONSOLE_ERR_BAD_CALL; } if (!ctx) { console_internal_error_print("console_handle_cmd: NULL ctx!"); return CONSOLE_ERR_INVALID_ARG; } if (!cmdline) { console_internal_error_print("console_handle_cmd: NULL cmdline!"); return CONSOLE_ERR_INVALID_ARG; } // cmd_ret may be null int argc; console_err_t rv; // ensure retval is inited if (pRetval) { *pRetval = 0; } if (pCommandSig) { *pCommandSig = NULL; } #if CONSOLE_USE_FREERTOS if (pdPASS != xSemaphoreTake(s_console_eval_mutex, pdMS_TO_TICKS(s_config.execution_lock_timeout_ms))) { return CONSOLE_ERR_TIMEOUT; } #elif CONSOLE_USE_PTHREADS struct timespec timeoutTime; clock_gettime(CLOCK_REALTIME, &timeoutTime); timeoutTime.tv_nsec += s_config.execution_lock_timeout_ms*1000LL; if(0 != pthread_mutex_timedlock(&s_console_eval_mutex, &timeoutTime)) { return CONSOLE_ERR_TIMEOUT; } #endif console_active_ctx = ctx; strncpy(s_line_buf, cmdline, CONSOLE_LINE_BUF_LEN); s_line_buf[CONSOLE_LINE_BUF_LEN - 1] = 0; // ensure it is terminated enum fuzzymatch_cmd_result status = FUZZY_CMD_NO_MATCH; size_t longest_nwords = 0; const cmd_item_t *cmd = fuzzy_recognize_command(s_line_buf, &status, &longest_nwords); if (status == FUZZY_CMD_AMBIGUOUS) { // check if we have an exact match among groups cmd_groups_item_t *g = NULL; STAILQ_FOREACH(g, &s_cmd_groups, next) { /* Check if command starts with buf */ const size_t grouplen = strlen(g->group); if (strncmp(s_line_buf, g->group, grouplen) == 0 && ((s_line_buf[grouplen] == 0) || (s_line_buf[grouplen] == ' '))) { rv = print_group_info(ctx, g); goto exit; } } } if (cmd == NULL) { // there is no match if (status == FUZZY_CMD_NO_MATCH) { // Check if this is a group name - if so, show group members. cmd_groups_item_t *g = NULL; STAILQ_FOREACH(g, &s_cmd_groups, next) { /* Check if command starts with buf */ const size_t grouplen = strlen(g->group); if (strncmp(s_line_buf, g->group, grouplen) == 0 && (s_line_buf[grouplen] == 0 || s_line_buf[grouplen] == ' ')) { rv = print_group_info(ctx, g); goto exit; } } // no match in groups... if (longest_nwords > 0) { console_color_printf(COLOR_RED, "Unknown command. Partial matches:\n"); print_command_suggestions(s_line_buf); } else { console_color_printf(COLOR_RED, "Unknown command.\n"); } } else if (status == FUZZY_CMD_AMBIGUOUS) { console_color_printf(COLOR_RED, "Ambiguous command. Possible matches:\n"); // This dump is shown in all cases, even non-interactive, to help debugging. print_ambiguous_command_suggestions(s_line_buf, longest_nwords); } rv = CONSOLE_ERR_UNKNOWN_CMD; goto exit; } if (ctx->interactive && status != FUZZY_CMD_EXACT_MATCH) { // Show the recognized command console_color_printf(COLOR_YELLOW, "%s\n", cmd->sig.command); } argc = console_split_argv(s_line_buf, s_argv, CONSOLE_MAX_NUM_ARGS); if (argc == 0) { rv = CONSOLE_ERR_BAD_CALL; goto exit; } // help for all commands for (int i = 1; i < argc; i++) { if (0 == strcmp(s_argv[i], "-h") || 0 == strcmp(s_argv[i], "-?") || 0 == strcmp(s_argv[i], "--help")) { print_help_onecmd(cmd, true); if (pRetval != NULL) { *pRetval = 0; } rv = CONSOLE_OK; goto exit; } } // now we know it's the command we want. // we have to manipulate argv to match the command signature (join multipart command words) if (longest_nwords > 1) { // const is discarded here - it's OK. nobody should try to free the strings, as they are normally // pieces of the global static char array s_argv[0] = (char *) cmd->sig.command; // This is safe, since user code see it through a const* // shift the tail for (int i = longest_nwords, j = 1; i <= argc; i++, j++) { s_argv[j] = s_argv[i]; } argc -= (int)longest_nwords - 1; } /* Run the command */ if (pCommandSig) { *pCommandSig = &cmd->sig; } if (!cmd->sig.custom_args) { // Parse argv using argtable3 int nerrors = arg_parse(argc, s_argv, (void **) cmd->sig.argtable); if (nerrors != 0) { #if CONSOLE_USE_MEMSTREAM // find end struct arg_hdr **arg = cmd->sig.argtable; int depth = 0; while (0 == ((*arg)->flag & ARG_TERMINATOR) && (depth < CONSOLE_MAX_NUM_ARGS)) { arg++; depth++; } // temporary stream FILE for argtable char *buf = NULL; size_t buf_size = 0; FILE *f = open_memstream(&buf, &buf_size); if (!f) { rv = CONSOLE_ERR_NO_MEM; goto exit; } arg_print_errors(f, (struct arg_end *) *arg, cmd->sig.command); // clean up fclose(f); console_print(buf); free(buf); // allocated by memstream #else console_printf_ctx(ctx, COLOR_RED, "bad args\n"); #endif rv = CONSOLE_ERR_INVALID_ARG; goto exit; } } // Expose some context information to the command handler ctx->argv = (const char **) &s_argv[0]; ctx->argc = argc; ctx->cmd = &cmd->sig; int cmd_rv = (*cmd->func)(ctx, NULL); if (pRetval) { *pRetval = cmd_rv; } // Clear the context pointers ctx->argv = NULL; ctx->argc = 0; ctx->cmd = NULL; rv = CONSOLE_OK; // fall through exit: console_active_ctx = NULL; #if CONSOLE_USE_FREERTOS xSemaphoreGive(s_console_eval_mutex); #elif CONSOLE_USE_PTHREADS pthread_mutex_unlock(&s_console_eval_mutex); #endif return rv; } static console_err_t print_help_onecmd(const cmd_item_t *cmd, bool with_args) { assert(NULL != cmd); /* First line: command name and hint * Pad all the hints to the same column */ const char *hint = (cmd->sig.hint) ? cmd->sig.hint : ""; if (console_active_ctx->use_colors) { console_printf("\x1b[1m%s\x1b[22;35m%s\x1b[m\n", cmd->sig.command, hint); } else { console_printf("%-s%s\n", cmd->sig.command, hint); } bool any_aliases = false; cmd_item_t *iter; /* Print all aliases */ STAILQ_FOREACH(iter, &s_cmd_list, next) { if (cmd != iter && cmd->func == iter->func) { if (!any_aliases) { console_print(" (aliases: "); } else { console_print(", "); } any_aliases = true; if (console_active_ctx->use_colors) { console_printf("\x1b[1m%s\x1b[m", iter->sig.command); } else { console_print(iter->sig.command); } } } if (any_aliases) { console_print(")\n"); } #if CONSOLE_USE_MEMSTREAM // argtable3 needs a FILE* char *buf = NULL; size_t buf_size = 0; FILE *capf = open_memstream(&buf, &buf_size); if (!capf) return CONSOLE_ERR_NO_MEM; if (cmd->sig.help && cmd->sig.help[0] != 0) { /* Second line: print help. * Argtable has a nice helper function for this which does line * wrapping. */ console_printf(" "); // arg_print_formatted does not indent the first line arg_print_formatted(capf, 2, 78, cmd->sig.help); } if (with_args) { /* Finally, print the list of arguments */ if (cmd->sig.argtable) { arg_print_glossary(capf, (void **) cmd->sig.argtable, " %12s %s\n"); } fputs("\n", capf); } // clean up the memstream FILE fclose(capf); console_print(buf); free(buf); // allocated by memstream #else // this is a fallback replacement for the argtable version if (cmd->sig.help && cmd->sig.help[0] != 0) { console_printf(" %s\n", cmd->sig.help); } const size_t argbuf_cap = 32; char argbuf[32]; struct arg_hdr **table = cmd->sig.argtable; int tabindex = 0; for(tabindex = 0; table[tabindex] && !(table[tabindex]->flag & ARG_TERMINATOR) && (tabindex < CONSOLE_MAX_NUM_ARGS); tabindex++) { *argbuf = 0; print_arg_hint_to_buffer(argbuf, argbuf_cap, table[tabindex]); console_print(" "); console_print(argbuf); console_print(" "); console_println(table[tabindex]->glossary); } #endif return CONSOLE_OK; } static const cmd_item_t *fuzzy_recognize_command(char *name, enum fuzzymatch_cmd_result *pStatus, size_t *pLongestNWords) { size_t source_words = pm_count_words(name, " "); cmd_item_t *it = NULL; size_t longest_nwords = 0; bool ambiguous = false; bool exact_match = false; const cmd_item_t *cmd = NULL; STAILQ_FOREACH(it, &s_cmd_list, next) { size_t nwords = pm_count_words(it->sig.command, " "); char *end = (char*) pm_skip_words(name, " ", nwords); if (!end) continue; char old_end = *end; // backup the old byte at the end position *end = 0; // terminate the string there if (1 == prefix_multipart_test(name, it->sig.command, " ", 0)) { // use the longest matching command if (!cmd || nwords > longest_nwords) { cmd = it; longest_nwords = nwords; ambiguous = false; // we found a longer match exact_match = (PM_TEST_MATCH == prefix_multipart_test(name, it->sig.command, " ", PREFIXMATCH_NOABBREV)); if (exact_match && nwords == source_words) { goto fuzzy_done; } } else if (nwords == longest_nwords && !exact_match) { if (PM_TEST_MATCH == prefix_multipart_test(name, it->sig.command, " ", PREFIXMATCH_NOABBREV)) { ambiguous = false; // we found a longer match exact_match = true; cmd = it; } else { if (cmd->alias_of == it->sig.command || it->alias_of == cmd->sig.command) { // We have two aliases of the same command, not really ambiguous } else { exact_match = false; ambiguous = true; // there is an ambiguity between two commands } } } } *end = old_end; } fuzzy_done: if (pLongestNWords) *pLongestNWords = longest_nwords; if (ambiguous) { if (pStatus) *pStatus = FUZZY_CMD_AMBIGUOUS; return NULL; } else { if (pStatus) *pStatus = cmd ? (exact_match ? FUZZY_CMD_EXACT_MATCH : FUZZY_CMD_PREFIX_MATCH) : FUZZY_CMD_NO_MATCH; return cmd; } } /** * Command: Show help */ static int cmd_help(console_ctx_t *ctx, cmd_signature_t *reg) { // this struct is only used to build the hint static struct { struct arg_str *cmd; struct arg_end *end; } args; if (reg) { args.cmd = arg_str1(NULL, NULL, "", "Command to describe (can be multi-part)"); args.end = arg_end(2); reg->custom_args = true; reg->help = "Show the help page for a command, or list all commands and their basic usage"; reg->argtable = &args; return 0; } /* recreate the original multi-part command, if given as multiple space-separated arguments */ // HACK: using s_line_buf as a scratch buffer char *const cmdname = s_line_buf; char *p = cmdname; *p = 0; // clear the string for (size_t i = 1; i < ctx->argc; i++) { p += sprintf(p, "%s ", ctx->argv[i]); } if (p != cmdname) p--; // remove the trailing space *p = 0; // add terminator after the built sequence if (*s_line_buf != 0) { // a command is selected enum fuzzymatch_cmd_result status = FUZZY_CMD_NO_MATCH; size_t longest_nwords = 0; const cmd_item_t *cmd = fuzzy_recognize_command(s_line_buf, &status, &longest_nwords); if (status == FUZZY_CMD_NO_MATCH) { console_color_printf(COLOR_RED, "\nHelp: No command matches: \"%s\"\n", s_line_buf); print_command_suggestions(s_line_buf); return CONSOLE_ERR_UNKNOWN_CMD; } if (status == FUZZY_CMD_AMBIGUOUS) { console_color_printf(COLOR_RED, "\nHelp: Ambiguous command: \"%s\"\n", s_line_buf); print_ambiguous_command_suggestions(s_line_buf, longest_nwords); return CONSOLE_ERR_UNKNOWN_CMD; } assert(cmd != NULL); if (!ctx->exit_allowed && cmd->func == cmd_exit) { console_print("\n\"exit\" is not available in this session.\n"); return 0; } print_help_onecmd(cmd, true); return 0; } else { cmd_item_t *it; /* Print summary of each command */ STAILQ_FOREACH(it, &s_cmd_list, next) { if (it->alias_of) continue; // aliases are listed as part of the regular entry if (!ctx->exit_allowed && it->func == cmd_exit) { continue; } print_help_onecmd(it, false); } return 0; } } const char* LS_USE_HELP_TO_LEARN_MORE = EXPENDABLE_STRING("Use `cmd -h` or `help cmd` to learn more about a command.\n"); /** * Command: List all commands in a short format. */ static int cmd_list(console_ctx_t *ctx, cmd_signature_t *reg) { // this struct is only used to build the hint static struct { struct arg_lit *all; struct arg_lit *aliases; struct arg_lit *describe; struct arg_lit *groupsonly; struct arg_str *group; struct arg_end *end; } args; if (reg) { args.group = arg_str0(NULL, NULL, "GROUP", EXPENDABLE_STRING("Show commands in a group, or starting with...")); args.all = arg_lit0("a", "all", EXPENDABLE_STRING("Show all commands, disable grouping")); args.describe = arg_lit0("d", "descr", EXPENDABLE_STRING("Show one command per line, with descriptions")); args.groupsonly = arg_lit0("g", "groups", EXPENDABLE_STRING("Show only groups")); args.aliases = arg_lit0("A", "aliases", EXPENDABLE_STRING("Include command aliases")); args.end = arg_end(3); reg->argtable = &args; reg->help = EXPENDABLE_STRING("List available commands"); return 0; } const char * const filter_group = args.group->count ? args.group->sval[0] : NULL; // fake the -a flag if executed as "la" if (0 == strcmp("la", ctx->argv[0])) { args.all->count = 1; } // fake the -d flag if executed as "ll" if (0 == strcmp("ll", ctx->argv[0])) { args.describe->count = 1; } bool show_groups = !args.all->count && !args.group->count; bool show_commands = !args.groupsonly->count; bool show_aliases = args.aliases->count; if (args.all->count && filter_group) { console_color_printf(COLOR_RED, "Filter argument cannot be used together with \"-a\""); return CONSOLE_ERR_INVALID_ARG; } return do_list_commands(ctx, args.all->count ? NULL : filter_group, (show_groups ? CMDLIST_SHOW_GROUPS : 0) | (show_commands ? CMDLIST_SHOW_CMDS : 0) | (show_aliases ? CMDLIST_SHOW_ALIASES : 0) | (args.describe->count ? CMDLIST_DETAILED : 0)); } static console_err_t do_list_commands(console_ctx_t *ctx, const char *filter_group, uint16_t flags) { #define SKIPLIST_LEN 32 uint32_t skipmask[SKIPLIST_LEN] = {};// should be enough bool have_spaces = pm_count_words(filter_group, " \t") > 0; int first_word_len = pm_word_len(filter_group, " \t"); // Count commands int count = 0; int index = 0; int max_cmd_len = 0; cmd_item_t *it; cmd_groups_item_t *grp; const bool show_groups = flags & CMDLIST_SHOW_GROUPS; const bool show_commands = flags & CMDLIST_SHOW_CMDS; const bool show_aliases = flags & CMDLIST_SHOW_ALIASES; const bool detailed = flags & CMDLIST_DETAILED; const bool ingroup = flags & CMDLIST_GROUPCONTENT; bool any_matches = false; bool any_aliases_shown = false; bool any_groups_shown = false; if (show_groups) { any_matches = true; STAILQ_FOREACH(grp, &s_cmd_groups, next) { int len = (int) strlen(grp->group); if (len > max_cmd_len) { max_cmd_len = len; } count++; index++; any_groups_shown = true; } } if (show_commands) { STAILQ_FOREACH(it, &s_cmd_list, next) { if (!ctx->exit_allowed && it->func == cmd_exit) { goto skip; } if (!show_aliases && it->alias_of) { goto skip; } if (filter_group) { // user tries to filter. // that means groups are hidden and then "all" listing is used. bool grp_matches = (it->group && 0 == strncasecmp(filter_group, it->group, MAX(first_word_len, strlen(it->group))) && (have_spaces || it->group[strlen(filter_group)]==0) ); bool prefix_matches = 0 == strncasecmp(filter_group, it->sig.command, strlen(filter_group)); if (!(grp_matches || (!ingroup && prefix_matches))) { goto skip; } if (grp_matches && !prefix_matches) { // bad prefix goto skip; } } else { // no filter word, this is the basic listing if (show_groups && it->group) { // do not show command that is grouped goto skip; } } int len = (int) strlen(it->sig.command); if (len > max_cmd_len) { max_cmd_len = len; } index++; count++; any_matches = true; if (it->alias_of) { any_aliases_shown = true; } continue; skip: assert((index >> 5) < SKIPLIST_LEN); skipmask[index >> 5] |= (1 << (index & 31)); index++; } } if (!any_matches) { console_print("No matching command or group found.\n"); return CONSOLE_OK; } if (detailed) { // Show descriptions for all matched commands index = 0; if (show_groups) { STAILQ_FOREACH(grp, &s_cmd_groups, next) { if (skipmask[index>>5] & (1<<(index&31))) { index++; continue; } index++; if (console_active_ctx->use_colors) { console_printf("\x1b[1m%s\x1b[m\x1b[22;35m Group with %d sub-commands\x1b[m\n", grp->group, grp->num_commands); if (grp->description) { console_printf(" %s\n", grp->description); } } else { console_printf("%s - Group with %d sub-commands\n", grp->group, grp->num_commands); if (grp->description) { console_printf(" %s\n", grp->description); } } } } if (show_commands) { STAILQ_FOREACH(it, &s_cmd_list, next) { if (skipmask[index >> 5] & (1 << (index & 31))) { index++; continue; } index++; print_help_onecmd(it, false); } } console_print("\n"); console_print(LS_USE_HELP_TO_LEARN_MORE); return CONSOLE_OK; } // this is a multi-column ls-like list of all commands const int COLW = max_cmd_len + 2; const int COLN = ( count < 6 ? 1 : count <= 12 ? 3 : (80 / COLW)); const int LINELEN = COLW * COLN + 1; // resolve number of rows needed for the full output const int paddedcount = count + (COLN>1?(COLN - count % COLN):0); // round up to a multiple of COLN const int rows = paddedcount / COLN; const size_t buffers_cap = (size_t) rows * LINELEN; char *buffers = console_malloc(buffers_cap); if (!buffers) { return CONSOLE_ERR_NO_MEM; } memset(buffers, ' ', buffers_cap); buffers[buffers_cap-1] = 0; int col = 0; int row = 0; index = 0; if (show_groups) { STAILQ_FOREACH(grp, &s_cmd_groups, next) { if (skipmask[index>>5] & (1<<(index&31))) { index++; continue; } index++; // see command printing below for explanation const size_t offset = row * LINELEN + col * COLW; int grpln = (int)strlen(grp->group); snprintf(buffers + offset, COLW+1, "%s/", grp->group); buffers[offset + grpln + 1] = ' '; // replace the terminator with space row++; if (row >= rows) { row = 0; col++; } } } if (show_commands) { STAILQ_FOREACH(it, &s_cmd_list, next) { if (skipmask[index >> 5] & (1 << (index & 31))) { index++; continue; } index++; // this always replaces the earlier '\0' in the line and adds a new one. // lines are 1 char longer than needed to accomodate the last column's '\0' without // overwriting the next line's first char. const size_t offset = row * LINELEN + col * COLW; bool alias = (it->alias_of != NULL); snprintf(buffers + offset, COLW + 1, "%s%-*s", (alias ? "*" : ""), COLW - alias, it->sig.command); buffers[offset + COLW] = ' '; // replace the terminator with space row++; if (row >= rows) { row = 0; col++; } } } for (int i = 0; i < rows; i++) { vconsole_write(buffers + i * LINELEN, LINELEN); vconsole_write("\n", 1); } console_free(buffers); vconsole_write("\n", 1); EXPENDABLE_CODE({ if (any_groups_shown) { console_print("\"/\" marks a group. Use \"ls GROUP\" to see its members.\n"); } if (any_aliases_shown) { console_print("\"*\" marks an alias.\n"); } }) console_print(LS_USE_HELP_TO_LEARN_MORE); return CONSOLE_OK; } /** * Command: Exit the console */ static int cmd_exit(console_ctx_t *ctx, cmd_signature_t *reg) { if (reg) { assert(NULL == ctx); assert(NULL != reg); reg->help = EXPENDABLE_STRING("Quit console."); reg->no_history = true; return 0; } // These will catch bugs when exiting console while testing assert(NULL != ctx); assert(NULL == reg); assert(NULL != ctx->argv); assert(NULL != ctx->cmd); if (!ctx->exit_allowed) { console_println("Exit not allowed in this session."); return CONSOLE_ERR_NOT_POSSIBLE; } ctx->exit_requested = true; console_println("Leaving console."); return 0; } /** * Command: Delay */ static int cmd_sleep(console_ctx_t *ctx, cmd_signature_t *reg) { (void) ctx; // unused static struct { struct arg_int *seconds; struct arg_int *millis; struct arg_end *end; } args; if (reg) { args.millis = arg_int0("m", "millis", "", "milliseconds"); args.seconds = arg_int0(NULL, NULL, "", "seconds"); args.end = arg_end(2); reg->argtable = &args; reg->help = EXPENDABLE_STRING("Stop the console for a given time (max 60s); useful in batched scripts. Both parameters may be combined."); return 0; } uint32_t s = args.seconds->count ? args.seconds->ival[0] : 0; uint32_t ms = s*1000 + (args.millis->count ? args.millis->ival[0] : 0); if (ms > 60000) { console_color_printf(COLOR_RED, "Interval out of range\n"); return CONSOLE_ERR_INVALID_ARG; } if (ms > 0) { vTaskDelay(pdMS_TO_TICKS(ms)); } return 0; } static void register_default_commands() { console_cmd_register(cmd_list, "list"); console_cmd_add_alias_fn(cmd_list, "ls"); console_cmd_add_alias_fn(cmd_list, "la"); console_cmd_add_alias_fn(cmd_list, "ll"); console_cmd_register(cmd_help, "help"); console_cmd_register(cmd_exit, "exit"); console_cmd_add_alias_fn(cmd_exit, "quit"); console_cmd_add_alias_fn(cmd_exit, "q"); console_cmd_register(cmd_sleep, "sleep"); } console_ctx_t *console_ctx_init( console_ctx_t *ctx, #if CONSOLE_USE_FILE_IO_STREAMS FILE* inf, FILE* outf #else void * ioctx #endif ) { if (ctx == NULL) { ctx = console_calloc(sizeof(struct console_ctx), 1); if (!ctx) { console_internal_error_print("console_ctx_init alloc fail!"); // Alloc failed return NULL; } ctx->__internal_heap_allocated = true; } // fill defaults and set magic console_ctx_defaults(ctx); #if CONSOLE_USE_FILE_IO_STREAMS ctx->in = inf; ctx->out = outf; #else ctx->ioctx = ioctx; #endif return ctx; } void console_ctx_destroy(console_ctx_t *ctx) { if (!ctx) { console_internal_error_print("console_ctx_destroy NULL param!"); return; } if (ctx->__internal_magic != CONSOLE_CTX_MAGIC) { console_internal_error_print("console_ctx_destroy bad magic!"); return; } if (ctx->__internal_heap_allocated) { console_free(ctx); } } void *console_task_posix(void *param) { console_task(param); return NULL; } static int my_linenoiseWrite(void *ctx, const char *text, int len) { return console_write_ctx(ctx, text, len); } static int my_linenoiseRead(void *ctx, char *dest, int count) { return console_read_ctx(ctx, dest, count); } void console_task(void *param) { if (!console_inited) { console_internal_error_print("console_task - console not inited!"); return; } console_ctx_t *ctx = param; if (!ctx) { console_internal_error_print("console_task NULL param!"); return; } if (ctx->__internal_magic != CONSOLE_CTX_MAGIC) { console_internal_error_print("console_task bad magic!"); return; } struct linenoiseState ln; consLnStateInit(&ln); // multi-line does not work reliably consLnSetMultiLine(&ln, 0); // Set pointer to the prompt buffer consLnSetPrompt(&ln, ctx->prompt); consLnSetBuf(&ln, ctx->line_buffer, CONSOLE_LINE_BUF_LEN); ln.allowCtrlDExit = ctx->exit_allowed; /* Tell linenoise where to get command completions and hints */ consLnSetCompletionCallback(&ln, &console_get_completion); consLnSetHintsCallback(&ln, (linenoiseHintsCallback *) &console_get_hint); #if CONSOLE_USE_TERMIOS if (ctx->in) { enableRawMode(ctx, fileno(ctx->in)); } #endif consLnSetReadWrite(&ln, my_linenoiseRead, my_linenoiseWrite, ctx); /* Set command history size */ consLnHistorySetMaxLen(&ln, CONSOLE_HISTORY_LEN); #if CONSOLE_FILE_SUPPORT if (ctx->history_file) { consLnHistoryLoad(&ln, ctx->history_file); } #endif /* Main loop */ while (true) { // Shutdown check if (ctx->exit_allowed && ctx->exit_requested) { // exiting, save history goto exit; } /* Get a line using linenoise. * The line is returned when ENTER is pressed. */ if (ctx->loop_handler) { ctx->loop_handler(ctx); } int len = consLnReadLine(&ln); if (len == 0) { /* Ignore empty lines */ continue; } if (len < 0) { // This happens if ^C is input at stdin //console_internal_error_print("read < 1"); goto exit; } /* Try to run the command */ int ret; bool add_to_history = true; const struct cmd_signature *the_cmd = NULL; int err = console_handle_cmd(ctx, ln.buf, &ret, &the_cmd); if (err == CONSOLE_ERR_UNKNOWN_CMD) { if (ctx->use_colors) { console_print_ctx(ctx, "\x1b[31;1mUnrecognized command:\x1b[22m \""); } else { console_print_ctx(ctx, "Unrecognized command: \""); } // pseudo-hexdump to make it more obvious when the terminal sends garbage // (this will miss some codes - like ESC - because linenoise tries to sanitize the line) for (char *pc = ln.buf; *pc != 0; pc++) { const char c = *pc; if (c >= 32 && c < 127) { console_write_ctx(ctx, &c, 1); } else { console_printf_ctx(ctx, COLOR_RESET, "\\x%02X", c); } } if (ctx->use_colors) { console_print_ctx(ctx, "\"\x1b[m\n"); } else { console_print_ctx(ctx, "\"\n"); } EXPENDABLE_CODE({ console_print_ctx(ctx, "Use `ls` or `help` for a list of commands.\n"); }) } else if (err == CONSOLE_ERR_BAD_CALL) { add_to_history = false; // command was empty } else if (err == CONSOLE_OK && ret != 0) { if (ctx->use_colors) { console_print_ctx(ctx, "\x1b[31;1mCommand err:\x1b[22m "); console_err_print_ctx(ctx, ret); console_print_ctx(ctx, "\nUse `-h` for help.\x1b[m\n"); } else { console_print_ctx(ctx, "Command err: "); console_err_print_ctx(ctx, ret); console_print_ctx(ctx, "\nUse `-h` for help.\n"); } } else if (err != CONSOLE_OK) { if (ctx->use_colors) { console_print_ctx(ctx, "\x1b[31;1mConsole err:\x1b[22m "); console_err_print_ctx(ctx, err); console_print_ctx(ctx, "\x1b[m\n"); } else { console_print_ctx(ctx, "Console err: "); console_err_print_ctx(ctx, err); console_print_ctx(ctx, "\n"); } } if (the_cmd && the_cmd->no_history) { add_to_history = false; } if (add_to_history) { /* Add the command to the history */ consLnHistoryAdd(&ln, ln.buf); } } exit: #if CONSOLE_FILE_SUPPORT if (ctx->history_file) { consLnHistorySave(&ln, ctx->history_file); } #endif consLnHistoryFree(&ln); // ln lives on stack, do not free! #if CONSOLE_USE_TERMIOS && CONSOLE_USE_FILE_IO_STREAMS if (ctx->in) { // Restore STDIN to normal state disableRawMode(ctx, fileno(ctx->in)); } #endif // Run shutdown handler. Shutdown handler could release user data & destroy the context, for example. if (ctx->shutdown_handler) { ctx->shutdown_handler(ctx); } } #if CONSOLE_USE_TERMIOS && CONSOLE_USE_FILE_IO_STREAMS #include #include /* Raw mode: 1960 magic shit. */ static int enableRawMode(console_ctx_t *ctx, int fd) { struct termios raw; if (!isatty(fd)) goto fatal; if (tcgetattr(fd,&ctx->orig_termios) == -1) goto fatal; raw = ctx->orig_termios; /* modify the original mode */ /* input modes: no break, no CR to NL, no parity check, no strip char, * no start/stop output control. */ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); /* output modes - disable post processing */ raw.c_oflag = OPOST | ONLCR; /* control modes - set 8 bit chars */ raw.c_cflag |= (CS8); /* local modes - choing off, canonical off, no extended functions, * no signal chars (^Z,^C) */ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); /* control chars - set return condition: min number of bytes and timer. * We want read to return every single byte, without timeout. */ raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ /* put terminal in raw mode after flushing */ if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; return 0; fatal: errno = ENOTTY; return -1; } static void disableRawMode(console_ctx_t *ctx, int fd) { tcsetattr(fd, TCSAFLUSH, &ctx->orig_termios); } #endif