From 3ebc96a2bf586a766619d187389cb3018f21791f 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 | 355 ++++++++++++++++++++-------------------- src/include/pgagroal.h | 42 ++--- src/include/utils.h | 82 +++++++++- src/libpgagroal/utils.c | 205 +++++++++++++++-------- 4 files changed, 417 insertions(+), 267 deletions(-) diff --git a/src/cli.c b/src/cli.c index 8d7a6ca1..9948f859 100644 --- a/src/cli.c +++ b/src/cli.c @@ -177,6 +177,12 @@ main(int argc, char** argv) 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; + enum pgagroal_cli_command_code command_code = COMMAND_UNKNOWN; + enum pgagroal_cli_command_code subcommand_code = COMMAND_UNKNOWN; + char parse_command_return_message[MISC_LENGTH] = {0}; + char* key = NULL; + char* value = NULL; + char* default_value = "*"; while (1) { @@ -355,209 +361,209 @@ 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)) + if (!parse_command(argc, argv, optind, &key, &value, &command_code, &subcommand_code, parse_command_return_message)) { - mode = FLUSH_IDLE; - action = ACTION_FLUSH; - pgagroal_log_trace("Command: [%s]", database); + printf("%s", parse_command_return_message); + goto done; } - 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)) - { - 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)) + + key = key == NULL ? default_value : key; + command_code = (subcommand_code != COMMAND_UNKNOWN) ? subcommand_code : command_code; + switch (command_code) { - action = strlen(server) > 0 ? ACTION_SWITCH_TO : ACTION_UNKNOWN; - pgagroal_log_trace("Command: [%s]", server); + case COMMAND_UNKNOWN: + errx(1, "Parsing of action reached impossible branch"); + case COMMAND_FLUSH_IDLE: + case DEPRECATED_COMMAND_FLUSH_IDLE: + mode = FLUSH_IDLE; + action = ACTION_FLUSH; + database = key; + pgagroal_log_trace("Command: [%s]", database); + break; + case COMMAND_PING: + case DEPRECATED_COMMAND_IS_ALIVE: + action = ACTION_ISALIVE; + pgagroal_log_trace("Command: "); + break; + case COMMAND_ENABLE: + action = ACTION_ENABLEDB; + database = key; + pgagroal_log_trace("Command: [%s]", database); + break; + case COMMAND_DISABLE: + action = ACTION_DISABLEDB; + database = key; + pgagroal_log_trace("Command: [%s]", database); + break; + case COMMAND_SHUTDOWN_IMMEDIATE: + case DEPRECATED_COMMAND_STOP: + action = ACTION_STOP; + pgagroal_log_trace("Command: "); + break; + case COMMAND_SHUTDOWN: + case COMMAND_SHUTDOWN_GRACEFULLY: + case DEPRECATED_COMMAND_SHUTDOWN_GRACEFULLY: + action = ACTION_GRACEFULLY; + pgagroal_log_trace("Command: "); + break; + case COMMAND_SHUTDOWN_CANCEL: + case DEPRECATED_COMMAND_CANCEL_SHUTDOWN: + action = ACTION_CANCELSHUTDOWN; + pgagroal_log_trace("Command: "); + break; + case COMMAND_STATUS: + action = ACTION_STATUS; + break; + case COMMAND_STATUS_DETAILS: + case DEPRECATED_COMMAND_STATUS_DETAILS: + action = ACTION_STATUS_DETAILS; + pgagroal_log_trace("Command: "); + break; + case COMMAND_SWITCH_TO: + action = ACTION_SWITCH_TO; + pgagroal_log_trace("Command: [%s]", key); + break; + case COMMAND_CONF: + break; + case COMMAND_CONF_RELOAD: + case DEPRECATED_COMMAND_RELOAD: + /* Local connection only */ + if (configuration_path != NULL) + { + action = ACTION_RELOAD; + } + pgagroal_log_trace("Command: "); + break; + case COMMAND_CONF_GET: + case DEPRECATED_COMMAND_CONFIG_GET: + action = ACTION_CONFIG_GET; + config_key = key; + pgagroal_log_trace("Command: [%s]", config_key); + break; + case COMMAND_CONF_SET: + case DEPRECATED_COMMAND_CONFIG_SET: + action = ACTION_CONFIG_SET; + config_key = key; + config_value = value; + pgagroal_log_trace("Command: [%s] = [%s]", config_key, config_value); + break; + case COMMAND_CONF_LS: + action = ACTION_CONFIG_LS; + pgagroal_log_debug("Command: "); // TODO: mention in the commit that before was log_debug + break; + case COMMAND_CLEAR: + case COMMAND_CLEAR_SERVER: + case DEPRECATED_COMMAND_RESET_SERVER: + action = ACTION_RESET_SERVER; + server = key; + pgagroal_log_trace("Command: [%s]", server); + break; + case COMMAND_FLUSH: + case COMMAND_FLUSH_GRACEFULLY: + case DEPRECATED_COMMAND_FLUSH_GRACEFULLY: + mode = FLUSH_GRACEFULLY; + action = ACTION_FLUSH; + database = key; + pgagroal_log_trace("Command: [%s]", database); + break; + case COMMAND_FLUSH_ALL: + case DEPRECATED_COMMAND_FLUSH_ALL: + mode = FLUSH_ALL; + action = ACTION_FLUSH; + database = key; + pgagroal_log_trace("Command: [%s]", database); + break; + case COMMAND_CLEAR_PROMETHEUS: + case DEPRECATED_COMMAND_RESET: + action = ACTION_RESET; + pgagroal_log_trace("Command: "); + break; } - else if (parse_command_simple(argc, argv, optind, "conf", "reload") - || parse_deprecated_command(argc, argv, optind, "reload", NULL, "conf reload", 1, 6)) + + 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) @@ -644,7 +650,6 @@ main(int argc, char** argv) 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..d3e12afb 100644 --- a/src/include/utils.h +++ b/src/include/utils.h @@ -375,6 +375,81 @@ pgagroal_backtrace(void); #endif +/* + * Command parsing for pgagroal-cli + */ + +#define MAX_NUMBER_OF_SUBCOMMANDS 4 + +enum pgagroal_cli_command_code { + COMMAND_UNKNOWN = 0, + + /* Command codes: add commands to the end */ + COMMAND_FLUSH = 1, + 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, +}; + +struct pgagroal_cli_command +{ + const char* string; + int argtype; + int subcommands_count; + enum pgagroal_cli_command_code subcommands[MAX_NUMBER_OF_SUBCOMMANDS]; + + /* Deprecation information */ + bool deprecated; + unsigned int deprecated_since_major; + unsigned int deprecated_since_minor; + const char* deprecated_by; +}; + +#define ARGTYPE_NONE 0x00001 +#define ARGTYPE_SUBCOMMAND 0x00010 +#define ARGTYPE_KEY 0x00100 +#define ARGTYPE_KEY_VALUE 0x01000 + +#define COMMAND_ACCEPTS(type, cmd) (type & cli_command_table[cmd].argtype) +#define COMMAND_STRICTLY_REQUIRES(num, cmd) !(num ^ cli_command_table[cmd].argtype) + +#define SUBCOMMAND_ACCEPTS(type, cmd) (type & cli_subcommand_table[cmd].argtype) + /** * Utility function to parse the command line * and search for a command. @@ -433,12 +508,11 @@ bool parse_command(int argc, char** argv, int offset, - char* command, - char* subcommand, char** key, - char* default_key, char** value, - char* default_value); + enum pgagroal_cli_command_code* command_code, + enum pgagroal_cli_command_code* subcommand_code, + char error_message[MISC_LENGTH]); /* * A wrapper function to parse a single command (and its subcommand) diff --git a/src/libpgagroal/utils.c b/src/libpgagroal/utils.c index d24064fa..f82d59ec 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,172 @@ pgagroal_backtrace(void) #endif +/* Parser for pgagroal-cli commands */ + +const struct pgagroal_cli_command cli_command_table[] = { + [COMMAND_FLUSH] = {.string = "flush", .argtype = ARGTYPE_NONE | ARGTYPE_SUBCOMMAND, .subcommands_count = 3, .subcommands = {COMMAND_FLUSH_ALL, COMMAND_FLUSH_GRACEFULLY, COMMAND_FLUSH_IDLE}, .deprecated = false}, + [COMMAND_PING] = {.string = "ping", .argtype = ARGTYPE_NONE, .subcommands_count = 0, .deprecated = false}, + [COMMAND_ENABLE] = {.string = "enable", .argtype = ARGTYPE_NONE | ARGTYPE_KEY, .subcommands_count = 0, .deprecated = false}, + [COMMAND_DISABLE] = {.string = "disable", .argtype = ARGTYPE_NONE | ARGTYPE_KEY, .subcommands_count = 0, .deprecated = false}, + [COMMAND_SHUTDOWN] = {.string = "shutdown", .argtype = ARGTYPE_NONE | ARGTYPE_SUBCOMMAND, .subcommands_count = 3, .subcommands = {COMMAND_SHUTDOWN_IMMEDIATE, COMMAND_SHUTDOWN_CANCEL, COMMAND_SHUTDOWN_GRACEFULLY}, .deprecated = false}, + [COMMAND_STATUS] = {.string = "status", .argtype = ARGTYPE_NONE | ARGTYPE_SUBCOMMAND, .subcommands_count = 1, .subcommands = {COMMAND_STATUS_DETAILS}, .deprecated = false}, + [COMMAND_SWITCH_TO] = {.string = "switch-to", .argtype = ARGTYPE_KEY, .subcommands_count = 0, .deprecated = false}, + [COMMAND_CONF] = {.string = "conf", .argtype = ARGTYPE_SUBCOMMAND, .subcommands_count = 4, .subcommands = {COMMAND_CONF_GET, COMMAND_CONF_LS, COMMAND_CONF_RELOAD, COMMAND_CONF_SET}, .deprecated = false}, + [COMMAND_CLEAR] = {.string = "clear", .argtype = ARGTYPE_SUBCOMMAND | ARGTYPE_KEY, .subcommands_count = 2, .subcommands = {COMMAND_CLEAR_SERVER, COMMAND_CLEAR_PROMETHEUS}, .deprecated = false}, + + /* Deprecated commands */ + [DEPRECATED_COMMAND_FLUSH_IDLE] = {.string = "flush-idle", .argtype = ARGTYPE_NONE, .deprecated = true, .deprecated_since_major = 1, .deprecated_since_minor = 6, .deprecated_by = "flush idle"}, + [DEPRECATED_COMMAND_FLUSH_ALL] = {.string = "flush-all", .argtype = ARGTYPE_NONE, .deprecated = true, .deprecated_since_major = 1, .deprecated_since_minor = 6, .deprecated_by = "flush all"}, + [DEPRECATED_COMMAND_FLUSH_GRACEFULLY] = {.string = "flush-gracefully", .argtype = ARGTYPE_NONE, .deprecated = true, .deprecated_since_major = 1, .deprecated_since_minor = 6, .deprecated_by = "flush gracefully"}, + [DEPRECATED_COMMAND_STOP] = {.string = "stop", .argtype = ARGTYPE_NONE, .deprecated = true, .deprecated_since_major = 1, .deprecated_since_minor = 6, .deprecated_by = "shutdown immediate"}, + [DEPRECATED_COMMAND_CANCEL_SHUTDOWN] = {.string = "cancel-shutdown", .argtype = ARGTYPE_NONE, .deprecated = true, .deprecated_since_major = 1, .deprecated_since_minor = 6, .deprecated_by = "shutdown cancel"}, + [DEPRECATED_COMMAND_SHUTDOWN_GRACEFULLY] = {.string = "gracefully", .argtype = ARGTYPE_NONE, .deprecated = true, .deprecated_since_major = 1, .deprecated_since_minor = 6, .deprecated_by = "shutdown gracefully"}, + [DEPRECATED_COMMAND_STATUS_DETAILS] = {.string = "details", .argtype = ARGTYPE_NONE, .deprecated = true, .deprecated_since_major = 1, .deprecated_since_minor = 6, .deprecated_by = "status details"}, + [DEPRECATED_COMMAND_IS_ALIVE] = {.string = "is-alive", .argtype = ARGTYPE_NONE, .deprecated = true, .deprecated_since_major = 1, .deprecated_since_minor = 6, .deprecated_by = "ping"}, + [DEPRECATED_COMMAND_RESET] = {.string = "reset", .argtype = ARGTYPE_NONE, .deprecated = true, .deprecated_since_major = 1, .deprecated_since_minor = 6, .deprecated_by = "clear prometheus"}, + [DEPRECATED_COMMAND_RESET_SERVER] = {.string = "reset-server", .argtype = ARGTYPE_NONE, .deprecated = true, .deprecated_since_major = 1, .deprecated_since_minor = 6, .deprecated_by = "clear server"}, + [DEPRECATED_COMMAND_RELOAD] = {.string = "reload", .argtype = ARGTYPE_NONE, .deprecated = true, .deprecated_since_major = 1, .deprecated_since_minor = 6, .deprecated_by = "conf reload"}, + [DEPRECATED_COMMAND_CONFIG_GET] = {.string = "config-get", .argtype = ARGTYPE_NONE, .deprecated = true, .deprecated_since_major = 1, .deprecated_since_minor = 6, .deprecated_by = "conf get"}, + [DEPRECATED_COMMAND_CONFIG_SET] = {.string = "config-set", .argtype = ARGTYPE_NONE, .deprecated = true, .deprecated_since_major = 1, .deprecated_since_minor = 6, .deprecated_by = "conf set"}, +}; + +const struct pgagroal_cli_command cli_subcommand_table[] = { + [COMMAND_FLUSH_GRACEFULLY] = {.string = "gracefully", .argtype = ARGTYPE_NONE | ARGTYPE_KEY, .deprecated = false }, + [COMMAND_FLUSH_IDLE] = {.string = "idle", .argtype = ARGTYPE_NONE | ARGTYPE_KEY, .deprecated = false }, + [COMMAND_FLUSH_ALL] = {.string = "all", .argtype = ARGTYPE_NONE, .deprecated = false }, + [COMMAND_SHUTDOWN_GRACEFULLY] = {.string = "gracefully", .argtype = ARGTYPE_NONE, .deprecated = false }, + [COMMAND_SHUTDOWN_IMMEDIATE] = {.string = "immediate", .argtype = ARGTYPE_NONE, .deprecated = false }, + [COMMAND_SHUTDOWN_CANCEL] = {.string = "cancel", .argtype = ARGTYPE_NONE, .deprecated = false }, + [COMMAND_CONF_RELOAD] = {.string = "reload", .argtype = ARGTYPE_NONE, .deprecated = false }, + [COMMAND_CONF_GET] = {.string = "get", .argtype = ARGTYPE_KEY, .deprecated = false }, + [COMMAND_CONF_SET] = {.string = "set", .argtype = ARGTYPE_KEY_VALUE, .deprecated = false }, + [COMMAND_CONF_LS] = {.string = "ls", .argtype = ARGTYPE_NONE, .deprecated = false }, + [COMMAND_CLEAR_SERVER] = {.string = "server", .argtype = ARGTYPE_KEY, .deprecated = false }, + [COMMAND_CLEAR_PROMETHEUS] = {.string = "prometheus", .argtype = ARGTYPE_NONE, .deprecated = false }, + [COMMAND_STATUS_DETAILS] = {.string = "details", .argtype = ARGTYPE_NONE, .deprecated = false }, +}; + bool parse_command(int argc, char** argv, int offset, - char* command, - char* subcommand, char** key, - char* default_key, char** value, - char* default_value) + enum pgagroal_cli_command_code* command_code, + enum pgagroal_cli_command_code* subcommand_code, + char error_message[MISC_LENGTH]) { +#define GET_NEXT_FROM_ARGV(_x) do { if (offset < argc) _x = argv[offset++]; } while (0) - // sanity check: if no arguments, nothing to parse! + char* command = NULL; + char* subcommand = NULL; if (argc <= offset) { + sprintf(error_message, "pgagroal-cli requires a command\n"); return false; } + GET_NEXT_FROM_ARGV(command); + GET_NEXT_FROM_ARGV(subcommand); + GET_NEXT_FROM_ARGV(*key); + GET_NEXT_FROM_ARGV(*value); - // 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)) + /* Command parsing */ + for (int command_index = COMMAND_FLUSH; command_index < DEPRECATED_COMMAND_CONFIG_SET; command_index++) + { + if (strncmp(command, cli_command_table[command_index].string, MISC_LENGTH) == 0) + { + *command_code = command_index; + break; + } + } + if (*command_code == COMMAND_UNKNOWN) { + sprintf(error_message, "Invalid command '%s'\n", command); return false; } - if (subcommand) + bool command_accepts_subcommand = COMMAND_ACCEPTS(ARGTYPE_SUBCOMMAND, *command_code); + bool command_accepts_key = COMMAND_ACCEPTS(ARGTYPE_KEY, *command_code); + if (subcommand == NULL) { - // thre must be a subcommand check - offset++; - - if (argc <= offset) + if (COMMAND_ACCEPTS(ARGTYPE_NONE, *command_code)) { - // not enough command args! - return false; + return true; } + sprintf(error_message, "Command '%s' expects %s%s%s\n", + command, + command_accepts_subcommand ? "subcommand" : "", + (command_accepts_subcommand && command_accepts_key) ? " or " : "", + command_accepts_key ? "argument" : ""); + return false; + } - if (strncmp(argv[offset], subcommand, MISC_LENGTH)) - { - return false; - } + if (COMMAND_STRICTLY_REQUIRES(ARGTYPE_NONE, *command_code)) + { + sprintf(error_message, "Command '%s' does not expect a subcommand or argument\n", command); + return false; } - if (key) + /* Subcommand (or key) parsing */ + const enum pgagroal_cli_command_code* subcommands = &(cli_command_table[*command_code].subcommands[0]); + int subcommands_count = cli_command_table[*command_code].subcommands_count; + 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, cli_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) + } + if (*subcommand_code == COMMAND_UNKNOWN) + { + if (!command_accepts_key) { - offset++; - *value = argc > offset ? argv[offset] : default_value; - - if (*value == NULL || strlen(*value) == 0) - { - goto error; - } - + sprintf(error_message, "Invalid subcommand '%s' for command '%s'\n", subcommand, command); + return false; } + *key = subcommand; } - return true; - -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) + if (*key == NULL) { + if (SUBCOMMAND_ACCEPTS(ARGTYPE_NONE, *subcommand_code)) + { + return true; + } + sprintf(error_message, "Command '%s %s' expects an argument\n", command, 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)) + bool subcommand_accepts_key = SUBCOMMAND_ACCEPTS(ARGTYPE_KEY, *subcommand_code); + bool subcommand_accepts_key_value = SUBCOMMAND_ACCEPTS(ARGTYPE_KEY_VALUE, *subcommand_code); + if (!command_accepts_key && !subcommand_accepts_key && !subcommand_accepts_key_value) { + sprintf(error_message, "Command '%s %s' does not expect an argument\n", command, subcommand); return false; } - if (value) + if (*value == NULL) { - // need to evaluate the database or server - offset++; - *value = argc > offset ? argv[offset] : "*"; + if (subcommand_accepts_key || command_accepts_key) + { + goto success; + } + sprintf(error_message, "Command '%s %s' does not expect an argument\n", command, subcommand); + return false; } - // warn the user if there is enough information - // about deprecation - if (deprecated_by - && pgagroal_version_ge(deprecated_since_major, deprecated_since_minor, 0)) +success: + /* Deprecation warning: warn the user if there is enough information about deprecation */ + if (cli_command_table[*command_code].deprecated + && pgagroal_version_ge(cli_command_table[*command_code].deprecated_since_major, + cli_command_table[*command_code].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); + cli_command_table[*command_code].string, + cli_command_table[*command_code].deprecated_by, + cli_command_table[*command_code].deprecated_since_major, + cli_command_table[*command_code].deprecated_since_minor); } return true; @@ -1075,7 +1133,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; } /**