From fb9b909f0e5648d0f455b30f0c5ec6ab4ac0ce30 Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Wed, 4 Oct 2023 04:14:10 -0400 Subject: [PATCH] [#253][#209] Refactor commands in `pgagroal-cli` and `pgagroal-admin` Now `pgagroal-cli` has a set of "logically" grouped commands and subcommands. For example, all the commands related to shutting down the pooler are under the `shutdown` command, that can operate with subcommands like `gracefully`, `immediate` or `cancel`. In order to provide this capability, new functions have been introduced as utilities: - `parse_command()` accepts the command line and seek for a command, possibly its subcommand, and an optional "value" (often the database or server name). - `parse_command_simple()` is a wrapper around the above `parse_command` that shorten the function call line because it does not require to specify the key and the value (and their defaults). - `parse_deprecated_command()` does pretty much the same thing but against the old command. Thanks to this, old commands can still work and the user will be warned about their deprecation, but the interface of `pgagroal-cli` is not broken. All the above functions require to know the offset at which start seeking for a command, and that depends on the number of options already parsed via `getopt_long()`. Since the `&option_index` is valued only for long options, I decided to use the `optind` global value, see getopt_long(3). This value is initialized with the "next thing" to seek on the command line, i.e., the next index on `argv`. In the case the command accepts an optional database name, the database value is automatically set to '*' (all databases) in case the database name is not found on the command line. Therefore: pgagroal-cli flush idle is equivalent to pgagroal-cli flush idle '*' On the other hand, commands that require a server name get the value automatically set to "\0" (an invalid server name) in order to "block" other pieces of code. Moroever, if the server has not been specified, the command is automatically set to "unknown" so that the help screen is shown. The `pgagroal-cli` has a set of `pgagroal_log_debug()` calls whenever a command is "parsed", so that it is possible to quickly follow the command line parsing. Also, since the `pgagroal-cli` exists if no command line arguments have been specified, the safety check aboutt `argc > 0` around the command line parsing has been removed. In the case the user specified an unknown command, she is warned on stdout before printing the `usage()` help screen. Deprecated commands are notified to the user via a warning message, printed on stderr, that provides some hints about the correct usage of the new command. The warning about deprecated commands is shown only if the currently running version of the software is greater than the version the command has been deprecated onto. In particular these commands have been deprecated since 1.6. This commit also introduces the command refactoring for `pgagroal-admin` in a way similar to the work done for `pgagroal-cli`. New commands are available: - user with being , , , . Updated: - documentation - shell completions - help screens - examples Close #290 #253 --- contrib/shell_comp/pgagroal_comp.bash | 38 +- contrib/shell_comp/pgagroal_comp.zsh | 65 +++- doc/ADMIN.md | 87 +++++ doc/CLI.md | 274 +++++++------- doc/tutorial/01_install.md | 3 +- doc/tutorial/02_prefill.md | 2 +- doc/tutorial/03_remote_management.md | 2 +- doc/tutorial/04_prometheus.md | 2 +- doc/tutorial/05_split_security.md | 4 +- src/admin.c | 99 +++-- src/cli.c | 504 +++++++++++++------------- src/include/utils.h | 119 ++++++ src/libpgagroal/configuration.c | 8 +- src/libpgagroal/utils.c | 125 +++++++ 14 files changed, 875 insertions(+), 457 deletions(-) create mode 100644 doc/ADMIN.md diff --git a/contrib/shell_comp/pgagroal_comp.bash b/contrib/shell_comp/pgagroal_comp.bash index 3111e8c7..bc3665d2 100644 --- a/contrib/shell_comp/pgagroal_comp.bash +++ b/contrib/shell_comp/pgagroal_comp.bash @@ -2,27 +2,59 @@ # COMP_WORDS contains # at index 0 the executable name (pgagroal-cli) -# at index 1 the command name (e.g., flush-all) +# at index 1 the command name (e.g., flush) +# at index 2, if required, the subcommand name (e.g., all) pgagroal_cli_completions() { if [ "${#COMP_WORDS[@]}" == "2" ]; then # main completion: the user has specified nothing at all # or a single word, that is a command - COMPREPLY=($(compgen -W "flush-idle flush-gracefully flush-all is-alive enable disable stop gracefully status details switch-to reload reset reset-server config-get config-set" "${COMP_WORDS[1]}")) + COMPREPLY=($(compgen -W "flush is-alive enable disable shutdown status details switch-to conf clear" "${COMP_WORDS[1]}")) + else + # the user has specified something else + # subcommand required? + case ${COMP_WORDS[1]} in + flush) + COMPREPLY+=($(compgen -W "gracefully idle all" "${COMP_WORDS[2]}")) + ;; + shutdown) + COMPREPLY+=($(compgen -W "gracefully immediate cancel" "${COMP_WORDS[2]}")) + ;; + clear) + COMPREPLY+=($(compgen -W "server prometheus" "${COMP_WORDS[2]}")) + ;; + conf) + COMPREPLY+=($(compgen -W "reload get set" "${COMP_WORDS[2]}")) + ;; + esac fi + + } + pgagroal_admin_completions() { if [ "${#COMP_WORDS[@]}" == "2" ]; then # main completion: the user has specified nothing at all # or a single word, that is a command - COMPREPLY=($(compgen -W "master-key add-user update-user remove-user list-users" "${COMP_WORDS[1]}")) + COMPREPLY=($(compgen -W "master-key user" "${COMP_WORDS[1]}")) + else + # the user has specified something else + # subcommand required? + case ${COMP_WORDS[1]} in + user) + COMPREPLY+=($(compgen -W "add del edit ls" "${COMP_WORDS[2]}")) + ;; + esac fi } + + + # install the completion functions complete -F pgagroal_cli_completions pgagroal-cli complete -F pgagroal_admin_completions pgagroal-admin diff --git a/contrib/shell_comp/pgagroal_comp.zsh b/contrib/shell_comp/pgagroal_comp.zsh index d2a08493..c1750a83 100644 --- a/contrib/shell_comp/pgagroal_comp.zsh +++ b/contrib/shell_comp/pgagroal_comp.zsh @@ -6,14 +6,75 @@ function _pgagroal_cli() { local line _arguments -C \ - "1: :(flush-idle flush-all flush-gracefully is-alive enable disable stop gracefully status details switch-to reload reset reset-server config-get config-set)" \ + "1: :(flush is-alive enable disable shutdown status details switch-to conf clear)" \ + "*::arg:->args" + + case $line[1] in + flush) + _pgagroal_cli_flush + ;; + shutdown) + _pgagroal_cli_shutdown + ;; + clear) + _pgagroal_cli_clear + ;; + conf) + _pgagroal_cli_conf + ;; + esac +} + +function _pgagroal_cli_flush() +{ + local line + _arguments -C \ + "1: :(gracefully idle all)" \ + "*::arg:->args" +} + +function _pgagroal_cli_conf() +{ + local line + _arguments -C \ + "1: :(reload get set)" \ + "*::arg:->args" +} + +function _pgagroal_cli_shutdown() +{ + local line + _arguments -C \ + "1: :(gracefully immediate cancel)" \ + "*::arg:->args" +} + +function _pgagroal_cli_clear() +{ + local line + _arguments -C \ + "1: :(server prometheus)" \ "*::arg:->args" } + function _pgagroal_admin() { local line _arguments -C \ - "1: :(master-key add-user update-user remove-user list-users)" \ + "1: :(master-key user)" \ + "*::arg:->args" + + case $line[1] in + user) + _pgagroal_admin_user + ;; + esac +} + +function _pgagroal_admin_user() +{ + _arguments -C \ + "1: :(add del edit ls)" \ "*::arg:->args" } diff --git a/doc/ADMIN.md b/doc/ADMIN.md new file mode 100644 index 00000000..ed06158b --- /dev/null +++ b/doc/ADMIN.md @@ -0,0 +1,87 @@ +# `pgagroal-admin` user guide + +`pgagroal-admin` is a command line interface to manage users known +to the `pgagroal` connection pooler. +The executable accepts a set of options, as well as a command to execute. +If no command is provided, the program will show the help screen. + +The `pgagroal-admin` utility has the following synopsis: + +``` +pgagroal-admin [ OPTIONS ] [ COMMAND ] +``` + + +## Options + +Available options are the following ones: + +``` + -f, --file FILE Set the path to a user file + -U, --user USER Set the user name + -P, --password PASSWORD Set the password for the user + -g, --generate Generate a password + -l, --length Password length + -V, --version Display version information + -?, --help Display help + +``` + +Options can be specified either in short or long form, in any position of the command line. + +The `-f` option is mandatory for every operation that involves user management. If no +user file is specified, `pgagroal-admin` will silently use the default one (`pgagroal_users.conf`). + +## Commands + +### user +The `user` command allows the management of the users known to the connection pooler. +The command accepts the following subcommands: +- `add` to add a new user to the system; +- `del` to remove an existing user from the system; +- `edit` to change the credentials of an existing user; +- `ls` to list all known users within the system. + +The command will edit the `pgagroal_users.conf` file or any file specified by means of the `-f` option flag. + +Unless the command is run with the `-U` and/or `-P` flags, the execution will be interactive. + +Examples: + +``` shell +pgagroal-admin user add -U simon -P secret +pgagroal-admin user del -U simon + +``` + +## master-key + +The `master-key` command allows the definition of a password to protect the vault of the users, +that is the "container" for users' credentials. + + +## Deprecated commands + +The following commands have been deprecated and will be removed +in later releases of `pgagroal`. +For each command, this is the corresponding current mapping +to the working command: + +- `add-user` is now `user add`; +- `remove-user` is now `user del`; +- `update-user` is now `user edit`; +- `list-users` is now `user ls`. + +Whenever you use a deprecated command, the `pgagroal-admin` will print on standard error a warning message. +If you don't want to get any warning about deprecated commands, you +can redirect the `stderr` to `/dev/null` or any other location with: + +``` +pgagroal-admin user-add -U luca -P strongPassword 2>/dev/null +``` + + +## Shell completion + +There is a minimal shell completion support for `pgagroal-admin`. +See the [Install pgagroal](https://github.com/pgagroal/pgagroal/blob/main/doc/tutorial/01_install.md) for more details. diff --git a/doc/CLI.md b/doc/CLI.md index 57e7782b..a7031e5b 100644 --- a/doc/CLI.md +++ b/doc/CLI.md @@ -1,8 +1,21 @@ -# pgagroal-cli user guide +# `pgagroal-cli` user guide + +`pgagroal-cli` is a command line interface to interact with `pgagroal`. +The executable accepts a set of options, as well as a command to execute. +If no command is provided, the program will show the help screen. + +The `pgagroal-cli` utility has the following synopsis: ``` -pgagroal-cli [ -c CONFIG_FILE ] [ COMMAND ] +pgagroal-cli [ OPTIONS ] [ COMMAND ] +``` + +## Options + +Available options are the following ones: + +``` -c, --config CONFIG_FILE Set the path to the pgagroal.conf file -h, --host HOST Set the host name -p, --port PORT Set the port number @@ -12,65 +25,41 @@ pgagroal-cli [ -c CONFIG_FILE ] [ COMMAND ] -v, --verbose Output text string of result -V, --version Display version information -?, --help Display help -``` - -Commands are described in the following sections. -Several commands work against an optional specified database. -It is possible to specify *all database* at once by means of the special string `*` (take care of shell expansion!). -If no database name is specified, the command is automatically run against all databases (i.e., as if `*` has been specified). - -## flush-idle -Flush idle connections. -Without any argument, or with `*` as only argument, -works against all configured databases. -Command - -``` -pgagroal-cli flush-idle [*|] ``` -Example - -``` -pgagroal-cli flush-idle -``` - -## flush-gracefully -Flush all connections gracefully. -Without any argument, or with `*` as only argument, -works against all configured databases. - -Command +Options can be specified either in short or long form, in any position of the command line. -``` -pgagroal-cli flush-gracefully [*|] -``` +## Commands -Example +### flush +The `flush` command performs a connection flushing. +It accepts a *mode* to operate the actual flushing: +- `gracefully` (the default if not specified), flush connections when possible; +- `idle` to flush only connections in state *idle*; +- `all` to flush all the connections (**use with caution!**). -``` -pgagroal-cli flush-gracefully -``` +The command accepts a database name, that if provided, restricts the scope of +`flush` only to connections related to such database. +If no database is provided, the `flush` command is operated against all databases. -## flush-all -Flush all connections. **USE WITH CAUTION !** -Without any argument, or with `*` as only argument, -works against all configured databases. Command ``` -pgagroal-cli flush-all [*|] +pgagroal-cli flush [gracefully|idle|all] [*|] ``` -Example +Examples ``` -pgagroal-cli flush-all mydb +pgagroal-cli flush # pgagroal-cli flush gracefully '*' +pgagroal-cli flush idle # pgagroal-cli flush idle '*' +pgagroal-cli flush all # pgagroal-cli flush all '*' +pgagroal-cli flush pgbench # pgagroal-cli flush gracefully pgbench ``` -## is-alive +### is-alive Is pgagroal alive Command @@ -85,10 +74,8 @@ Example pgagroal-cli is-alive ``` -## enable -Enables the specified database. -Without any argument, or with `*` as only argument, -works against all configured databases. +### enable +Enables a database (or all databases). Command @@ -102,11 +89,8 @@ Example pgagroal-cli enable ``` -## disable -Disables a database specified by its name. -Without any argument, or with `*` as only argument, -works against all configured databases. - +### disable +Disables a database (or all databases). Command @@ -120,52 +104,33 @@ Example pgagroal-cli disable ``` -## gracefully -Stop pgagroal gracefully - -Command - -``` -pgagroal-cli gracefully -``` - -Example +### shutdown +The `shutdown` command is used to stop the connection pooler. +It supports the following operating modes: +- `gracefully` (the default) closes the pooler as soon as no active connections are running; +- `immediate` force an immediate stop. -``` -pgagroal-cli gracefully -``` +If the `gracefully` mode is requested, chances are the system will take some time to +perform the effective shutdown, and therefore it is possible to abort the request +issuing another `shutdown` command with the mode `cancel`. -## stop -Stop pgagroal Command ``` -pgagroal-cli stop +pgagroal-cli shutdown [gracefully|immediate|cancel] ``` -Example - -``` -pgagroal-cli stop -``` - -## cancel-shutdown -Cancel the graceful shutdown - -Command +Examples ``` -pgagroal-cli cancel-shutdown +pgagroal-cli shutdown # pgagroal-cli shutdown gracefully +... +pgagroal-cli shutdown cancel # stops the above command ``` -Example - -``` -pgagroal-cli cancel-shutdown -``` -## status +### status Status of pgagroal Command @@ -180,7 +145,7 @@ Example pgagroal-cli status ``` -## details +### details Detailed status of pgagroal Command @@ -195,8 +160,8 @@ Example pgagroal-cli details ``` -## switch-to -Switch to another primary +### switch-to +Switch to another primary server. Command @@ -210,51 +175,33 @@ Example pgagroal-cli switch-to replica ``` -## reload -Reload the configuration +### conf +Manages the configuration of the running instance. +This command requires one subcommand, that can be: +- `reload` issue a reload of the configuration, applying at runtime any changes from the configuration files; +- `get` provides a configuration parameter value; +- `set` modifies a configuration parameter at runtime. Command ``` -pgagroal-cli reload +pgagroal-cli conf ``` -Example - -``` -pgagroal-cli reload -``` - -## reset -Reset the Prometheus statistics -Command - -``` -pgagroal-cli reset -``` - -Example +Examples ``` -pgagroal-cli reset -``` +pgagroal-cli conf reload -## reset-server -Reset the state of a server +pgagroal-cli conf get max_connections -Command +pgagroal-cli conf set max_connections 25 -``` -pgagroal-cli reset-server ``` -Example - -``` -pgagroal-cli reset-server primary -``` +The details about how to get and set values at run-time are explained in the following. -## config-get +### conf get Given a configuration setting name, provides the current value for such setting. The configuration setting name must be the same as the one used in the configuration files. @@ -278,13 +225,13 @@ the form `section.context.key` where: Examples ``` -pgagroal-cli config-get pipeline +pgagroal-cli conf get pipeline performance -pgagroal-cli config-get limit.pgbench.max_size +pgagroal-cli conf get limit.pgbench.max_size 2 -pgagroal-cli config-get server.venkman.primary +pgagroal-cli conf get server.venkman.primary off ``` @@ -296,7 +243,7 @@ The `server.venkman.primary` searches for the configuration parameter `primary` If the `--verbose` option is specified, a descriptive string of the configuration parameter is printed as *name = value*: ``` -pgagroal-cli config-get max_connections --verbose +pgagroal-cli conf get max_connections --verbose max_connections = 4 Success (0) ``` @@ -304,29 +251,30 @@ Success (0) If the parameter name specified is not found or invalid, the program `pgagroal-cli` exit normally without printing any value. -## config-set + +### conf set Allows the setting of a configuration parameter at run-time, if possible. Examples ``` -pgagroal-cli config-set log_level debug -pgagroal-cli config-set server.venkman.port 6432 -pgagroal config-set limit.pgbench.max_size 2 +pgagroal-cli conf set log_level debug +pgagroal-cli conf set server.venkman.port 6432 +pgagroal conf set limit.pgbench.max_size 2 ``` -The syntax for setting parameters is the same as for the command `config-get`, therefore parameters are organized into namespaces: +The syntax for setting parameters is the same as for the command `conf get`, therefore parameters are organized into namespaces: - `main` (optional) is the main pgagroal configuration namespace, for example `main.log_level` or simply `log_level`; - `server` is the namespace referred to a specific server. It has to be followed by the name of the server and the name of the parameter to change, in a dotted notation, like `server.venkman.port`; - `limit` is the namespace referred to a specific limit entry, followed by the name of the username used in the limit entry. -When executed, the `config-set` command returns the run-time setting of the specified parameter: if such parameter is equal to the value supplied, the change has been applied, otherwise it means that the old setting has been kept. +When executed, the `conf set` command returns the run-time setting of the specified parameter: if such parameter is equal to the value supplied, the change has been applied, otherwise it means that the old setting has been kept. The `--verbose` flag can be used to understand if the change has been applied: ``` -$ pgagroal-cli config-set log_level debug +$ pgagroal-cli conf set log_level debug debug -$ pgagroal-cli config-set log_level debug --verbose +$ pgagroal-cli conf set log_level debug --verbose log_level = debug pgagroal-cli: Success (0) ``` @@ -334,15 +282,15 @@ pgagroal-cli: Success (0) When a setting modification cannot be applied, the system returns the "old" setting value and, if `--verbose` is specified, the error indication: ``` -$ pgagroal-cli config-set max_connections 100 +$ pgagroal-cli conf set max_connections 100 40 -$ pgagroal-cli config-set max_connections 100 --verbose +$ pgagroal-cli conf set max_connections 100 --verbose max_connections = 40 pgagroal-cli: Error (2) ``` -When a `config-set` cannot be applied, the system will report in the logs an indication about the problem. With regard to the previous example, the system reports in the logs something like the following (depending on your `log_level`): +When a `conf set` cannot be applied, the system will report in the logs an indication about the problem. With regard to the previous example, the system reports in the logs something like the following (depending on your `log_level`): ``` DEBUG Trying to change main configuration setting to <100> @@ -352,6 +300,62 @@ DEBUG pgagroal_management_write_config_set: unable to apply changes to ] +``` + +Examples + +``` +pgagroal-cli clear spengler # pgagroal-cli clear server spengler +pgagroal-cli clear prometheus +``` + + +## Deprecated commands + +The following commands have been deprecated and will be removed +in later releases of `pgagroal`. +For each command, this is the corresponding current mapping +to the working command: + +- `flush-idle` is equivalent to `flush idle`; +- `flush-all` is equivalent to `flush all`; +- `flush-gracefully` is equivalent to `flush gracefully` or simply `flush`; +- `stop` is equivalent to `shutdown immediate`; +- `gracefully` is equivalent to `shutdown gracefully` or simply `shutdown`; +- `reset` is equivalent to `clear prometheus`; +- `reset-server` is equivalent to `clear server` or simply `clear`; +- `config-get` and `config-set` are respectively `conf get` and `conf set`; +- `reload` is equivalent to `conf reload`. + + +Whenever you use a deprecated command, the `pgagroal-cli` will print on standard error a warning message. +For example: + +``` +pgagroal-cli reset-server + +WARN: command has been deprecated by since version 1.6.x +``` + +If you don't want to get any warning about deprecated commands, you +can redirect the `stderr` to `/dev/null` or any other location with: + +``` +pgagroal-cli reset-server 2>/dev/null +``` + + + + ## Shell completions There is a minimal shell completion support for `pgagroal-cli`. diff --git a/doc/tutorial/01_install.md b/doc/tutorial/01_install.md index d33a15e1..6ed6a8e5 100644 --- a/doc/tutorial/01_install.md +++ b/doc/tutorial/01_install.md @@ -134,7 +134,8 @@ As the `pgagroal` operating system user, add a master key to protect the `pgagro ``` pgagroal-admin master-key -pgagroal-admin -f /etc/pgagroal/pgagroal_users.conf -U myuser -P mypassword add-user + +pgagroal-admin -f /etc/pgagroal/pgagroal_users.conf -U myuser -P mypassword user add ``` **You have to choose a password for the master key - remember it!** diff --git a/doc/tutorial/02_prefill.md b/doc/tutorial/02_prefill.md index 5ec0891b..ae880505 100644 --- a/doc/tutorial/02_prefill.md +++ b/doc/tutorial/02_prefill.md @@ -51,7 +51,7 @@ In order to apply changes to the prefill configuration, you need to restart `pga You can do so by stopping it and then re-launch the daemon, as `pgagroal` operating system user: ``` -pgagroal-cli -c /etc/pgagroal/pgagroal.conf stop +pgagroal-cli -c /etc/pgagroal/pgagroal.conf shutdown pgagroal -c /etc/pgagroal/pgagroal.conf -a /etc/pgagroal/pgagroal_hba.conf -u /etc/pgagroal/pgagroal_users.conf -l /etc/pgagroal/pgagroal_databases.conf ``` diff --git a/doc/tutorial/03_remote_management.md b/doc/tutorial/03_remote_management.md index c47dfe0b..7e3901e7 100644 --- a/doc/tutorial/03_remote_management.md +++ b/doc/tutorial/03_remote_management.md @@ -57,7 +57,7 @@ The above will create the `admin` username with the `admin1234` password. In order to make the changes available, and therefore activate the remote management, you have to restart `pgagroal`, for example by issuing the following commands from the `pgagroal` operatng system user: ``` -pgagroal-cli -c /etc/pgagroal/pgagroal.conf stop +pgagroal-cli -c /etc/pgagroal/pgagroal.conf shutdown pgagroal -c /etc/pgagroal/pgagroal.conf -a /etc/pgagroal/pgagroal_hba.conf -u /etc/pgagroal/pgagroal_users.conf -A /etc/pgagroal/pgagroal_admins.conf ``` diff --git a/doc/tutorial/04_prometheus.md b/doc/tutorial/04_prometheus.md index 96bb5ca1..8f2f9dd1 100644 --- a/doc/tutorial/04_prometheus.md +++ b/doc/tutorial/04_prometheus.md @@ -39,7 +39,7 @@ In order to apply changes, you need to restart `pgagroal`, therefore run the fol as the `pgagroal` operating system user: ``` -pgagroal-cli -c /etc/pgagroal/pgagroal.conf stop +pgagroal-cli -c /etc/pgagroal/pgagroal.conf shutdown pgagroal -c /etc/pgagroal/pgagroal.conf -a /etc/pgagroal/pgagroal_hba.conf ``` diff --git a/doc/tutorial/05_split_security.md b/doc/tutorial/05_split_security.md index bd40ef5a..60e6af5a 100644 --- a/doc/tutorial/05_split_security.md +++ b/doc/tutorial/05_split_security.md @@ -27,7 +27,7 @@ As an example, consider the user `myuser` created in the [Installing pgagroal tu To achieve this, as `pgagroal` operating system run the following command: ``` -pgagroal-admin -f /etc/pgagroal/pgagroal_frontend_users.conf -U myuser -P application_password add-user +pgagroal-admin -f /etc/pgagroal/pgagroal_frontend_users.conf -U myuser -P application_password user add ``` (`pgagroal` user) @@ -39,7 +39,7 @@ You will need a password mapping for each user defined in the `pgagroal_users.co In order to apply changes, you need to restart `pgagroal`, so as the `pgagroal` operating system user do: ``` -pgagroal-cli -c /etc/pgagroal/pgagroal.conf stop +pgagroal-cli -c /etc/pgagroal/pgagroal.conf shutdown pgagroal -c /etc/pgagroal/pgagroal.conf -a /etc/pgagroal/pgagroal_hba.conf -u /etc/pgagroal/pgagroal_users.conf -F /etc/pgagroal/pgagroal_frontend_users.conf ``` diff --git a/src/admin.c b/src/admin.c index 453629c8..e96e8c1d 100644 --- a/src/admin.c +++ b/src/admin.c @@ -77,6 +77,7 @@ usage(void) printf("\n"); printf("Options:\n"); printf(" -f, --file FILE Set the path to a user file\n"); + printf(" Defaults to %s", PGAGROAL_DEFAULT_USERS_FILE); printf(" -U, --user USER Set the user name\n"); printf(" -P, --password PASSWORD Set the password for the user\n"); printf(" -g, --generate Generate a password\n"); @@ -86,10 +87,11 @@ usage(void) printf("\n"); printf("Commands:\n"); printf(" master-key Create or update the master key\n"); - printf(" add-user Add a user\n"); - printf(" update-user Update a user\n"); - printf(" remove-user Remove a user\n"); - printf(" list-users List all users\n"); + printf(" user Manage a specific user, where can be\n"); + printf(" - add to add a new user\n"); + printf(" - del to remove an existing user\n"); + printf(" - edit to change the password for an existing user\n"); + printf(" - ls to list all available users\n"); printf("\n"); printf("pgagroal: %s\n", PGAGROAL_HOMEPAGE); printf("Report bugs: %s\n", PGAGROAL_ISSUES); @@ -98,7 +100,6 @@ usage(void) int main(int argc, char** argv) { - int exit_code = 0; int c; char* username = NULL; char* password = NULL; @@ -169,23 +170,47 @@ main(int argc, char** argv) { action = ACTION_MASTER_KEY; } - else if (!strcmp("add-user", argv[argc - 1])) + 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 (!strcmp("update-user", argv[argc - 1])) + 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 (!strcmp("remove-user", argv[argc - 1])) + 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 (!strcmp("list-users", argv[argc - 1])) + 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; } + // 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 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) + { + file_path = PGAGROAL_DEFAULT_USERS_FILE; + } + if (action == ACTION_MASTER_KEY) { if (master_key(password, generate_pwd, pwd_length)) @@ -195,69 +220,41 @@ main(int argc, char** argv) } else if (action == ACTION_ADD_USER) { - if (file_path != NULL) + if (add_user(file_path, username, password, generate_pwd, pwd_length)) { - if (add_user(file_path, username, password, generate_pwd, pwd_length)) - { - errx(1, "Error for add-user"); - } - } - else - { - errx(1, "Missing file argument"); + errx(1, "Error for "); } } else if (action == ACTION_UPDATE_USER) { - if (file_path != NULL) - { - if (update_user(file_path, username, password, generate_pwd, pwd_length)) - { - errx(1, "Error for update-user"); - } - } - else + if (update_user(file_path, username, password, generate_pwd, pwd_length)) { - errx(1, "Missing file argument"); + errx(1, "Error for "); } } else if (action == ACTION_REMOVE_USER) { - if (file_path != NULL) - { - if (remove_user(file_path, username)) - { - errx(1, "Error for remove-user"); - } - } - else + + if (remove_user(file_path, username)) { - errx(1, "Missing file argument"); + errx(1, "Error for "); } } else if (action == ACTION_LIST_USERS) { - if (file_path != NULL) - { - if (list_users(file_path)) - { - errx(1, "Error for list-users"); - } - } - else + + if (list_users(file_path)) { - errx(1, "Missing file argument"); + errx(1, "Error for "); } + } } - if (action == ACTION_UNKNOWN) - { - usage(); - exit_code = 1; - } + exit(0); - return exit_code; +error: + exit(1); } static int diff --git a/src/cli.c b/src/cli.c index ba784a32..a41b23b3 100644 --- a/src/cli.c +++ b/src/cli.c @@ -98,7 +98,7 @@ usage(void) printf("\n"); printf("Usage:\n"); - printf(" pgagroal-cli [ -c CONFIG_FILE ] [ COMMAND ] \n"); + printf(" pgagroal-cli [ OPTIONS ] [ COMMAND ] \n"); printf("\n"); printf("Options:\n"); printf(" -c, --config CONFIG_FILE Set the path to the pgagroal.conf file\n"); @@ -113,26 +113,37 @@ usage(void) printf(" -?, --help Display help\n"); printf("\n"); printf("Commands:\n"); - printf(" flush-idle Flush idle connections\n"); - printf(" flush-gracefully Flush all connections gracefully\n"); - printf(" flush-all Flush all connections. USE WITH CAUTION !\n"); - printf(" is-alive Is pgagroal alive\n"); - printf(" enable Enable a database\n"); - printf(" disable Disable a database\n"); - printf(" gracefully Stop pgagroal gracefully\n"); - printf(" stop Stop pgagroal\n"); - printf(" cancel-shutdown Cancel the graceful shutdown\n"); + printf(" flush [mode] [database] Flush connections according to .\n"); + printf(" Allowed modes are:\n"); + printf(" - 'gracefully' (default) to flush all connections gracefully\n"); + printf(" - 'idle' to flush only idle connections\n"); + printf(" - 'all' to flush all connections. USE WITH CAUTION!\n"); + printf(" If no name is specified, applies to all databases.\n"); + printf(" is-alive Is pgagroal alive?\n"); + printf(" enable [database] Enables the specified databases (or all databases)\n"); + printf(" disable [database] Disables the specified databases (or all databases)\n"); + printf(" shutdown [mode] Stops pgagroal pooler. The can be:\n"); + printf(" - 'gracefully' (default) waits for active connections to quit\n"); + printf(" - 'immediate' forces connections to close and terminate\n"); + printf(" - 'cancel' avoid a previously issued 'shutdown gracefully'\n"); printf(" status Status of pgagroal\n"); printf(" details Detailed status of pgagroal\n"); - printf(" switch-to Switch to another primary\n"); - printf(" reload Reload the configuration\n"); - printf(" reset Reset the Prometheus statistics\n"); - printf(" reset-server Reset the state of a server\n"); - printf(" config-get Retrieves a configuration value\n"); - printf(" config-set Modifies a configuration value\n"); + printf(" switch-to Switches to the specified primary server\n"); + printf(" conf Manages the configuration (e.g., reloads the configuration\n"); + printf(" The subcommand can be:\n"); + printf(" - 'reload' to issue a configuration reload;\n"); + printf(" - 'get' to obtain information about a runtime configuration value;\n"); + printf(" conf get \n."); + printf(" - 'set' to modify a configuration value;\n"); + printf(" conf set \n."); + printf(" clear Resets either the Prometheus statistics or the specified server.\n"); + printf(" can be\n"); + printf(" - 'server' (default) followed by a server name\n"); + printf(" - a server name on its own\n"); + printf(" - 'prometheus' to reset the Prometheus metrics\n"); printf("\n"); - printf("pgagroal: %s\n", PGAGROAL_HOMEPAGE); - printf("Report bugs: %s\n", PGAGROAL_ISSUES); + printf("pgagroal: <%s>\n", PGAGROAL_HOMEPAGE); + printf("Report bugs: <%s>\n", PGAGROAL_ISSUES); } int @@ -326,151 +337,129 @@ main(int argc, char** argv) } } - if (argc > 0) + 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 (!strcmp("flush-idle", argv[argc - 1]) || !strcmp("flush-idle", argv[argc - 2])) - { - mode = FLUSH_IDLE; - action = ACTION_FLUSH; - if (!strcmp("flush-idle", argv[argc - 1])) - { - database = "*"; - } - else - { - database = argv[argc - 1]; - } - } - else if (!strcmp("flush-gracefully", argv[argc - 1]) || !strcmp("flush-gracefully", argv[argc - 2])) - { - mode = FLUSH_GRACEFULLY; - action = ACTION_FLUSH; - if (!strcmp("flush-gracefully", argv[argc - 1])) - { - database = "*"; - } - else - { - database = argv[argc - 1]; - } - } - else if (!strcmp("flush-all", argv[argc - 1]) || !strcmp("flush-all", argv[argc - 2])) - { - mode = FLUSH_ALL; - action = ACTION_FLUSH; - if (!strcmp("flush-all", argv[argc - 1])) - { - database = "*"; - } - else - { - database = argv[argc - 1]; - } - } - else if (!strcmp("enable", argv[argc - 1]) || !strcmp("enable", argv[argc - 2])) - { - action = ACTION_ENABLEDB; - if (!strcmp("enable", argv[argc - 1])) - { - database = "*"; - } - else - { - database = argv[argc - 1]; - } - } - else if (!strcmp("disable", argv[argc - 1]) || !strcmp("disable", argv[argc - 2])) - { - action = ACTION_DISABLEDB; - if (!strcmp("disable", argv[argc - 1])) - { - database = "*"; - } - else - { - database = argv[argc - 1]; - } - } - else if (!strcmp("gracefully", argv[argc - 1])) - { - action = ACTION_GRACEFULLY; - } - else if (!strcmp("stop", argv[argc - 1])) - { - action = ACTION_STOP; - } - else if (!strcmp("status", argv[argc - 1])) - { - action = ACTION_STATUS; - } - else if (!strcmp("details", argv[argc - 1])) - { - action = ACTION_DETAILS; - } - else if (!strcmp("is-alive", argv[argc - 1])) - { - action = ACTION_ISALIVE; - } - else if (!strcmp("cancel-shutdown", argv[argc - 1])) - { - action = ACTION_CANCELSHUTDOWN; - } - else if (!strcmp("reset", argv[argc - 1])) - { - action = ACTION_RESET; - } - else if (!strcmp("reset-server", argv[argc - 1]) || !strcmp("reset-server", argv[argc - 2])) - { - if (!strcmp("reset-server", argv[argc - 2])) - { - action = ACTION_RESET_SERVER; - server = argv[argc - 1]; - } - } - else if (!strcmp("switch-to", argv[argc - 1]) || !strcmp("switch-to", argv[argc - 2])) + 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", NULL)) + { + action = ACTION_STATUS; + pgagroal_log_trace("Command: "); + } + else if (parse_command_simple(argc, argv, optind, "details", NULL)) + { + action = ACTION_DETAILS; + pgagroal_log_trace("Command:
"); + } + else if (parse_command_simple(argc, argv, optind, "is-alive", NULL)) + { + 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); + } + else if (parse_command_simple(argc, argv, optind, "conf", "reload") + || parse_deprecated_command(argc, argv, optind, "reload", NULL, "conf reload", 1, 6)) + { + /* Local connection only */ + if (configuration_path != NULL) { - if (!strcmp("switch-to", argv[argc - 2])) - { - action = ACTION_SWITCH_TO; - server = argv[argc - 1]; - } + action = ACTION_RELOAD; } - else if (!strcmp("reload", argv[argc - 1])) + pgagroal_log_debug("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_debug("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_debug("Command: [%s] = [%s]", config_key, config_value); + } + + if (action != ACTION_UNKNOWN) + { + 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; } } - else if (argc > 2 && !strncmp("config-get", argv[argc - 2], MISC_LENGTH) && strlen(argv[argc - 1]) > 0) - { - /* get a configuration value */ - action = ACTION_CONFIG_GET; - config_key = argv[argc - 1]; - } - else if (argc > 3 && !strncmp("config-set", argv[argc - 3], MISC_LENGTH) - && strlen(argv[argc - 2]) > 0 - && strlen(argv[argc - 1]) > 0) - { - /* set a configuration value */ - action = ACTION_CONFIG_SET; - config_key = argv[argc - 2]; - config_value = argv[argc - 1]; - } - - 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 */ @@ -495,125 +484,127 @@ main(int argc, char** argv) goto done; } - /* User name */ - if (username == NULL) - { + } + + /* User name */ + if (username == NULL) + { 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]; - } + printf("User name: "); - if (username == NULL || strlen(username) == 0) + memset(&un, 0, sizeof(un)); + if (fgets(&un[0], sizeof(un), stdin) == NULL) { - goto username; + exit_code = 1; + goto done; } + un[strlen(un) - 1] = 0; + username = &un[0]; + } - /* Password */ - if (password == NULL) - { -password: - if (password != NULL) - { - free(password); - password = NULL; - } - - printf("Password : "); - password = pgagroal_get_password(); - printf("\n"); - } - else - { - do_free = false; - } + if (username == NULL || strlen(username) == 0) + { + goto username; + } - for (int i = 0; i < strlen(password); i++) + /* Password */ + if (password == NULL) + { + if (password != NULL) { - if ((unsigned char)(*(password + i)) & 0x80) - { - goto password; - } + free(password); + password = NULL; } - /* Authenticate */ - if (pgagroal_remote_management_scram_sha256(username, password, socket, &s_ssl) != AUTH_SUCCESS) + printf("Password : "); + password = pgagroal_get_password(); + printf("\n"); + } + else + { + do_free = false; + } + + for (int i = 0; i < strlen(password); i++) + { + if ((unsigned char)(*(password + i)) & 0x80) { + warnx("Bad credentials for %s\n", username); goto done; } } - } - if (action == ACTION_FLUSH) - { - exit_code = flush(s_ssl, socket, mode, database); - } - else if (action == ACTION_ENABLEDB) - { - exit_code = enabledb(s_ssl, socket, database); - } - else if (action == ACTION_DISABLEDB) - { - exit_code = disabledb(s_ssl, socket, database); - } - else if (action == ACTION_GRACEFULLY) - { - exit_code = gracefully(s_ssl, socket); - } - else if (action == ACTION_STOP) - { - exit_code = stop(s_ssl, socket); - } - else if (action == ACTION_CANCELSHUTDOWN) - { - exit_code = cancel_shutdown(s_ssl, socket); - } - else if (action == ACTION_STATUS) - { - exit_code = status(s_ssl, socket); - } - else if (action == ACTION_DETAILS) - { - exit_code = details(s_ssl, socket); - } - else if (action == ACTION_ISALIVE) - { - exit_code = isalive(s_ssl, socket); - } - else if (action == ACTION_RESET) - { - exit_code = reset(s_ssl, socket); - } - else if (action == ACTION_RESET_SERVER) - { - exit_code = reset_server(s_ssl, socket, server); - } - else if (action == ACTION_SWITCH_TO) - { - exit_code = switch_to(s_ssl, socket, server); - } - else if (action == ACTION_RELOAD) - { - exit_code = reload(s_ssl, socket); - } - else if (action == ACTION_CONFIG_GET) - { - exit_code = config_get(s_ssl, socket, config_key, verbose); - } - else if (action == ACTION_CONFIG_SET) - { - exit_code = config_set(s_ssl, socket, config_key, config_value, verbose); + /* 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); + } + else if (action == ACTION_ENABLEDB) + { + exit_code = enabledb(s_ssl, socket, database); + } + else if (action == ACTION_DISABLEDB) + { + exit_code = disabledb(s_ssl, socket, database); + } + else if (action == ACTION_GRACEFULLY) + { + exit_code = gracefully(s_ssl, socket); + } + else if (action == ACTION_STOP) + { + exit_code = stop(s_ssl, socket); + } + else if (action == ACTION_CANCELSHUTDOWN) + { + exit_code = cancel_shutdown(s_ssl, socket); + } + else if (action == ACTION_STATUS) + { + exit_code = status(s_ssl, socket); + } + else if (action == ACTION_DETAILS) + { + exit_code = details(s_ssl, socket); + } + else if (action == ACTION_ISALIVE) + { + exit_code = isalive(s_ssl, socket); + } + else if (action == ACTION_RESET) + { + exit_code = reset(s_ssl, socket); + } + else if (action == ACTION_RESET_SERVER) + { + exit_code = reset_server(s_ssl, socket, server); + } + else if (action == ACTION_SWITCH_TO) + { + exit_code = switch_to(s_ssl, socket, server); + } + else if (action == ACTION_RELOAD) + { + exit_code = reload(s_ssl, socket); + } + else if (action == ACTION_CONFIG_GET) + { + exit_code = config_get(s_ssl, socket, config_key, verbose); + } + else if (action == ACTION_CONFIG_SET) + { + exit_code = config_set(s_ssl, socket, config_key, config_value, verbose); + } + done: if (s_ssl != NULL) @@ -633,6 +624,7 @@ 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/utils.h b/src/include/utils.h index 166ffd70..4cb000ed 100644 --- a/src/include/utils.h +++ b/src/include/utils.h @@ -375,6 +375,125 @@ pgagroal_backtrace(void); #endif +/** + * Utility function to parse the command line + * and search for a command. + * + * The function tries to be smart, in helping to find out + * a command with the possible subcommand. + * + * @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 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. + * + * @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); #ifdef __cplusplus } #endif diff --git a/src/libpgagroal/configuration.c b/src/libpgagroal/configuration.c index 05dfbbf4..f138eb5a 100644 --- a/src/libpgagroal/configuration.c +++ b/src/libpgagroal/configuration.c @@ -4011,10 +4011,10 @@ pgagroal_apply_main_configuration(struct configuration* config, } else if (key_in_section("max_connection_age", section, key, true, &unknown)) { - if (as_int(value, &config->max_connection_age)) - { - unknown = true; - } + if (as_int(value, &config->max_connection_age)) + { + unknown = true; + } } else if (key_in_section("validation", section, key, true, &unknown)) { diff --git a/src/libpgagroal/utils.c b/src/libpgagroal/utils.c index 734023b6..bfc86de1 100644 --- a/src/libpgagroal/utils.c +++ b/src/libpgagroal/utils.c @@ -44,6 +44,7 @@ #include #include #include +#include #ifndef EVBACKEND_LINUXAIO #define EVBACKEND_LINUXAIO 0x00000040U @@ -952,3 +953,127 @@ 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; + } + + if (subcommand) + { + // thre must be a subcommand check + offset++; + + if (argc <= offset) + { + // not enough command args! + return false; + } + + if (strncmp(argv[offset], subcommand, MISC_LENGTH)) + { + return false; + } + } + + if (key) + { + // need to evaluate the database or server or configuration key + offset++; + *key = argc > offset ? argv[offset] : default_key; + if (*key == NULL || strlen(*key) == 0) + { + goto error; + } + + // do I need also a value? + if (value) + { + offset++; + *value = argc > offset ? argv[offset] : default_value; + + if (*value == NULL || strlen(*value) == 0) + { + goto error; + } + + } + } + + 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) + { + 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; + } + + if (value) + { + // need to evaluate the database or server + offset++; + *value = argc > offset ? argv[offset] : "*"; + } + + // warn the user if there is enough information + // about deprecation + if (deprecated_by + && pgagroal_version_ge(deprecated_since_major, deprecated_since_minor, 0)) + { + warnx("command <%s> has been deprecated by <%s> since version %d.%d\n", + command, deprecated_by, deprecated_since_major, 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); +}