From 11a1f475631534797aab63d6ad34d3990f0f9e70 Mon Sep 17 00:00:00 2001 From: Henrique de Carvalho Date: Fri, 8 Mar 2024 17:28:19 -0300 Subject: [PATCH] [#403][#410] Improve error messages for pgagroal-cli This commit improves the error messages for pgagroal-cli and pgagroal-admin commands by modifying the `parse_command` function. `parse_command` now involves the interpretation of a `command_table` of `struct pgagroal_command`, defined in each cli.c and admin.c files. The `struct pgagroal_command` holds, beyond other things, the command, the subcommand and the accepted count of arguments. With this information, `parse_command` is now able to display error messages when (a) the typed command is invalid, (b) the typed command requires a subcommand, (c) the typed subcommand is invalid, or when, (d) for the typed command, there are too few or too many arguments. Now adding a command with the same invoking structure as the others (i.e., " [subcommand] [arg] [arg] ...") requires inserting an entry in the `command_table` by filling the `struct pgagroal_command`. --- src/admin.c | 204 ++++++++----- src/cli.c | 647 +++++++++++++++++++++++++++------------- src/include/utils.h | 160 ++++------ src/libpgagroal/utils.c | 160 +++++----- 4 files changed, 705 insertions(+), 466 deletions(-) diff --git a/src/admin.c b/src/admin.c index 91205fad..93678cae 100644 --- a/src/admin.c +++ b/src/admin.c @@ -68,6 +68,94 @@ static int remove_user(char* users_path, char* username); static int list_users(char* users_path); static char* generate_password(int pwd_length); +const struct pgagroal_command command_table[] = +{ + { + .command = "master-key", + .subcommand = "", + .accepted_argument_count = {0}, + .deprecated = false, + .action = ACTION_MASTER_KEY, + .log_message = "", + }, + { + .command = "user", + .subcommand = "add", + .accepted_argument_count = {0}, + .deprecated = false, + .action = ACTION_ADD_USER, + .log_message = " [%s]", + }, + { + .command = "user", + .subcommand = "edit", + .accepted_argument_count = {0}, + .deprecated = false, + .action = ACTION_UPDATE_USER, + .log_message = " [%s]", + }, + { + .command = "user", + .subcommand = "del", + .accepted_argument_count = {0}, + .deprecated = false, + .action = ACTION_REMOVE_USER, + .log_message = " [%s]", + }, + { + .command = "user", + .subcommand = "ls", + .accepted_argument_count = {0}, + .deprecated = false, + .action = ACTION_LIST_USERS, + .log_message = "", + }, + { + .command = "add-user", + .subcommand = "", + .accepted_argument_count = {0}, + .deprecated = true, + .action = ACTION_ADD_USER, + .log_message = " [%s]", + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "user add", + }, + { + .command = "update-user", + .subcommand = "", + .accepted_argument_count = {0}, + .deprecated = true, + .action = ACTION_UPDATE_USER, + .log_message = " [%s]", + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "user edit", + }, + { + .command = "remove-user", + .subcommand = "", + .accepted_argument_count = {0}, + .deprecated = true, + .action = ACTION_REMOVE_USER, + .log_message = "", + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "user del", + }, + { + .command = "list-users", + .subcommand = "", + .accepted_argument_count = {0}, + .deprecated = true, + .action = ACTION_LIST_USERS, + .log_message = "", + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "user ls", + }, +}; + static void version(void) { @@ -117,7 +205,8 @@ main(int argc, char** argv) bool generate_pwd = false; int pwd_length = DEFAULT_PASSWORD_LENGTH; int option_index = 0; - int32_t action = ACTION_UNKNOWN; + size_t command_count = sizeof(command_table) / sizeof(struct pgagroal_command); + struct pgagroal_parsed_command parsed = {.cmd = NULL, .args = {0}}; while (1) { @@ -174,96 +263,67 @@ main(int argc, char** argv) errx(1, "Using the root account is not allowed"); } - if (argc > 0) + if (!parse_command(argc, argv, optind, &parsed, command_table, command_count)) { - if (!strcmp("master-key", argv[argc - 1])) - { - action = ACTION_MASTER_KEY; - } - else if (parse_command_simple(argc, argv, optind, "user", "add") - || parse_deprecated_command(argc, argv, optind, "add-user", NULL, "user add", 1, 6)) - { - action = ACTION_ADD_USER; - } - else if (parse_command_simple(argc, argv, optind, "user", "edit") - || parse_deprecated_command(argc, argv, optind, "update-user", NULL, "user edit", 1, 6)) - { - action = ACTION_UPDATE_USER; - } - else if (parse_command_simple(argc, argv, optind, "user", "del") - || parse_deprecated_command(argc, argv, optind, "remove-user", NULL, "user del", 1, 6)) - { - action = ACTION_REMOVE_USER; - } - else if (parse_command_simple(argc, argv, optind, "user", "ls") - || parse_deprecated_command(argc, argv, optind, "list-users", NULL, "user ls", 1, 6)) - { - action = ACTION_LIST_USERS; - } + usage(); + goto error; + } - // exit immediatly if the action is not understood! - if (action == ACTION_UNKNOWN) - { - warnx("unknown command or subcommand <%s>", argv[optind]); - usage(); - goto error; - } + // if here, the action is understood, but we need + // the file to operate onto! + // Therefore, if the user did not specify any config file + // the default one is used. Note that in the case of ACTION_MASTER_KEY + // there is no need for the file_path to be set, so setting to a default + // value does nothing. + // Setting the file also means we don't have to check against the file_path value. + if (file_path == NULL) + { + file_path = PGAGROAL_DEFAULT_USERS_FILE; + } - // if here, the action is understood, but we need - // the file to oeprate onto! - // Therefore, if the user did not specify any config file - // the default one is used. Note that in the case of ACTION_MASTER_KEY - // there is no need for the file_path to be set, so setting to a default - // value does nothing. - // Setting the file also means we don't have to check against the file_path value. - if (file_path == NULL) + if (parsed.cmd->action == ACTION_MASTER_KEY) + { + if (master_key(password, generate_pwd, pwd_length)) { - file_path = PGAGROAL_DEFAULT_USERS_FILE; + errx(1, "Error for master key"); } - - if (action == ACTION_MASTER_KEY) + } + else if (parsed.cmd->action == ACTION_ADD_USER) + { + if (add_user(file_path, username, password, generate_pwd, pwd_length)) { - if (master_key(password, generate_pwd, pwd_length)) - { - errx(1, "Error for master key"); - } + errx(1, "Error for "); } - else if (action == ACTION_ADD_USER) + } + else if (parsed.cmd->action == ACTION_UPDATE_USER) + { + if (update_user(file_path, username, password, generate_pwd, pwd_length)) { - if (add_user(file_path, username, password, generate_pwd, pwd_length)) - { - errx(1, "Error for "); - } + errx(1, "Error for "); } - else if (action == ACTION_UPDATE_USER) + } + else if (parsed.cmd->action == ACTION_REMOVE_USER) + { + + if (remove_user(file_path, username)) { - if (update_user(file_path, username, password, generate_pwd, pwd_length)) - { - errx(1, "Error for "); - } + errx(1, "Error for "); } - else if (action == ACTION_REMOVE_USER) - { + } + else if (parsed.cmd->action == ACTION_LIST_USERS) + { - if (remove_user(file_path, username)) - { - errx(1, "Error for "); - } - } - else if (action == ACTION_LIST_USERS) + if (list_users(file_path)) { - - if (list_users(file_path)) - { - errx(1, "Error for "); - } - + errx(1, "Error for "); } + } exit(0); error: + exit(1); } diff --git a/src/cli.c b/src/cli.c index 8d7a6ca1..0c0e6988 100644 --- a/src/cli.c +++ b/src/cli.c @@ -85,6 +85,338 @@ static int config_ls(SSL* ssl, int socket, char output_format); static int config_get(SSL* ssl, int socket, char* config_key, bool verbose, char output_format); static int config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verbose, char output_format); +const struct pgagroal_command command_table[] = { + { + .command = "flush", + .subcommand = "", + .accepted_argument_count = {0, 1}, + .action = ACTION_FLUSH, + .mode = FLUSH_GRACEFULLY, + .default_argument = "*", + .deprecated = false, + .log_message = " [%s]", + }, + { + .command = "ping", + .subcommand = "", + .accepted_argument_count = {0}, + .action = ACTION_ISALIVE, + .deprecated = false, + .log_message = "" + }, + { + .command = "enable", + .subcommand = "", + .accepted_argument_count = {0, 1}, + .action = ACTION_ENABLEDB, + .default_argument = "*", + .deprecated = false, + .log_message = " [%s]", + }, + { + .command = "disable", + .subcommand = "", + .accepted_argument_count = {0, 1}, + .action = ACTION_DISABLEDB, + .default_argument = "*", + .deprecated = false, + .log_message = " [%s]", + }, + { + .command = "shutdown", + .subcommand = "", + .accepted_argument_count = {0}, + .action = ACTION_GRACEFULLY, + .deprecated = false, + .log_message = "" + }, + { + .command = "status", + .subcommand = "", + .accepted_argument_count = {0}, + .action = ACTION_STATUS, + .deprecated = false, + .log_message = "" + }, + { + .command = "switch-to", + .subcommand = "", + .accepted_argument_count = {1}, + .action = ACTION_SWITCH_TO, + .deprecated = false, + .log_message = " [%s]" + }, + { + .command = "clear", + .subcommand = "", + .accepted_argument_count = {1}, + .action = ACTION_RESET_SERVER, + .deprecated = false, + .log_message = "", + }, + { + .command = "shutdown", + .subcommand = "gracefully", + .accepted_argument_count = {0}, + .action = ACTION_GRACEFULLY, + .deprecated = false, + .log_message = "" + }, + { + .command = "shutdown", + .subcommand = "immediate", + .accepted_argument_count = {0}, + .action = ACTION_STOP, + .deprecated = false, + .log_message = "" + }, + { + .command = "shutdown", + .subcommand = "cancel", + .accepted_argument_count = {0}, + .action = ACTION_CANCELSHUTDOWN, + .deprecated = false, + .log_message = "" + }, + { + .command = "conf", + .subcommand = "reload", + .accepted_argument_count = {0}, + .action = ACTION_RELOAD, + .deprecated = false, + .log_message = "" + }, + { + .command = "conf", + .subcommand = "get", + .accepted_argument_count = {1}, + .action = ACTION_CONFIG_GET, + .deprecated = false, + .log_message = " [%s]" + }, + { + .command = "conf", + .subcommand = "set", + .accepted_argument_count = {2}, + .action = ACTION_CONFIG_SET, + .deprecated = false, + .log_message = " [%s] = [%s]" + }, + { + .command = "conf", + .subcommand = "ls", + .accepted_argument_count = {0}, + .action = ACTION_CONFIG_LS, + .deprecated = false, + .log_message = "" + }, + { + .command = "clear", + .subcommand = "server", + .accepted_argument_count = {0, 1}, + .action = ACTION_RESET_SERVER, + .default_argument = "server", + .deprecated = false, + .log_message = " [%s]", + }, + { + .command = "flush", + .subcommand = "idle", + .accepted_argument_count = {0, 1}, + .action = ACTION_FLUSH, + .mode = FLUSH_IDLE, + .default_argument = "*", + .deprecated = false, + .log_message = " [%s]", + }, + { + .command = "flush", + .subcommand = "gracefully", + .accepted_argument_count = {0, 1}, + .action = ACTION_FLUSH, + .mode = FLUSH_GRACEFULLY, + .default_argument = "*", + .deprecated = false, + .log_message = " [%s]", + }, + { + .command = "flush", + .subcommand = "all", + .accepted_argument_count = {0, 1}, + .action = ACTION_FLUSH, + .mode = FLUSH_ALL, + .default_argument = "*", + .deprecated = false, + .log_message = " [%s]", + }, + { + .command = "clear", + .subcommand = "prometheus", + .accepted_argument_count = {0}, + .action = ACTION_RESET, + .deprecated = false, + .log_message = "" + }, + { + .command = "status", + .subcommand = "details", + .accepted_argument_count = {0}, + .action = ACTION_STATUS_DETAILS, + .deprecated = false, + .log_message = "" + }, + { + .command = "flush-idle", + .subcommand = "", + .accepted_argument_count = {0, 1}, + .action = ACTION_FLUSH, + .mode = FLUSH_IDLE, + .default_argument = "*", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "flush idle", + .log_message = " [%s]", + }, + { + .command = "flush-all", + .subcommand = "", + .accepted_argument_count = {0, 1}, + .action = ACTION_FLUSH, + .mode = FLUSH_ALL, + .default_argument = "*", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "flush all", + .log_message = " [%s]", + }, + { + .command = "flush-gracefully", + .subcommand = "", + .accepted_argument_count = {0, 1}, + .action = ACTION_FLUSH, + .mode = FLUSH_GRACEFULLY, + .default_argument = "*", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "flush gracefully", + .log_message = " [%s]", + }, + { + .command = "stop", + .subcommand = "", + .accepted_argument_count = {0}, + .action = ACTION_STOP, + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "shutdown immediate", + .log_message = "", + }, + { + .command = "cancel-shutdown", + .subcommand = "", + .accepted_argument_count = {0}, + .action = ACTION_CANCELSHUTDOWN, + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "shutdown cancel", + .log_message = "", + }, + { + .command = "gracefully", + .subcommand = "", + .accepted_argument_count = {0}, + .action = ACTION_GRACEFULLY, + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "shutdown gracefully", + .log_message = "", + }, + { + .command = "details", + .subcommand = "", + .accepted_argument_count = {0}, + .action = ACTION_STATUS_DETAILS, + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "status details", + .log_message = "", + }, + { + .command = "is-alive", + .subcommand = "", + .accepted_argument_count = {0}, + .action = ACTION_ISALIVE, + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "ping", + .log_message = "", + }, + { + .command = "reset", + .subcommand = "", + .accepted_argument_count = {0}, + .action = ACTION_RESET, + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "clear prometheus", + .log_message = "", + }, + { + .command = "reset-server", + .subcommand = "", + .accepted_argument_count = {0, 1}, + .action = ACTION_RESET_SERVER, + .default_argument = "*", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "clear server", + .log_message = "", + }, + { + .command = "reload", + .subcommand = "", + .accepted_argument_count = {0}, + .action = ACTION_RELOAD, + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "conf reload", + .log_message = "", + }, + { + .command = "config-get", + .subcommand = "", + .accepted_argument_count = {0, 1}, + .action = ACTION_CONFIG_GET, + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "conf get", + .log_message = "", + }, + { + .command = "config-set", + .subcommand = "", + .accepted_argument_count = {2}, + .action = ACTION_CONFIG_SET, + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "conf set", + .log_message = " [%s] = [%s]", + }, +}; + static void version(void) { @@ -166,17 +498,13 @@ main(int argc, char** argv) int c; int option_index = 0; size_t size; - int32_t action = ACTION_UNKNOWN; - int32_t mode = FLUSH_IDLE; - char* database = NULL; char un[MAX_USERNAME_LENGTH]; - char* server = NULL; struct configuration* config = NULL; bool remote_connection = false; long l_port; - char* config_key = NULL; /* key for a configuration setting */ - char* config_value = NULL; /* value for a configuration setting */ char output_format = COMMAND_OUTPUT_FORMAT_TEXT; + size_t command_count = sizeof(command_table) / sizeof(struct pgagroal_command); + struct pgagroal_parsed_command parsed = {.cmd = NULL, .args = {0}}; while (1) { @@ -355,272 +683,169 @@ main(int argc, char** argv) } } - if (parse_command(argc, argv, optind, "flush", "idle", &database, "*", NULL, NULL) - || parse_deprecated_command(argc, argv, optind, "flush-idle", &database, "flush idle", 1, 6)) - { - mode = FLUSH_IDLE; - action = ACTION_FLUSH; - pgagroal_log_trace("Command: [%s]", database); - } - else if (parse_command(argc, argv, optind, "flush", "all", &database, "*", NULL, NULL) - || parse_deprecated_command(argc, argv, optind, "flush-all", &database, "flush all", 1, 6)) - { - mode = FLUSH_ALL; - action = ACTION_FLUSH; - pgagroal_log_trace("Command: [%s]", database); - } - else if (parse_command(argc, argv, optind, "flush", "gracefully", &database, "*", NULL, NULL) - || parse_command(argc, argv, optind, "flush", NULL, &database, "*", NULL, NULL) - || parse_deprecated_command(argc, argv, optind, "flush-gracefully", &database, "flush", 1, 6)) - { - mode = FLUSH_GRACEFULLY; - action = ACTION_FLUSH; - pgagroal_log_trace("Command: [%s]", database); - } - else if (parse_command(argc, argv, optind, "enable", NULL, &database, "*", NULL, NULL)) - { - action = ACTION_ENABLEDB; - pgagroal_log_trace("Command: [%s]", database); - } - else if (parse_command(argc, argv, optind, "disable", NULL, &database, "*", NULL, NULL)) - { - action = ACTION_DISABLEDB; - pgagroal_log_trace("Command: [%s]", database); - } - else if (parse_command_simple(argc, argv, optind, "shutdown", "immediate") - || parse_deprecated_command(argc, argv, optind, "stop", NULL, "shutdown immediate", 1, 6)) - { - action = ACTION_STOP; - pgagroal_log_trace("Command: "); - } - else if (parse_command_simple(argc, argv, optind, "shutdown", "cancel") - || parse_deprecated_command(argc, argv, optind, "cancel-shutdown", NULL, "shutdown cancel", 1, 6)) - { - action = ACTION_CANCELSHUTDOWN; - pgagroal_log_trace("Command: "); - } - else if (parse_command_simple(argc, argv, optind, "shutdown", "gracefully") - || parse_command_simple(argc, argv, optind, "shutdown", NULL) - || parse_deprecated_command(argc, argv, optind, "gracefully", NULL, "shutdown gracefully", 1, 6)) - { - action = ACTION_GRACEFULLY; - pgagroal_log_trace("Command: "); - } - else if (parse_command_simple(argc, argv, optind, "status", "details") - || parse_deprecated_command(argc, argv, optind, "details", NULL, "status details", 1, 6)) - { - /* the 'status details' has to be parsed before the normal 'status' command !*/ - action = ACTION_STATUS_DETAILS; - pgagroal_log_trace("Command: "); - } - else if (parse_command_simple(argc, argv, optind, "status", NULL)) - { - action = ACTION_STATUS; - pgagroal_log_trace("Command: "); - } - else if (parse_command_simple(argc, argv, optind, "ping", NULL) - || parse_deprecated_command(argc, argv, optind, "is-alive", NULL, "ping", 1, 6)) + if (!parse_command(argc, argv, optind, &parsed, command_table, command_count)) { - action = ACTION_ISALIVE; - pgagroal_log_trace("Command: "); - } - else if (parse_command_simple(argc, argv, optind, "clear", "prometheus") - || parse_deprecated_command(argc, argv, optind, "reset", NULL, "clear prometheus", 1, 6)) - { - action = ACTION_RESET; - pgagroal_log_trace("Command: "); - } - else if (parse_command(argc, argv, optind, "clear", "server", &server, "\0", NULL, NULL) - || parse_command(argc, argv, optind, "clear", NULL, &server, "\0", NULL, NULL) - || parse_deprecated_command(argc, argv, optind, "reset-server", &server, "clear server", 1, 6)) - { - action = strlen(server) > 0 ? ACTION_RESET_SERVER : ACTION_UNKNOWN; - pgagroal_log_trace("Command: [%s]", server); - } - else if (parse_command(argc, argv, optind, "switch-to", NULL, &server, "\0", NULL, NULL)) - { - action = strlen(server) > 0 ? ACTION_SWITCH_TO : ACTION_UNKNOWN; - pgagroal_log_trace("Command: [%s]", server); + usage(); + goto done; } - else if (parse_command_simple(argc, argv, optind, "conf", "reload") - || parse_deprecated_command(argc, argv, optind, "reload", NULL, "conf reload", 1, 6)) + pgagroal_log_trace((char*)parsed.cmd->log_message, parsed.args[0], parsed.args[1]); + + if (!remote_connection) { - /* Local connection only */ - if (configuration_path != NULL) + /* Local connection */ + if (pgagroal_connect_unix_socket(config->unix_socket_dir, MAIN_UDS, &socket)) { - action = ACTION_RELOAD; + exit_code = 1; + goto done; } - pgagroal_log_trace("Command: "); - } - else if (parse_command(argc, argv, optind, "conf", "get", &config_key, NULL, NULL, NULL) - || parse_deprecated_command(argc, argv, optind, "config-get", NULL, "conf get", 1, 6)) - { - action = config_key != NULL && strlen(config_key) > 0 ? ACTION_CONFIG_GET : ACTION_UNKNOWN; - pgagroal_log_trace("Command: [%s]", config_key); - } - else if (parse_command(argc, argv, optind, "conf", "set", &config_key, NULL, &config_value, NULL) - || parse_deprecated_command(argc, argv, optind, "config-set", NULL, "conf set", 1, 6)) - { - // if there is no configuration key set the action to unknown, so the help screen will be printed - action = config_key != NULL && strlen(config_key) > 0 ? ACTION_CONFIG_SET : ACTION_UNKNOWN; - pgagroal_log_trace("Command: [%s] = [%s]", config_key, config_value); } - else if (parse_command_simple(argc, argv, optind, "conf", "ls")) - { - pgagroal_log_debug("Command: "); - action = ACTION_CONFIG_LS; - } - - if (action != ACTION_UNKNOWN) + else { - if (!remote_connection) - { - /* Local connection */ - if (pgagroal_connect_unix_socket(config->unix_socket_dir, MAIN_UDS, &socket)) - { - exit_code = 1; - goto done; - } - } - else + /* Remote connection */ + if (pgagroal_connect(host, atoi(port), &socket)) { /* Remote connection */ - if (pgagroal_connect(host, atoi(port), &socket)) - { - /* Remote connection */ - - l_port = strtol(port, NULL, 10); - if ((errno == ERANGE && (l_port == LONG_MAX || l_port == LONG_MIN)) || (errno != 0 && l_port == 0)) - { - warnx("Specified port %s out of range", port); - goto done; - } - - // cannot connect to port less than 1024 because pgagroal - // cannot be run as root! - if (l_port <= 1024) - { - warnx("Not allowed port %ld", l_port); - goto done; - } - - if (pgagroal_connect(host, (int)l_port, &socket)) - { - warnx("No route to host: %s:%ld\n", host, l_port); - goto done; - } + l_port = strtol(port, NULL, 10); + if ((errno == ERANGE && (l_port == LONG_MAX || l_port == LONG_MIN)) || (errno != 0 && l_port == 0)) + { + warnx("Specified port %s out of range", port); + goto done; } - /* User name */ - if (username == NULL) + // cannot connect to port less than 1024 because pgagroal + // cannot be run as root! + if (l_port <= 1024) { -username: - printf("User name: "); - - memset(&un, 0, sizeof(un)); - if (fgets(&un[0], sizeof(un), stdin) == NULL) - { - exit_code = 1; - goto done; - } - un[strlen(un) - 1] = 0; - username = &un[0]; + warnx("Not allowed port %ld", l_port); + goto done; } - if (username == NULL || strlen(username) == 0) + if (pgagroal_connect(host, (int)l_port, &socket)) { - goto username; + warnx("No route to host: %s:%ld\n", host, l_port); + goto done; } - /* Password */ - if (password == NULL) + } + + /* User name */ + if (username == NULL) + { +username: + printf("User name: "); + + memset(&un, 0, sizeof(un)); + if (fgets(&un[0], sizeof(un), stdin) == NULL) { - printf("Password : "); - password = pgagroal_get_password(); - printf("\n"); + exit_code = 1; + goto done; } + un[strlen(un) - 1] = 0; + username = &un[0]; + } - for (int i = 0; i < strlen(password); i++) - { - if ((unsigned char)(*(password + i)) & 0x80) - { + if (username == NULL || strlen(username) == 0) + { + goto username; + } - warnx("Bad credentials for %s\n", username); - goto done; - } - } + /* Password */ + if (password == NULL) + { + printf("Password : "); + password = pgagroal_get_password(); + printf("\n"); + } - /* Authenticate */ - if (pgagroal_remote_management_scram_sha256(username, password, socket, &s_ssl) != AUTH_SUCCESS) + for (int i = 0; i < strlen(password); i++) + { + if ((unsigned char)(*(password + i)) & 0x80) { - printf("pgagroal-cli: Bad credentials for %s\n", username); + + warnx("Bad credentials for %s\n", username); goto done; } } + + /* Authenticate */ + if (pgagroal_remote_management_scram_sha256(username, password, socket, &s_ssl) != AUTH_SUCCESS) + { + printf("pgagroal-cli: Bad credentials for %s\n", username); + goto done; + } } - if (action == ACTION_FLUSH) + if (parsed.cmd->action == ACTION_FLUSH) { - exit_code = flush(s_ssl, socket, mode, database); + exit_code = flush(s_ssl, socket, parsed.cmd->mode, parsed.args[0]); } - else if (action == ACTION_ENABLEDB) + else if (parsed.cmd->action == ACTION_ENABLEDB) { - exit_code = enabledb(s_ssl, socket, database); + exit_code = enabledb(s_ssl, socket, parsed.args[0]); } - else if (action == ACTION_DISABLEDB) + else if (parsed.cmd->action == ACTION_DISABLEDB) { - exit_code = disabledb(s_ssl, socket, database); + exit_code = disabledb(s_ssl, socket, parsed.args[0]); } - else if (action == ACTION_GRACEFULLY) + else if (parsed.cmd->action == ACTION_GRACEFULLY) { exit_code = gracefully(s_ssl, socket); } - else if (action == ACTION_STOP) + else if (parsed.cmd->action == ACTION_STOP) { exit_code = stop(s_ssl, socket); } - else if (action == ACTION_CANCELSHUTDOWN) + else if (parsed.cmd->action == ACTION_CANCELSHUTDOWN) { exit_code = cancel_shutdown(s_ssl, socket); } - else if (action == ACTION_STATUS) + else if (parsed.cmd->action == ACTION_STATUS) { exit_code = status(s_ssl, socket, output_format); } - else if (action == ACTION_STATUS_DETAILS) + else if (parsed.cmd->action == ACTION_STATUS_DETAILS) { exit_code = details(s_ssl, socket, output_format); } - else if (action == ACTION_ISALIVE) + else if (parsed.cmd->action == ACTION_ISALIVE) { exit_code = isalive(s_ssl, socket, output_format); } - else if (action == ACTION_RESET) + else if (parsed.cmd->action == ACTION_RESET) { exit_code = reset(s_ssl, socket); } - else if (action == ACTION_RESET_SERVER) + else if (parsed.cmd->action == ACTION_RESET_SERVER) { - exit_code = reset_server(s_ssl, socket, server); + exit_code = reset_server(s_ssl, socket, parsed.args[0]); } - else if (action == ACTION_SWITCH_TO) + else if (parsed.cmd->action == ACTION_SWITCH_TO) { - exit_code = switch_to(s_ssl, socket, server); + exit_code = switch_to(s_ssl, socket, parsed.args[0]); } - else if (action == ACTION_RELOAD) + else if (parsed.cmd->action == ACTION_RELOAD) { - exit_code = reload(s_ssl, socket); + if (configuration_path == NULL) + { + warnx("Configuration path has to specified to use "); + parsed.cmd = NULL; + goto done; + } + else + { + exit_code = reload(s_ssl, socket); + } } - else if (action == ACTION_CONFIG_GET) + else if (parsed.cmd->action == ACTION_CONFIG_GET) { - exit_code = config_get(s_ssl, socket, config_key, verbose, output_format); + exit_code = config_get(s_ssl, socket, parsed.args[0], verbose, output_format); } - else if (action == ACTION_CONFIG_SET) + else if (parsed.cmd->action == ACTION_CONFIG_SET) { - exit_code = config_set(s_ssl, socket, config_key, config_value, verbose, output_format); + exit_code = config_set(s_ssl, socket, parsed.args[0], parsed.args[1], verbose, output_format); } - else if (action == ACTION_CONFIG_LS) + else if (parsed.cmd->action == ACTION_CONFIG_LS) { exit_code = config_ls(s_ssl, socket, output_format); } @@ -642,16 +867,9 @@ main(int argc, char** argv) pgagroal_disconnect(socket); - if (action == ACTION_UNKNOWN) - { - printf("pgagroal-cli: unknown command %s\n", argv[optind]); - usage(); - exit_code = 1; - } - if (configuration_path != NULL) { - if (action != ACTION_UNKNOWN) + if (parsed.cmd != NULL) { switch (exit_code) { @@ -662,7 +880,6 @@ main(int argc, char** argv) case EXIT_STATUS_OK: break; } - } } diff --git a/src/include/utils.h b/src/include/utils.h index adbdd7f9..7c377186 100644 --- a/src/include/utils.h +++ b/src/include/utils.h @@ -47,6 +47,58 @@ struct signal_info int slot; /**< The slot */ }; +/** @struct + * Defines pgagroal commands. + * The necessary fields are marked with an ">". + * + * Fields: + * > command: The primary name of the command. + * > subcommand: The subcommand name. If there is no subcommand, it should be filled with an empty literal string. + * > accepted_argument_count: An array defining all the number of arguments this command accepts. + * Each entry represents a valid count of arguments, allowing the command to support overloads. + * - default_argument: A default value for the command argument, used when no explicit argument is provided. + * - log_message: A template string for logging command execution, which can include placeholders for dynamic values. + * > action: A value indicating the specific action. + * - mode: A value specifying the mode of operation or context in which the command applies. + * > deprecated: A flag indicating whether this command is deprecated. + * - deprecated_since_major: The major version number in which the command was deprecated. + * - deprecated_since_minor: The minor version number in which the command was deprecated. + * - deprecated_by: A string naming the command that replaces the deprecated command. + * + * This struct is key to extending and maintaining the command processing functionality in pgagroal, + * allowing for clear definition and handling of all supported commands. + */ +struct pgagroal_command +{ + const char* command; + const char* subcommand; + const int accepted_argument_count[MISC_LENGTH]; + + const int action; + const int mode; + const char* default_argument; + const char* log_message; + + /* Deprecation information */ + bool deprecated; + unsigned int deprecated_since_major; + unsigned int deprecated_since_minor; + const char* deprecated_by; +}; + +/** @struct + * Holds parsed command data. + * + * Fields: + * - cmd: A pointer to the command struct that was parsed. + * - args: An array of pointers to the parsed arguments of the command (points to argv). + */ +struct pgagroal_parsed_command +{ + const struct pgagroal_command* cmd; + char* args[MISC_LENGTH]; +}; + /** * Get the request identifier * @param msg The message @@ -386,114 +438,22 @@ pgagroal_backtrace(void); * @param argv the command line as provided to the application * @param offset the position at which the next token out of `argv` * has to be read. This is usually the `optind` set by getopt_long(). - * @param command the string to search for as a main command - * @param subcommand if not NULL, a subcommand that should be - * matched. If no matches are found with the subcommand, the - * function fails. - * - * @param key if not null, a pointer to a string that will be - * filled with the next value on the command line (usually - * the name of a database/server or a configuration parameter - * name) - * @param default_key the default value to be specified for a key - * if none is found on the command line. For example, if the key - * represents a database name, the "*" could be the default_key - * to indicate every possible database. - * - * @param value if not null, a pointer to a string that will be - * filled with the extrac value for the command. For example, in the case - * of a configuration subcommand, the value will be the setting to apply. - * - * @param default_value the default value to set on the `value` pointer - * variable if nothing is found on the command line. - * + * @param parsed an `struct pgagroal_parsed_command` to hold the parsed + * data. It is modified inside the function to be accessed outside. + * @param command_table array containing one `struct pgagroal_command` for + * every possible command. + * @param command_count number of commands in `command_table`. * @return true if the parsing of the command line was succesful, false * otherwise * - * - * Possible command lines: - * - * flush gracefully pgbench - * flush gracefully - * flush - * flush pgbench - * conf get log_level - * conf set log_level debug - * - * that in turn are match by - * - * parse_command(argv, argc, "flush", "gracefully", &database, "*", NULL, NULL) - * parse_command(argv, argc, "flush", "gracefully", NULL, "*", NULL, NULL) - * parse_command(argv, argc, "flush", NULL, NULL, "*", NULL, NULL) - * parse_command(argv, argc, "flush", NULL, &database, "*", NULL, NULL) - * parse_command(argv, argc, "conf", "get", &config_key, NULL, NULL, NULL) - * parse_command(argv, argc, "conf", "set", &config_key, NULL, &config_value, NULL) */ bool parse_command(int argc, char** argv, int offset, - char* command, - char* subcommand, - char** key, - char* default_key, - char** value, - char* default_value); - -/* - * A wrapper function to parse a single command (and its subcommand) - * without any optional argument. - * It calls the parse_command with NULL key, value and defaults. - * - * Thanks to this wrapper, it is simpler to write the command parsing because - * the two following lines are equivalent: - * - * parse_command( argc, argv, optind, "conf", "reload", NULL, NULL, NULL; NULL ); - * - * parse_command_simple( argc, argv, optind, "conf", "reload"); - * - * @see parse_command - */ -bool -parse_command_simple(int argc, - char** argv, - int offset, - char* command, - char* subcommand); - -/** - * A function to match against a deprecated command. - * It prints out a message to warn the user about - * the deprecated usage of the command if there is a specific - * "deprecated-by" and "deprecated since" set of information. - * - * - * @param argc the command line counter - * @param argv the command line as provided to the application - * @param offset the position at which the next token out of `argv` - * has to be read. This is usually the `optind` set by getopt_long(). - * @param command the string to search for as a main command - * @param deprecated_by the name of the command to use - * instead of the deprecated one - * @param value if not null, a pointer to a string that will be - * filled with the value of the database. If no database is found - * on the command line, the special value "*" will be placed to - * mean "all the database" - * @param deprecated_since_major major version since the command has been deprecated - * @param deprecated_since_minor minor version since the command has been deprecated - * - * @return true if the parsing of the command line was succesful, false - * otherwise - */ -bool -parse_deprecated_command(int argc, - char** argv, - int offset, - char* command, - char** value, - char* deprecated_by, - unsigned int deprecated_since_major, - unsigned int deprecated_since_minor); + struct pgagroal_parsed_command* parsed, + const struct pgagroal_command command_table[], + size_t command_count); /** * Given a server state, it returns a string that diff --git a/src/libpgagroal/utils.c b/src/libpgagroal/utils.c index d24064fa..040ad2d4 100644 --- a/src/libpgagroal/utils.c +++ b/src/libpgagroal/utils.c @@ -954,128 +954,130 @@ pgagroal_backtrace(void) #endif +/* Parser for pgagroal-cli commands */ bool parse_command(int argc, char** argv, int offset, - char* command, - char* subcommand, - char** key, - char* default_key, - char** value, - char* default_value) + struct pgagroal_parsed_command* parsed, + const struct pgagroal_command command_table[], + size_t command_count) { +#define EMPTY_STR(_s) (_s[0] == 0) - // sanity check: if no arguments, nothing to parse! - if (argc <= offset) + char* command = NULL; + char* subcommand = NULL; + bool command_match = false; + int default_command_match = -1; + int arg_count = -1; + int command_index = -1; + + /* Parse command, and exit if there is no match */ + if (offset < argc) { - return false; + command = argv[offset++]; } - - // first of all check if the command is the same - // as the first argument on the command line - if (strncmp(argv[offset], command, MISC_LENGTH)) + else { + warnx("A command is required\n"); return false; } - if (subcommand) + if (offset < argc) { - // thre must be a subcommand check - offset++; + subcommand = argv[offset]; + } - if (argc <= offset) + for (int i = 0; i < command_count; i++) + { + if (strncmp(command, command_table[i].command, MISC_LENGTH) == 0) { - // not enough command args! - return false; + command_match = true; + if (subcommand && strncmp(subcommand, command_table[i].subcommand, MISC_LENGTH) == 0) + { + offset++; + command_index = i; + break; + } + else if (EMPTY_STR(command_table[i].subcommand)) + { + /* Default command does not require a subcommand, might be followed by an argument */ + default_command_match = i; + } } + } - if (strncmp(argv[offset], subcommand, MISC_LENGTH)) - { - return false; - } + if (command_match == false) + { + warnx("Unknown command '%s'\n", command); + return false; } - if (key) + if (command_index == -1 && default_command_match >= 0) { - // need to evaluate the database or server or configuration key - offset++; - *key = argc > offset ? argv[offset] : default_key; - if (*key == NULL || strlen(*key) == 0) + command_index = default_command_match; + subcommand = ""; + } + else if (command_index == -1) /* Command was matched, but subcommand was not */ + { + if (subcommand) { - goto error; + warnx("Unknown subcommand '%s' for command '%s'\n", subcommand, command); } - - // do I need also a value? - if (value) + else /* User did not type a subcommand */ { - offset++; - *value = argc > offset ? argv[offset] : default_value; - - if (*value == NULL || strlen(*value) == 0) - { - goto error; - } - + warnx("Command '%s' requires a subcommand\n", command); } + return false; } - return true; + parsed->cmd = &command_table[command_index]; -error: - return false; -} - -bool -parse_deprecated_command(int argc, - char** argv, - int offset, - char* command, - char** value, - char* deprecated_by, - unsigned int deprecated_since_major, - unsigned int deprecated_since_minor) -{ - // sanity check: if no arguments, nothing to parse! - if (argc <= offset) + /* Iterate until find an accepted_arg_count that is equal or greater than the typed command arg_count */ + arg_count = argc - offset; + int j; + for (j = 0; j < MISC_LENGTH; j++) + { + if (parsed->cmd->accepted_argument_count[j] >= arg_count) + { + break; + } + } + if (arg_count < parsed->cmd->accepted_argument_count[0]) { + warnx("Too few arguments provided for command '%s%s%s'\n", command, + (command && !EMPTY_STR(subcommand)) ? " " : "", subcommand); return false; } - - // first of all check if the command is the same - // as the first argument on the command line - if (strncmp(argv[offset], command, MISC_LENGTH)) + if (j == MISC_LENGTH || arg_count > parsed->cmd->accepted_argument_count[j]) { + warnx("Too many arguments provided for command '%s%s%s'\n", command, + (command && !EMPTY_STR(subcommand)) ? " " : "", subcommand); return false; } - if (value) + /* Copy argv + offset pointers into parsed->args */ + for (int i = 0; i < arg_count; i++) { - // need to evaluate the database or server - offset++; - *value = argc > offset ? argv[offset] : "*"; + parsed->args[i] = argv[i + offset]; } + parsed->args[0] = parsed->args[0] ? parsed->args[0] : (char*) parsed->cmd->default_argument; - // warn the user if there is enough information - // about deprecation - if (deprecated_by - && pgagroal_version_ge(deprecated_since_major, deprecated_since_minor, 0)) + /* Warn the user if there is enough information about deprecation */ + if (parsed->cmd->deprecated + && pgagroal_version_ge(parsed->cmd->deprecated_since_major, + parsed->cmd->deprecated_since_minor, 0)) { warnx("command <%s> has been deprecated by <%s> since version %d.%d", - command, deprecated_by, deprecated_since_major, deprecated_since_minor); + parsed->cmd->command, + parsed->cmd->deprecated_by, + parsed->cmd->deprecated_since_major, + parsed->cmd->deprecated_since_minor); } return true; -} -bool -parse_command_simple(int argc, - char** argv, - int offset, - char* command, - char* subcommand) -{ - return parse_command(argc, argv, offset, command, subcommand, NULL, NULL, NULL, NULL); +#undef EMPTY_STR } /**