diff options
Diffstat (limited to '')
-rw-r--r-- | src/utils/knotc/interactive.c | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/src/utils/knotc/interactive.c b/src/utils/knotc/interactive.c new file mode 100644 index 0000000..1c001c4 --- /dev/null +++ b/src/utils/knotc/interactive.c @@ -0,0 +1,434 @@ +/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program 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. + + This program 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 this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <stdio.h> +#include <histedit.h> + +#include "knot/common/log.h" +#include "utils/common/lookup.h" +#include "utils/knotc/interactive.h" +#include "utils/knotc/commands.h" +#include "contrib/string.h" + +#define PROGRAM_NAME "knotc" +#define HISTORY_FILE ".knotc_history" + +extern params_t params; + +static void cmds_lookup(EditLine *el, const char *str, size_t str_len) +{ + lookup_t lookup; + int ret = lookup_init(&lookup); + if (ret != KNOT_EOK) { + return; + } + + // Fill the lookup with command names. + for (const cmd_desc_t *desc = cmd_table; desc->name != NULL; desc++) { + ret = lookup_insert(&lookup, desc->name, NULL); + if (ret != KNOT_EOK) { + goto cmds_lookup_finish; + } + } + + lookup_complete(&lookup, str, str_len, el, true); + +cmds_lookup_finish: + lookup_deinit(&lookup); +} + +static void local_zones_lookup(EditLine *el, const char *str, size_t str_len) +{ + lookup_t lookup; + int ret = lookup_init(&lookup); + if (ret != KNOT_EOK) { + return; + } + + knot_dname_txt_storage_t buff; + + // Fill the lookup with local zone names. + for (conf_iter_t iter = conf_iter(conf(), C_ZONE); + iter.code == KNOT_EOK; conf_iter_next(conf(), &iter)) { + conf_val_t val = conf_iter_id(conf(), &iter); + char *name = knot_dname_to_str(buff, conf_dname(&val), sizeof(buff)); + + ret = lookup_insert(&lookup, name, NULL); + if (ret != KNOT_EOK) { + conf_iter_finish(conf(), &iter); + goto local_zones_lookup_finish; + } + } + + lookup_complete(&lookup, str, str_len, el, true); + +local_zones_lookup_finish: + lookup_deinit(&lookup); +} + +static char *get_id_name(const char *section) +{ + const cmd_desc_t *desc = cmd_table; + while (desc->name != NULL && desc->cmd != CTL_CONF_LIST) { + desc++; + } + assert(desc->name != NULL); + + knot_ctl_data_t query = { + [KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(desc->cmd), + [KNOT_CTL_IDX_SECTION] = section + }; + + knot_ctl_t *ctl = NULL; + knot_ctl_type_t type; + knot_ctl_data_t reply; + + // Try to get the first group item (possible id). + if (set_ctl(&ctl, desc, ¶ms) != KNOT_EOK || + knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &query) != KNOT_EOK || + knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL) != KNOT_EOK || + knot_ctl_receive(ctl, &type, &reply) != KNOT_EOK || + type != KNOT_CTL_TYPE_DATA || reply[KNOT_CTL_IDX_ERROR] != NULL) { + unset_ctl(ctl); + return NULL; + } + + char *id_name = strdup(reply[KNOT_CTL_IDX_ITEM]); + + unset_ctl(ctl); + + return id_name; +} + +static void id_lookup(EditLine *el, const char *str, size_t str_len, + const cmd_desc_t *cmd_desc, const char *section, bool add_space) +{ + // Decide which confdb transaction to ask. + unsigned ctl_code = (cmd_desc->flags & CMD_FREQ_TXN) ? + CTL_CONF_GET : CTL_CONF_READ; + + const cmd_desc_t *desc = cmd_table; + while (desc->name != NULL && desc->cmd != ctl_code) { + desc++; + } + assert(desc->name != NULL); + + char *id_name = get_id_name(section); + if (id_name == NULL) { + return; + } + + knot_ctl_data_t query = { + [KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(desc->cmd), + [KNOT_CTL_IDX_SECTION] = section, + [KNOT_CTL_IDX_ITEM] = id_name + }; + + lookup_t lookup; + knot_ctl_t *ctl = NULL; + + if (set_ctl(&ctl, desc, ¶ms) != KNOT_EOK || + knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &query) != KNOT_EOK || + knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL) != KNOT_EOK || + lookup_init(&lookup) != KNOT_EOK) { + unset_ctl(ctl); + free(id_name); + return; + } + + free(id_name); + + while (true) { + knot_ctl_type_t type; + knot_ctl_data_t reply; + + // Receive one section id. + if (knot_ctl_receive(ctl, &type, &reply) != KNOT_EOK) { + goto id_lookup_finish; + } + + // Stop if finished transfer. + if (type != KNOT_CTL_TYPE_DATA) { + break; + } + + // Insert the id into the lookup. + if (reply[KNOT_CTL_IDX_ERROR] != NULL || + lookup_insert(&lookup, reply[KNOT_CTL_IDX_DATA], NULL) != KNOT_EOK) { + goto id_lookup_finish; + } + } + + lookup_complete(&lookup, str, str_len, el, add_space); + +id_lookup_finish: + lookup_deinit(&lookup); + unset_ctl(ctl); +} + +static void list_lookup(EditLine *el, const char *section, const char *item) +{ + const cmd_desc_t *desc = cmd_table; + while (desc->name != NULL && desc->cmd != CTL_CONF_LIST) { + desc++; + } + assert(desc->name != NULL); + + knot_ctl_data_t query = { + [KNOT_CTL_IDX_CMD] = ctl_cmd_to_str(desc->cmd), + [KNOT_CTL_IDX_SECTION] = section + }; + + lookup_t lookup; + knot_ctl_t *ctl = NULL; + + if (set_ctl(&ctl, desc, ¶ms) != KNOT_EOK || + knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &query) != KNOT_EOK || + knot_ctl_send(ctl, KNOT_CTL_TYPE_BLOCK, NULL) != KNOT_EOK || + lookup_init(&lookup) != KNOT_EOK) { + unset_ctl(ctl); + return; + } + + while (true) { + knot_ctl_type_t type; + knot_ctl_data_t reply; + + // Receive one section/item name. + if (knot_ctl_receive(ctl, &type, &reply) != KNOT_EOK) { + goto list_lookup_finish; + } + + // Stop if finished transfer. + if (type != KNOT_CTL_TYPE_DATA) { + break; + } + + const char *str = (section == NULL) ? reply[KNOT_CTL_IDX_SECTION] : + reply[KNOT_CTL_IDX_ITEM]; + + // Insert the name into the lookup. + if (reply[KNOT_CTL_IDX_ERROR] != NULL || + lookup_insert(&lookup, str, NULL) != KNOT_EOK) { + goto list_lookup_finish; + } + } + + lookup_complete(&lookup, item, strlen(item), el, section != NULL); + +list_lookup_finish: + lookup_deinit(&lookup); + unset_ctl(ctl); +} + +static void item_lookup(EditLine *el, const char *str, const cmd_desc_t *cmd_desc) +{ + // List all sections. + if (str == NULL) { + list_lookup(el, NULL, ""); + return; + } + + // Check for id specification. + char *id = (strchr(str, '[')); + if (id != NULL) { + char *section = strndup(str, id - str); + + // Check for completed id specification. + char *id_stop = (strchr(id, ']')); + if (id_stop != NULL) { + // Complete the item name. + if (*(id_stop + 1) == '.') { + list_lookup(el, section, id_stop + 2); + } + } else { + // Complete the section id. + id_lookup(el, id + 1, strlen(id + 1), cmd_desc, section, false); + } + + free(section); + } else { + // Check for item specification. + char *dot = (strchr(str, '.')); + if (dot != NULL) { + // Complete the item name. + char *section = strndup(str, dot - str); + list_lookup(el, section, dot + 1); + free(section); + } else { + // Complete the section name. + list_lookup(el, NULL, str); + } + } +} + +static unsigned char complete(EditLine *el, int ch) +{ + int argc, token, pos; + const char **argv; + + const LineInfo *li = el_line(el); + Tokenizer *tok = tok_init(NULL); + + // Parse the line. + int ret = tok_line(tok, li, &argc, &argv, &token, &pos); + if (ret != 0) { + goto complete_exit; + } + + // Show possible commands. + if (argc == 0) { + print_commands(); + goto complete_exit; + } + + // Complete the command name. + if (token == 0) { + cmds_lookup(el, argv[0], pos); + goto complete_exit; + } + + // Find the command descriptor. + const cmd_desc_t *desc = cmd_table; + while (desc->name != NULL && strcmp(desc->name, argv[0]) != 0) { + desc++; + } + if (desc->name == NULL) { + goto complete_exit; + } + + // Finish if a command with no or unsupported arguments. + if (desc->flags == CMD_FNONE || desc->flags == CMD_FREAD || + desc->flags == CMD_FWRITE) { + goto complete_exit; + } + + ret = set_config(desc, ¶ms); + if (ret != KNOT_EOK) { + goto complete_exit; + } + + // Complete the zone name. + if (desc->flags & (CMD_FREQ_ZONE | CMD_FOPT_ZONE)) { + if (token > 1 && !(desc->flags & CMD_FOPT_ZONE)) { + goto complete_exit; + } + + if (desc->flags & CMD_FREAD) { + local_zones_lookup(el, argv[token], pos); + } else { + id_lookup(el, argv[token], pos, desc, "zone", true); + } + goto complete_exit; + // Complete the section/id/item name. + } else if (desc->flags & (CMD_FOPT_ITEM | CMD_FREQ_ITEM)) { + if (token == 1) { + item_lookup(el, argv[1], desc); + } + goto complete_exit; + } + +complete_exit: + conf_update(NULL, CONF_UPD_FNONE); + tok_reset(tok); + tok_end(tok); + + return CC_REDISPLAY; +} + +static char *prompt(EditLine *el) +{ + return PROGRAM_NAME"> "; +} + +int interactive_loop(params_t *process_params) +{ + char *hist_file = NULL; + const char *home = getenv("HOME"); + if (home != NULL) { + hist_file = sprintf_alloc("%s/%s", home, HISTORY_FILE); + } + if (hist_file == NULL) { + log_notice("failed to get home directory"); + } + + EditLine *el = el_init(PROGRAM_NAME, stdin, stdout, stderr); + if (el == NULL) { + log_error("interactive mode not available"); + free(hist_file); + return KNOT_ERROR; + } + + History *hist = history_init(); + if (hist == NULL) { + log_error("interactive mode not available"); + el_end(el); + free(hist_file); + return KNOT_ERROR; + } + + HistEvent hev = { 0 }; + history(hist, &hev, H_SETSIZE, 1000); + history(hist, &hev, H_SETUNIQUE, 1); + el_set(el, EL_HIST, history, hist); + history(hist, &hev, H_LOAD, hist_file); + + el_set(el, EL_TERMINAL, NULL); + el_set(el, EL_EDITOR, "emacs"); + el_set(el, EL_PROMPT, prompt); + el_set(el, EL_SIGNAL, 1); + el_source(el, NULL); + + el_set(el, EL_ADDFN, PROGRAM_NAME"-complete", + "Perform "PROGRAM_NAME" completion.", complete); + el_set(el, EL_BIND, "^I", PROGRAM_NAME"-complete", NULL); + + int count; + const char *line; + while ((line = el_gets(el, &count)) != NULL && count > 0) { + Tokenizer *tok = tok_init(NULL); + + // Tokenize the current line. + int argc; + const char **argv; + const LineInfo *li = el_line(el); + int ret = tok_line(tok, li, &argc, &argv, NULL, NULL); + if (ret != 0 || argc == 0) { + continue; + } + + history(hist, &hev, H_ENTER, line); + history(hist, &hev, H_SAVE, hist_file); + + // Process the command. + ret = process_cmd(argc, argv, process_params); + + tok_reset(tok); + tok_end(tok); + + // Check for the exit command. + if (ret == KNOT_CTL_ESTOP) { + break; + } + } + + history_end(hist); + free(hist_file); + + el_end(el); + + return KNOT_EOK; +} |