/* * * 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 <string.h> #include <getopt.h> #include <ell/ell.h> #include <readline/readline.h> #include "client/command.h" #include "client/display.h" static struct l_queue *command_families; static struct l_queue *command_options; static int exit_status; static bool interactive_mode; static struct command_noninteractive { char **argv; int argc; } command_noninteractive; struct command_option { const char *name; char *value; }; static void command_options_destroy(void *data) { struct command_option *option = data; l_free(option->value); l_free(option); } bool command_option_get(const char *name, const char **value_out) { const struct l_queue_entry *entry; for (entry = l_queue_get_entries(command_options); entry; entry = entry->next) { const struct command_option *option = entry->data; if (strcmp(option->name, name)) continue; if (value_out) *value_out = option->value; return true; } return false; } bool command_needs_no_agent(void) { return command_option_get(COMMAND_OPTION_DONTASK, NULL) && (l_queue_length(command_options) == 1); } static enum cmd_status cmd_version(const char *entity, char **argv, int argc) { display("IWD version %s\n", VERSION); return CMD_STATUS_DONE; } static enum cmd_status cmd_quit(const char *entity, char **argv, int argc) { display_quit(); l_main_quit(); return CMD_STATUS_DONE; } static const struct command misc_commands[] = { { NULL, "version", NULL, cmd_version, "Display version" }, { NULL, "quit", NULL, cmd_quit, "Quit program" }, { NULL, "exit", NULL, cmd_quit }, { NULL, "help" }, { } }; static char *cmd_generator(const char *text, int state) { static const struct l_queue_entry *entry; static size_t index; static size_t len; const char *cmd; if (!state) { len = strlen(text); index = 0; entry = l_queue_get_entries(command_families); } while (entry) { const struct command_family *family = entry->data; entry = entry->next; if (strncmp(family->name, text, len)) continue; return l_strdup(family->name); } while ((cmd = misc_commands[index].cmd)) { index++; if (strncmp(cmd, text, len)) continue; return l_strdup(cmd); } return NULL; } static bool cmd_completion_cmd_has_arg(const char *cmd, const struct command_family *family) { size_t i; char **matches = NULL; bool status; for (i = 0; family->command_list[i].cmd; i++) { if (strcmp(family->command_list[i].cmd, cmd)) continue; return family->command_list[i].arg ? true : false; } if (!family->family_arg_completion) return false; matches = rl_completion_matches(cmd, family->family_arg_completion); if (!matches) return false; status = false; for (i = 0; matches[i]; i++) { if (strcmp(matches[i], cmd)) continue; status = true; break; } l_strfreev(matches); return status; } static bool find_next_token(int *i, const char *token, int token_len) { char *line = rl_line_buffer; while (*i && line[*i] == ' ') (*i)--; while (*i && line[*i] != ' ') (*i)--; return !strncmp(line + (line[*i] == ' ' ? *i + 1 : *i), token, token_len); } bool command_line_find_token(const char *token, uint8_t num_to_inspect) { int i = rl_point - 1; int len = strlen(token); if (!len) return false; while (i && num_to_inspect) { if (find_next_token(&i, token, len)) return true; num_to_inspect--; } return false; } /* * Work around readline limitations of not being able to pass a context pointer * to match functions. Set the command match function/entity to these globals * and call a generic match function which can call the _real_ match function * and include the entity. */ static command_completion_func_t cmd_current_completion_func = NULL; static const char *cmd_current_entity = NULL; static char *cmd_completion_generic(const char *text, int state) { return cmd_current_completion_func(text, state, cmd_current_entity); } static char **cmd_completion_match_entity_cmd(const char *cmd, const char *text, const struct command *cmd_list) { char **matches = NULL; size_t i; char *family = NULL; char *entity = NULL; char *prompt = NULL; for (i = 0; cmd_list[i].cmd; i++) { char *tmp; if (strcmp(cmd_list[i].cmd, cmd)) continue; if (!cmd_list[i].completion) break; if (cmd_list[i].entity) { prompt = rl_copy_text(0, rl_end); family = strtok_r(prompt, " ", &tmp); if (!family) goto done; entity = strtok_r(NULL, " ", &tmp); } done: cmd_current_completion_func = cmd_list[i].completion; cmd_current_entity = entity; matches = rl_completion_matches(text, cmd_completion_generic); l_free(prompt); cmd_current_completion_func = NULL; cmd_current_entity = NULL; break; } return matches; } static char **cmd_completion_match_family_cmd(const char *cmd_family, char *args, const char *text, bool ends_with_space) { const struct l_queue_entry *entry; const char *arg1; const char *arg2; const char *arg3; char **matches = NULL; for (entry = l_queue_get_entries(command_families); entry; entry = entry->next) { const struct command_family *family = entry->data; if (strcmp(family->name, cmd_family)) continue; arg1 = strtok_r(NULL, " ", &args); if (!arg1) { if (!family->family_arg_completion) break; matches = rl_completion_matches(text, family->family_arg_completion); break; } arg2 = strtok_r(NULL, " ", &args); if (!arg2 && !ends_with_space) { if (!family->family_arg_completion) break; matches = rl_completion_matches(text, family->family_arg_completion); break; } else if (!arg2 && ends_with_space) { if (!cmd_completion_cmd_has_arg(arg1, family)) break; if (!family->entity_arg_completion) break; matches = rl_completion_matches(text, family->entity_arg_completion); break; } arg3 = strtok_r(NULL, " ", &args); if (!arg3 && !ends_with_space) { if (!family->entity_arg_completion) break; matches = rl_completion_matches(text, family->entity_arg_completion); break; } matches = cmd_completion_match_entity_cmd(arg2, text, family->command_list); break; } return matches; } char **command_completion(const char *text, int start, int end) { char **matches = NULL; const char *family; char *args = NULL; char *prompt = NULL; bool ends_with_space = false; if (display_agent_is_active()) { rl_attempted_completion_over = 1; return NULL; } if (!start) { matches = rl_completion_matches(text, cmd_generator); goto done; } prompt = rl_copy_text(0, rl_end); family = strtok_r(prompt, " ", &args); if (!family) goto done; if (args) { int len = strlen(args); if (len > 0 && args[len - 1] == ' ') ends_with_space = true; } matches = cmd_completion_match_family_cmd(family, args, text, ends_with_space); done: l_free(prompt); if (!matches) rl_attempted_completion_over = 1; return matches; } char *command_entity_arg_completion(const char *text, int state, const struct command *command_list) { static size_t index; static size_t len; const char *cmd; if (!state) { index = 0; len = strlen(text); } while ((cmd = command_list[index].cmd)) { if (!command_list[index++].entity) continue; if (strncmp(cmd, text, len)) continue; return l_strdup(cmd); } return NULL; } static void execute_cmd(const char *family, const char *entity, const struct command *cmd, char **argv, int argc) { enum cmd_status status; display_refresh_set_cmd(family, entity, cmd, argv, argc); status = cmd->function(entity, argv, argc); if (status != CMD_STATUS_TRIGGERED && status != CMD_STATUS_DONE) goto error; if (status == CMD_STATUS_DONE && !interactive_mode) { l_main_quit(); return; } if (!interactive_mode) return; if (cmd->refreshable) display_refresh_timeout_set(); return; error: switch (status) { case CMD_STATUS_INVALID_ARGS: display("Invalid command. Use the following pattern:\n"); display_command_line(family, cmd); break; case CMD_STATUS_INVALID_VALUE: break; case CMD_STATUS_UNSUPPORTED: display_refresh_reset(); display("Unsupported command\n"); break; case CMD_STATUS_FAILED: goto failure; case CMD_STATUS_TRIGGERED: case CMD_STATUS_DONE: l_error("Unknown command status."); break; } if (interactive_mode) return; failure: exit_status = EXIT_FAILURE; l_main_quit(); } static bool match_cmd(const char *family, const char *param, char **argv, int argc, const struct command *command_list) { size_t i; for (i = 0; command_list[i].cmd; i++) { const char *entity; const char *cmd; int offset; if (command_list[i].entity) { if (argc < 1) continue; entity = param; cmd = argv[0]; offset = 1; } else { entity = NULL; cmd = param; offset = 0; } if (strcmp(command_list[i].cmd, cmd)) continue; if (!command_list[i].function) return false; execute_cmd(family, entity, &command_list[i], argv + offset, argc - offset); return true; } return false; } static bool match_cmd_family(char **argv, int argc) { const struct l_queue_entry *entry; if (argc < 2) return false; for (entry = l_queue_get_entries(command_families); entry; entry = entry->next) { const struct command_family *family = entry->data; if (strcmp(family->name, argv[0])) continue; return match_cmd(family->name, argv[1], argv + 2, argc - 2, family->command_list); } return false; } static void list_commands(const char *command_family, const struct command *cmd_list) { size_t i; for (i = 0; cmd_list[i].cmd; i++) { if (!cmd_list[i].desc) continue; display_command_line(command_family, &cmd_list[i]); } } static void list_cmd_options(void) { display(MARGIN "--%-*s %s\n", 48, COMMAND_OPTION_USERNAME, "Provide username"); display(MARGIN "--%-*s %s\n", 48, COMMAND_OPTION_PASSWORD, "Provide password"); display(MARGIN "--%-*s %s\n", 48, COMMAND_OPTION_PASSPHRASE, "Provide passphrase"); display(MARGIN "--%-*s %s\n", 48, COMMAND_OPTION_DONTASK, "Don't ask for missing\n" "\t\t\t\t\t\t credentials"); display(MARGIN "--%-*s %s\n", 48, "help", "Display help"); } static void list_cmd_families(void) { const struct l_queue_entry *entry; for (entry = l_queue_get_entries(command_families); entry; entry = entry->next) { const struct command_family *family = entry->data; display("\n%s:\n", family->caption); list_commands(family->name, family->command_list); } } static void command_display_help(void) { display("\n"); display_table_header("iwctl version " VERSION, MARGIN "%-*s", 5, "Usage"); display(MARGIN "iwctl [--options] [commands]\n"); display_table_footer(); display_table_header("Available options", MARGIN "%-*s %-*s", 50, "Options", 28, "Description"); list_cmd_options(); display_table_footer(); display_table_header("Available commands", MARGIN "%-*s %-*s", 50, "Commands", 28, "Description"); list_cmd_families(); display_table_footer(); if (!interactive_mode) return; display("\nMiscellaneous:\n"); list_commands(NULL, misc_commands); } static bool command_match_misc_commands(char **argv, int argc) { if (match_cmd(NULL, argv[0], argv + 1, argc - 1, misc_commands)) return true; if (strcmp(argv[0], "help")) return false; command_display_help(); return true; } void command_process_prompt(char **argv, int argc) { if (argc == 0) return; if (match_cmd_family(argv, argc)) return; if (!interactive_mode) { if (command_match_misc_commands(argv, argc)) { exit_status = EXIT_SUCCESS; goto quit; } display_error("Invalid command\n"); exit_status = EXIT_FAILURE; quit: l_main_quit(); return; } display_refresh_reset(); if (command_match_misc_commands(argv, argc)) return; display_error("Invalid command\n"); } void command_noninteractive_trigger(void) { if (!command_noninteractive.argc) return; command_process_prompt(command_noninteractive.argv, command_noninteractive.argc); } bool command_is_interactive_mode(void) { return interactive_mode; } void command_set_exit_status(int status) { exit_status = status; } int command_get_exit_status(void) { return exit_status; } void command_family_register(const struct command_family *family) { l_queue_push_tail(command_families, (void *) family); } void command_family_unregister(const struct command_family *family) { l_queue_remove(command_families, (void *) family); } static const struct option command_opts[] = { { COMMAND_OPTION_USERNAME, required_argument, NULL, 'u' }, { COMMAND_OPTION_PASSWORD, required_argument, NULL, 'p' }, { COMMAND_OPTION_PASSPHRASE, required_argument, NULL, 'P' }, { COMMAND_OPTION_DONTASK, no_argument, NULL, 'd' }, { "help", no_argument, NULL, 'h' }, { } }; extern struct command_family_desc __start___command[]; extern struct command_family_desc __stop___command[]; bool command_init(char **argv, int argc) { struct command_family_desc *desc; int opt; command_families = l_queue_new(); command_options = l_queue_new(); for (desc = __start___command; desc < __stop___command; desc++) { if (!desc->init) continue; desc->init(); } for (;;) { struct command_option *option; opt = getopt_long(argc, argv, "u:p:P:dh", command_opts, NULL); switch (opt) { case 'u': option = l_new(struct command_option, 1); option->name = COMMAND_OPTION_USERNAME; option->value = l_strdup(optarg); l_queue_push_tail(command_options, option); break; case 'p': option = l_new(struct command_option, 1); option->name = COMMAND_OPTION_PASSWORD; option->value = l_strdup(optarg); l_queue_push_tail(command_options, option); break; case 'P': option = l_new(struct command_option, 1); option->name = COMMAND_OPTION_PASSPHRASE; option->value = l_strdup(optarg); l_queue_push_tail(command_options, option); break; case 'd': option = l_new(struct command_option, 1); option->name = COMMAND_OPTION_DONTASK; l_queue_push_tail(command_options, option); break; case 'h': command_display_help(); l_main_quit(); return true; case -1: goto options_parsed; case '?': exit_status = EXIT_FAILURE; return true; } } options_parsed: argv += optind; argc -= optind; if (argc < 1) { interactive_mode = true; return false; } command_noninteractive.argv = argv; command_noninteractive.argc = argc; return false; } void command_exit(void) { struct command_family_desc *desc; for (desc = __start___command; desc < __stop___command; desc++) { if (!desc->exit) continue; desc->exit(); } l_queue_destroy(command_families, NULL); command_families = NULL; l_queue_destroy(command_options, command_options_destroy); command_options = NULL; }