| /* |
| * |
| * Wireless daemon for Linux |
| * |
| * Copyright (C) 2017-2019 Intel Corporation. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library 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 |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #define _GNU_SOURCE |
| #include <stdio.h> |
| #include <signal.h> |
| #include <sys/stat.h> |
| #include <sys/ioctl.h> |
| #include <unistd.h> |
| |
| #include <readline/history.h> |
| #include <readline/readline.h> |
| #include <ell/ell.h> |
| |
| #include "client/agent.h" |
| #include "client/command.h" |
| #include "client/display.h" |
| |
| #define IWD_PROMPT \ |
| "\001" COLOR_GREEN "\002" "[iwd]" "\001" COLOR_OFF "\002" "# " |
| #define LINE_LEN 81 |
| |
| static struct l_signal *window_change_signal; |
| static struct l_io *io; |
| static char dashed_line[LINE_LEN] = { [0 ... LINE_LEN - 2] = '-' }; |
| static char empty_line[LINE_LEN] = { [0 ... LINE_LEN - 2] = ' ' }; |
| static struct l_timeout *refresh_timeout; |
| static struct saved_input *agent_saved_input; |
| |
| static struct display_refresh { |
| bool enabled; |
| char *family; |
| char *entity; |
| const struct command *cmd; |
| char **argv; |
| int argc; |
| size_t undo_lines; |
| struct l_queue *redo_entries; |
| bool recording; |
| } display_refresh = { .enabled = true }; |
| |
| struct saved_input { |
| char *line; |
| int point; |
| }; |
| |
| static struct saved_input *save_input(void) |
| { |
| struct saved_input *input; |
| |
| if (RL_ISSTATE(RL_STATE_DONE)) |
| return NULL; |
| |
| input = l_new(struct saved_input, 1); |
| |
| input->point = rl_point; |
| input->line = rl_copy_text(0, rl_end); |
| rl_save_prompt(); |
| rl_replace_line("", 0); |
| rl_redisplay(); |
| |
| return input; |
| } |
| |
| static void restore_input(struct saved_input *input) |
| { |
| if (!input) |
| return; |
| |
| rl_restore_prompt(); |
| rl_replace_line(input->line, 0); |
| rl_point = input->point; |
| rl_forced_update_display(); |
| |
| l_free(input->line); |
| l_free(input); |
| } |
| |
| static void display_refresh_undo_lines(void) |
| { |
| size_t num_lines = display_refresh.undo_lines; |
| |
| printf("\033[%dA", (int) num_lines); |
| |
| do { |
| printf("%s\n", empty_line); |
| } while (--display_refresh.undo_lines); |
| |
| printf("\033[%dA", (int) num_lines); |
| } |
| |
| static void display_refresh_redo_lines(void) |
| { |
| const struct l_queue_entry *entry; |
| struct saved_input *input; |
| |
| input = save_input(); |
| |
| for (entry = l_queue_get_entries(display_refresh.redo_entries); entry; |
| entry = entry->next) { |
| char *line = entry->data; |
| |
| printf("%s", line); |
| |
| display_refresh.undo_lines++; |
| } |
| |
| restore_input(input); |
| display_refresh.recording = true; |
| |
| l_timeout_modify(refresh_timeout, 1); |
| } |
| |
| void display_refresh_reset(void) |
| { |
| l_free(display_refresh.family); |
| display_refresh.family = NULL; |
| |
| l_free(display_refresh.entity); |
| display_refresh.entity = NULL; |
| |
| display_refresh.cmd = NULL; |
| |
| l_strfreev(display_refresh.argv); |
| display_refresh.argv = NULL; |
| display_refresh.argc = 0; |
| |
| display_refresh.undo_lines = 0; |
| display_refresh.recording = false; |
| |
| l_queue_clear(display_refresh.redo_entries, l_free); |
| } |
| |
| void display_refresh_set_cmd(const char *family, const char *entity, |
| const struct command *cmd, |
| char **argv, int argc) |
| { |
| int i; |
| |
| if (cmd->refreshable) { |
| l_free(display_refresh.family); |
| display_refresh.family = l_strdup(family); |
| |
| l_free(display_refresh.entity); |
| display_refresh.entity = l_strdup(entity); |
| |
| display_refresh.cmd = cmd; |
| |
| l_strfreev(display_refresh.argv); |
| display_refresh.argc = argc; |
| |
| display_refresh.argv = l_new(char *, argc + 1); |
| |
| for (i = 0; i < argc; i++) |
| display_refresh.argv[i] = l_strdup(argv[i]); |
| |
| l_queue_clear(display_refresh.redo_entries, l_free); |
| |
| display_refresh.recording = false; |
| display_refresh.undo_lines = 0; |
| |
| return; |
| } |
| |
| if (display_refresh.family && family && |
| !strcmp(display_refresh.family, family)) { |
| struct l_string *buf = l_string_new(128); |
| L_AUTO_FREE_VAR(char *, args); |
| char *prompt; |
| |
| for (i = 0; i < argc; i++) { |
| bool needs_quotes = false; |
| char *p = argv[i]; |
| |
| for (p = argv[i]; *p != '\0'; p++) { |
| if (*p != ' ') |
| continue; |
| |
| needs_quotes = true; |
| break; |
| } |
| |
| if (needs_quotes) |
| l_string_append_printf(buf, "\"%s\" ", argv[i]); |
| else |
| l_string_append_printf(buf, "%s ", argv[i]); |
| } |
| |
| args = l_string_unwrap(buf); |
| |
| prompt = l_strdup_printf(IWD_PROMPT"%s%s%s %s %s\n", family, |
| entity ? " " : "", |
| entity ? : "", |
| cmd->cmd ? : "", args ? : ""); |
| |
| l_queue_push_tail(display_refresh.redo_entries, prompt); |
| display_refresh.undo_lines++; |
| |
| display_refresh.recording = true; |
| } else { |
| display_refresh_reset(); |
| } |
| } |
| |
| static void display_refresh_check_feasibility(void) |
| { |
| const struct winsize ws; |
| |
| ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws); |
| |
| if (ws.ws_col < LINE_LEN - 1) { |
| if (display_refresh.enabled) { |
| display_refresh.recording = false; |
| display(COLOR_YELLOW "Auto-refresh is disabled. " |
| "Enlarge window width to at least %u to enable." |
| "\n" COLOR_OFF, LINE_LEN - 1); |
| display_refresh.recording = true; |
| } |
| |
| display_refresh.enabled = false; |
| } else { |
| display_refresh.enabled = true; |
| } |
| } |
| |
| static void display_refresh_check_applicability(void) |
| { |
| if (display_refresh.enabled && display_refresh.cmd) |
| display_refresh_redo_lines(); |
| else if (display_refresh.cmd) |
| display_refresh_timeout_set(); |
| } |
| |
| static void timeout_callback(struct l_timeout *timeout, void *user_data) |
| { |
| struct saved_input *input; |
| |
| if (!display_refresh.enabled || !display_refresh.cmd) { |
| if (display_refresh.cmd) |
| display_refresh_timeout_set(); |
| |
| return; |
| } |
| |
| input = save_input(); |
| display_refresh_undo_lines(); |
| restore_input(input); |
| |
| display_refresh.recording = false; |
| display_refresh.cmd->function(display_refresh.entity, |
| display_refresh.argv, |
| display_refresh.argc); |
| } |
| |
| void display_refresh_timeout_set(void) |
| { |
| if (refresh_timeout) |
| l_timeout_modify(refresh_timeout, 1); |
| else |
| refresh_timeout = l_timeout_create(1, timeout_callback, |
| NULL, NULL); |
| } |
| |
| static void display_text(const char *text) |
| { |
| struct saved_input *input = save_input(); |
| |
| printf("%s", text); |
| |
| restore_input(input); |
| |
| if (!display_refresh.cmd) |
| return; |
| |
| display_refresh.undo_lines++; |
| |
| if (display_refresh.recording) |
| l_queue_push_tail(display_refresh.redo_entries, l_strdup(text)); |
| } |
| |
| void display(const char *fmt, ...) |
| { |
| va_list args; |
| char *text; |
| |
| va_start(args, fmt); |
| text = l_strdup_vprintf(fmt, args); |
| va_end(args); |
| |
| display_text(text); |
| |
| l_free(text); |
| } |
| |
| void display_error(const char *error) |
| { |
| char *text = l_strdup_printf(COLOR_RED "%s" COLOR_OFF "\n", error); |
| |
| display_text(text); |
| |
| l_free(text); |
| } |
| |
| static char get_flasher(void) |
| { |
| static char c; |
| |
| if (c == ' ') |
| c = '*'; |
| else |
| c = ' '; |
| |
| return c; |
| } |
| |
| void display_table_header(const char *caption, const char *fmt, ...) |
| { |
| va_list args; |
| char *text; |
| char *body; |
| int caption_pos = |
| (int) ((sizeof(dashed_line) - 1) / 2 + strlen(caption) / 2); |
| |
| text = l_strdup_printf("%*s" COLOR_BOLDGRAY "%*c" COLOR_OFF "\n", |
| caption_pos, caption, |
| LINE_LEN - 2 - caption_pos, |
| display_refresh.cmd ? get_flasher() : ' '); |
| display_text(text); |
| l_free(text); |
| |
| text = l_strdup_printf("%s%s%s\n", COLOR_GRAY, dashed_line, COLOR_OFF); |
| display_text(text); |
| l_free(text); |
| |
| va_start(args, fmt); |
| text = l_strdup_vprintf(fmt, args); |
| va_end(args); |
| |
| body = l_strdup_printf("%s%s%s\n", COLOR_BOLDGRAY, text, COLOR_OFF); |
| display_text(body); |
| l_free(body); |
| l_free(text); |
| |
| text = l_strdup_printf("%s%s%s\n", COLOR_GRAY, dashed_line, COLOR_OFF); |
| display_text(text); |
| l_free(text); |
| } |
| |
| void display_table_footer(void) |
| { |
| display_text("\n"); |
| |
| display_refresh_check_applicability(); |
| } |
| |
| void display_command_line(const char *command_family, |
| const struct command *cmd) |
| { |
| char *cmd_line = l_strdup_printf("%s%s%s%s%s%s%s", |
| command_family ? : "", |
| command_family ? " " : "", |
| cmd->entity ? : "", |
| cmd->entity ? " " : "", |
| cmd->cmd, |
| cmd->arg ? " " : "", |
| cmd->arg ? : ""); |
| |
| display(MARGIN "%-*s%s\n", 50, cmd_line, cmd->desc ? : ""); |
| |
| l_free(cmd_line); |
| } |
| |
| static void display_completion_matches(char **matches, int num_matches, |
| int max_length) |
| { |
| char *prompt; |
| char *entry; |
| char line[LINE_LEN]; |
| size_t index; |
| size_t line_used; |
| char *input = rl_copy_text(0, rl_end); |
| |
| prompt = l_strdup_printf("%s%s\n", IWD_PROMPT, input); |
| l_free(input); |
| |
| display_text(prompt); |
| l_free(prompt); |
| |
| for (index = 1, line_used = 0; matches[index]; index++) { |
| if ((line_used + max_length) > LINE_LEN) { |
| strcpy(&line[line_used], "\n"); |
| |
| display_text(line); |
| |
| line_used = 0; |
| } |
| |
| entry = l_strdup_printf("%-*s ", max_length, matches[index]); |
| l_strlcpy(&line[line_used], entry, sizeof(line) - line_used); |
| l_free(entry); |
| |
| line_used += max_length + 1; |
| } |
| |
| strcpy(&line[line_used], "\n"); |
| |
| display_text(line); |
| } |
| |
| #define MAX_PASSPHRASE_LEN 63 |
| |
| static struct masked_input { |
| bool use_mask; |
| char passphrase[MAX_PASSPHRASE_LEN + 1]; |
| uint8_t point; |
| uint8_t end; |
| } masked_input; |
| |
| static void mask_input(void) |
| { |
| if (!masked_input.use_mask) |
| return; |
| |
| if (rl_end > MAX_PASSPHRASE_LEN) { |
| rl_end = MAX_PASSPHRASE_LEN; |
| rl_point = masked_input.point; |
| |
| goto set_mask; |
| } |
| |
| if (masked_input.end == rl_end) { |
| /* Moving cursor. */ |
| goto done; |
| } else if (masked_input.end < rl_end) { |
| /* Insertion. */ |
| memcpy(masked_input.passphrase + rl_point, |
| masked_input.passphrase + masked_input.point, |
| masked_input.end - masked_input.point); |
| memcpy(masked_input.passphrase + masked_input.point, |
| rl_line_buffer + masked_input.point, |
| rl_point - masked_input.point); |
| } else { |
| /* Deletion. */ |
| if (masked_input.point > rl_point) |
| /* Backspace key. */ |
| memcpy(masked_input.passphrase + rl_point, |
| masked_input.passphrase + masked_input.point, |
| masked_input.end - masked_input.point); |
| else |
| /* Delete key. */ |
| memcpy(masked_input.passphrase + rl_point, |
| masked_input.passphrase + masked_input.point |
| + 1, |
| rl_end - rl_point); |
| memset(masked_input.passphrase + rl_end, 0, |
| masked_input.end - rl_end); |
| } |
| |
| set_mask: |
| memset(rl_line_buffer, '*', rl_end); |
| rl_line_buffer[rl_end] = '\0'; |
| |
| rl_redisplay(); |
| |
| masked_input.end = rl_end; |
| done: |
| masked_input.point = rl_point; |
| } |
| |
| static void reset_masked_input(void) |
| { |
| memset(masked_input.passphrase, 0, MAX_PASSPHRASE_LEN + 1); |
| masked_input.point = 0; |
| masked_input.end = 0; |
| } |
| |
| static void readline_callback(char *prompt) |
| { |
| char **argv; |
| int argc; |
| |
| HIST_ENTRY *previous_prompt; |
| |
| if (agent_prompt(masked_input.use_mask ? |
| masked_input.passphrase : prompt)) |
| goto done; |
| |
| if (!prompt) { |
| display_quit(); |
| |
| l_main_quit(); |
| |
| return; |
| } |
| |
| if (!strlen(prompt)) |
| goto done; |
| |
| previous_prompt = history_get(history_base + history_length - 1); |
| if (!previous_prompt || strcmp(previous_prompt->line, prompt)) { |
| add_history(prompt); |
| } |
| |
| argv = l_parse_args(prompt, &argc); |
| if (!argv) { |
| display("Invalid command\n"); |
| goto done; |
| } |
| |
| command_process_prompt(argv, argc); |
| |
| l_strfreev(argv); |
| done: |
| l_free(prompt); |
| } |
| |
| bool display_agent_is_active(void) |
| { |
| if (agent_saved_input) |
| return true; |
| |
| return false; |
| } |
| |
| static bool read_handler(struct l_io *io, void *user_data) |
| { |
| rl_callback_read_char(); |
| |
| if (display_agent_is_active() || !command_is_interactive_mode()) |
| mask_input(); |
| |
| return true; |
| } |
| |
| static void disconnect_callback(struct l_io *io, void *user_data) |
| { |
| l_main_quit(); |
| } |
| |
| void display_enable_cmd_prompt(void) |
| { |
| if (!io) |
| io = l_io_new(fileno(stdin)); |
| |
| l_io_set_read_handler(io, read_handler, NULL, NULL); |
| l_io_set_disconnect_handler(io, disconnect_callback, NULL, NULL); |
| |
| rl_set_prompt(IWD_PROMPT); |
| |
| /* |
| * The following sequence of rl_* commands forces readline to properly |
| * update its internal state and re-display the new prompt. |
| */ |
| rl_save_prompt(); |
| rl_redisplay(); |
| rl_restore_prompt(); |
| rl_forced_update_display(); |
| } |
| |
| void display_disable_cmd_prompt(void) |
| { |
| display_refresh_reset(); |
| |
| rl_set_prompt("Waiting for IWD to start..."); |
| printf("\r"); |
| rl_on_new_line(); |
| rl_redisplay(); |
| } |
| |
| void display_agent_prompt(const char *label, bool mask_input) |
| { |
| char *prompt; |
| |
| masked_input.use_mask = mask_input; |
| |
| if (mask_input) |
| reset_masked_input(); |
| |
| prompt = l_strdup_printf(COLOR_BLUE "%s " COLOR_OFF, label); |
| |
| if (command_is_interactive_mode()) { |
| if (agent_saved_input) { |
| l_free(prompt); |
| return; |
| } |
| |
| agent_saved_input = l_new(struct saved_input, 1); |
| |
| agent_saved_input->point = rl_point; |
| agent_saved_input->line = rl_copy_text(0, rl_end); |
| rl_set_prompt(""); |
| rl_replace_line("", 0); |
| rl_redisplay(); |
| |
| rl_erase_empty_line = 0; |
| rl_set_prompt(prompt); |
| } else { |
| rl_callback_handler_install(prompt, readline_callback); |
| |
| if (!io) |
| io = l_io_new(fileno(stdin)); |
| |
| l_io_set_read_handler(io, read_handler, NULL, NULL); |
| } |
| |
| l_free(prompt); |
| |
| rl_redisplay(); |
| } |
| |
| void display_agent_prompt_release(const char *label) |
| { |
| if (!command_is_interactive_mode()) { |
| rl_callback_handler_remove(); |
| l_io_destroy(io); |
| |
| return; |
| } |
| |
| if (!agent_saved_input) |
| return; |
| |
| if (display_refresh.cmd) { |
| char *text = rl_copy_text(0, rl_end); |
| char *prompt = l_strdup_printf(COLOR_BLUE "%s " COLOR_OFF |
| "%s\n", label, text); |
| l_free(text); |
| |
| l_queue_push_tail(display_refresh.redo_entries, prompt); |
| display_refresh.undo_lines++; |
| } |
| |
| rl_erase_empty_line = 1; |
| |
| rl_replace_line(agent_saved_input->line, 0); |
| rl_point = agent_saved_input->point; |
| |
| l_free(agent_saved_input->line); |
| l_free(agent_saved_input); |
| agent_saved_input = NULL; |
| |
| rl_set_prompt(IWD_PROMPT); |
| } |
| |
| void display_quit(void) |
| { |
| rl_crlf(); |
| } |
| |
| static void window_change_signal_handler(void *user_data) |
| { |
| display_refresh_check_feasibility(); |
| } |
| |
| static char *history_path; |
| |
| void display_init(void) |
| { |
| const char *data_home; |
| char *data_path; |
| |
| display_refresh.redo_entries = l_queue_new(); |
| |
| stifle_history(24); |
| |
| data_home = getenv("XDG_DATA_HOME"); |
| if (!data_home || *data_home != '/') { |
| const char *home_path; |
| |
| home_path = getenv("HOME"); |
| if (home_path) |
| data_path = l_strdup_printf("%s/%s/iwctl", |
| home_path, ".local/share"); |
| else |
| data_path = NULL; |
| } else { |
| data_path = l_strdup_printf("%s/iwctl", data_home); |
| } |
| |
| if (data_path) { |
| /* |
| * If mkdir succeeds that means its a new directory, no need |
| * to read the history since it doesn't exist |
| */ |
| if (mkdir(data_path, 0700) != 0) { |
| history_path = l_strdup_printf("%s/history", data_path); |
| read_history(history_path); |
| } |
| |
| l_free(data_path); |
| } else { |
| history_path = NULL; |
| } |
| |
| setlinebuf(stdout); |
| |
| window_change_signal = |
| l_signal_create(SIGWINCH, window_change_signal_handler, NULL, |
| NULL); |
| |
| rl_attempted_completion_function = command_completion; |
| rl_completion_display_matches_hook = display_completion_matches; |
| |
| rl_completer_quote_characters = "\""; |
| rl_erase_empty_line = 1; |
| rl_callback_handler_install("Waiting for IWD to start...", |
| readline_callback); |
| |
| rl_redisplay(); |
| |
| display_refresh_check_feasibility(); |
| } |
| |
| void display_exit(void) |
| { |
| if (agent_saved_input) { |
| l_free(agent_saved_input->line); |
| l_free(agent_saved_input); |
| agent_saved_input = NULL; |
| } |
| |
| l_timeout_remove(refresh_timeout); |
| refresh_timeout = NULL; |
| |
| l_queue_destroy(display_refresh.redo_entries, l_free); |
| |
| rl_callback_handler_remove(); |
| |
| l_io_destroy(io); |
| |
| l_signal_remove(window_change_signal); |
| |
| if (history_path) |
| write_history(history_path); |
| |
| l_free(history_path); |
| |
| display_quit(); |
| } |