From e0cb97be04637af6d166ca959ce65ff5383d24e4 Mon Sep 17 00:00:00 2001 From: Henrique de Carvalho Date: Wed, 6 Mar 2024 19:21:53 -0300 Subject: [PATCH] [#403] Improve error messages for pgagroal-cli This commit attempts to improve the error messages for pgagroal-cli commands by modifying the parsing step. The command parsing now involves tables that guides the parsing of commands. --- src/cli.c | 644 ++++++++++++++++++++++++++++------------ src/include/pgagroal.h | 42 +-- src/include/utils.h | 75 ++++- src/libpgagroal/utils.c | 297 ++++++++++++------ 4 files changed, 747 insertions(+), 311 deletions(-) diff --git a/src/cli.c b/src/cli.c index 8d7a6ca1..938e0837 100644 --- a/src/cli.c +++ b/src/cli.c @@ -85,6 +85,373 @@ 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); +enum pgagroal_cli_command_code { + /* Command codes: add commands to the end of this list */ + COMMAND_FLUSH = 0, + COMMAND_PING, + COMMAND_ENABLE, + COMMAND_DISABLE, + COMMAND_SHUTDOWN, + COMMAND_STATUS, + COMMAND_SWITCH_TO, + COMMAND_CONF, + COMMAND_CLEAR, + + /* Deprecated command codes */ + DEPRECATED_COMMAND_FLUSH_IDLE, // "flush-idle" + DEPRECATED_COMMAND_FLUSH_ALL, // "flush-all" + DEPRECATED_COMMAND_FLUSH_GRACEFULLY, // "flush-gracefully" + DEPRECATED_COMMAND_STOP, // "stop" for "shutdown immediate" + DEPRECATED_COMMAND_CANCEL_SHUTDOWN, // "cancel-shutdown" + DEPRECATED_COMMAND_SHUTDOWN_GRACEFULLY, // "gracefully" for "shutdown gracefully" + DEPRECATED_COMMAND_STATUS_DETAILS, // "details" for "status details" + DEPRECATED_COMMAND_IS_ALIVE, // "is-alive" for "ping" + DEPRECATED_COMMAND_RESET, // "reset" for "clear prometheus" + DEPRECATED_COMMAND_RESET_SERVER, // "reset-server" for "clear server" + DEPRECATED_COMMAND_RELOAD, // "reload" for "conf reload" + DEPRECATED_COMMAND_CONFIG_GET, // "config-get" for "conf get" + DEPRECATED_COMMAND_CONFIG_SET, // "config-set" for "conf set" + + /* Subcommand codes */ + COMMAND_FLUSH_GRACEFULLY, + COMMAND_FLUSH_IDLE, + COMMAND_FLUSH_ALL, + COMMAND_SHUTDOWN_GRACEFULLY, + COMMAND_SHUTDOWN_IMMEDIATE, + COMMAND_SHUTDOWN_CANCEL, + COMMAND_STATUS_DETAILS, + COMMAND_CONF_RELOAD, + COMMAND_CONF_GET, + COMMAND_CONF_SET, + COMMAND_CONF_LS, + COMMAND_CLEAR_SERVER, + COMMAND_CLEAR_PROMETHEUS, +}; + +const struct pgagroal_command command_table[] = { + [COMMAND_FLUSH] = { + .string = "flush", + .transition_states = STATE_END | STATE_SUBCOMMAND, + .subcommands_count = 3, + .subcommands = {COMMAND_FLUSH_ALL, COMMAND_FLUSH_GRACEFULLY, COMMAND_FLUSH_IDLE}, + .deprecated = false, + .action = ACTION_FLUSH, + .mode = FLUSH_GRACEFULLY, + .log_trace = " [%s]", + .default_argument = "*", + }, + [COMMAND_PING] = { + .string = "ping", + .transition_states = STATE_END, + .subcommands_count = 0, + .deprecated = false, + .action = ACTION_ISALIVE, + .log_trace = "" + }, + [COMMAND_ENABLE] = { + .string = "enable", + .transition_states = STATE_END | STATE_ARGUMENT, + .subcommands_count = 0, + .deprecated = false, + .action = ACTION_ENABLEDB, + .log_trace = " [%s]", + .default_argument = "*", + }, + [COMMAND_DISABLE] = { + .string = "disable", + .transition_states = STATE_END | STATE_ARGUMENT, + .subcommands_count = 0, + .deprecated = false, + .action = ACTION_DISABLEDB, + .log_trace = " [%s]", + .default_argument = "*", + }, + [COMMAND_SHUTDOWN] = { + .string = "shutdown", + .transition_states = STATE_END | STATE_SUBCOMMAND, + .subcommands_count = 3, + .subcommands = {COMMAND_SHUTDOWN_IMMEDIATE, COMMAND_SHUTDOWN_CANCEL, COMMAND_SHUTDOWN_GRACEFULLY}, + .deprecated = false, + .action = ACTION_GRACEFULLY, + .log_trace = "" + }, + [COMMAND_STATUS] = { + .string = "status", + .transition_states = STATE_END | STATE_SUBCOMMAND, + .subcommands_count = 1, + .subcommands = {COMMAND_STATUS_DETAILS}, + .deprecated = false, + .action = ACTION_STATUS, + .log_trace = "" + }, + [COMMAND_SWITCH_TO] = { + .string = "switch-to", + .transition_states = STATE_ARGUMENT, + .subcommands_count = 0, + .deprecated = false, + .action = ACTION_SWITCH_TO, + .log_trace = " [%s]" + }, + [COMMAND_CONF] = { + .string = "conf", + .transition_states = STATE_SUBCOMMAND, + .subcommands_count = 4, + .subcommands = {COMMAND_CONF_GET, COMMAND_CONF_LS, COMMAND_CONF_RELOAD, COMMAND_CONF_SET}, + .deprecated = false + }, + [COMMAND_CLEAR] = { + .string = "clear", + .transition_states = STATE_SUBCOMMAND | STATE_ARGUMENT, + .subcommands_count = 2, + .subcommands = {COMMAND_CLEAR_SERVER, COMMAND_CLEAR_PROMETHEUS}, + .action = ACTION_RESET_SERVER, + .log_trace = "", + .default_argument = "server", + .deprecated = false, + }, + + /* Deprecated commands */ + [DEPRECATED_COMMAND_FLUSH_IDLE] = { + .string = "flush-idle", + .transition_states = STATE_END, + .action = ACTION_FLUSH, + .mode = FLUSH_IDLE, + .log_trace = " [%s]", + .default_argument = "*", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "flush idle", + }, + [DEPRECATED_COMMAND_FLUSH_ALL] = { + .string = "flush-all", + .transition_states = STATE_END, + .action = ACTION_FLUSH, + .mode = FLUSH_ALL, + .log_trace = " [%s]", + .default_argument = "*", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "flush all", + }, + [DEPRECATED_COMMAND_FLUSH_GRACEFULLY] = { + .string = "flush-gracefully", + .transition_states = STATE_END, + .action = ACTION_FLUSH, + .mode = FLUSH_GRACEFULLY, + .log_trace = " [%s]", + .default_argument = "*", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "flush gracefully", + }, + [DEPRECATED_COMMAND_STOP] = { + .string = "stop", + .transition_states = STATE_END, + .action = ACTION_STOP, + .log_trace = "", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "shutdown immediate" + }, + [DEPRECATED_COMMAND_CANCEL_SHUTDOWN] = { + .string = "cancel-shutdown", + .transition_states = STATE_END, + .action = ACTION_CANCELSHUTDOWN, + .log_trace = "", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "shutdown cancel" + }, + [DEPRECATED_COMMAND_SHUTDOWN_GRACEFULLY] = { + .string = "gracefully", + .transition_states = STATE_END, + .action = ACTION_GRACEFULLY, + .log_trace = "", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "shutdown gracefully" + }, + [DEPRECATED_COMMAND_STATUS_DETAILS] = { + .string = "details", + .transition_states = STATE_END, + .action = ACTION_STATUS_DETAILS, + .log_trace = "", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "status details" + }, + [DEPRECATED_COMMAND_IS_ALIVE] = { + .string = "is-alive", + .transition_states = STATE_END, + .action = ACTION_ISALIVE, + .log_trace = "", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "ping" + }, + [DEPRECATED_COMMAND_RESET] = { + .string = "reset", + .transition_states = STATE_END, + .action = ACTION_RESET, + .log_trace = "", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "clear prometheus" + }, + [DEPRECATED_COMMAND_RESET_SERVER] = { + .string = "reset-server", + .transition_states = STATE_END, + .action = ACTION_RESET_SERVER, + .log_trace = "", + .default_argument = "*", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "clear server" + }, + [DEPRECATED_COMMAND_RELOAD] = { + .string = "reload", + .transition_states = STATE_END, + .action = ACTION_RELOAD, + .log_trace = "", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "conf reload" + }, + [DEPRECATED_COMMAND_CONFIG_GET] = { + .string = "config-get", + .transition_states = STATE_ARGUMENT, + .action = ACTION_CONFIG_GET, + .log_trace = "", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "conf get" + }, + [DEPRECATED_COMMAND_CONFIG_SET] = { + .string = "config-set", + .transition_states = STATE_ARGUMENT, + .action = ACTION_CONFIG_SET, + .log_trace = " [%s] = [%s]", + .deprecated = true, + .deprecated_since_major = 1, + .deprecated_since_minor = 6, + .deprecated_by = "conf set" + } +}; + +const struct pgagroal_command subcommand_table[] = { + + [COMMAND_SHUTDOWN_GRACEFULLY] = { + .string = "gracefully", + .transition_states = STATE_END, + .deprecated = false, + .action = ACTION_GRACEFULLY, + .log_trace = "" + }, + [COMMAND_SHUTDOWN_IMMEDIATE] = { + .string = "immediate", + .transition_states = STATE_END, + .deprecated = false, + .action = ACTION_STOP, + .log_trace = "" + }, + [COMMAND_SHUTDOWN_CANCEL] = { + .string = "cancel", + .transition_states = STATE_END, + .deprecated = false, + .action = ACTION_CANCELSHUTDOWN, + .log_trace = "" + }, + [COMMAND_CONF_RELOAD] = { + .string = "reload", + .transition_states = STATE_END, + .deprecated = false, + .action = ACTION_RELOAD, + .log_trace = "" + }, + [COMMAND_CONF_GET] = { + .string = "get", + .transition_states = STATE_ARGUMENT, + .deprecated = false, + .action = ACTION_CONFIG_GET, + .log_trace = " [%s]" + }, + [COMMAND_CONF_SET] = { + .string = "set", + .transition_states = STATE_ARGUMENT, + .deprecated = false, + .action = ACTION_CONFIG_SET, + .log_trace = " [%s] = [%s]" + }, + [COMMAND_CONF_LS] = { + .string = "ls", + .transition_states = STATE_END, + .deprecated = false, + .action = ACTION_CONFIG_LS, + .log_trace = "" + }, + [COMMAND_CLEAR_SERVER] = { + .string = "server", + .transition_states = STATE_END | STATE_ARGUMENT, + .deprecated = false, + .action = ACTION_RESET_SERVER, + .log_trace = " [%s]", + .default_argument = "\0", + }, + [COMMAND_FLUSH_IDLE] = { + .string = "idle", + .transition_states = STATE_END | STATE_ARGUMENT, + .deprecated = false, + .action = ACTION_FLUSH, + .mode = FLUSH_IDLE, + .log_trace = " [%s]", + .default_argument = "*" + }, + [COMMAND_FLUSH_GRACEFULLY] = { + .string = "gracefully", + .transition_states = STATE_END | STATE_ARGUMENT, + .deprecated = false, + .action = ACTION_FLUSH, + .mode = FLUSH_GRACEFULLY, + .log_trace = " [%s]", + .default_argument = "*", + }, + [COMMAND_FLUSH_ALL] = { + .string = "all", + .transition_states = STATE_END | STATE_ARGUMENT, + .deprecated = false, + .action = ACTION_FLUSH, + .mode = FLUSH_ALL, + .log_trace = " [%s]", + .default_argument = "*" + }, + [COMMAND_CLEAR_PROMETHEUS] = { + .string = "prometheus", + .transition_states = STATE_END, + .deprecated = false, + .action = ACTION_RESET, + .log_trace = "" + }, + [COMMAND_STATUS_DETAILS] = { + .string = "details", + .transition_states = STATE_END, + .deprecated = false, + .action = ACTION_STATUS_DETAILS, + .log_trace = "" + }, +}; + static void version(void) { @@ -168,15 +535,13 @@ main(int argc, char** argv) 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; + char error_message[MISC_LENGTH] = {0}; + struct pgagroal_parsed_command cmd = {.args = {0}, .command_code = COMMAND_UNKNOWN, .subcommand_code = COMMAND_UNKNOWN, .arg_cnt = -1}; while (1) { @@ -355,222 +720,111 @@ 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)) - { - 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)) + if (!command_parser(argc, argv, optind, &cmd, error_message, command_table, subcommand_table)) { - action = ACTION_RESET; - pgagroal_log_trace("Command: "); + goto done; } - 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); - } - else if (parse_command_simple(argc, argv, optind, "conf", "reload") - || parse_deprecated_command(argc, argv, optind, "reload", NULL, "conf reload", 1, 6)) + action = cmd.action; + mode = cmd.mode; + pgagroal_log_trace(cmd.log_trace, cmd.args[0], cmd.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) { - exit_code = flush(s_ssl, socket, mode, database); + exit_code = flush(s_ssl, socket, mode, cmd.args[0]); } else if (action == ACTION_ENABLEDB) { - exit_code = enabledb(s_ssl, socket, database); + exit_code = enabledb(s_ssl, socket, cmd.args[0]); } else if (action == ACTION_DISABLEDB) { - exit_code = disabledb(s_ssl, socket, database); + exit_code = disabledb(s_ssl, socket, cmd.args[0]); } else if (action == ACTION_GRACEFULLY) { @@ -602,23 +856,31 @@ main(int argc, char** argv) } else if (action == ACTION_RESET_SERVER) { - exit_code = reset_server(s_ssl, socket, server); + exit_code = reset_server(s_ssl, socket, cmd.args[0]); } else if (action == ACTION_SWITCH_TO) { - exit_code = switch_to(s_ssl, socket, server); + exit_code = switch_to(s_ssl, socket, cmd.args[0]); } else if (action == ACTION_RELOAD) { - exit_code = reload(s_ssl, socket); + if (configuration_path == NULL) + { + sprintf(error_message, "Configuration path has to specified to use "); + action = ACTION_UNKNOWN; + } + else + { + exit_code = reload(s_ssl, socket); + } } else if (action == ACTION_CONFIG_GET) { - exit_code = config_get(s_ssl, socket, config_key, verbose, output_format); + exit_code = config_get(s_ssl, socket, cmd.args[0], verbose, output_format); } else if (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, cmd.args[0], cmd.args[1], verbose, output_format); } else if (action == ACTION_CONFIG_LS) { @@ -642,9 +904,13 @@ main(int argc, char** argv) pgagroal_disconnect(socket); + if (error_message[0]) + { + printf("%s", error_message); + } + if (action == ACTION_UNKNOWN) { - printf("pgagroal-cli: unknown command %s\n", argv[optind]); usage(); exit_code = 1; } diff --git a/src/include/pgagroal.h b/src/include/pgagroal.h index b76c72cf..77378565 100644 --- a/src/include/pgagroal.h +++ b/src/include/pgagroal.h @@ -174,14 +174,14 @@ extern "C" { #define unlikely(x) __builtin_expect (!!(x), 0) #define MAX(a, b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a > _b ? _a : _b; }) + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) #define MIN(a, b) \ - ({ __typeof__ (a) _a = (a); \ - __typeof__ (b) _b = (b); \ - _a < _b ? _a : _b; }) + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) /* * Common piece of code to perform a sleeping. @@ -194,13 +194,13 @@ extern "C" { * */ #define SLEEP(zzz) \ - do \ - { \ - struct timespec ts_private; \ - ts_private.tv_sec = 0; \ - ts_private.tv_nsec = zzz; \ - nanosleep(&ts_private, NULL); \ - } while (0); + do \ + { \ + struct timespec ts_private; \ + ts_private.tv_sec = 0; \ + ts_private.tv_nsec = zzz; \ + nanosleep(&ts_private, NULL); \ + } while (0); /* * Commonly used block of code to sleep @@ -217,14 +217,14 @@ extern "C" { SLEEP_AND_GOTO(100000L, retry) */ #define SLEEP_AND_GOTO(zzz, goto_to) \ - do \ - { \ - struct timespec ts_private; \ - ts_private.tv_sec = 0; \ - ts_private.tv_nsec = zzz; \ - nanosleep(&ts_private, NULL); \ - goto goto_to; \ - } while (0); + do \ + { \ + struct timespec ts_private; \ + ts_private.tv_sec = 0; \ + ts_private.tv_nsec = zzz; \ + nanosleep(&ts_private, NULL); \ + goto goto_to; \ + } while (0); /** * The shared memory segment diff --git a/src/include/utils.h b/src/include/utils.h index adbdd7f9..2c4677f9 100644 --- a/src/include/utils.h +++ b/src/include/utils.h @@ -375,6 +375,51 @@ pgagroal_backtrace(void); #endif +/* + * Command parsing for pgagroal-cli + */ + +#define COMMAND_UNKNOWN (-1) + +struct pgagroal_parsed_command +{ + int command_code; + int subcommand_code; + char* args[MISC_LENGTH]; + int arg_cnt; + int action; + int mode; + char* log_trace; +}; + +#define MAX_NUMBER_OF_SUBCOMMANDS 4 +#define COMMAND_COUNT 22 + +#define STATE_START 0b000001 +#define STATE_COMMAND 0b000010 +#define STATE_SUBCOMMAND 0b000100 +#define STATE_ARGUMENT 0b001000 +#define STATE_ERROR 0b010000 +#define STATE_END 0b100000 + +struct pgagroal_command +{ + const char* string; + int transition_states; + int subcommands_count; + int subcommands[MAX_NUMBER_OF_SUBCOMMANDS]; + char* default_argument; + char* log_trace; + int action; + int mode; + + /* Deprecation information */ + bool deprecated; + unsigned int deprecated_since_major; + unsigned int deprecated_since_minor; + const char* deprecated_by; +}; + /** * Utility function to parse the command line * and search for a command. @@ -422,23 +467,21 @@ pgagroal_backtrace(void); * * 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) + * command_parser(argv, argc, "flush", "gracefully", &database, "*", NULL, NULL) + * command_parser(argv, argc, "flush", "gracefully", NULL, "*", NULL, NULL) + * command_parser(argv, argc, "flush", NULL, NULL, "*", NULL, NULL) + * command_parser(argv, argc, "flush", NULL, &database, "*", NULL, NULL) + * command_parser(argv, argc, "conf", "get", &config_key, NULL, NULL, NULL) + * command_parser(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); +command_parser(int argc, + char** argv, + int offset, + struct pgagroal_parsed_command* cmd, + char error_message[MISC_LENGTH], + const struct pgagroal_command command_table[], + const struct pgagroal_command subcommand_table[]); /* * A wrapper function to parse a single command (and its subcommand) @@ -452,7 +495,7 @@ parse_command(int argc, * * parse_command_simple( argc, argv, optind, "conf", "reload"); * - * @see parse_command + * @see command_parser */ bool parse_command_simple(int argc, diff --git a/src/libpgagroal/utils.c b/src/libpgagroal/utils.c index d24064fa..999801b5 100644 --- a/src/libpgagroal/utils.c +++ b/src/libpgagroal/utils.c @@ -45,6 +45,7 @@ #include #include #include +#include #ifndef EVBACKEND_LINUXAIO #define EVBACKEND_LINUXAIO 0x00000040U @@ -954,115 +955,228 @@ pgagroal_backtrace(void) #endif -bool -parse_command(int argc, - char** argv, - int offset, - char* command, - char* subcommand, - char** key, - char* default_key, - char** value, - char* default_value) -{ - - // sanity check: if no arguments, nothing to parse! - if (argc <= offset) - { - 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)) - { - return false; - } +/* Parser for pgagroal-cli commands */ - if (subcommand) +void +parse_command(const char* command, + int* command_code, + const struct pgagroal_command command_table[]) +{ + *command_code = COMMAND_UNKNOWN; + for (int command_index = 0; command_index < COMMAND_COUNT; command_index++) { - // thre must be a subcommand check - offset++; - - if (argc <= offset) - { - // not enough command args! - return false; - } - - if (strncmp(argv[offset], subcommand, MISC_LENGTH)) + if (strncmp(command, command_table[command_index].string, MISC_LENGTH) == 0) { - return false; + *command_code = command_index; + break; } } +} - if (key) +void +parse_subcommand(const char* subcommand, + int command_code, + int* subcommand_code, + const struct pgagroal_command command_table[], + const struct pgagroal_command subcommand_table[]) +{ + int subcommands_count = command_table[command_code].subcommands_count; + const int* subcommands = &(command_table[command_code].subcommands[0]); + for (int subcommand_index = 0; subcommand_index < subcommands_count; subcommand_index++) { - // need to evaluate the database or server or configuration key - offset++; - *key = argc > offset ? argv[offset] : default_key; - if (*key == NULL || strlen(*key) == 0) + if (strncmp(subcommand, subcommand_table[subcommands[subcommand_index]].string, MISC_LENGTH) == 0) { - goto error; + *subcommand_code = subcommands[subcommand_index]; + break; } + } +} - // do I need also a value? - if (value) - { - offset++; - *value = argc > offset ? argv[offset] : default_value; +bool +command_parser(int argc, + char** argv, + int offset, + struct pgagroal_parsed_command* cmd, + char error_message[MISC_LENGTH], + const struct pgagroal_command command_table[], + const struct pgagroal_command subcommand_table[]) +{ +#define CONSUME(_x) _x = (offset < argc) ? argv[offset++] : NULL - if (*value == NULL || strlen(*value) == 0) - { - goto error; - } + int state = STATE_START; + char* command = NULL; + char* subcommand = NULL; + char* tmp = NULL; + while (1) + { + switch (state) + { + case STATE_START: + if (argc <= offset) + { + sprintf(error_message, "pgagroal-cli requires a command\n"); + state = STATE_ERROR; + } + else + { + state = STATE_COMMAND; + } + break; + case STATE_COMMAND: + CONSUME(command); + parse_command(command, &cmd->command_code, command_table); + if (cmd->command_code == COMMAND_UNKNOWN) + { + sprintf(error_message, "Invalid command '%s'\n", command); + state = STATE_ERROR; + } + else + { + state = command_table[cmd->command_code].transition_states; + } + break; + case STATE_SUBCOMMAND: + CONSUME(subcommand); + if (subcommand == NULL) + { + sprintf(error_message, "Command '%s' requires a subcommand\n", command); + state = STATE_ERROR; + } + else + { + parse_subcommand(subcommand, cmd->command_code, &cmd->subcommand_code, command_table, subcommand_table); + if (cmd->subcommand_code == COMMAND_UNKNOWN) + { + sprintf(error_message, "Invalid subcommand '%s' for command '%s'\n", subcommand, command); + state = STATE_ERROR; + + } + else + { + state = subcommand_table[cmd->subcommand_code].transition_states; + } + } + break; + case STATE_ARGUMENT: + CONSUME(cmd->args[++cmd->arg_cnt]); + if (cmd->args[cmd->arg_cnt] == NULL) + { + sprintf(error_message, "Command '%s%s%s' requires an argument\n", command, + command && subcommand ? " " : "", subcommand ? subcommand : ""); + state = STATE_ERROR; + } + else + { + state = STATE_ARGUMENT | STATE_END; + } + break; + case STATE_SUBCOMMAND | STATE_ARGUMENT: + CONSUME(tmp); + if (tmp == NULL) + { + sprintf(error_message, + "Command '%s%s%s' requires either a subcommand or an argument\n", + command, command && subcommand ? " " : "", subcommand ? subcommand : ""); + state = STATE_ERROR; + } + else + { + parse_subcommand(tmp, cmd->command_code, &cmd->subcommand_code, command_table, subcommand_table); + if (cmd->subcommand_code == COMMAND_UNKNOWN) + { + cmd->args[++(cmd->arg_cnt)] = tmp; + state = STATE_ARGUMENT | STATE_END; + } + else + { + subcommand = tmp; + state = subcommand_table[cmd->subcommand_code].transition_states; + } + } + break; + case STATE_SUBCOMMAND | STATE_END: + CONSUME(subcommand); + if (subcommand == NULL) + { + state = STATE_END; + } + else + { + parse_subcommand(subcommand, cmd->command_code, &cmd->subcommand_code, command_table, subcommand_table); + if (cmd->subcommand_code == COMMAND_UNKNOWN) + { + sprintf(error_message, "Invalid subcommand '%s' for command '%s'\n", subcommand, command); + state = STATE_ERROR; + } + else + { + state = subcommand_table[cmd->subcommand_code].transition_states; + } + } + break; + case STATE_ARGUMENT | STATE_END: + CONSUME(cmd->args[++cmd->arg_cnt]); + if (cmd->args[cmd->arg_cnt] == NULL) + { + state = STATE_END; + } + break; + case STATE_ERROR: + goto failure; + break; + case STATE_END: + CONSUME(cmd->args[++cmd->arg_cnt]); + if (cmd->args[cmd->arg_cnt] != NULL) + { + sprintf(error_message, "Command '%s%s%s' does not expect an argument\n", command, + command && subcommand ? " " : "", subcommand ? subcommand : ""); + state = STATE_ERROR; + } + else + { + goto success; + } + break; + default: + errx(1, "Unknown state"); + break; } } - return true; +failure: +#undef CONSUME -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) - { - return false; - } +success: +#undef CONSUME - // 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)) + /* Deprecation warning: warn the user if there is enough information about deprecation */ + if (command_table[cmd->command_code].deprecated + && pgagroal_version_ge(command_table[cmd->command_code].deprecated_since_major, + command_table[cmd->command_code].deprecated_since_minor, 0)) { - return false; + warnx("command <%s> has been deprecated by <%s> since version %d.%d", + command_table[cmd->command_code].string, + command_table[cmd->command_code].deprecated_by, + command_table[cmd->command_code].deprecated_since_major, + command_table[cmd->command_code].deprecated_since_minor); } - if (value) + if (cmd->subcommand_code != COMMAND_UNKNOWN) { - // need to evaluate the database or server - offset++; - *value = argc > offset ? argv[offset] : "*"; + cmd->action = subcommand_table[cmd->subcommand_code].action; + cmd->mode = subcommand_table[cmd->subcommand_code].mode; + cmd->log_trace = subcommand_table[cmd->subcommand_code].log_trace; + cmd->args[0] = cmd->args[0] == NULL ? subcommand_table[cmd->subcommand_code].default_argument : cmd->args[0]; } - - // warn the user if there is enough information - // about deprecation - if (deprecated_by - && pgagroal_version_ge(deprecated_since_major, deprecated_since_minor, 0)) + else { - warnx("command <%s> has been deprecated by <%s> since version %d.%d", - command, deprecated_by, deprecated_since_major, deprecated_since_minor); + cmd->action = command_table[cmd->command_code].action; + cmd->mode = command_table[cmd->command_code].mode; + cmd->log_trace = command_table[cmd->command_code].log_trace; + cmd->args[0] = cmd->args[0] == NULL ? command_table[cmd->command_code].default_argument : cmd->args[0]; } return true; @@ -1075,7 +1189,20 @@ parse_command_simple(int argc, char* command, char* subcommand) { - return parse_command(argc, argv, offset, command, subcommand, NULL, NULL, NULL, NULL); + 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) +{ + return false; } /**