From 6112ec62cda83a4e1711b6ebbc7092170ca55695 Mon Sep 17 00:00:00 2001 From: haotian Date: Mon, 3 Apr 2023 11:12:58 +0800 Subject: [PATCH 01/24] [#361] Add a `max_connection_age` setting This defines how long a connection will live in seconds - Add a `max_connection_age` member to `struct configuration`. It will be checked upon returned to the pool, or during idle timeout. - Add new STATE, TRACKER, and Prometheus metric for `max_connection_age` - Add documentation for `max_connection_age` - Add a `start_time` member to `struct connection`. Its implementation is similar to `timestamp` --- doc/ARCHITECTURE.md | 1 + doc/CONFIGURATION.md | 1 + doc/PIPELINES.md | 4 +- doc/man/pgagroal.conf.5.rst | 3 + src/include/pgagroal.h | 42 +++++----- src/include/pool.h | 6 ++ src/include/prometheus.h | 6 ++ src/include/tracker.h | 23 +++--- src/libpgagroal/configuration.c | 18 +++++ src/libpgagroal/management.c | 2 + src/libpgagroal/pool.c | 135 +++++++++++++++++++++++++++++++- src/libpgagroal/prometheus.c | 28 +++++++ src/libpgagroal/utils.c | 2 + src/main.c | 26 ++++++ 14 files changed, 264 insertions(+), 33 deletions(-) diff --git a/doc/ARCHITECTURE.md b/doc/ARCHITECTURE.md index 0d7e8aa1..10e6373e 100644 --- a/doc/ARCHITECTURE.md +++ b/doc/ARCHITECTURE.md @@ -35,6 +35,7 @@ connection, and move them around in the connection state diagram. The state diag | `STATE_GRACEFULLY` | The connection will be killed upon return to the pool | | `STATE_FLUSH` | The connection is being flushed | | `STATE_IDLE_CHECK` | The connection is being idle timeout checked | +| `STATE_MAX_CONNECTION_AGE` | The connection is being max connection age checked | | `STATE_VALIDATION` | The connection is being validated | | `STATE_REMOVE` | The connection is being removed | diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 9d2718a7..6a668fd4 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -61,6 +61,7 @@ The available keys and their accepted values are reported in the table below. | log_disconnections | `off` | Bool | No | Log disconnects | | blocking_timeout | 30 | Int | No | The number of seconds the process will be blocking for a connection (disable = 0) | | idle_timeout | 0 | Int | No | The number of seconds a connection is been kept alive (disable = 0) | +| max_connection_age | 0 | Int | No | The maximum number of seconds that a connection will live (disable = 0) | | validation | `off` | String | No | Should connection validation be performed. Valid options: `off`, `foreground` and `background` | | background_interval | 300 | Int | No | The interval between background validation scans in seconds | | max_retries | 5 | Int | No | The maximum number of iterations to obtain a connection | diff --git a/doc/PIPELINES.md b/doc/PIPELINES.md index d67c6bbf..52d9ee5f 100644 --- a/doc/PIPELINES.md +++ b/doc/PIPELINES.md @@ -97,8 +97,8 @@ latency. __Important__ Make sure that the `blocking_timeout` settings to set to 0. Otherwise active clients -may timeout during their workload. Likewise it is best to disable idle connection timeout by -setting `idle_timeout` to 0. +may timeout during their workload. Likewise it is best to disable idle connection timeout +and max connection age by setting `idle_timeout` and `max_connection_age` to 0. It is highly recommended that you prefill all connections for each user. diff --git a/doc/man/pgagroal.conf.5.rst b/doc/man/pgagroal.conf.5.rst index bf788933..c36ed259 100644 --- a/doc/man/pgagroal.conf.5.rst +++ b/doc/man/pgagroal.conf.5.rst @@ -95,6 +95,9 @@ blocking_timeout idle_timeout The number of seconds a connection is been kept alive (disable = 0). Default is 0 +max_connection_age + The maximum number of seconds that a connection will live (disable = 0). Default is 0 + validation Should connection validation be performed. Valid options: off, foreground and background. Default is off diff --git a/src/include/pgagroal.h b/src/include/pgagroal.h index 473bb101..ede1bde0 100644 --- a/src/include/pgagroal.h +++ b/src/include/pgagroal.h @@ -92,15 +92,16 @@ extern "C" { #define NUMBER_OF_SECURITY_MESSAGES 5 -#define STATE_NOTINIT -2 -#define STATE_INIT -1 -#define STATE_FREE 0 -#define STATE_IN_USE 1 -#define STATE_GRACEFULLY 2 -#define STATE_FLUSH 3 -#define STATE_IDLE_CHECK 4 -#define STATE_VALIDATION 5 -#define STATE_REMOVE 6 +#define STATE_NOTINIT -2 +#define STATE_INIT -1 +#define STATE_FREE 0 +#define STATE_IN_USE 1 +#define STATE_GRACEFULLY 2 +#define STATE_FLUSH 3 +#define STATE_IDLE_CHECK 4 +#define STATE_MAX_CONNECTION_AGE 5 +#define STATE_VALIDATION 6 +#define STATE_REMOVE 7 #define SECURITY_INVALID -2 #define SECURITY_REJECT -1 @@ -280,6 +281,7 @@ struct connection int backend_secret; /**< The backend secret */ signed char limit_rule; /**< The limit rule used */ + time_t start_time; /**< The start timestamp */ time_t timestamp; /**< The last used timestamp */ pid_t pid; /**< The associated process id */ int fd; /**< The descriptor */ @@ -359,16 +361,17 @@ struct prometheus atomic_ulong session_time[HISTOGRAM_BUCKETS]; /**< The histogram buckets */ atomic_ulong session_time_sum; /**< Total session time */ - atomic_ulong connection_error; /**< The number of error calls */ - atomic_ulong connection_kill; /**< The number of kill calls */ - atomic_ulong connection_remove; /**< The number of remove calls */ - atomic_ulong connection_timeout; /**< The number of timeout calls */ - atomic_ulong connection_return; /**< The number of return calls */ - atomic_ulong connection_invalid; /**< The number of invalid calls */ - atomic_ulong connection_get; /**< The number of get calls */ - atomic_ulong connection_idletimeout; /**< The number of idle timeout calls */ - atomic_ulong connection_flush; /**< The number of flush calls */ - atomic_ulong connection_success; /**< The number of success calls */ + atomic_ulong connection_error; /**< The number of error calls */ + atomic_ulong connection_kill; /**< The number of kill calls */ + atomic_ulong connection_remove; /**< The number of remove calls */ + atomic_ulong connection_timeout; /**< The number of timeout calls */ + atomic_ulong connection_return; /**< The number of return calls */ + atomic_ulong connection_invalid; /**< The number of invalid calls */ + atomic_ulong connection_get; /**< The number of get calls */ + atomic_ulong connection_idletimeout; /**< The number of idle timeout calls */ + atomic_ulong connection_max_connection_age; /**< The number of max connection age calls */ + atomic_ulong connection_flush; /**< The number of flush calls */ + atomic_ulong connection_success; /**< The number of success calls */ /**< The number of connection awaiting due to `blocking_timeout` */ atomic_ulong connections_awaiting[NUMBER_OF_LIMITS]; @@ -451,6 +454,7 @@ struct configuration int blocking_timeout; /**< The blocking timeout in seconds */ int idle_timeout; /**< The idle timeout in seconds */ + int max_connection_age; /**< The max connection age in seconds */ int validation; /**< Validation mode */ int background_interval; /**< Background validation timer in seconds */ int max_retries; /**< The maximum number of retries */ diff --git a/src/include/pool.h b/src/include/pool.h index 67aa558e..434e7e9a 100644 --- a/src/include/pool.h +++ b/src/include/pool.h @@ -77,6 +77,12 @@ pgagroal_kill_connection(int slot, SSL* ssl); void pgagroal_idle_timeout(void); +/** + * Perform max connection age check + */ +void +pgagroal_max_connection_age(void); + /** * Perform connection validation */ diff --git a/src/include/prometheus.h b/src/include/prometheus.h index 179330de..51ea8b37 100644 --- a/src/include/prometheus.h +++ b/src/include/prometheus.h @@ -125,6 +125,12 @@ pgagroal_prometheus_connection_get(void); void pgagroal_prometheus_connection_idletimeout(void); +/** + * Connection max connection age + */ +void +pgagroal_prometheus_connection_max_connection_age(void); + /** * Connection awaiting due to `blocking_timeout`. * Tracks the total awaiting connections and also the diff --git a/src/include/tracker.h b/src/include/tracker.h index 30ac19ab..c167f4c0 100644 --- a/src/include/tracker.h +++ b/src/include/tracker.h @@ -51,17 +51,18 @@ extern "C" { #define TRACKER_BAD_CONNECTION 9 #define TRACKER_IDLE_TIMEOUT 10 -#define TRACKER_INVALID_CONNECTION 11 -#define TRACKER_FLUSH 12 -#define TRACKER_REMOVE_CONNECTION 13 - -#define TRACKER_PREFILL 14 -#define TRACKER_PREFILL_RETURN 15 -#define TRACKER_PREFILL_KILL 16 -#define TRACKER_WORKER_RETURN1 17 -#define TRACKER_WORKER_RETURN2 18 -#define TRACKER_WORKER_KILL1 19 -#define TRACKER_WORKER_KILL2 20 +#define TRACKER_MAX_CONNECTION_AGE 11 +#define TRACKER_INVALID_CONNECTION 12 +#define TRACKER_FLUSH 13 +#define TRACKER_REMOVE_CONNECTION 14 + +#define TRACKER_PREFILL 15 +#define TRACKER_PREFILL_RETURN 16 +#define TRACKER_PREFILL_KILL 17 +#define TRACKER_WORKER_RETURN1 18 +#define TRACKER_WORKER_RETURN2 19 +#define TRACKER_WORKER_KILL1 20 +#define TRACKER_WORKER_KILL2 21 #define TRACKER_TX_RETURN_CONNECTION_START 30 #define TRACKER_TX_RETURN_CONNECTION_STOP 31 diff --git a/src/libpgagroal/configuration.c b/src/libpgagroal/configuration.c index f75c2c37..88729e88 100644 --- a/src/libpgagroal/configuration.c +++ b/src/libpgagroal/configuration.c @@ -132,6 +132,7 @@ pgagroal_init_configuration(void* shm) config->blocking_timeout = 30; config->idle_timeout = 0; + config->max_connection_age = 0; config->validation = VALIDATION_OFF; config->background_interval = 300; config->max_retries = 5; @@ -645,6 +646,11 @@ pgagroal_validate_configuration(void* shm, bool has_unix_socket, bool has_main_s pgagroal_log_warn("pgagroal: Using idle_timeout for the transaction pipeline is not recommended"); } + if (config->max_connection_age > 0) + { + pgagroal_log_warn("pgagroal: Using max_connection_age for the transaction pipeline is not recommended"); + } + if (config->validation == VALIDATION_FOREGROUND) { pgagroal_log_warn("pgagroal: Using foreground validation for the transaction pipeline is not recommended"); @@ -2262,6 +2268,7 @@ transfer_configuration(struct configuration* config, struct configuration* reloa config->blocking_timeout = reload->blocking_timeout; config->idle_timeout = reload->idle_timeout; + config->max_connection_age = reload->max_connection_age; config->validation = reload->validation; config->background_interval = reload->background_interval; config->max_retries = reload->max_retries; @@ -3190,6 +3197,10 @@ pgagroal_write_config_value(char* buffer, char* config_key, size_t buffer_size) { return to_int(buffer, config->idle_timeout); } + else if (!strncmp(key, "max_connection_age", MISC_LENGTH)) + { + return to_int(buffer, config->max_connection_age); + } else if (!strncmp(key, "validation", MISC_LENGTH)) { return to_validation(buffer, config->validation); @@ -3998,6 +4009,13 @@ pgagroal_apply_main_configuration(struct configuration* config, unknown = true; } } + else if (key_in_section("max_connection_age", section, key, true, &unknown)) + { + if (as_int(value, &config->max_connection_age)) + { + unknown = true; + } + } else if (key_in_section("validation", section, key, true, &unknown)) { config->validation = as_validation(value); diff --git a/src/libpgagroal/management.c b/src/libpgagroal/management.c index dfdbb436..1b104149 100644 --- a/src/libpgagroal/management.c +++ b/src/libpgagroal/management.c @@ -648,6 +648,7 @@ pgagroal_management_write_status(int socket, bool graceful) case STATE_FREE: case STATE_FLUSH: case STATE_IDLE_CHECK: + case STATE_MAX_CONNECTION_AGE: case STATE_VALIDATION: case STATE_REMOVE: total++; @@ -914,6 +915,7 @@ pgagroal_management_write_details(int socket) memset(&details, 0, sizeof(details)); + pgagroal_write_long(details, (long)config->connections[i].start_time); pgagroal_write_long(details, (long)config->connections[i].timestamp); pgagroal_write_int32(details + 8, (int)config->connections[i].pid); pgagroal_write_int32(details + 12, (int)config->connections[i].fd); diff --git a/src/libpgagroal/pool.c b/src/libpgagroal/pool.c index 47bd0d9c..98c9aab5 100644 --- a/src/libpgagroal/pool.c +++ b/src/libpgagroal/pool.c @@ -267,6 +267,11 @@ pgagroal_get_connection(char* username, char* database, bool reuse, bool transac } } + if (config->connections[*slot].start_time == -1) + { + config->connections[*slot].start_time = time(NULL); + } + config->connections[*slot].timestamp = time(NULL); atomic_store(&prometheus->client_wait_time, difftime(time(NULL), start_time)); @@ -364,9 +369,31 @@ pgagroal_return_connection(int slot, SSL* ssl, bool transaction_mode) { int state; struct configuration* config; + time_t now; + signed char in_use; + signed char age_check; config = (struct configuration*)shmem; + /* Kill the connection, if it lives longer than max_connection_age */ + if (config->max_connection_age > 0) + { + now = time(NULL); + in_use = STATE_IN_USE; + age_check = STATE_MAX_CONNECTION_AGE; + if (atomic_compare_exchange_strong(&config->states[slot], &in_use, age_check)) + { + double age = difftime(now, config->connections[slot].start_time); + if ((age >= (double) config->max_connection_age && !config->connections[slot].tx_mode) || + !atomic_compare_exchange_strong(&config->states[slot], &age_check, STATE_IN_USE)) + { + pgagroal_prometheus_connection_max_connection_age(); + pgagroal_tracking_event_slot(TRACKER_MAX_CONNECTION_AGE, slot); + return pgagroal_kill_connection(slot, ssl); + } + } + } + /* Verify the socket for the slot */ if (!transaction_mode && !pgagroal_socket_isvalid(config->connections[slot].fd)) { @@ -509,6 +536,7 @@ pgagroal_kill_connection(int slot, SSL* ssl) config->connections[slot].backend_secret = 0; config->connections[slot].limit_rule = -1; + config->connections[slot].start_time = -1; config->connections[slot].timestamp = -1; config->connections[slot].fd = -1; config->connections[slot].pid = -1; @@ -579,6 +607,65 @@ pgagroal_idle_timeout(void) exit(0); } +void +pgagroal_max_connection_age(void) +{ + bool prefill; + time_t now; + signed char free; + signed char age_check; + struct configuration* config; + + pgagroal_start_logging(); + pgagroal_memory_init(); + + config = (struct configuration*)shmem; + now = time(NULL); + prefill = false; + + pgagroal_log_debug("pgagroal_max_connection_age"); + + /* Here we run backwards in order to keep hot connections in the beginning */ + for (int i = config->max_connections - 1; i >= 0; i--) + { + free = STATE_FREE; + age_check = STATE_MAX_CONNECTION_AGE; + + if (atomic_compare_exchange_strong(&config->states[i], &free, age_check)) + { + double age = difftime(now, config->connections[i].start_time); + if (age >= (double)config->max_connection_age && !config->connections[i].tx_mode) + { + pgagroal_prometheus_connection_max_connection_age(); + pgagroal_tracking_event_slot(TRACKER_MAX_CONNECTION_AGE, i); + pgagroal_kill_connection(i, NULL); + prefill = true; + } + else + { + if (!atomic_compare_exchange_strong(&config->states[i], &age_check, STATE_FREE)) + { + pgagroal_prometheus_connection_max_connection_age(); + pgagroal_tracking_event_slot(TRACKER_MAX_CONNECTION_AGE, i); + pgagroal_kill_connection(i, NULL); + prefill = true; + } + } + } + } + + if (prefill) + { + pgagroal_prefill_if_can(true, false); + } + + pgagroal_pool_status(); + pgagroal_memory_destroy(); + pgagroal_stop_logging(); + + exit(0); +} + void pgagroal_validation(void) { @@ -605,7 +692,7 @@ pgagroal_validation(void) if (atomic_compare_exchange_strong(&config->states[i], &free, validation)) { bool kill = false; - double diff; + double diff, age; /* Verify the socket for the slot */ if (!pgagroal_socket_isvalid(config->connections[i].fd)) @@ -623,6 +710,16 @@ pgagroal_validation(void) } } + /* Also check for max_connection_age */ + if (!kill && config->max_connection_age > 0) + { + age = difftime(now, config->connections[i].start_time); + if (age >= (double)config->max_connection_age) + { + kill = true; + } + } + /* Ok, send SELECT 1 */ if (!kill) { @@ -757,6 +854,7 @@ pgagroal_flush(int mode, char* database) atomic_store(&config->states[i], STATE_GRACEFULLY); break; case STATE_IDLE_CHECK: + case STATE_MAX_CONNECTION_AGE: case STATE_VALIDATION: case STATE_REMOVE: atomic_store(&config->states[i], STATE_GRACEFULLY); @@ -817,6 +915,7 @@ pgagroal_flush_server(signed char server) atomic_store(&config->states[i], STATE_GRACEFULLY); break; case STATE_IDLE_CHECK: + case STATE_MAX_CONNECTION_AGE: case STATE_VALIDATION: case STATE_REMOVE: atomic_store(&config->states[i], STATE_GRACEFULLY); @@ -977,6 +1076,7 @@ pgagroal_pool_init(void) config->connections[i].server = -1; config->connections[i].has_security = SECURITY_INVALID; config->connections[i].limit_rule = -1; + config->connections[i].start_time = -1; config->connections[i].timestamp = -1; config->connections[i].fd = -1; config->connections[i].pid = -1; @@ -1137,6 +1237,7 @@ connection_details(int slot) { int state; char time_buf[32]; + char start_buf[32]; struct configuration* config; struct connection connection; @@ -1149,6 +1250,10 @@ connection_details(int slot) ctime_r(&(connection.timestamp), &time_buf[0]); time_buf[strlen(time_buf) - 1] = 0; + memset(&start_buf, 0, sizeof(start_buf)); + ctime_r(&(connection.start_time), &start_buf[0]); + start_buf[strlen(start_buf) - 1] = 0; + switch (state) { case STATE_NOTINIT: @@ -1169,6 +1274,7 @@ connection_details(int slot) pgagroal_log_debug(" Database: %s", connection.database); pgagroal_log_debug(" AppName: %s", connection.appname); pgagroal_log_debug(" Rule: %d", connection.limit_rule); + pgagroal_log_debug(" Start: %s", &start_buf[0]); pgagroal_log_debug(" Time: %s", &time_buf[0]); pgagroal_log_debug(" FD: %d", connection.fd); pgagroal_log_trace(" PID: %d", connection.pid); @@ -1189,6 +1295,7 @@ connection_details(int slot) pgagroal_log_debug(" Database: %s", connection.database); pgagroal_log_debug(" AppName: %s", connection.appname); pgagroal_log_debug(" Rule: %d", connection.limit_rule); + pgagroal_log_debug(" Start: %s", &start_buf[0]); pgagroal_log_debug(" Time: %s", &time_buf[0]); pgagroal_log_debug(" FD: %d", connection.fd); pgagroal_log_trace(" PID: %d", connection.pid); @@ -1209,6 +1316,7 @@ connection_details(int slot) pgagroal_log_debug(" Database: %s", connection.database); pgagroal_log_debug(" AppName: %s", connection.appname); pgagroal_log_debug(" Rule: %d", connection.limit_rule); + pgagroal_log_debug(" Start: %s", &start_buf[0]); pgagroal_log_debug(" Time: %s", &time_buf[0]); pgagroal_log_debug(" FD: %d", connection.fd); pgagroal_log_trace(" PID: %d", connection.pid); @@ -1229,6 +1337,7 @@ connection_details(int slot) pgagroal_log_debug(" Database: %s", connection.database); pgagroal_log_debug(" AppName: %s", connection.appname); pgagroal_log_debug(" Rule: %d", connection.limit_rule); + pgagroal_log_debug(" Start: %s", &start_buf[0]); pgagroal_log_debug(" Time: %s", &time_buf[0]); pgagroal_log_debug(" FD: %d", connection.fd); pgagroal_log_trace(" PID: %d", connection.pid); @@ -1249,6 +1358,28 @@ connection_details(int slot) pgagroal_log_debug(" Database: %s", connection.database); pgagroal_log_debug(" AppName: %s", connection.appname); pgagroal_log_debug(" Rule: %d", connection.limit_rule); + pgagroal_log_debug(" Start: %s", &start_buf[0]); + pgagroal_log_debug(" Time: %s", &time_buf[0]); + pgagroal_log_debug(" FD: %d", connection.fd); + pgagroal_log_trace(" PID: %d", connection.pid); + pgagroal_log_trace(" Auth: %d", connection.has_security); + for (int i = 0; i < NUMBER_OF_SECURITY_MESSAGES; i++) + { + pgagroal_log_trace(" Size: %zd", connection.security_lengths[i]); + pgagroal_log_mem(&connection.security_messages[i], connection.security_lengths[i]); + } + pgagroal_log_trace(" Backend PID: %d", connection.backend_pid); + pgagroal_log_trace(" Backend Secret: %d", connection.backend_secret); + break; + case STATE_MAX_CONNECTION_AGE: + pgagroal_log_debug("pgagroal_pool_status: State: MAX CONNECTION AGE"); + pgagroal_log_debug(" Slot: %d", slot); + pgagroal_log_debug(" Server: %d", connection.server); + pgagroal_log_debug(" User: %s", connection.username); + pgagroal_log_debug(" Database: %s", connection.database); + pgagroal_log_debug(" AppName: %s", connection.appname); + pgagroal_log_debug(" Rule: %d", connection.limit_rule); + pgagroal_log_debug(" Start: %s", &start_buf[0]); pgagroal_log_debug(" Time: %s", &time_buf[0]); pgagroal_log_debug(" FD: %d", connection.fd); pgagroal_log_trace(" PID: %d", connection.pid); @@ -1269,6 +1400,7 @@ connection_details(int slot) pgagroal_log_debug(" Database: %s", connection.database); pgagroal_log_debug(" AppName: %s", connection.appname); pgagroal_log_debug(" Rule: %d", connection.limit_rule); + pgagroal_log_debug(" Start: %s", &start_buf[0]); pgagroal_log_debug(" Time: %s", &time_buf[0]); pgagroal_log_debug(" FD: %d", connection.fd); pgagroal_log_trace(" PID: %d", connection.pid); @@ -1289,6 +1421,7 @@ connection_details(int slot) pgagroal_log_debug(" Database: %s", connection.database); pgagroal_log_debug(" AppName: %s", connection.appname); pgagroal_log_debug(" Rule: %d", connection.limit_rule); + pgagroal_log_debug(" Start: %s", &start_buf[0]); pgagroal_log_debug(" Time: %s", &time_buf[0]); pgagroal_log_debug(" FD: %d", connection.fd); pgagroal_log_trace(" PID: %d", connection.pid); diff --git a/src/libpgagroal/prometheus.c b/src/libpgagroal/prometheus.c index 82735a02..d5bd1f20 100644 --- a/src/libpgagroal/prometheus.c +++ b/src/libpgagroal/prometheus.c @@ -184,6 +184,7 @@ pgagroal_init_prometheus(size_t* p_size, void** p_shmem) atomic_init(&prometheus->connection_invalid, 0); atomic_init(&prometheus->connection_get, 0); atomic_init(&prometheus->connection_idletimeout, 0); + atomic_init(&prometheus->connection_max_connection_age, 0); atomic_init(&prometheus->connection_flush, 0); atomic_init(&prometheus->connection_success, 0); @@ -401,6 +402,16 @@ pgagroal_prometheus_connection_idletimeout(void) atomic_fetch_add(&prometheus->connection_idletimeout, 1); } +void +pgagroal_prometheus_connection_max_connection_age(void) +{ + struct prometheus* prometheus; + + prometheus = (struct prometheus*)prometheus_shmem; + + atomic_fetch_add(&prometheus->connection_max_connection_age, 1); +} + void pgagroal_prometheus_connection_awaiting(int limit_index) { @@ -650,6 +661,7 @@ pgagroal_prometheus_reset(void) atomic_store(&prometheus->connection_invalid, 0); atomic_store(&prometheus->connection_get, 0); atomic_store(&prometheus->connection_idletimeout, 0); + atomic_store(&prometheus->connection_max_connection_age, 0); atomic_store(&prometheus->connection_flush, 0); atomic_store(&prometheus->connection_success, 0); @@ -1014,6 +1026,7 @@ home_page(int client_fd) data = pgagroal_append(data, "
  • gracefully
  • \n"); data = pgagroal_append(data, "
  • flush
  • \n"); data = pgagroal_append(data, "
  • idle_check
  • \n"); + data = pgagroal_append(data, "
  • max_connection_age
  • \n"); data = pgagroal_append(data, "
  • validation
  • \n"); data = pgagroal_append(data, "
  • remove
  • \n"); data = pgagroal_append(data, " \n"); @@ -1100,6 +1113,10 @@ home_page(int client_fd) data = pgagroal_append(data, "

    \n"); data = pgagroal_append(data, " Number of connection idle timeouts\n"); data = pgagroal_append(data, "

    \n"); + data = pgagroal_append(data, "

    pgagroal_connection_max_connection_age

    \n"); + data = pgagroal_append(data, "

    \n"); + data = pgagroal_append(data, " Number of connection max age timeouts\n"); + data = pgagroal_append(data, "

    \n"); data = pgagroal_append(data, "

    pgagroal_connection_flush

    \n"); data = pgagroal_append(data, "

    \n"); data = pgagroal_append(data, " Number of connection flushes\n"); @@ -1485,6 +1502,7 @@ connection_information(int client_fd) case STATE_FREE: case STATE_FLUSH: case STATE_IDLE_CHECK: + case STATE_MAX_CONNECTION_AGE: case STATE_VALIDATION: case STATE_REMOVE: total++; @@ -1561,6 +1579,9 @@ connection_information(int client_fd) case STATE_IDLE_CHECK: data = pgagroal_append(data, "idle_check"); break; + case STATE_MAX_CONNECTION_AGE: + data = pgagroal_append(data, "max_connection_age"); + break; case STATE_VALIDATION: data = pgagroal_append(data, "validation"); break; @@ -1584,6 +1605,7 @@ connection_information(int client_fd) case STATE_GRACEFULLY: case STATE_FLUSH: case STATE_IDLE_CHECK: + case STATE_MAX_CONNECTION_AGE: case STATE_VALIDATION: case STATE_REMOVE: data = pgagroal_append(data, "1"); @@ -1879,6 +1901,12 @@ pool_information(int client_fd) data = pgagroal_append_ulong(data, atomic_load(&prometheus->connection_idletimeout)); data = pgagroal_append(data, "\n\n"); + data = pgagroal_append(data, "#HELP pgagroal_connection_max_connection_age Number of connection max age timeouts\n"); + data = pgagroal_append(data, "#TYPE pgagroal_connection_max_connection_age counter\n"); + data = pgagroal_append(data, "pgagroal_connection_max_connection_age "); + data = pgagroal_append_ulong(data, atomic_load(&prometheus->connection_max_connection_age)); + data = pgagroal_append(data, "\n\n"); + data = pgagroal_append(data, "#HELP pgagroal_connection_flush Number of connection flushes\n"); data = pgagroal_append(data, "#TYPE pgagroal_connection_flush counter\n"); data = pgagroal_append(data, "pgagroal_connection_flush "); diff --git a/src/libpgagroal/utils.c b/src/libpgagroal/utils.c index 061bde27..734023b6 100644 --- a/src/libpgagroal/utils.c +++ b/src/libpgagroal/utils.c @@ -266,6 +266,8 @@ pgagroal_get_state_string(signed char state) return "Flush"; case STATE_IDLE_CHECK: return "Idle check"; + case STATE_MAX_CONNECTION_AGE: + return "Max connection age check"; case STATE_VALIDATION: return "Validating"; case STATE_REMOVE: diff --git a/src/main.c b/src/main.c index 18f033a6..eeacc2e6 100644 --- a/src/main.c +++ b/src/main.c @@ -77,6 +77,7 @@ static void reload_cb(struct ev_loop* loop, ev_signal* w, int revents); static void graceful_cb(struct ev_loop* loop, ev_signal* w, int revents); static void coredump_cb(struct ev_loop* loop, ev_signal* w, int revents); static void idle_timeout_cb(struct ev_loop* loop, ev_periodic* w, int revents); +static void max_connection_age_cb(struct ev_loop* loop, ev_periodic* w, int revents); static void validation_cb(struct ev_loop* loop, ev_periodic* w, int revents); static void disconnect_client_cb(struct ev_loop* loop, ev_periodic* w, int revents); static bool accept_fatal(int error); @@ -310,6 +311,7 @@ main(int argc, char** argv) void* tmp_shmem = NULL; struct signal_info signal_watcher[6]; struct ev_periodic idle_timeout; + struct ev_periodic max_connection_age; struct ev_periodic validation; struct ev_periodic disconnect_client; struct rlimit flimit; @@ -1020,6 +1022,13 @@ main(int argc, char** argv) ev_periodic_start (main_loop, &idle_timeout); } + if (config->max_connection_age > 0) + { + ev_periodic_init (&max_connection_age, max_connection_age_cb, 0., + MAX(1. * config->max_connection_age / 2., 5.), 0); + ev_periodic_start (main_loop, &max_connection_age); + } + if (config->validation == VALIDATION_BACKGROUND) { ev_periodic_init (&validation, validation_cb, 0., @@ -1785,6 +1794,23 @@ idle_timeout_cb(struct ev_loop* loop, ev_periodic* w, int revents) } } +static void +max_connection_age_cb(struct ev_loop* loop, ev_periodic* w, int revents) +{ + if (EV_ERROR & revents) + { + pgagroal_log_trace("max_connection_age_cb: got invalid event: %s", strerror(errno)); + return; + } + + /* max_connection_age() is always in a fork() */ + if (!fork()) + { + shutdown_ports(); + pgagroal_max_connection_age(); + } +} + static void validation_cb(struct ev_loop* loop, ev_periodic* w, int revents) { From 3792436d73a4ea9296d03ed25fc8e71b0487d316 Mon Sep 17 00:00:00 2001 From: jesperpedersen Date: Fri, 14 Jul 2023 10:59:55 -0400 Subject: [PATCH 02/24] Support yes/no for bool --- doc/CONFIGURATION.md | 2 +- src/libpgagroal/configuration.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 6a668fd4..109309f3 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -20,7 +20,7 @@ All properties within a section are in the format `key = value`. The characters `#` and `;` can be used for comments. A line is totally ignored if the very first non-space character is a comment one, but it is possible to put a comment at the end of a line. -The `Bool` data type supports the following values: `on`, `1`, `true`, `off`, `0` and `false`. +The `Bool` data type supports the following values: `on`, `yes`, `1`, `true`, `off`, `no`, `0` and `false`. Each value can be quoted by means of `"` or `'`. Quoted strings must begin and end with the very same quoting character. It is possible to nest quotes. As an example of configuration snippet including quoting and comments: diff --git a/src/libpgagroal/configuration.c b/src/libpgagroal/configuration.c index 88729e88..05dfbbf4 100644 --- a/src/libpgagroal/configuration.c +++ b/src/libpgagroal/configuration.c @@ -1814,13 +1814,13 @@ as_int(char* str, int* i) static int as_bool(char* str, bool* b) { - if (!strcasecmp(str, "true") || !strcasecmp(str, "on") || !strcasecmp(str, "1")) + if (!strcasecmp(str, "true") || !strcasecmp(str, "on") || !strcasecmp(str, "yes") || !strcasecmp(str, "1")) { *b = true; return 0; } - if (!strcasecmp(str, "false") || !strcasecmp(str, "off") || !strcasecmp(str, "0")) + if (!strcasecmp(str, "false") || !strcasecmp(str, "off") || !strcasecmp(str, "no") || !strcasecmp(str, "0")) { *b = false; return 0; From ade40240317bad155dbf1e40866c96257b688b90 Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Wed, 4 Oct 2023 04:14:10 -0400 Subject: [PATCH 03/24] [#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 43a0073d..f5ff06ac 100644 --- a/src/admin.c +++ b/src/admin.c @@ -87,6 +87,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"); @@ -96,10 +97,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); @@ -108,7 +110,6 @@ usage(void) int main(int argc, char** argv) { - int exit_code = 0; int c; char* username = NULL; char* password = NULL; @@ -179,23 +180,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)) @@ -205,69 +230,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); +} From 5f15164b8eff7063445ac454baefa4b4242c962f Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Mon, 23 Oct 2023 20:11:18 +0200 Subject: [PATCH 04/24] [#381] Changes to `pgagroal-cli` commands This commit changes two commands in `pgagroal-cli`. The `is-alive` command is deprecated by means of the `ping` command. Documentation has been modified accordingly. The `details` command is now deprecated by the `status details` one. To achieve this, the `status details` is parsed _before_ the `status` one (that has not changed at all). In order to better reflect this change, the internal constant `ACTION_DETAILS` has been renamed to `ACTION_STATUS_DETAIL`. Documentation updated accordingly. Shell completions updated accordingly. Close #381 --- contrib/shell_comp/pgagroal_comp.bash | 4 ++- contrib/shell_comp/pgagroal_comp.zsh | 13 +++++++++- doc/CLI.md | 36 ++++++++++++++++----------- src/cli.c | 24 ++++++++++-------- 4 files changed, 49 insertions(+), 28 deletions(-) diff --git a/contrib/shell_comp/pgagroal_comp.bash b/contrib/shell_comp/pgagroal_comp.bash index bc3665d2..cefc72c1 100644 --- a/contrib/shell_comp/pgagroal_comp.bash +++ b/contrib/shell_comp/pgagroal_comp.bash @@ -10,7 +10,7 @@ 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 is-alive enable disable shutdown status details switch-to conf clear" "${COMP_WORDS[1]}")) + COMPREPLY=($(compgen -W "flush ping enable disable shutdown status switch-to conf clear" "${COMP_WORDS[1]}")) else # the user has specified something else # subcommand required? @@ -27,6 +27,8 @@ pgagroal_cli_completions() conf) COMPREPLY+=($(compgen -W "reload get set" "${COMP_WORDS[2]}")) ;; + status) + COMPREPLY+=($(compgen -W "details" "${COMP_WORDS[2]}")) esac fi diff --git a/contrib/shell_comp/pgagroal_comp.zsh b/contrib/shell_comp/pgagroal_comp.zsh index c1750a83..b0555423 100644 --- a/contrib/shell_comp/pgagroal_comp.zsh +++ b/contrib/shell_comp/pgagroal_comp.zsh @@ -6,7 +6,7 @@ function _pgagroal_cli() { local line _arguments -C \ - "1: :(flush is-alive enable disable shutdown status details switch-to conf clear)" \ + "1: :(flush ping enable disable shutdown status switch-to conf clear)" \ "*::arg:->args" case $line[1] in @@ -22,6 +22,9 @@ function _pgagroal_cli() conf) _pgagroal_cli_conf ;; + status) + _pgagroal_cli_status + ;; esac } @@ -78,3 +81,11 @@ function _pgagroal_admin_user() "1: :(add del edit ls)" \ "*::arg:->args" } + +function _pgagroal_cli_status() +{ + local line + _arguments -C \ + "1: :(details)" \ + "*::arg:->args" +} diff --git a/doc/CLI.md b/doc/CLI.md index a7031e5b..62f6ec1c 100644 --- a/doc/CLI.md +++ b/doc/CLI.md @@ -59,19 +59,28 @@ pgagroal-cli flush all # pgagroal-cli flush all '*' pgagroal-cli flush pgbench # pgagroal-cli flush gracefully pgbench ``` -### is-alive -Is pgagroal alive +### ping +The `ping` command checks if `pgagroal` is running. +In case of success, the command does not print anything on the standard output unless the `--verbose` flag is used. Command ``` -pgagroal-cli is-alive +pgagroal-cli ping ``` Example ``` -pgagroal-cli is-alive +pgagroal-cli ping --verbose # pgagroal-cli: Success (0) +pgagroal-cli ping # $? = 0 +``` + +In the case `pgagroal` is not running, a message is printed on the standard error and the exit status is set to a non-zero value: + +``` +pgagroal-cli ping # $? = 1 +Connection error on /tmp ``` ### enable @@ -131,7 +140,8 @@ pgagroal-cli shutdown cancel # stops the above command ### status -Status of pgagroal +The `status` command reports the current status of the `pgagroal` pooler. +Without any subcommand, `status` reports back a short set of information about the pooler. Command @@ -143,21 +153,15 @@ Example ``` pgagroal-cli status -``` - -### details -Detailed status of pgagroal - -Command ``` -pgagroal-cli details -``` + +With the `details` subcommand, a more verbose output is printed with a detail about every connection. Example ``` -pgagroal-cli details +pgagroal-cli status details ``` ### switch-to @@ -334,7 +338,9 @@ to the working command: - `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`. +- `reload` is equivalent to `conf reload`; +- `is-alive` is equivalent to `ping`; +- `details` is equivalent to `status details`. Whenever you use a deprecated command, the `pgagroal-cli` will print on standard error a warning message. diff --git a/src/cli.c b/src/cli.c index a41b23b3..3b747c2e 100644 --- a/src/cli.c +++ b/src/cli.c @@ -55,7 +55,7 @@ #define ACTION_GRACEFULLY 2 #define ACTION_STOP 3 #define ACTION_STATUS 4 -#define ACTION_DETAILS 5 +#define ACTION_STATUS_DETAILS 5 #define ACTION_ISALIVE 6 #define ACTION_CANCELSHUTDOWN 7 #define ACTION_ENABLEDB 8 @@ -119,15 +119,14 @@ usage(void) 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(" ping Verifies if pgagroal is up and running\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(" status [details] Status of pgagroal, with optional details\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"); @@ -388,17 +387,20 @@ main(int argc, char** argv) 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, "details", NULL)) - { - action = ACTION_DETAILS; - pgagroal_log_trace("Command:
    "); - } - else if (parse_command_simple(argc, argv, optind, "is-alive", NULL)) + 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: "); @@ -572,7 +574,7 @@ main(int argc, char** argv) { exit_code = status(s_ssl, socket); } - else if (action == ACTION_DETAILS) + else if (action == ACTION_STATUS_DETAILS) { exit_code = details(s_ssl, socket); } From d3a92c7f0caafd7f529ee23435a2b16efbea3c72 Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Mon, 23 Oct 2023 20:21:18 +0200 Subject: [PATCH 05/24] [#382] Remove extra line in deprecated command warning. Removes an extra new line at the end of a warning message about a deprecated command. Close #382 --- src/libpgagroal/utils.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libpgagroal/utils.c b/src/libpgagroal/utils.c index bfc86de1..52406571 100644 --- a/src/libpgagroal/utils.c +++ b/src/libpgagroal/utils.c @@ -1061,7 +1061,7 @@ parse_deprecated_command(int argc, 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", + warnx("command <%s> has been deprecated by <%s> since version %d.%d", command, deprecated_by, deprecated_since_major, deprecated_since_minor); } From e2dcb10899839e8127bf15b7f13d4721c40331b7 Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Thu, 26 Oct 2023 11:36:13 -0400 Subject: [PATCH 06/24] [#386] Help screen in `pgagroal-cli` has dots on newlines There were a couple of dots after a "\n" in the help screen. Removed the dots, since they can be confused in the output with the command argument. Close #386 --- src/cli.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cli.c b/src/cli.c index 3b747c2e..dc3a0a30 100644 --- a/src/cli.c +++ b/src/cli.c @@ -132,9 +132,9 @@ usage(void) 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(" conf get \n"); printf(" - 'set' to modify a configuration value;\n"); - printf(" conf set \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"); From 5988613a32332c13bc6df8b71290e2989bd711e0 Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Tue, 7 Nov 2023 02:17:43 -0500 Subject: [PATCH 07/24] [#388] Add `conf ls` to get the configuration file locations Introducing the command `conf ls` to `pgagroal-cli` that prints out where the configuration files are located. A new management key, action and three new management methods have been added to implement the logic. The idea is that, once the action is required, the management prints on the socket every single `struct configuration*` path variables, and then the management read method reads back in the same order to the same list of values to be printed on the output screen. In order to achieve this, two new static functions have been added to the managemenet in order to write and read from the socket a single path. A path is written with a size and the following path, assuming it will never be larger than MAX_PATH or an error will be thrown. In the case a path is NULL or empty, an empty string will be written on the socket and so the final result is that the entry will be printed as empty in the command output. Documentation updated. Shell completions updated. Fixes also a few log_trace in the cli application. Close #388 --- contrib/shell_comp/pgagroal_comp.bash | 2 +- contrib/shell_comp/pgagroal_comp.zsh | 2 +- doc/CLI.md | 20 ++- src/cli.c | 44 ++++- src/include/management.h | 55 ++++++ src/libpgagroal/management.c | 245 ++++++++++++++++++++++++++ src/main.c | 4 + 7 files changed, 363 insertions(+), 9 deletions(-) diff --git a/contrib/shell_comp/pgagroal_comp.bash b/contrib/shell_comp/pgagroal_comp.bash index cefc72c1..12b025e9 100644 --- a/contrib/shell_comp/pgagroal_comp.bash +++ b/contrib/shell_comp/pgagroal_comp.bash @@ -25,7 +25,7 @@ pgagroal_cli_completions() COMPREPLY+=($(compgen -W "server prometheus" "${COMP_WORDS[2]}")) ;; conf) - COMPREPLY+=($(compgen -W "reload get set" "${COMP_WORDS[2]}")) + COMPREPLY+=($(compgen -W "reload get set ls" "${COMP_WORDS[2]}")) ;; status) COMPREPLY+=($(compgen -W "details" "${COMP_WORDS[2]}")) diff --git a/contrib/shell_comp/pgagroal_comp.zsh b/contrib/shell_comp/pgagroal_comp.zsh index b0555423..5b0ef704 100644 --- a/contrib/shell_comp/pgagroal_comp.zsh +++ b/contrib/shell_comp/pgagroal_comp.zsh @@ -40,7 +40,7 @@ function _pgagroal_cli_conf() { local line _arguments -C \ - "1: :(reload get set)" \ + "1: :(reload get set ls)" \ "*::arg:->args" } diff --git a/doc/CLI.md b/doc/CLI.md index 62f6ec1c..3a3d5a6a 100644 --- a/doc/CLI.md +++ b/doc/CLI.md @@ -184,7 +184,8 @@ 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. +- `set` modifies a configuration parameter at runtime; +- `ls` prints where the configuration files are located. Command @@ -205,7 +206,7 @@ pgagroal-cli conf set max_connections 25 The details about how to get and set values at run-time are explained in the following. -### conf 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. @@ -256,7 +257,7 @@ If the parameter name specified is not found or invalid, the program `pgagroal-c -### conf set +#### conf set Allows the setting of a configuration parameter at run-time, if possible. Examples @@ -303,7 +304,20 @@ WARN 1 settings cannot be applied DEBUG pgagroal_management_write_config_set: unable to apply changes to -> <100> ``` +#### conf ls +The command `conf ls` provides information about the location of the configuration files. +As an example: + +``` +Main Configuration file: /etc/pgagroal/pgagroal.conf +HBA file: /etc/pgagroal/pgagroal_hba.conf +Limit file: /etc/pgagroal/pgagroal_databases.conf +Frontend users file: /etc/pgagroal/pgagroal_frontend_users.conf +Admins file: /etc/pgagroal/pgagroal_admins.conf +Superuser file: +Users file: /etc/pgagroal/pgagroal_users.conf +``` ### clear Resets different parts of the pooler. It accepts an operational mode: diff --git a/src/cli.c b/src/cli.c index dc3a0a30..75c47ee5 100644 --- a/src/cli.c +++ b/src/cli.c @@ -66,6 +66,7 @@ #define ACTION_RELOAD 13 #define ACTION_CONFIG_GET 14 #define ACTION_CONFIG_SET 15 +#define ACTION_CONFIG_LS 16 static int flush(SSL* ssl, int socket, int32_t mode, char* database); static int enabledb(SSL* ssl, int socket, char* database); @@ -82,6 +83,7 @@ static int switch_to(SSL* ssl, int socket, char* server); static int reload(SSL* ssl, int socket); static int config_get(SSL* ssl, int socket, char* config_key, bool verbose); static int config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verbose); +static int config_ls(SSL* ssl, int socket); static void version(void) @@ -134,7 +136,8 @@ usage(void) 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(" conf set ;\n"); + printf(" - 'ls' lists the configuration files used.\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"); @@ -431,20 +434,25 @@ main(int argc, char** argv) { action = ACTION_RELOAD; } - pgagroal_log_debug("Command: "); + 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_debug("Command: [%s]", config_key); + 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_debug("Command: [%s] = [%s]", config_key, config_value); + 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) @@ -606,6 +614,10 @@ main(int argc, char** argv) { exit_code = config_set(s_ssl, socket, config_key, config_value, verbose); } + else if (action == ACTION_CONFIG_LS) + { + exit_code = config_ls(s_ssl, socket); + } done: @@ -970,3 +982,27 @@ config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verb error: return EXIT_STATUS_CONNECTION_ERROR; } + +/** + * Asks the daemon about the configuration file location. + * + * @returns 0 on success + */ +static int +config_ls(SSL* ssl, int socket) +{ + + if (pgagroal_management_conf_ls(ssl, socket)) + { + goto error; + } + + if (pgagroal_management_read_conf_ls(ssl, socket)) + { + goto error; + } + + return EXIT_STATUS_OK; +error: + return EXIT_STATUS_CONNECTION_ERROR; +} diff --git a/src/include/management.h b/src/include/management.h index 11b26476..0f8ad2ea 100644 --- a/src/include/management.h +++ b/src/include/management.h @@ -61,6 +61,7 @@ extern "C" { #define MANAGEMENT_REMOVE_FD 19 #define MANAGEMENT_CONFIG_GET 20 #define MANAGEMENT_CONFIG_SET 21 +#define MANAGEMENT_CONFIG_LS 22 /** * Read the management header @@ -386,6 +387,60 @@ pgagroal_management_config_set(SSL* ssl, int socket, char* config_key, char* con int pgagroal_management_write_config_set(int socket, char* config_key, char* config_value); +/** + * Entry point for managing the `conf ls` command that + * will list all the configuration files used by the running + * daemon. + * + * @param ssl the SSL handler + * @param fd the socket file descriptor + * @returns 0 on success + */ +int +pgagroal_management_conf_ls(SSL* ssl, int fd); + +/** + * Reads out of the socket the list of configuration + * files and prints them out to the standard output. + * + * The order of the read paths is: + * - configuration path + * - HBA path + * - limit path + * - frontend users path + * - admins path + * - Superusers path + * - users path + * + * @param socket the file descriptor of the open socket + * @param ssl the SSL handler + * @returns 0 on success + */ +int +pgagroal_management_read_conf_ls(SSL* ssl, int socket); + +/** + * The management function responsible for sending + * the configuration paths into the socket. + * + * The function sends every path following the path length, + * that must be limited to MAX_PATH size. + * + * The order of the sent paths is: + * - configuration path + * - HBA path + * - limit path + * - frontend users path + * - admins path + * - Superusers path + * - users path + * + * @params socket the file descriptor of the open socket + * @returns 0 on success + */ +int +pgagroal_management_write_conf_ls(int socket); + #ifdef __cplusplus } #endif diff --git a/src/libpgagroal/management.c b/src/libpgagroal/management.c index 1b104149..908b6766 100644 --- a/src/libpgagroal/management.c +++ b/src/libpgagroal/management.c @@ -59,6 +59,9 @@ static int write_socket(int socket, void* buf, size_t size); static int write_ssl(SSL* ssl, void* buf, size_t size); static int write_header(SSL* ssl, int fd, signed char type, int slot); +static int pgagroal_management_write_conf_ls_detail(int socket, char* what); +static int pgagroal_management_read_conf_ls_detail(SSL* ssl, int socket, char* buffer); + int pgagroal_management_read_header(int socket, signed char* id, int32_t* slot) { @@ -175,6 +178,7 @@ pgagroal_management_read_payload(int socket, signed char id, int* payload_i, cha case MANAGEMENT_DISABLEDB: case MANAGEMENT_CONFIG_GET: case MANAGEMENT_CONFIG_SET: + if (read_complete(NULL, socket, &buf4[0], sizeof(buf4))) { goto error; @@ -213,6 +217,7 @@ pgagroal_management_read_payload(int socket, signed char id, int* payload_i, cha case MANAGEMENT_DETAILS: case MANAGEMENT_RESET: case MANAGEMENT_RELOAD: + case MANAGEMENT_CONFIG_LS: break; default: goto error; @@ -1726,3 +1731,243 @@ pgagroal_management_write_config_set(int socket, char* config_key, char* config_ return 1; } + +int +pgagroal_management_conf_ls(SSL* ssl, int fd) +{ + if (write_header(ssl, fd, MANAGEMENT_CONFIG_LS, -1)) + { + pgagroal_log_warn("pgagroal_management_conf_ls: write: %d", fd); + errno = 0; + goto error; + } + + return 0; + +error: + + return 1; +} + +int +pgagroal_management_read_conf_ls(SSL* ssl, int socket) +{ + char buf[4]; + char* buffer; + + memset(&buf, 0, sizeof(buf)); + buffer = calloc(1, MAX_PATH); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + printf("Main Configuration file: %s\n", buffer); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + printf("HBA file: %s\n", buffer); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + printf("Limit file: %s\n", buffer); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + printf("Frontend users file: %s\n", buffer); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + printf("Admins file: %s\n", buffer); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + printf("Superuser file: %s\n", buffer); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + printf("Users file: %s\n", buffer); + + free(buffer); + + return 0; + +error: + free(buffer); + pgagroal_log_warn("pgagroal_management_read_conf_ls: read: %d %s", socket, strerror(errno)); + errno = 0; + + return 1; +} + +int +pgagroal_management_write_conf_ls(int socket) +{ + struct configuration* config; + + config = (struct configuration*)shmem; + + if (pgagroal_management_write_conf_ls_detail(socket, config->configuration_path)) + { + goto error; + } + + if (pgagroal_management_write_conf_ls_detail(socket, config->hba_path)) + { + goto error; + } + + if (pgagroal_management_write_conf_ls_detail(socket, config->limit_path)) + { + goto error; + } + + // 4 + if (pgagroal_management_write_conf_ls_detail(socket, config->frontend_users_path)) + { + goto error; + } + //5 + if (pgagroal_management_write_conf_ls_detail(socket, config->admins_path)) + { + goto error; + } + //6 + if (pgagroal_management_write_conf_ls_detail(socket, config->superuser_path)) + { + goto error; + } + // 7 + if (pgagroal_management_write_conf_ls_detail(socket, config->users_path)) + { + goto error; + } + + return 0; + +error: + pgagroal_log_debug("pgagroal_management_write_conf_ls: error writing out file paths"); + return 1; +} + +/** + * Utility function to write a single configuration path to the socket. + * + * @param socket the file descriptor of the open socket + * @param what the pointer to the path to send out on the socket. It cannot + * exceed in size MAX_PATH - 1. + * @returns 0 on success + */ +static int +pgagroal_management_write_conf_ls_detail(int socket, char* what) +{ + char buf[4]; + size_t size = 0; + char data[MAX_PATH]; + + if (what && strlen(what) > MAX_PATH) + { + goto error; + } + + memset(&buf, 0, sizeof(buf)); + memset(&data, 0, sizeof(data)); + + size = what ? strlen(what) + 1 : 0; + if (size > MAX_PATH) + { + errno = EMSGSIZE; + goto error; + } + + pgagroal_write_int32(&buf, size); + + if (write_complete(NULL, socket, &buf, sizeof(buf))) + { + goto error; + } + + memcpy(&data[0], what, size); + if (write_complete(NULL, socket, data, size)) + { + goto error; + } + + pgagroal_log_trace("pgagroal_management_write_conf_ls_deail: writing <%s> with %d bytes", what, size); + return 0; + +error: + pgagroal_log_debug("pgagroal_management_write_conf_ls_detail: error %d %s", errno, strerror(errno)); + errno = 0; + return 1; +} + +/** + * Utility function to read back from the socket a configuration path. + * + * It does zero fill the buffer pointed by its argument, so + * it is safe to call this function with a prefilled buffer, but its content + * will be lost. + * + * The buffer will be considered able to store MAX_PATH bytes. + * + * @param socket the file descriptor of the open socket + * @param buffer an already allocated buffer where to place the read value. Only + * MAX_PATH bytes will be read out of socket. + * @return 0 on success + */ +static int +pgagroal_management_read_conf_ls_detail(SSL* ssl, int socket, char* buffer) +{ + char buf[4]; + int size = 0; + + memset(&buf, 0, sizeof(buf)); + memset(buffer, 0, MAX_PATH); + + if (read_complete(ssl, socket, &buf[0], sizeof(buf))) + { + goto error; + } + + size = pgagroal_read_int32(&buf); + + if (size > MAX_PATH) + { + errno = EMSGSIZE; + goto error; + } + + if (read_complete(ssl, socket, buffer, size)) + { + goto error; + } + + return 0; + +error: + memset(buffer, 0, MAX_PATH); + pgagroal_log_warn("pgagroal_management_read_conf_ls_detail: read: %d %s", socket, strerror(errno)); + errno = 0; + + return 1; +} diff --git a/src/main.c b/src/main.c index eeacc2e6..05a46a4d 100644 --- a/src/main.c +++ b/src/main.c @@ -1496,6 +1496,10 @@ accept_mgt_cb(struct ev_loop* loop, struct ev_io* watcher, int revents) pgagroal_log_debug("pgagroal: Management isalive"); pgagroal_management_write_isalive(client_fd, config->gracefully); break; + case MANAGEMENT_CONFIG_LS: + pgagroal_log_debug("pgagroal: Management conf ls"); + pgagroal_management_write_conf_ls(client_fd); + break; case MANAGEMENT_RESET: pgagroal_log_debug("pgagroal: Management reset"); pgagroal_prometheus_reset(); From cc10abcd2f7f007c6ff5ae1c85acaf8b504a28f3 Mon Sep 17 00:00:00 2001 From: jesperpedersen Date: Wed, 6 Dec 2023 09:37:43 -0500 Subject: [PATCH 08/24] New mail address --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 7a805e7d..c168069b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,6 +1,6 @@ pgagroal was created by the following authors: -Jesper Pedersen +Jesper Pedersen David Fetter Will Leinweber Junduo Dong From 2b8c221ad971160373ce33e346e76a7957851cd6 Mon Sep 17 00:00:00 2001 From: jesperpedersen Date: Sat, 30 Dec 2023 10:31:44 -0500 Subject: [PATCH 09/24] Change email address --- CODE_OF_CONDUCT.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 33c5d516..5d1b5d23 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -33,7 +33,7 @@ projects. This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a maintainer of the project, Jesper Pedersen . +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a maintainer of the project, Jesper Pedersen . ### pgagroal Events Code of Conduct From 766ca1d4243e7f2d55d0c1a92f8fef280c362f5b Mon Sep 17 00:00:00 2001 From: jesperpedersen Date: Sat, 6 Jan 2024 09:22:25 -0500 Subject: [PATCH 10/24] Update copyright year, and move copyright decisions to the community --- LICENSE | 2 +- src/admin.c | 2 +- src/cli.c | 2 +- src/include/configuration.h | 2 +- src/include/logging.h | 2 +- src/include/management.h | 2 +- src/include/memory.h | 2 +- src/include/message.h | 2 +- src/include/network.h | 2 +- src/include/pgagroal.h | 2 +- src/include/pipeline.h | 2 +- src/include/pool.h | 2 +- src/include/prometheus.h | 2 +- src/include/remote.h | 2 +- src/include/security.h | 2 +- src/include/server.h | 2 +- src/include/shmem.h | 2 +- src/include/tracker.h | 2 +- src/include/utils.h | 2 +- src/include/worker.h | 2 +- src/libpgagroal/configuration.c | 2 +- src/libpgagroal/logging.c | 2 +- src/libpgagroal/management.c | 2 +- src/libpgagroal/memory.c | 2 +- src/libpgagroal/message.c | 2 +- src/libpgagroal/network.c | 2 +- src/libpgagroal/pipeline_perf.c | 2 +- src/libpgagroal/pipeline_session.c | 2 +- src/libpgagroal/pipeline_transaction.c | 2 +- src/libpgagroal/pool.c | 2 +- src/libpgagroal/prometheus.c | 2 +- src/libpgagroal/remote.c | 2 +- src/libpgagroal/security.c | 2 +- src/libpgagroal/server.c | 2 +- src/libpgagroal/shmem.c | 2 +- src/libpgagroal/tracker.c | 2 +- src/libpgagroal/utils.c | 2 +- src/libpgagroal/worker.c | 2 +- src/main.c | 2 +- 39 files changed, 39 insertions(+), 39 deletions(-) diff --git a/LICENSE b/LICENSE index 2ff07806..31384ba0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ -Copyright (C) 2023 Red Hat +Copyright (C) 2024 The pgagroal community Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/src/admin.c b/src/admin.c index f5ff06ac..91205fad 100644 --- a/src/admin.c +++ b/src/admin.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/cli.c b/src/cli.c index 75c47ee5..d59fd0ec 100644 --- a/src/cli.c +++ b/src/cli.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/configuration.h b/src/include/configuration.h index 8e0e2ecf..a1e2b67e 100644 --- a/src/include/configuration.h +++ b/src/include/configuration.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/logging.h b/src/include/logging.h index ef3d4dd0..f099a6ff 100644 --- a/src/include/logging.h +++ b/src/include/logging.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/management.h b/src/include/management.h index 0f8ad2ea..cd640027 100644 --- a/src/include/management.h +++ b/src/include/management.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/memory.h b/src/include/memory.h index f8fed883..a4b42ce4 100644 --- a/src/include/memory.h +++ b/src/include/memory.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/message.h b/src/include/message.h index 985b8111..39d6b618 100644 --- a/src/include/message.h +++ b/src/include/message.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/network.h b/src/include/network.h index c05809fd..e72ca291 100644 --- a/src/include/network.h +++ b/src/include/network.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/pgagroal.h b/src/include/pgagroal.h index ede1bde0..4f3f4806 100644 --- a/src/include/pgagroal.h +++ b/src/include/pgagroal.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/pipeline.h b/src/include/pipeline.h index 6463e31b..8dc0b02a 100644 --- a/src/include/pipeline.h +++ b/src/include/pipeline.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/pool.h b/src/include/pool.h index 434e7e9a..2ebd5233 100644 --- a/src/include/pool.h +++ b/src/include/pool.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/prometheus.h b/src/include/prometheus.h index 51ea8b37..af2e61a6 100644 --- a/src/include/prometheus.h +++ b/src/include/prometheus.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/remote.h b/src/include/remote.h index 730f3317..e8b5a378 100644 --- a/src/include/remote.h +++ b/src/include/remote.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/security.h b/src/include/security.h index 33da1f84..e3c8c41c 100644 --- a/src/include/security.h +++ b/src/include/security.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/server.h b/src/include/server.h index b6068416..bff9d32b 100644 --- a/src/include/server.h +++ b/src/include/server.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/shmem.h b/src/include/shmem.h index 92e0128b..d2b9ecc6 100644 --- a/src/include/shmem.h +++ b/src/include/shmem.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/tracker.h b/src/include/tracker.h index c167f4c0..7d1ccee7 100644 --- a/src/include/tracker.h +++ b/src/include/tracker.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/utils.h b/src/include/utils.h index 4cb000ed..f9fd7db5 100644 --- a/src/include/utils.h +++ b/src/include/utils.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/include/worker.h b/src/include/worker.h index 7c1a87a1..4196ab93 100644 --- a/src/include/worker.h +++ b/src/include/worker.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/configuration.c b/src/libpgagroal/configuration.c index f138eb5a..0665882a 100644 --- a/src/libpgagroal/configuration.c +++ b/src/libpgagroal/configuration.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/logging.c b/src/libpgagroal/logging.c index 6f0be7be..a1c6301f 100644 --- a/src/libpgagroal/logging.c +++ b/src/libpgagroal/logging.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/management.c b/src/libpgagroal/management.c index 908b6766..3dd4719e 100644 --- a/src/libpgagroal/management.c +++ b/src/libpgagroal/management.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/memory.c b/src/libpgagroal/memory.c index 25c231c5..5efecbde 100644 --- a/src/libpgagroal/memory.c +++ b/src/libpgagroal/memory.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/message.c b/src/libpgagroal/message.c index 548bf29d..cbb97c59 100644 --- a/src/libpgagroal/message.c +++ b/src/libpgagroal/message.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/network.c b/src/libpgagroal/network.c index 4e0b7f11..42125a36 100644 --- a/src/libpgagroal/network.c +++ b/src/libpgagroal/network.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/pipeline_perf.c b/src/libpgagroal/pipeline_perf.c index e36f1af4..c12c8701 100644 --- a/src/libpgagroal/pipeline_perf.c +++ b/src/libpgagroal/pipeline_perf.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/pipeline_session.c b/src/libpgagroal/pipeline_session.c index 08a672e2..562aea8a 100644 --- a/src/libpgagroal/pipeline_session.c +++ b/src/libpgagroal/pipeline_session.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/pipeline_transaction.c b/src/libpgagroal/pipeline_transaction.c index c8e0c4ed..20de6211 100644 --- a/src/libpgagroal/pipeline_transaction.c +++ b/src/libpgagroal/pipeline_transaction.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/pool.c b/src/libpgagroal/pool.c index 98c9aab5..ee72d020 100644 --- a/src/libpgagroal/pool.c +++ b/src/libpgagroal/pool.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/prometheus.c b/src/libpgagroal/prometheus.c index d5bd1f20..a836d9b8 100644 --- a/src/libpgagroal/prometheus.c +++ b/src/libpgagroal/prometheus.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/remote.c b/src/libpgagroal/remote.c index 472f4645..515fd6b2 100644 --- a/src/libpgagroal/remote.c +++ b/src/libpgagroal/remote.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/security.c b/src/libpgagroal/security.c index 5859c848..d3de00ec 100644 --- a/src/libpgagroal/security.c +++ b/src/libpgagroal/security.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/server.c b/src/libpgagroal/server.c index 74ba0d11..b4b48c3b 100644 --- a/src/libpgagroal/server.c +++ b/src/libpgagroal/server.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/shmem.c b/src/libpgagroal/shmem.c index 61a37812..bb24487b 100644 --- a/src/libpgagroal/shmem.c +++ b/src/libpgagroal/shmem.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/tracker.c b/src/libpgagroal/tracker.c index 56ab6363..bf444cd5 100644 --- a/src/libpgagroal/tracker.c +++ b/src/libpgagroal/tracker.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/utils.c b/src/libpgagroal/utils.c index 52406571..896e8ca1 100644 --- a/src/libpgagroal/utils.c +++ b/src/libpgagroal/utils.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/libpgagroal/worker.c b/src/libpgagroal/worker.c index 1b1838e3..d0c01deb 100644 --- a/src/libpgagroal/worker.c +++ b/src/libpgagroal/worker.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: diff --git a/src/main.c b/src/main.c index 05a46a4d..aef6e287 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Red Hat + * Copyright (C) 2024 The pgagroal community * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: From 9012b555d51d2e3b7c1085ad5109f541389e1e74 Mon Sep 17 00:00:00 2001 From: jesperpedersen Date: Thu, 1 Feb 2024 16:54:01 -0500 Subject: [PATCH 11/24] [#395] Increase SECURITY_BUFFER_SIZE --- src/include/pgagroal.h | 2 +- src/libpgagroal/security.c | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/include/pgagroal.h b/src/include/pgagroal.h index 4f3f4806..c4dff957 100644 --- a/src/include/pgagroal.h +++ b/src/include/pgagroal.h @@ -67,7 +67,7 @@ extern "C" { #define MAX_BUFFER_SIZE 65535 #define DEFAULT_BUFFER_SIZE 65535 -#define SECURITY_BUFFER_SIZE 512 +#define SECURITY_BUFFER_SIZE 1024 #define MAX_USERNAME_LENGTH 128 #define MAX_DATABASE_LENGTH 256 diff --git a/src/libpgagroal/security.c b/src/libpgagroal/security.c index d3de00ec..0020a0d8 100644 --- a/src/libpgagroal/security.c +++ b/src/libpgagroal/security.c @@ -2128,6 +2128,7 @@ server_passthrough(struct message* msg, int auth_type, SSL* c_ssl, int client_fd if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -2155,6 +2156,7 @@ server_passthrough(struct message* msg, int auth_type, SSL* c_ssl, int client_fd if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -2180,6 +2182,7 @@ server_passthrough(struct message* msg, int auth_type, SSL* c_ssl, int client_fd { if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -2203,6 +2206,7 @@ server_passthrough(struct message* msg, int auth_type, SSL* c_ssl, int client_fd if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -2233,6 +2237,7 @@ server_passthrough(struct message* msg, int auth_type, SSL* c_ssl, int client_fd { if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -2321,6 +2326,7 @@ server_authenticate(struct message* msg, int auth_type, char* username, char* pa if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -2439,6 +2445,7 @@ server_password(char* username, char* password, int slot, SSL* server_ssl) status = pgagroal_read_block_message(server_ssl, server_fd, &auth_msg); if (auth_msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(auth_msg); pgagroal_log_error("Security message too large: %ld", auth_msg->length); goto error; } @@ -2450,6 +2457,7 @@ server_password(char* username, char* password, int slot, SSL* server_ssl) { if (auth_msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(auth_msg); pgagroal_log_error("Security message too large: %ld", auth_msg->length); goto error; } @@ -2556,6 +2564,7 @@ server_md5(char* username, char* password, int slot, SSL* server_ssl) status = pgagroal_read_block_message(server_ssl, server_fd, &auth_msg); if (auth_msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(auth_msg); pgagroal_log_error("Security message too large: %ld", auth_msg->length); goto error; } @@ -2567,6 +2576,7 @@ server_md5(char* username, char* password, int slot, SSL* server_ssl) { if (auth_msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(auth_msg); pgagroal_log_error("Security message too large: %ld", auth_msg->length); goto error; } @@ -2686,6 +2696,7 @@ server_scram256(char* username, char* password, int slot, SSL* server_ssl) status = pgagroal_read_block_message(server_ssl, server_fd, &msg); if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -2750,6 +2761,7 @@ server_scram256(char* username, char* password, int slot, SSL* server_ssl) status = pgagroal_read_block_message(server_ssl, server_fd, &msg); if (msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(msg); pgagroal_log_error("Security message too large: %ld", msg->length); goto error; } @@ -5004,6 +5016,7 @@ auth_query_server_md5(struct message* startup_response_msg, char* username, char status = pgagroal_read_block_message(server_ssl, socket, &auth_msg); if (auth_msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(auth_msg); pgagroal_log_error("Security message too large: %ld", auth_msg->length); goto error; } @@ -5015,6 +5028,7 @@ auth_query_server_md5(struct message* startup_response_msg, char* username, char { if (auth_msg->length > SECURITY_BUFFER_SIZE) { + pgagroal_log_message(auth_msg); pgagroal_log_error("Security message too large: %ld", auth_msg->length); goto error; } From 981b4ff155a976eeed10dd1fd63fa3deeacd95a6 Mon Sep 17 00:00:00 2001 From: Ashutosh Sharma Date: Sun, 4 Feb 2024 14:20:21 +0530 Subject: [PATCH 12/24] [#393] Add Valgrind Support - Added suppression rules in contrib/valgrind/pgagroal.supp file - Added README page for valgrind support - Modified AUTHORS file --- AUTHORS | 1 + contrib/valgrind/README.md | 16 ++ contrib/valgrind/pgagroal.supp | 268 +++++++++++++++++++++++++++++++++ 3 files changed, 285 insertions(+) create mode 100644 contrib/valgrind/README.md create mode 100644 contrib/valgrind/pgagroal.supp diff --git a/AUTHORS b/AUTHORS index c168069b..3707d5a4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -8,3 +8,4 @@ Luca Ferrari Nikita Bugrovsky Lawrence Wu Yongting You <2010youy01@gmail.com> +Ashutosh Sharma \ No newline at end of file diff --git a/contrib/valgrind/README.md b/contrib/valgrind/README.md new file mode 100644 index 00000000..592f4912 --- /dev/null +++ b/contrib/valgrind/README.md @@ -0,0 +1,16 @@ +# Valgrind + +The [Valgrind](https://valgrind.org/) tool suite provides a number of debugging and profiling tools that help you make your programs faster and more correct. The most popular and the default of these tools is called **Memcheck**. It can detect many memory-related errors that can lead to crashes and unpredictable behaviour. + +# Run memory management detection + +``` bash +valgrind --leak-check=full --show-leak-kinds=all --log-file=%p.log --trace-children=yes --track-origins=yes --read-var-info=yes ./pgagroal -c pgagroal.conf -a pgagroal_hba.conf +``` + +# Generate valgrind report with suppressed rules + +``` bash +valgrind --suppressions=../../contrib/valgrind/pgagroal.supp --leak-check=full --show-leak-kinds=all --log-file=%p.log --trace-children=yes --track-origins=yes --read-var-info=yes ./pgagroal -c pgagroal.conf -a pgagroal_hba.conf +``` + diff --git a/contrib/valgrind/pgagroal.supp b/contrib/valgrind/pgagroal.supp new file mode 100644 index 00000000..15582074 --- /dev/null +++ b/contrib/valgrind/pgagroal.supp @@ -0,0 +1,268 @@ +# +# pgagroal rules +# + +{ + pgagroal_bind_host + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + fun:bind_host + fun:pgagroal_bind + fun:main +} + +{ + pgagroal_set_proc_title_calloc + Memcheck:Leak + match-leak-kinds: indirect + fun:calloc + fun:pgagroal_set_proc_title + fun:main +} + +{ + pgagroal_set_proc_title_malloc + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:pgagroal_set_proc_title + fun:main +} + +# +# non-pgagroal rules +# + +{ + dl_open_malloc_inline + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:UnknownInlinedFun + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error +} + +{ + dl_open_calloc + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + fun:UnknownInlinedFun + fun:_dl_check_map_versions + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error + fun:_dlerror_run + fun:UnknownInlinedFun + fun:dlopen@@GLIBC_2.34 +} + +{ + dl_open_calloc_inline + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + fun:UnknownInlinedFun + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error +} + +{ + dl_open_worker_malloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:malloc + fun:strdup + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:dlopen_doit + fun:_dl_catch_exception + fun:_dl_catch_error + fun:_dlerror_run +} + + +{ + ev_loop_timers_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:realloc + fun:ev_realloc + fun:feed_reverse + fun:timers_reify + fun:ev_run.cold + fun:ev_loop + fun:main +} + +{ + ev_io_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:realloc + fun:ev_realloc + fun:fd_change + fun:ev_io_start.cold + fun:evtimerfd_init + fun:ev_periodic_start.cold + fun:main +} + +{ + ev_io_management_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:realloc + fun:ev_realloc + fun:ev_io_start.cold + fun:start_management + fun:main +} + +{ + ev_io_uds_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:realloc + fun:ev_realloc + fun:ev_io_start.cold + fun:start_uds + fun:main +} + +{ + ev_io_metrics_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:realloc + fun:ev_realloc + fun:ev_io_start.cold + fun:start_metrics + fun:main +} + +{ + ev_fd_change_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:realloc + fun:ev_realloc + fun:fd_change + fun:ev_io_start.cold + fun:main +} + +{ + ev_periodics_start_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:realloc + fun:ev_realloc + fun:ev_periodic_start.cold + fun:main +} + +{ + ev_loop_evpipe_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:realloc + fun:ev_realloc + fun:ev_io_start.cold + fun:evpipe_init + fun:ev_signal_start.cold + fun:ev_default_loop + fun:main +} + +{ + ev_loop_epoll_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:realloc + fun:ev_realloc + fun:epoll_init + fun:epoll_init.isra.0 + fun:loop_init + fun:ev_default_loop + fun:main +} + +{ + ev_run_feed_event_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:realloc + fun:ev_realloc + fun:ev_feed_event.cold + fun:fd_event_nocheck + fun:fd_event + fun:epoll_poll + fun:ev_run + fun:ev_run + fun:ev_loop + fun:main +} + +{ + ev_invoke_pending_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:add_client + fun:accept_main_cb + fun:ev_invoke_pending + fun:ev_run + fun:ev_run + fun:ev_loop + fun:main +} + +{ + ev_periodics_realloc + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:realloc + fun:ev_realloc + fun:feed_reverse + fun:periodics_reify + fun:ev_run + fun:ev_run.cold + fun:ev_loop + fun:main +} \ No newline at end of file From b73fb43c87746487c118c41ac8376e5200f48850 Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Thu, 8 Feb 2024 11:22:18 +0100 Subject: [PATCH 13/24] [#396] List `libatomic` on the requirements list Improve the documentation listing `libatomic`, a possible way to install all the dependencies on Rocky Linux and provides some instructions for enabling `crb` so to install `rst2man`. Close #396 --- README.md | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bebe5bb8..7298f721 100644 --- a/README.md +++ b/README.md @@ -68,12 +68,29 @@ See [Architecture](./doc/ARCHITECTURE.md) for the architecture of `pgagroal`. * [OpenSSL](http://www.openssl.org/) * [systemd](https://www.freedesktop.org/wiki/Software/systemd/) * [rst2man](https://docutils.sourceforge.io/) +* [libatomic](https://gcc.gnu.org/wiki/Atomic) + +On Rocky Linux (and similar) operating systems, the dependencies +can be installed via `dnf(8)` as follows: + +```sh +dnf install git gcc cmake make \ + libev libev-devel \ + openssl openssl-devel \ + systemd systemd-devel \ + python3-docutils \ + libatomic +``` + +Please note that, on Rocky Linux, in order to install the `python3-docutils` +package (that provides `rst2man` executable), you need to enable the `crb` repository: ```sh -dnf install git gcc cmake make libev libev-devel openssl openssl-devel systemd systemd-devel python3-docutils +dnf config-manager --set-enabled crb ``` -Alternative [clang 8+](https://clang.llvm.org/) can be used. + +Alternatively to GCC, [clang 8+](https://clang.llvm.org/) can be used. ### Release build From 8b13185c4ea47bf7e09547b8813511db7dd014fd Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Wed, 6 Dec 2023 08:14:47 -0500 Subject: [PATCH 14/24] [#385][#390] JSON command output implementation for `pgagroal-cli` This commit introduces the capaiblity for `pgagroal-cli` to print out the command results in a JSON format. A new dependency on the library 'cJSON' has been introduced; see . A new set of functions, named 'pgagroal_json_xxx' have been added in a separated file json.c (include file json.h) to handle JSON structures in a more consistent way. Each function that manipulates a JSON object has been named with the 'pgagroal_json_' prefix. Each management function that reads data out of the protocol and creates or handles json data has been named with the prefix 'pgagroal_management_json_'. A few functions with a prefix name 'pgagroal_management_json_print' are used to print out a JSON object as normal text. Every command output, in the JSON format, is structured with a 'command' is the main object contained in the output, that in turns has different attributes: - 'output' is an object that contains the command specific information (e.g., list of databases, messages, and so on); - `error` a boolean-like value that indicates if the command was in error or not (0 means success, 1 means error); - `status` a string that contains either 'OK' or an error message in the case the `error` flag is set; - `exit-status` an integer value with the exit status of the command, 0 in case of success, a different value in case of failure. The JSON object for a result includes also another object, named 'application', that can be used for introspection: such object describes which executable has launched the command (so far, only `pgagroal-cli`) and at which version. In the 'output' object, every thing that has a list (e.g., connections, limits, databases, servers) will be wrapped into an object with the attributes 'list' and 'count': the former contains the details of every "thing", while the latter contains the count (i.e., the size of the array 'list' ). Whenever possible, a 'state' string is placed to indicate the state of the single entry. The command `status` and `status details` have been unified on the management side. There is a single function that handles both the cases of reading the answer for the `status` or the `status details` commands. This has been done because `status details` includes the output of `status`. The function `pgagroal_management_json_read_status_details` is in charge of getting the answer of the management protocol (i.e., read the socket) and invoke the printing function in the case the output is of type text. The above `pgagroal_management_json_read_status_details` returns always a JSON object, that can be converted back to the text output via `pgagroal_management_json_print_status_details`. In this way, the output between the JSON and the text formats are always coherent for these commands. The `ping` (i.e., `is-alive`) command does not print nothing by default. In the JSON version it provides the numerical status of the server and a string that represents a human-readable status. The `conf get` command has been refactored to be symmetric with other commands: the logic to print out the result is now within the management function (pgagroal_management_read_config_get) as per other commands. The JSON provides the `config-key` and the `config-value` as strings. See #390 The `conf set` command has been refactored similarly to `conf get` in order to have all the logic to print out the information into the management read method (see #390). The exit status provided by the command is now the result of the matching within the JSON object of the expected configuration value and the requested configuration value. The `conf ls` command has been refactored to produce a JSON object when reading data out of the management socket. Such JSON object is then printed as normal text if required. A new utility function, named 'pgagroal_server_status_as_string' has been added to the utils.c stuff. The idea is to have a consistent way to translate the numerical status representation into an human readable string. The text output format of commands has slightly changed due to the refactoring of some internal methods. Documentation updated. CI workflow updated. Library list updated. Added a FindcJSON.cmake file to help in finding out the library. Update copyright of the json files to current year and community. Close #385 Close #390 --- .github/workflows/ci.yml | 4 + CMakeLists.txt | 10 + README.md | 4 +- cmake/FindcJSON.cmake | 51 ++ doc/CLI.md | 206 +++++++++ src/CMakeLists.txt | 7 + src/cli.c | 146 ++---- src/include/json.h | 170 +++++++ src/include/management.h | 29 +- src/include/utils.h | 14 + src/libpgagroal/json.c | 268 +++++++++++ src/libpgagroal/management.c | 872 +++++++++++++++++++++++++++-------- src/libpgagroal/utils.c | 31 ++ 13 files changed, 1514 insertions(+), 298 deletions(-) create mode 100644 cmake/FindcJSON.cmake create mode 100644 src/include/json.h create mode 100644 src/libpgagroal/json.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb1429ec..9cd0fa05 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,6 +19,8 @@ jobs: run: sudo apt update -y - name: Install libev run: sudo apt install -y libev4 libev-dev + - name: Install cJSON + run: sudo apt install -y libcjson1 libcjson-dev - name: Install systemd run: sudo apt install -y libsystemd-dev - name: Install rst2man @@ -63,6 +65,8 @@ jobs: run: brew install openssl - name: Install libev run: brew install libev + - name: Install cJSON + run: brew install cjson - name: Install rst2man run: brew install docutils - name: Install clang diff --git a/CMakeLists.txt b/CMakeLists.txt index 94c34ae8..a60633b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -78,6 +78,16 @@ else () message(FATAL_ERROR "rst2man needed") endif() +# search for cJSON library +# +find_package(cJSON) +if (cJSON_FOUND) + message(STATUS "cJSON found version ${CJSON_VERSION}") +else () + message(FATAL_ERROR "cJSON needed") +endif() + + if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") find_package(Libatomic) if (LIBATOMIC_FOUND) diff --git a/README.md b/README.md index 7298f721..070703a4 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ See [Architecture](./doc/ARCHITECTURE.md) for the architecture of `pgagroal`. * [systemd](https://www.freedesktop.org/wiki/Software/systemd/) * [rst2man](https://docutils.sourceforge.io/) * [libatomic](https://gcc.gnu.org/wiki/Atomic) +* [cJSON](https://github.com/DaveGamble/cJSON) On Rocky Linux (and similar) operating systems, the dependencies can be installed via `dnf(8)` as follows: @@ -79,7 +80,8 @@ dnf install git gcc cmake make \ openssl openssl-devel \ systemd systemd-devel \ python3-docutils \ - libatomic + libatomic \ + cjson cjson-devel ``` Please note that, on Rocky Linux, in order to install the `python3-docutils` diff --git a/cmake/FindcJSON.cmake b/cmake/FindcJSON.cmake new file mode 100644 index 00000000..6f30e309 --- /dev/null +++ b/cmake/FindcJSON.cmake @@ -0,0 +1,51 @@ +# FindcJSON.cmake +# Tries to find cJSON libraries on the system +# (e.g., on Rocky Linux: cjson and cjson-devel) +# +# Inspired by +# +# If cJSON is found, sets the following variables: +# - CJSON_INCLUDE_DIRS +# - CJSON_LIBRARIES +# - CJSON_VERSION +# +# In the header file cJSON.h the library version is specified as: +# #define CJSON_VERSION_MAJOR 1 +# #define CJSON_VERSION_MINOR 7 +# #define CJSON_VERSION_PATCH 14 + + +find_path( + CJSON_INCLUDE_DIR + NAMES cjson/cJSON.h + PATH_SUFFIXES include) +find_library( + CJSON_LIBRARY + NAMES cjson + PATH_SUFFIXES lib) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(cJSON REQUIRED_VARS CJSON_INCLUDE_DIR + CJSON_LIBRARY) +if(CJSON_FOUND) + # these variables are needed for the build + set( CJSON_INCLUDE_DIRS "${CJSON_INCLUDE_DIR}" ) + set( CJSON_LIBRARIES "${CJSON_LIBRARY}" ) + + # try to get out the library version from the headers + file(STRINGS "${CJSON_INCLUDE_DIR}/cjson/cJSON.h" + CJSON_VERSION_MAJOR REGEX "^#define[ \t]+CJSON_VERSION_MAJOR[ \t]+[0-9]+") + file(STRINGS "${CJSON_INCLUDE_DIR}/cjson/cJSON.h" + CJSON_VERSION_MINOR REGEX "^#define[ \t]+CJSON_VERSION_MINOR[ \t]+[0-9]+") + file(STRINGS "${CJSON_INCLUDE_DIR}/cjson/cJSON.h" + CJSON_VERSION_PATCH REGEX "^#define[ \t]+CJSON_VERSION_PATCH[ \t]+[0-9]+") + string(REGEX REPLACE "[^0-9]+" "" CJSON_VERSION_MAJOR "${CJSON_VERSION_MAJOR}") + string(REGEX REPLACE "[^0-9]+" "" CJSON_VERSION_MINOR "${CJSON_VERSION_MINOR}") + string(REGEX REPLACE "[^0-9]+" "" CJSON_VERSION_PATCH "${CJSON_VERSION_PATCH}") + set(CJSON_VERSION "${CJSON_VERSION_MAJOR}.${CJSON_VERSION_MINOR}.${CJSON_VERSION_PATCH}") + unset(CJSON_VERSION_MINOR) + unset(CJSON_VERSION_MAJOR) + unset(CJSON_VERSION_PATCH) +endif() + +mark_as_advanced( CJSON_INCLUDE_DIR CJSON_LIBRARY ) diff --git a/doc/CLI.md b/doc/CLI.md index 3a3d5a6a..5bf6056f 100644 --- a/doc/CLI.md +++ b/doc/CLI.md @@ -22,6 +22,7 @@ Available options are the following ones: -U, --user USERNAME Set the user name -P, --password PASSWORD Set the password -L, --logfile FILE Set the log file +-F, --format text|json Set the output format -v, --verbose Output text string of result -V, --version Display version information -?, --help Display help @@ -30,6 +31,11 @@ Available options are the following ones: Options can be specified either in short or long form, in any position of the command line. +By default the command output, if any, is reported as text. It is possible to specify JSON as the output format, +and this is the suggested format if there is the need to automtically parse the command output, since the text format +could be subject to changes in future releases. For more information about the JSON output format, +please see the [JSON Output Format](#json-output-format) section. + ## Commands ### flush @@ -380,3 +386,203 @@ pgagroal-cli reset-server 2>/dev/null There is a minimal shell completion support for `pgagroal-cli`. Please refer to the [Install pgagroal](https://github.com/agroal/pgagroal/blob/master/doc/tutorial/01_install.md) tutorial for detailed information about how to enable and use shell completions. + + +## JSON Output Format + +It is possible to obtain the output of a command in a JSON format by specyfing the `-F` (`--format`) option on the command line. +Supported output formats are: +- `text` (the default) +- `json` + +As an example, the following are invocations of commands with different output formats: + +``` +pgagroal-cli status # defaults to text output format + +pgagroal-cli status --format text # same as above +pgagroal-cli status -F text # same as above + +pgagroal-cli status --format json # outputs as JSON text +pgagroal-cli status -F json # same as above +``` + +Whenever a command produces output, the latter can be obtained in a JSON format. +Every command output consists of an object that contains two other objects: +- a `command` object, with all the details about the command and its output; +- an `application` object, with all the details about the executable that launched the command (e.g., `pgagroal-cli`). + +In the following, details about every object are provided: + +### The `application` object + +The `application` object is made by the following attributes: +- `name` a string representing the name of the executable that launched the command; +- `version` a string representing the version of the executable; +- `major`, `minor`, `patch` are integers representing every single part of the version of the application. + +As an example, when `pgagroal-cli` launches a command, the output includes an `application` object like the following: + +``` + "application": { + "name": "pgagroal-cli", + "major": 1, + "minor": 6, + "patch": 0, + "version": "1.6.0" + } +``` + + +### The `command` object + +The `command` object represents the launched command and contains also the answer from the `pgagroal`. +The object is made by the following attributes: +- `name` a string representing the command launched (e.g., `status`); +- `status` a string that contains either "OK" or an error string if the command failed; +- `error` an interger value used as a flag to indicate if the command was in error or not, where `0` means success and `1` means error; +- `exit-status` an integer that contains zero if the command run succesfully, another value depending on the specific command in case of failure; +- `output` an object that contains the details of the executed command. + +The `output` object is *the variable part* in the JSON command output, that means its effective content depends on the launched command. + +Whenever the command output includes an array of stuff, for example a connection list, such array is wrapped into a `list` JSON array with a sibling named `count` that contains the integer size of the array (number of elements). + + +The following are a few examples of commands that provide output in JSON: + + +``` +pgagroal-cli ping --format json +{ + "command": { + "name": "ping", + "status": "OK", + "error": 0, + "exit-status": 0, + "output": { + "status": 1, + "message": "running" + } + }, + "application": { + "name": "pgagroal-cli", + "major": 1, + "minor": 6, + "patch": 0, + "version": "1.6.0" + } +} + + + +pgagroal-cli status --format json +{ + "command": { + "name": "status", + "status": "OK", + "error": 0, + "exit-status": 0, + "output": { + "status": { + "message": "Running", + "status": 1 + }, + "connections": { + "active": 0, + "total": 2, + "max": 15 + }, + "databases": { + "disabled": { + "count": 0, + "state": "disabled", + "list": [] + } + } + } + }, + "application": { + "name": "pgagroal-cli", + "major": 1, + "minor": 6, + "patch": 0, + "version": "1.6.0" + } +} +``` + +As an example, the following is the output of a faulty `conf set` command (note the `status`, `error` and `exist-status` values): + +``` +pgagroal-cli conf set max_connections 1000 --format json +{ + "command": { + "name": "conf set", + "status": "Current and expected values are different", + "error": true, + "exit-status": 2, + "output": { + "key": "max_connections", + "value": "15", + "expected": "1000" + } + }, + "application": { + "name": "pgagroal-cli", + "major": 1, + "minor": 6, + "patch": 0, + "version": "1.6.0" + } +} +``` + + +The `conf ls` command returns an array named `files` where each entry is made by a couple `description` and `path`, where the former +is the mnemonic name of the configuration file, and the latter is the value of the configuration file used: + +``` +$ pgagroal-cli conf ls --format json +{ + "command": { + "name": "conf ls", + "status": "OK", + "error": 0, + "exit-status": 0, + "output": { + "files": { + "list": [{ + "description": "Main Configuration file", + "path": "/etc/pgagroal/pgagroal.conf" + }, { + "description": "HBA File", + "path": "/etc/pgagroal/pgagroal_hba.conf" + }, { + "description": "Limit file", + "path": "/etc/pgagroal/pgagroal_databases.conf" + }, { + "description": "Frontend users file", + "path": "/etc/pgagroal/pgagroal_frontend_users.conf" + }, { + "description": "Admins file", + "path": "/etc/pgagroal/pgagroal_admins.conf" + }, { + "description": "Superuser file", + "path": "" + }, { + "description": "Users file", + "path": "/etc/pgagroal/pgagroal_users.conf" + }] + } + } + }, + "application": { + "name": "pgagroal-cli", + "major": 1, + "minor": 6, + "patch": 0, + "version": "1.6.0" + } +} +``` diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 51ce9027..fea97ab9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,6 +22,7 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") ${LIBEV_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIR} ${SYSTEMD_INCLUDE_DIRS} + ${CJSON_INCLUDE_DIRS} ) # @@ -33,6 +34,7 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux") ${OPENSSL_SSL_LIBRARY} ${SYSTEMD_LIBRARIES} ${LIBATOMIC_LIBRARY} + ${CJSON_LIBRARIES} ) set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") @@ -69,6 +71,7 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") ${CMAKE_CURRENT_SOURCE_DIR}/include ${LIBEV_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIRS} + ${CJSON_INCLUDE_DIRS} ) # @@ -77,6 +80,7 @@ elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") link_libraries( ${LIBEV_LIBRARIES} ${OPENSSL_LIBRARIES} + ${CJSON_LIBRARIES} ) else() @@ -98,6 +102,7 @@ else() ${CMAKE_CURRENT_SOURCE_DIR}/include ${LIBEV_INCLUDE_DIRS} ${OPENSSL_INCLUDE_DIRS} + ${CJSON_INCLUDE_DIRS} ) # @@ -106,6 +111,7 @@ else() link_libraries( ${LIBEV_LIBRARIES} ${OPENSSL_LIBRARIES} + ${CJSON_LIBRARIES} ) endif() @@ -314,3 +320,4 @@ endif() target_link_libraries(pgagroal-admin-bin pgagroal) install(TARGETS pgagroal-admin-bin DESTINATION ${CMAKE_INSTALL_BINDIR}) + diff --git a/src/cli.c b/src/cli.c index d59fd0ec..0ce08efc 100644 --- a/src/cli.c +++ b/src/cli.c @@ -74,16 +74,16 @@ static int disabledb(SSL* ssl, int socket, char* database); static int gracefully(SSL* ssl, int socket); static int stop(SSL* ssl, int socket); static int cancel_shutdown(SSL* ssl, int socket); -static int status(SSL* ssl, int socket); -static int details(SSL* ssl, int socket); -static int isalive(SSL* ssl, int socket); +static int status(SSL* ssl, int socket, char output_format); +static int details(SSL* ssl, int socket, char output_format); +static int isalive(SSL* ssl, int socket, char output_format); static int reset(SSL* ssl, int socket); static int reset_server(SSL* ssl, int socket, char* server); static int switch_to(SSL* ssl, int socket, char* server); static int reload(SSL* ssl, int socket); -static int config_get(SSL* ssl, int socket, char* config_key, bool verbose); -static int config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verbose); -static int config_ls(SSL* ssl, int socket); +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); static void version(void) @@ -110,6 +110,7 @@ usage(void) printf(" -U, --user USERNAME Set the user name\n"); printf(" -P, --password PASSWORD Set the password\n"); printf(" -L, --logfile FILE Set the log file\n"); + printf(" -F, --format text|json Set the output format\n"); printf(" -v, --verbose Output text string of result\n"); printf(" -V, --version Display version information\n"); printf(" -?, --help Display help\n"); @@ -176,6 +177,7 @@ main(int argc, char** argv) 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; while (1) { @@ -187,12 +189,13 @@ main(int argc, char** argv) {"user", required_argument, 0, 'U'}, {"password", required_argument, 0, 'P'}, {"logfile", required_argument, 0, 'L'}, + {"format", required_argument, 0, 'F' }, {"verbose", no_argument, 0, 'v'}, {"version", no_argument, 0, 'V'}, {"help", no_argument, 0, '?'} }; - c = getopt_long(argc, argv, "vV?c:h:p:U:P:L:", + c = getopt_long(argc, argv, "vV?c:h:p:U:P:L:F:", long_options, &option_index); if (c == -1) @@ -220,6 +223,16 @@ main(int argc, char** argv) case 'L': logfile = optarg; break; + case 'F': + if (!strncmp(optarg, "json", MISC_LENGTH)) + { + output_format = COMMAND_OUTPUT_FORMAT_JSON; + } + else + { + output_format = COMMAND_OUTPUT_FORMAT_TEXT; + } + break; case 'v': verbose = true; break; @@ -580,15 +593,15 @@ main(int argc, char** argv) } else if (action == ACTION_STATUS) { - exit_code = status(s_ssl, socket); + exit_code = status(s_ssl, socket, output_format); } else if (action == ACTION_STATUS_DETAILS) { - exit_code = details(s_ssl, socket); + exit_code = details(s_ssl, socket, output_format); } else if (action == ACTION_ISALIVE) { - exit_code = isalive(s_ssl, socket); + exit_code = isalive(s_ssl, socket, output_format); } else if (action == ACTION_RESET) { @@ -608,15 +621,15 @@ main(int argc, char** argv) } else if (action == ACTION_CONFIG_GET) { - exit_code = config_get(s_ssl, socket, config_key, verbose); + exit_code = config_get(s_ssl, socket, config_key, verbose, output_format); } else if (action == ACTION_CONFIG_SET) { - exit_code = config_set(s_ssl, socket, config_key, config_value, verbose); + exit_code = config_set(s_ssl, socket, config_key, config_value, verbose, output_format); } else if (action == ACTION_CONFIG_LS) { - exit_code = config_ls(s_ssl, socket); + exit_code = config_ls(s_ssl, socket, output_format); } done: @@ -743,11 +756,11 @@ cancel_shutdown(SSL* ssl, int socket) } static int -status(SSL* ssl, int socket) +status(SSL* ssl, int socket, char output_format) { if (pgagroal_management_status(ssl, socket) == 0) { - return pgagroal_management_read_status(ssl, socket); + return pgagroal_management_read_status(ssl, socket, output_format); } else { @@ -756,14 +769,12 @@ status(SSL* ssl, int socket) } static int -details(SSL* ssl, int socket) +details(SSL* ssl, int socket, char output_format) { if (pgagroal_management_details(ssl, socket) == 0) { - if (pgagroal_management_read_status(ssl, socket) == 0) - { - return pgagroal_management_read_details(ssl, socket); - } + return pgagroal_management_read_details(ssl, socket, output_format); + } // if here, an error occurred @@ -772,18 +783,18 @@ details(SSL* ssl, int socket) } static int -isalive(SSL* ssl, int socket) +isalive(SSL* ssl, int socket, char output_format) { int status = -1; if (pgagroal_management_isalive(ssl, socket) == 0) { - if (pgagroal_management_read_isalive(ssl, socket, &status)) + if (pgagroal_management_read_isalive(ssl, socket, &status, output_format)) { return EXIT_STATUS_CONNECTION_ERROR; } - if (status != 1 && status != 2) + if (status != PING_STATUS_RUNNING && status != PING_STATUS_SHUTDOWN_GRACEFULLY) { return EXIT_STATUS_CONNECTION_ERROR; } @@ -851,12 +862,12 @@ reload(SSL* ssl, int socket) * @param config_key the key of the configuration parameter, that is the name * of the configuration parameter to read. * @param verbose if true the function will print on STDOUT also the config key + * @param output_format the format for the output (e.g., json) * @returns 0 on success, 1 on network failure, 2 on data failure */ static int -config_get(SSL* ssl, int socket, char* config_key, bool verbose) +config_get(SSL* ssl, int socket, char* config_key, bool verbose, char output_format) { - char* buffer = NULL; if (!config_key || strlen(config_key) > MISC_LENGTH) { @@ -867,40 +878,10 @@ config_get(SSL* ssl, int socket, char* config_key, bool verbose) { goto error; } - else - { - buffer = calloc(1, MISC_LENGTH); - if (buffer == NULL) - { - goto error; - } - if (pgagroal_management_read_config_get(socket, &buffer)) - { - free(buffer); - goto error; - } - // an empty response means that the - // requested configuration parameter has not been - // found, so throw an error - if (buffer && strlen(buffer)) - { - if (verbose) - { - printf("%s = %s\n", config_key, buffer); - } - else - { - printf("%s\n", buffer); - } - } - else - { - free(buffer); - return EXIT_STATUS_DATA_ERROR; - } - - free(buffer); + if (pgagroal_management_read_config_get(socket, config_key, NULL, verbose, output_format)) + { + goto error; } return EXIT_STATUS_OK; @@ -923,10 +904,10 @@ config_get(SSL* ssl, int socket, char* config_key, bool verbose) * @return 0 on success */ static int -config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verbose) +config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verbose, char output_format) { - char* buffer = NULL; - int status = EXIT_STATUS_DATA_ERROR; + + int status = EXIT_STATUS_OK; if (!config_key || strlen(config_key) > MISC_LENGTH || !config_value || strlen(config_value) > MISC_LENGTH) @@ -938,45 +919,8 @@ config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verb { goto error; } - else - { - buffer = malloc(MISC_LENGTH); - memset(buffer, 0, MISC_LENGTH); - if (pgagroal_management_read_config_get(socket, &buffer)) - { - free(buffer); - goto error; - } - // if the setting we sent is different from the setting we get - // than the system has not applied, so it is an error - if (strncmp(config_value, buffer, MISC_LENGTH) == 0) - { - status = EXIT_STATUS_OK; - } - else - { - status = EXIT_STATUS_DATA_ERROR; - } - - // assume an empty response is ok, - // do not throw an error to indicate no configuration - // setting with such name as been found - if (buffer && strlen(buffer)) - { - if (verbose) - { - printf("%s = %s\n", config_key, buffer); - } - else - { - printf("%s\n", buffer); - } - } - - free(buffer); - return status; - } + status = pgagroal_management_read_config_get(socket, config_key, config_value, verbose, output_format); return status; error: @@ -989,7 +933,7 @@ config_set(SSL* ssl, int socket, char* config_key, char* config_value, bool verb * @returns 0 on success */ static int -config_ls(SSL* ssl, int socket) +config_ls(SSL* ssl, int socket, char output_format) { if (pgagroal_management_conf_ls(ssl, socket)) @@ -997,7 +941,7 @@ config_ls(SSL* ssl, int socket) goto error; } - if (pgagroal_management_read_conf_ls(ssl, socket)) + if (pgagroal_management_read_conf_ls(ssl, socket, output_format)) { goto error; } diff --git a/src/include/json.h b/src/include/json.h new file mode 100644 index 00000000..6f6c1761 --- /dev/null +++ b/src/include/json.h @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2024 The pgagroal community + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may + * be used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* pgagroal */ +#include + +#include + +/** + * JSON related command tags, used to build and retrieve + * a JSON piece of information related to a single command + */ +#define JSON_TAG_COMMAND "command" +#define JSON_TAG_COMMAND_NAME "name" +#define JSON_TAG_COMMAND_STATUS "status" +#define JSON_TAG_COMMAND_ERROR "error" +#define JSON_TAG_COMMAND_OUTPUT "output" +#define JSON_TAG_COMMAND_EXIT_STATUS "exit-status" + +#define JSON_TAG_APPLICATION_NAME "name" +#define JSON_TAG_APPLICATION_VERSION_MAJOR "major" +#define JSON_TAG_APPLICATION_VERSION_MINOR "minor" +#define JSON_TAG_APPLICATION_VERSION_PATCH "patch" +#define JSON_TAG_APPLICATION_VERSION "version" + +#define JSON_TAG_ARRAY_NAME "list" + +/** + * JSON pre-defined values + */ +#define JSON_STRING_SUCCESS "OK" +#define JSON_STRING_ERROR "KO" +#define JSON_BOOL_SUCCESS 0 +#define JSON_BOOL_ERROR 1 + +/** + * Utility method to create a new JSON object that wraps a + * single command. This method should be called to initialize the + * object and then the other specific methods that read the + * answer from pgagroal should populate the object accordingly. + * + * Moreover, an 'application' object is placed to indicate from + * where the command has been launched (i.e., which executable) + * and at which version. + * + * @param command_name the name of the command this object wraps + * an answer for + * @param success true if the command is supposed to be succesfull + * @returns the new JSON object to use and populate + * @param executable_name the name of the executable that is creating this + * response object + */ +cJSON* +pgagroal_json_create_new_command_object(char* command_name, bool success, char* executable_name); + +/** + * Utility method to "jump" to the output JSON object wrapped into + * a command object. + * + * The "output" object is the one that every single method that reads + * back an answer from pgagroal has to populate in a specific + * way according to the data received from pgagroal. + * + * @param json the command object that wraps the command + * @returns the pointer to the output object of NULL in case of an error + */ +cJSON* +pgagroal_json_extract_command_output_object(cJSON* json); + +/** + * Utility function to set a command JSON object as faulty, that + * means setting the 'error' and 'status' message accordingly. + * + * @param json the whole json object that must include the 'command' + * tag + * @param message the message to use to set the faulty diagnostic + * indication + * + * @param exit status + * + * @returns 0 on success + * + * Example: + * json_set_command_object_faulty( json, strerror( errno ) ); + */ +int +pgagroal_json_set_command_object_faulty(cJSON* json, char* message, int exit_status); + +/** + * Utility method to inspect if a JSON object that wraps a command + * is faulty, that means if it has the error flag set to true. + * + * @param json the json object to analyzer + * @returns the value of the error flag in the object, or false if + * the object is not valid + */ +bool +pgagroal_json_is_command_object_faulty(cJSON* json); + +/** + * Utility method to extract the message related to the status + * of the command wrapped in the JSON object. + * + * @param json the JSON object to analyze + * #returns the status message or NULL in case the JSON object is not valid + */ +const char* +pgagroal_json_get_command_object_status(cJSON* json); + +/** + * Utility method to check if a JSON object wraps a specific command name. + * + * @param json the JSON object to analyze + * @param command_name the name to search for + * @returns true if the command name matches, false otherwise and in case + * the JSON object is not valid or the command name is not valid + */ +bool +pgagroal_json_is_command_name_equals_to(cJSON* json, char* command_name); + +/** + * Utility method to print out the JSON object + * on standard output. + * + * After the object has been printed, it is destroyed, so + * calling this method will make the pointer invalid + * and the jeon object cannot be used anymore. + * + * This should be the last method to be called + * when there is the need to print out the information + * contained in a json object. + * + * @param json the json object to print + */ +void +pgagroal_json_print_and_free_json_object(cJSON* json); + +/** + * Utility function to get the exit status of a given command wrapped in a JSON object. + * + * @param json the json object + * @returns the exit status of the command + */ +int +pgagroal_json_command_object_exit_status(cJSON* json); diff --git a/src/include/management.h b/src/include/management.h index cd640027..167473e4 100644 --- a/src/include/management.h +++ b/src/include/management.h @@ -63,6 +63,18 @@ extern "C" { #define MANAGEMENT_CONFIG_SET 21 #define MANAGEMENT_CONFIG_LS 22 +/** + * Status for the 'ping' (i.e., is-alive) command + */ +#define PING_STATUS_RUNNING 1 +#define PING_STATUS_SHUTDOWN_GRACEFULLY 2 + +/** + * Available command output formats + */ +#define COMMAND_OUTPUT_FORMAT_TEXT 'T' +#define COMMAND_OUTPUT_FORMAT_JSON 'J' + /** * Read the management header * @param socket The socket descriptor @@ -179,10 +191,11 @@ pgagroal_management_status(SSL* ssl, int socket); /** * Management: Read status * @param socket The socket + * @param output_format a char describing the type of output (text or json) * @return 0 upon success, otherwise 1 */ int -pgagroal_management_read_status(SSL* ssl, int socket); +pgagroal_management_read_status(SSL* ssl, int socket, char output_format); /** * Management: Write status @@ -205,10 +218,11 @@ pgagroal_management_details(SSL* ssl, int socket); /** * Management: Read details * @param socket The socket + * @param output_format the output format for this command (text, json) * @return 0 upon success, otherwise 1 */ int -pgagroal_management_read_details(SSL* ssl, int socket); +pgagroal_management_read_details(SSL* ssl, int socket, char output_format); /** * Management: Write details @@ -233,7 +247,7 @@ pgagroal_management_isalive(SSL* ssl, int socket); * @return 0 upon success, otherwise 1 */ int -pgagroal_management_read_isalive(SSL* ssl, int socket, int* status); +pgagroal_management_read_isalive(SSL* ssl, int socket, int* status, char output_format); /** * Management: Write isalive @@ -332,10 +346,14 @@ pgagroal_management_config_get(SSL* ssl, int socket, char* config_key); * @see pgagroal_management_read_payload * * @param ssl the socket file descriptor + * @param config_key the key to read (is used only to print in the output) + * @param verbose verbosity flag + * @param output_format the output format + * @param expected_value if set, a value that the configuration should match * @return 0 on success */ int -pgagroal_management_read_config_get(int socket, char** data); +pgagroal_management_read_config_get(int socket, char* config_key, char* expected_value, bool verbose, char output_format); /** * Management operation: write the result of a config_get action on the socket. @@ -414,10 +432,11 @@ pgagroal_management_conf_ls(SSL* ssl, int fd); * * @param socket the file descriptor of the open socket * @param ssl the SSL handler + * @param output_format the format to output the command result * @returns 0 on success */ int -pgagroal_management_read_conf_ls(SSL* ssl, int socket); +pgagroal_management_read_conf_ls(SSL* ssl, int socket, char output_format); /** * The management function responsible for sending diff --git a/src/include/utils.h b/src/include/utils.h index f9fd7db5..adbdd7f9 100644 --- a/src/include/utils.h +++ b/src/include/utils.h @@ -494,6 +494,20 @@ parse_deprecated_command(int argc, char* deprecated_by, unsigned int deprecated_since_major, unsigned int deprecated_since_minor); + +/** + * Given a server state, it returns a string that + * described the state in a human-readable form. + * + * If the state cannot be determined, the numeric + * form of the state is returned as a string. + * + * @param state the value of the sate for the server + * @returns the string representing the state + */ +char* +pgagroal_server_state_as_string(signed char state); + #ifdef __cplusplus } #endif diff --git a/src/libpgagroal/json.c b/src/libpgagroal/json.c new file mode 100644 index 00000000..3f50109f --- /dev/null +++ b/src/libpgagroal/json.c @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2024 The pgagroal community + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this + * list of conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may + * be used to endorse or promote products derived from this software without specific + * prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* pgagroal */ +#include +#include + +cJSON* +pgagroal_json_create_new_command_object(char* command_name, bool success, char* executable_name) +{ + // root of the JSON structure + cJSON* json = cJSON_CreateObject(); + + if (!json) + { + goto error; + } + + // the command structure + cJSON* command = cJSON_CreateObject(); + if (!command) + { + goto error; + } + + // insert meta-data about the command + cJSON_AddStringToObject(command, JSON_TAG_COMMAND_NAME, command_name); + cJSON_AddStringToObject(command, JSON_TAG_COMMAND_STATUS, success ? JSON_STRING_SUCCESS : JSON_STRING_ERROR); + cJSON_AddNumberToObject(command, JSON_TAG_COMMAND_ERROR, success ? JSON_BOOL_SUCCESS : JSON_BOOL_ERROR); + cJSON_AddNumberToObject(command, JSON_TAG_COMMAND_EXIT_STATUS, success ? 0 : EXIT_STATUS_DATA_ERROR); + + // the output of the command, this has to be filled by the caller + cJSON* output = cJSON_CreateObject(); + if (!output) + { + goto error; + } + + cJSON_AddItemToObject(command, JSON_TAG_COMMAND_OUTPUT, output); + + // who has launched the command ? + cJSON* application = cJSON_CreateObject(); + if (!application) + { + goto error; + } + + cJSON_AddStringToObject(application, JSON_TAG_APPLICATION_NAME, executable_name); + cJSON_AddNumberToObject(application, JSON_TAG_APPLICATION_VERSION_MAJOR, PGAGROAL_MAJOR_VERSION); + cJSON_AddNumberToObject(application, JSON_TAG_APPLICATION_VERSION_MINOR, PGAGROAL_MINOR_VERSION); + cJSON_AddNumberToObject(application, JSON_TAG_APPLICATION_VERSION_PATCH, PGAGROAL_PATCH_VERSION); + cJSON_AddStringToObject(application, JSON_TAG_APPLICATION_VERSION, PGAGROAL_VERSION); + + // add objects to the whole json thing + cJSON_AddItemToObject(json, "command", command); + cJSON_AddItemToObject(json, "application", application); + + return json; + +error: + if (json) + { + cJSON_Delete(json); + } + + return NULL; + +} + +cJSON* +pgagroal_json_extract_command_output_object(cJSON* json) +{ + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + return cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_OUTPUT); + +error: + return NULL; + +} + +bool +pgagroal_json_is_command_name_equals_to(cJSON* json, char* command_name) +{ + if (!json || !command_name || strlen(command_name) <= 0) + { + goto error; + } + + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + cJSON* cName = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_NAME); + if (!cName || !cJSON_IsString(cName) || !cName->valuestring) + { + goto error; + } + + return !strncmp(command_name, + cName->valuestring, + MISC_LENGTH); + +error: + return false; +} + +int +pgagroal_json_set_command_object_faulty(cJSON* json, char* message, int exit_status) +{ + if (!json) + { + goto error; + } + + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + cJSON* current = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_STATUS); + if (!current) + { + goto error; + } + + cJSON_SetValuestring(current, message); + + current = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_ERROR); + if (!current) + { + goto error; + } + + cJSON_SetIntValue(current, JSON_BOOL_ERROR); // cannot use cJSON_SetBoolValue unless cJSON >= 1.7.16 + + current = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_EXIT_STATUS); + if (!current) + { + goto error; + } + + cJSON_SetIntValue(current, exit_status); + + return 0; + +error: + return 1; + +} + +bool +pgagroal_json_is_command_object_faulty(cJSON* json) +{ + if (!json) + { + goto error; + } + + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + cJSON* status = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_ERROR); + if (!status || !cJSON_IsNumber(status)) + { + goto error; + } + + return status->valueint == JSON_BOOL_SUCCESS ? false : true; + +error: + return false; + +} + +int +pgagroal_json_command_object_exit_status(cJSON* json) +{ + if (!json) + { + goto error; + } + + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + cJSON* status = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_EXIT_STATUS); + if (!status || !cJSON_IsNumber(status)) + { + goto error; + } + + return status->valueint; + +error: + return EXIT_STATUS_DATA_ERROR; +} + +const char* +pgagroal_json_get_command_object_status(cJSON* json) +{ + if (!json) + { + goto error; + } + + cJSON* command = cJSON_GetObjectItemCaseSensitive(json, JSON_TAG_COMMAND); + if (!command) + { + goto error; + } + + cJSON* status = cJSON_GetObjectItemCaseSensitive(command, JSON_TAG_COMMAND_STATUS); + if (!cJSON_IsString(status) || (status->valuestring == NULL)) + { + goto error; + } + + return status->valuestring; +error: + return NULL; + +} + +void +pgagroal_json_print_and_free_json_object(cJSON* json) +{ + printf("%s\n", cJSON_Print(json)); + cJSON_Delete(json); +} diff --git a/src/libpgagroal/management.c b/src/libpgagroal/management.c index 3dd4719e..f624b3c1 100644 --- a/src/libpgagroal/management.c +++ b/src/libpgagroal/management.c @@ -35,6 +35,7 @@ #include #include #include +#include /* system */ #include @@ -62,6 +63,14 @@ static int write_header(SSL* ssl, int fd, signed char type, int slot); static int pgagroal_management_write_conf_ls_detail(int socket, char* what); static int pgagroal_management_read_conf_ls_detail(SSL* ssl, int socket, char* buffer); +static int pgagroal_management_json_print_status_details(cJSON* json); + +static cJSON* pgagroal_management_json_read_status_details(SSL* ssl, int socket, bool include_details); +static cJSON* pgagroal_managment_json_read_config_get(int socket, char* config_key, char* expected_value); + +static cJSON* pgagroal_management_json_read_conf_ls(SSL* ssl, int socket); +static int pgagroal_management_json_print_conf_ls(cJSON* json); + int pgagroal_management_read_header(int socket, signed char* id, int32_t* slot) { @@ -560,7 +569,50 @@ pgagroal_management_status(SSL* ssl, int fd) } int -pgagroal_management_read_status(SSL* ssl, int socket) +pgagroal_management_read_status(SSL* ssl, int socket, char output_format) +{ + cJSON* json = pgagroal_management_json_read_status_details(ssl, socket, false); + + // check we have an answer and it is not an error + if (!json || pgagroal_json_is_command_object_faulty(json)) + { + goto error; + } + + // print out the command answer + if (output_format == COMMAND_OUTPUT_FORMAT_JSON) + { + pgagroal_json_print_and_free_json_object(json); + } + else + { + pgagroal_management_json_print_status_details(json); + } + + return 0; + +error: + pgagroal_log_warn("pgagroal_management_read_status: command error [%s]", + (json == NULL ? "" : pgagroal_json_get_command_object_status(json))); + return 1; +} + +/** + * Utility method that reads the answer from pgagroal about + * either the 'status' or the 'status details' command. + * The answer is then wrapped into a JSON object + * that contains all the information needed to be printed out in either + * JSON format or text format. + * + * @param ssl the SSL file descriptor for the socket + * @param socket the socket file descriptor + * @param include_details true if the method has to handle the 'status details' command + * or false if the answer is related only to the 'status' command + * + * @returns the json object, faulty if something goes wrong + */ +static cJSON* +pgagroal_management_json_read_status_details(SSL* ssl, int socket, bool include_details) { char buf[16]; char disabled[NUMBER_OF_DISABLED][MAX_DATABASE_LENGTH]; @@ -568,21 +620,27 @@ pgagroal_management_read_status(SSL* ssl, int socket) int active; int total; int max; + int max_connections = 0; + int limits = 0; + int servers = 0; + char header[12 + MAX_NUMBER_OF_CONNECTIONS]; memset(&buf, 0, sizeof(buf)); memset(&disabled, 0, sizeof(disabled)); + memset(&header, 0, sizeof(header)); + + cJSON* json = pgagroal_json_create_new_command_object(include_details ? "status details" : "status", true, "pgagroal-cli"); + cJSON* output = pgagroal_json_extract_command_output_object(json); if (read_complete(ssl, socket, &buf[0], sizeof(buf))) { - pgagroal_log_warn("pgagroal_management_read_status: read: %d %s", socket, strerror(errno)); - errno = 0; + pgagroal_log_warn("pgagroal_management_json_read_status_details: read: %d %s", socket, strerror(errno)); goto error; } if (read_complete(ssl, socket, &disabled[0], sizeof(disabled))) { - pgagroal_log_warn("pgagroal_management_read_status: read: %d %s", socket, strerror(errno)); - errno = 0; + pgagroal_log_warn("pgagroal_management_json_read_status_details: read: %d %s", socket, strerror(errno)); goto error; } @@ -591,10 +649,24 @@ pgagroal_management_read_status(SSL* ssl, int socket) total = pgagroal_read_int32(&(buf[8])); max = pgagroal_read_int32(&(buf[12])); - printf("Status: %s\n", (status == 1 ? "Running" : "Graceful shutdown")); - printf("Active connections: %d\n", active); - printf("Total connections: %d\n", total); - printf("Max connections: %d\n", max); + // status information + cJSON* status_json = cJSON_CreateObject(); + cJSON_AddStringToObject(status_json, "message", (status == 1 ? "Running" : "Graceful shutdown")); + cJSON_AddNumberToObject(status_json, "status", status); + cJSON_AddItemToObject(output, "status", status_json); + + // define all the information about connections + cJSON* connections = cJSON_CreateObject(); + cJSON_AddNumberToObject(connections, "active", active); + cJSON_AddNumberToObject(connections, "total", total); + cJSON_AddNumberToObject(connections, "max", max); + cJSON_AddItemToObject(output, "connections", connections); + + // define all the information about disabled databases + cJSON* databases = cJSON_CreateObject(); + cJSON* databases_array = cJSON_CreateArray(); + + int counter = 0; for (int i = 0; i < NUMBER_OF_DISABLED; i++) { @@ -602,20 +674,165 @@ pgagroal_management_read_status(SSL* ssl, int socket) { if (!strcmp(disabled[i], "*")) { - printf("Disabled database: ALL\n"); + cJSON_AddItemToArray(databases_array, cJSON_CreateString("ALL")); + counter = -1; } else { - printf("Disabled database: %s\n", disabled[i]); + cJSON_AddItemToArray(databases_array, cJSON_CreateString(disabled[i])); + counter++; } } } - return 0; + cJSON* disabled_databases = cJSON_CreateObject(); + cJSON_AddNumberToObject(disabled_databases, "count", counter); + cJSON_AddStringToObject(disabled_databases, "state", "disabled"); + cJSON_AddItemToObject(disabled_databases, JSON_TAG_ARRAY_NAME, databases_array); + cJSON_AddItemToObject(databases, "disabled", disabled_databases); + cJSON_AddItemToObject(output, "databases", databases); -error: + // the 'status' command ends here + if (!include_details) + { + goto end; + } - return 1; + /*********** 'status details ************/ + + memset(&header, 0, sizeof(header)); + + if (read_complete(ssl, socket, &header[0], sizeof(header))) + { + goto error; + } + + // quantity informations + max_connections = pgagroal_read_int32(&header); + limits = pgagroal_read_int32(&(header[4])); + servers = pgagroal_read_int32(&(header[8])); + + cJSON* json_servers = cJSON_CreateObject(); + cJSON* json_servers_array = cJSON_CreateArray(); + cJSON_AddItemToObject(output, "servers", json_servers); + cJSON_AddNumberToObject(json_servers, "count", servers); + + // details about the servers + for (int i = 0; i < servers; i++) + { + char server[5 + MISC_LENGTH + MISC_LENGTH]; + + memset(&server, 0, sizeof(server)); + + if (read_complete(ssl, socket, &server[0], sizeof(server))) + { + goto error; + } + + cJSON* current_server_json = cJSON_CreateObject(); + cJSON_AddStringToObject(current_server_json, "server", pgagroal_read_string(&(server[0]))); + cJSON_AddStringToObject(current_server_json, "host", pgagroal_read_string(&(server[MISC_LENGTH]))); + cJSON_AddNumberToObject(current_server_json, "port", pgagroal_read_int32(&(server[MISC_LENGTH + MISC_LENGTH]))); + cJSON_AddStringToObject(current_server_json, "state", pgagroal_server_state_as_string(pgagroal_read_byte(&(server[MISC_LENGTH + MISC_LENGTH + 4])))); + + cJSON_AddItemToArray(json_servers_array, current_server_json); + } + + cJSON_AddItemToObject(json_servers, JSON_TAG_ARRAY_NAME, json_servers_array); + + // details about the limits + cJSON* json_limits = cJSON_CreateObject(); + cJSON* json_limits_array = cJSON_CreateArray(); + cJSON_AddItemToObject(json_limits, JSON_TAG_ARRAY_NAME, json_limits_array); + cJSON_AddItemToObject(output, "limits", json_limits); + cJSON_AddNumberToObject(json_limits, "count", limits); + + for (int i = 0; i < limits; i++) + { + char limit[16 + MAX_DATABASE_LENGTH + MAX_USERNAME_LENGTH]; + memset(&limit, 0, sizeof(limit)); + + if (read_complete(ssl, socket, &limit[0], sizeof(limit))) + { + goto error; + } + + cJSON* current_limit_json = cJSON_CreateObject(); + + cJSON_AddStringToObject(current_limit_json, "database", pgagroal_read_string(&(limit[16]))); + cJSON_AddStringToObject(current_limit_json, "username", pgagroal_read_string(&(limit[16 + MAX_DATABASE_LENGTH]))); + + cJSON* current_connections = cJSON_CreateObject(); + + cJSON_AddNumberToObject(current_connections, "active", pgagroal_read_int32(&(limit))); + cJSON_AddNumberToObject(current_connections, "max", pgagroal_read_int32(&(limit[4]))); + cJSON_AddNumberToObject(current_connections, "initial", pgagroal_read_int32(&(limit[8]))); + cJSON_AddNumberToObject(current_connections, "min", pgagroal_read_int32(&(limit[12]))); + + cJSON_AddItemToObject(current_limit_json, "connections", current_connections); + cJSON_AddItemToArray(json_limits_array, current_limit_json); + + } + + // max connections details (note that the connections json object has been created + // as part of the status output) + cJSON* connections_array = cJSON_CreateArray(); + cJSON_AddItemToObject(connections, JSON_TAG_ARRAY_NAME, connections_array); + + for (int i = 0; i < max_connections; i++) + { + char details[16 + MAX_DATABASE_LENGTH + MAX_USERNAME_LENGTH + MAX_APPLICATION_NAME]; + signed char state; + long time; + time_t t; + char ts[20] = {0}; + int pid; + char p[10] = {0}; + int fd; + char f[10] = {0}; + + memset(&details, 0, sizeof(details)); + + if (read_complete(ssl, socket, &details[0], sizeof(details))) + { + + goto error; + } + + state = (signed char)header[12 + i]; + time = pgagroal_read_long(&(details[0])); + pid = pgagroal_read_int32(&(details[8])); + fd = pgagroal_read_int32(&(details[12])); + + t = time; + strftime(ts, 20, "%Y-%m-%d %H:%M:%S", localtime(&t)); + + sprintf(p, "%d", pid); + sprintf(f, "%d", fd); + + cJSON* current_connection_json = cJSON_CreateObject(); + + cJSON_AddNumberToObject(current_connection_json, "number", i); + cJSON_AddStringToObject(current_connection_json, "state", pgagroal_server_state_as_string(state)); + cJSON_AddStringToObject(current_connection_json, "time", time > 0 ? ts : ""); + cJSON_AddStringToObject(current_connection_json, "pid", pid > 0 ? p : ""); + cJSON_AddStringToObject(current_connection_json, "fd", fd > 0 ? f : ""); + cJSON_AddStringToObject(current_connection_json, "database", pgagroal_read_string(&(details[16]))); + cJSON_AddStringToObject(current_connection_json, "user", pgagroal_read_string(&(details[16 + MAX_DATABASE_LENGTH]))); + cJSON_AddStringToObject(current_connection_json, "detail", pgagroal_read_string(&(details[16 + MAX_DATABASE_LENGTH + MAX_USERNAME_LENGTH]))); + + cJSON_AddItemToArray(connections_array, current_connection_json); + + } + +end: + return json; + +error: + // set the json object as faulty and erase the errno + pgagroal_json_set_command_object_faulty(json, strerror(errno), errno); + errno = 0; + return json; } int @@ -706,143 +923,31 @@ pgagroal_management_details(SSL* ssl, int fd) } int -pgagroal_management_read_details(SSL* ssl, int socket) +pgagroal_management_read_details(SSL* ssl, int socket, char output_format) { - char header[12 + MAX_NUMBER_OF_CONNECTIONS]; - int max_connections = 0; - int limits = 0; - int servers = 0; + cJSON* json = pgagroal_management_json_read_status_details(ssl, socket, true); - memset(&header, 0, sizeof(header)); - - if (read_complete(ssl, socket, &header[0], sizeof(header))) + // check we have an answer and it is not an error + if (!json || pgagroal_json_is_command_object_faulty(json)) { - pgagroal_log_warn("pgagroal_management_read_details: read: %d %s", socket, strerror(errno)); - errno = 0; goto error; } - max_connections = pgagroal_read_int32(&header); - limits = pgagroal_read_int32(&(header[4])); - servers = pgagroal_read_int32(&(header[8])); - - for (int i = 0; i < servers; i++) + // print out the command answer + if (output_format == COMMAND_OUTPUT_FORMAT_JSON) { - char server[5 + MISC_LENGTH + MISC_LENGTH]; - signed char state; - - memset(&server, 0, sizeof(server)); - - if (read_complete(ssl, socket, &server[0], sizeof(server))) - { - pgagroal_log_warn("pgagroal_management_read_details: read: %d %s", socket, strerror(errno)); - errno = 0; - goto error; - } - - state = pgagroal_read_byte(&(server[MISC_LENGTH + MISC_LENGTH + 4])); - - printf("---------------------\n"); - printf("Server: %s\n", pgagroal_read_string(&(server[0]))); - printf("Host: %s\n", pgagroal_read_string(&(server[MISC_LENGTH]))); - printf("Port: %d\n", pgagroal_read_int32(&(server[MISC_LENGTH + MISC_LENGTH]))); - - switch (state) - { - case SERVER_NOTINIT: - printf("State: Not init\n"); - break; - case SERVER_NOTINIT_PRIMARY: - printf("State: Not init (primary)\n"); - break; - case SERVER_PRIMARY: - printf("State: Primary\n"); - break; - case SERVER_REPLICA: - printf("State: Replica\n"); - break; - case SERVER_FAILOVER: - printf("State: Failover\n"); - break; - case SERVER_FAILED: - printf("State: Failed\n"); - break; - default: - printf("State: %d\n", state); - break; - } - } - - printf("---------------------\n"); - - for (int i = 0; i < limits; i++) - { - char limit[16 + MAX_DATABASE_LENGTH + MAX_USERNAME_LENGTH]; - - memset(&limit, 0, sizeof(limit)); - - if (read_complete(ssl, socket, &limit[0], sizeof(limit))) - { - pgagroal_log_warn("pgagroal_management_read_details: read: %d %s", socket, strerror(errno)); - errno = 0; - goto error; - } - - printf("Database: %s\n", pgagroal_read_string(&(limit[16]))); - printf("Username: %s\n", pgagroal_read_string(&(limit[16 + MAX_DATABASE_LENGTH]))); - printf("Active connections: %d\n", pgagroal_read_int32(&(limit))); - printf("Max connections: %d\n", pgagroal_read_int32(&(limit[4]))); - printf("Initial connections: %d\n", pgagroal_read_int32(&(limit[8]))); - printf("Min connections: %d\n", pgagroal_read_int32(&(limit[12]))); - printf("---------------------\n"); + pgagroal_json_print_and_free_json_object(json); } - - for (int i = 0; i < max_connections; i++) + else { - char details[16 + MAX_DATABASE_LENGTH + MAX_USERNAME_LENGTH + MAX_APPLICATION_NAME]; - signed char state; - long time; - time_t t; - char ts[20] = {0}; - int pid; - char p[10] = {0}; - int fd; - char f[10] = {0}; - - memset(&details, 0, sizeof(details)); - - if (read_complete(ssl, socket, &details[0], sizeof(details))) - { - pgagroal_log_warn("pgagroal_management_read_details: read: %d %s", socket, strerror(errno)); - errno = 0; - goto error; - } - - state = (signed char)header[12 + i]; - time = pgagroal_read_long(&(details[0])); - pid = pgagroal_read_int32(&(details[8])); - fd = pgagroal_read_int32(&(details[12])); - - t = time; - strftime(ts, 20, "%Y-%m-%d %H:%M:%S", localtime(&t)); - - sprintf(p, "%d", pid); - sprintf(f, "%d", fd); - - printf("Connection %4d: %-15s %-19s %-6s %-6s %s %s %s\n", - i, - pgagroal_get_state_string(state), - time > 0 ? ts : "", - pid > 0 ? p : "", - fd > 0 ? f : "", - pgagroal_read_string(&(details[16])), - pgagroal_read_string(&(details[16 + MAX_DATABASE_LENGTH])), - pgagroal_read_string(&(details[16 + MAX_DATABASE_LENGTH + MAX_USERNAME_LENGTH]))); + pgagroal_management_json_print_status_details(json); } return 0; error: + pgagroal_log_warn("pgagroal_management_read_details: command error [%s]", + (json == NULL ? "" : pgagroal_json_get_command_object_status(json))); return 1; } @@ -962,7 +1067,7 @@ pgagroal_management_isalive(SSL* ssl, int fd) } int -pgagroal_management_read_isalive(SSL* ssl, int socket, int* status) +pgagroal_management_read_isalive(SSL* ssl, int socket, int* status, char output_format) { char buf[4]; @@ -977,6 +1082,31 @@ pgagroal_management_read_isalive(SSL* ssl, int socket, int* status) *status = pgagroal_read_int32(&buf); + // do I need to provide JSON output? + if (output_format == COMMAND_OUTPUT_FORMAT_JSON) + { + cJSON* json = pgagroal_json_create_new_command_object("ping", true, "pgagroal-cli"); + cJSON* output = pgagroal_json_extract_command_output_object(json); + + cJSON_AddNumberToObject(output, "status", *status); + + if (*status == PING_STATUS_RUNNING) + { + cJSON_AddStringToObject(output, "message", "running"); + } + else if (*status == PING_STATUS_SHUTDOWN_GRACEFULLY) + { + cJSON_AddStringToObject(output, "message", "shutdown gracefully"); + } + else + { + cJSON_AddStringToObject(output, "message", "unknown"); + } + + pgagroal_json_print_and_free_json_object(json); + + } + return 0; error: @@ -993,11 +1123,11 @@ pgagroal_management_write_isalive(int socket, bool gracefully) if (!gracefully) { - pgagroal_write_int32(buf, 1); + pgagroal_write_int32(buf, PING_STATUS_RUNNING); } else { - pgagroal_write_int32(buf, 2); + pgagroal_write_int32(buf, PING_STATUS_SHUTDOWN_GRACEFULLY); } if (write_complete(NULL, socket, &buf, sizeof(buf))) @@ -1608,11 +1738,105 @@ pgagroal_management_write_config_get(int socket, char* config_key) } -int -pgagroal_management_read_config_get(int socket, char** data) +/** + * Utility method to wrap the answer about a configuration setting + * into a JSON object. + * + * @param socket the socket from which reading the data from + * @param config_key the key requested, used only to populate the json + * @param expected_value the config value expected in the case of a `config set`. + * If the expetced_value is not null, the function checks if the obtained config value and + * the expected one are equal, and in case are not set the JSON object as faulty. + * + * @return the JSON object + */ +static cJSON* +pgagroal_managment_json_read_config_get(int socket, char* config_key, char* expected_value) { + int size = MISC_LENGTH; - return pgagroal_management_read_payload(socket, MANAGEMENT_CONFIG_GET, &size, data); + char* buffer = NULL; + bool is_config_set = false; + + buffer = calloc(1, size); + if (buffer == NULL) + { + goto error; + } + + if (pgagroal_management_read_payload(socket, MANAGEMENT_CONFIG_GET, &size, &buffer)) + { + goto error; + } + + // is this the answer from a 'conf set' command ? + is_config_set = (expected_value && strlen(expected_value) > 0); + + cJSON* json = pgagroal_json_create_new_command_object(is_config_set ? "conf set" : "conf get", true, "pgagroal-cli"); + cJSON* output = pgagroal_json_extract_command_output_object(json); + cJSON_AddStringToObject(output, "key", config_key); + cJSON_AddStringToObject(output, "value", buffer); + + if (is_config_set) + { + cJSON_AddStringToObject(output, "expected", expected_value); + + // if the expected value is not what we get, this means there is an error + // (e.g., cannot apply the config set) + if (strncmp(buffer, expected_value, size)) + { + pgagroal_json_set_command_object_faulty(json, "Current and expected values are different", EXIT_STATUS_DATA_ERROR); + } + } + + free(buffer); + return json; +error: + if (buffer) + { + free(buffer); + } + return NULL; +} + +int +pgagroal_management_read_config_get(int socket, char* config_key, char* expected_value, bool verbose, char output_format) +{ + + cJSON* json = pgagroal_managment_json_read_config_get(socket, config_key, expected_value); + + if (!json) + { + goto error; + } + + if (output_format == COMMAND_OUTPUT_FORMAT_JSON) + { + pgagroal_json_print_and_free_json_object(json); + goto end; + } + + // if here, print out in text format + cJSON* output = pgagroal_json_extract_command_output_object(json); + cJSON* value = cJSON_GetObjectItemCaseSensitive(output, "value"); + cJSON* key = cJSON_GetObjectItemCaseSensitive(output, "key"); + if (verbose) + { + printf("%s = %s\n", key->valuestring, value->valuestring); + } + else + { + printf("%s\n", value->valuestring); + } + +end: + return pgagroal_json_command_object_exit_status(json); + +error: + + pgagroal_log_warn("pgagroal_management_read_config_get : error retrieving configuration for <%s> : %s", config_key, strerror(errno)); + errno = 0; + return EXIT_STATUS_DATA_ERROR; } int @@ -1750,69 +1974,31 @@ pgagroal_management_conf_ls(SSL* ssl, int fd) } int -pgagroal_management_read_conf_ls(SSL* ssl, int socket) +pgagroal_management_read_conf_ls(SSL* ssl, int socket, char output_format) { - char buf[4]; - char* buffer; - - memset(&buf, 0, sizeof(buf)); - buffer = calloc(1, MAX_PATH); - - if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) - { - goto error; - } - printf("Main Configuration file: %s\n", buffer); + // get the JSON output + cJSON* json = pgagroal_management_json_read_conf_ls(ssl, socket); - if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + // check we have an answer and it is not an error + if (!json || pgagroal_json_is_command_object_faulty(json)) { goto error; } - printf("HBA file: %s\n", buffer); - - if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + // print out the command answer + if (output_format == COMMAND_OUTPUT_FORMAT_JSON) { - goto error; + pgagroal_json_print_and_free_json_object(json); } - - printf("Limit file: %s\n", buffer); - - if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) - { - goto error; - } - - printf("Frontend users file: %s\n", buffer); - - if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) - { - goto error; - } - - printf("Admins file: %s\n", buffer); - - if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) - { - goto error; - } - - printf("Superuser file: %s\n", buffer); - - if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + else { - goto error; + pgagroal_management_json_print_conf_ls(json); } - printf("Users file: %s\n", buffer); - - free(buffer); - return 0; error: - free(buffer); pgagroal_log_warn("pgagroal_management_read_conf_ls: read: %d %s", socket, strerror(errno)); errno = 0; @@ -1971,3 +2157,307 @@ pgagroal_management_read_conf_ls_detail(SSL* ssl, int socket, char* buffer) return 1; } + +/** + * Utility function to print out the result of a 'status' + * or a 'status details' command already wrapped into a + * JSON object. + * The function tries to understand from the command name + * within the JSON object if the output refers to the + * 'status' or 'status details' command. + * + * If the command is faulty, this method does nothing, therefore + * printing out information about faulty commands has to be done + * at an higher level. + * + * @param json the JSON object + * + * @returns 0 on success + */ +int +pgagroal_management_json_print_status_details(cJSON* json) +{ + bool is_command_details = false; /* is this command 'status details' ? */ + + // sanity check + if (!json || pgagroal_json_is_command_object_faulty(json)) + { + return 1; + } + + // the command must be 'status' or 'status details' + if (pgagroal_json_is_command_name_equals_to(json, "status")) + { + is_command_details = false; + } + else if (pgagroal_json_is_command_name_equals_to(json, "status details")) + { + is_command_details = true; + } + else + { + goto error; + } + + // now get the output and start printing it + cJSON* output = pgagroal_json_extract_command_output_object(json); + + // overall status + printf("Status: %s\n", + cJSON_GetObjectItemCaseSensitive(cJSON_GetObjectItemCaseSensitive(output, "status"), "message")->valuestring); + + // connections + cJSON* connections = cJSON_GetObjectItemCaseSensitive(output, "connections"); + if (!connections) + { + goto error; + } + + printf("Active connections: %d\n", cJSON_GetObjectItemCaseSensitive(connections, "active")->valueint); + printf("Total connections: %d\n", cJSON_GetObjectItemCaseSensitive(connections, "total")->valueint); + printf("Max connections: %d\n", cJSON_GetObjectItemCaseSensitive(connections, "max")->valueint); + + // databases + cJSON* databases = cJSON_GetObjectItemCaseSensitive(output, "databases"); + if (!databases) + { + goto error; + } + + cJSON* disabled_databases = cJSON_GetObjectItemCaseSensitive(databases, "disabled"); + if (!disabled_databases) + { + goto error; + } + + cJSON* disabled_databases_list = cJSON_GetObjectItemCaseSensitive(disabled_databases, JSON_TAG_ARRAY_NAME); + cJSON* current; + cJSON_ArrayForEach(current, disabled_databases_list) + { + printf("Disabled database: %s\n", current->valuestring); + } + + // the status command ends here + if (!is_command_details) + { + goto end; + } + + // dump the servers information + cJSON* servers = cJSON_GetObjectItemCaseSensitive(output, "servers"); + if (!servers) + { + goto error; + } + + cJSON* servers_list = cJSON_GetObjectItemCaseSensitive(servers, JSON_TAG_ARRAY_NAME); + cJSON_ArrayForEach(current, servers_list) + { + printf("---------------------\n"); + printf("Server: %s\n", cJSON_GetObjectItemCaseSensitive(current, "server")->valuestring); + printf("Host: %s\n", cJSON_GetObjectItemCaseSensitive(current, "host")->valuestring); + printf("Port: %d\n", cJSON_GetObjectItemCaseSensitive(current, "port")->valueint); + printf("State: %s\n", cJSON_GetObjectItemCaseSensitive(current, "state")->valuestring); + printf("---------------------\n"); + + } + + // dump the limits information + cJSON* limits = cJSON_GetObjectItemCaseSensitive(output, "limits"); + cJSON* limits_list = cJSON_GetObjectItemCaseSensitive(limits, JSON_TAG_ARRAY_NAME); + cJSON_ArrayForEach(current, limits_list) + { + printf("---------------------\n"); + printf("Database: %s\n", cJSON_GetObjectItemCaseSensitive(current, "database")->valuestring); + printf("Username: %s\n", cJSON_GetObjectItemCaseSensitive(current, "username")->valuestring); + cJSON* current_connections = cJSON_GetObjectItemCaseSensitive(current, "connections"); + printf("Active connections: %d\n", cJSON_GetObjectItemCaseSensitive(current_connections, "active")->valueint); + printf("Max connections: %d\n", cJSON_GetObjectItemCaseSensitive(current_connections, "max")->valueint); + printf("Initial connections: %d\n", cJSON_GetObjectItemCaseSensitive(current_connections, "initial")->valueint); + printf("Min connections: %d\n", cJSON_GetObjectItemCaseSensitive(current_connections, "min")->valueint); + printf("---------------------\n"); + } + + // print the connection information + int i = 0; + cJSON_ArrayForEach(current, cJSON_GetObjectItemCaseSensitive(connections, JSON_TAG_ARRAY_NAME)) + { + printf("Connection %4d: %-15s %-19s %-6s %-6s %s %s %s\n", + i++, + cJSON_GetObjectItemCaseSensitive(current, "state")->valuestring, + cJSON_GetObjectItemCaseSensitive(current, "time")->valuestring, + cJSON_GetObjectItemCaseSensitive(current, "pid")->valuestring, + cJSON_GetObjectItemCaseSensitive(current, "fd")->valuestring, + cJSON_GetObjectItemCaseSensitive(current, "user")->valuestring, + cJSON_GetObjectItemCaseSensitive(current, "database")->valuestring, + cJSON_GetObjectItemCaseSensitive(current, "detail")->valuestring); + + } + +end: + return 0; + +error: + return 1; + +} + +/** + * Utility method to get the information about the `conf ls` command. + * This method produces a cJSON object that needs to be printed out in textual format. + * + * @param ssl the SSL file descriptor + * @param socket the file descriptor for the socket + * + * @returns the cJSON object, faulty if something went wrong + */ +static cJSON* +pgagroal_management_json_read_conf_ls(SSL* ssl, int socket) +{ + char buf[4]; + char* buffer; + + cJSON* json = pgagroal_json_create_new_command_object("conf ls", true, "pgagroal-cli"); + cJSON* output = pgagroal_json_extract_command_output_object(json); + + // add an array that will contain the files + cJSON* files = cJSON_CreateObject(); + cJSON* files_array = cJSON_CreateArray(); + cJSON_AddItemToObject(output, "files", files); + cJSON_AddItemToObject(files, JSON_TAG_ARRAY_NAME, files_array); + + memset(&buf, 0, sizeof(buf)); + buffer = calloc(1, MAX_PATH); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + // add the main configuration file entry + cJSON* mainConf = cJSON_CreateObject(); + cJSON_AddStringToObject(mainConf, "description", "Main Configuration file"); + cJSON_AddStringToObject(mainConf, "path", buffer); + cJSON_AddItemToArray(files_array, mainConf); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + // add the HBA file + cJSON* hbaConf = cJSON_CreateObject(); + cJSON_AddStringToObject(hbaConf, "description", "HBA File"); + cJSON_AddStringToObject(hbaConf, "path", buffer); + cJSON_AddItemToArray(files_array, hbaConf); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + // add the limit file + cJSON* limitConf = cJSON_CreateObject(); + cJSON_AddStringToObject(limitConf, "description", "Limit file"); + cJSON_AddStringToObject(limitConf, "path", buffer); + cJSON_AddItemToArray(files_array, limitConf); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + // add the frontend file + cJSON* frontendConf = cJSON_CreateObject(); + cJSON_AddStringToObject(frontendConf, "description", "Frontend users file"); + cJSON_AddStringToObject(frontendConf, "path", buffer); + cJSON_AddItemToArray(files_array, frontendConf); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + // add the admins file + cJSON* adminsConf = cJSON_CreateObject(); + cJSON_AddStringToObject(adminsConf, "description", "Admins file"); + cJSON_AddStringToObject(adminsConf, "path", buffer); + cJSON_AddItemToArray(files_array, adminsConf); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + // add the superuser file + cJSON* superuserConf = cJSON_CreateObject(); + cJSON_AddStringToObject(superuserConf, "description", "Superuser file"); + cJSON_AddStringToObject(superuserConf, "path", buffer); + cJSON_AddItemToArray(files_array, superuserConf); + + if (pgagroal_management_read_conf_ls_detail(ssl, socket, buffer)) + { + goto error; + } + + // add the users file + cJSON* usersConf = cJSON_CreateObject(); + cJSON_AddStringToObject(usersConf, "description", "Users file"); + cJSON_AddStringToObject(usersConf, "path", buffer); + cJSON_AddItemToArray(files_array, usersConf); + + // all done + goto end; + +error: + free(buffer); + pgagroal_log_warn("pgagroal_management_json_read_conf_ls: read: %d %s", socket, strerror(errno)); + errno = 0; + pgagroal_json_set_command_object_faulty(json, strerror(errno), errno); + +end: + free(buffer); + return json; + +} + +/** + * Utility function to handle a JSON object and print it out + * as normal text. + * + * @param json the JSON object + * @returns 0 on success + */ +static int +pgagroal_management_json_print_conf_ls(cJSON* json) +{ + // sanity check + if (!json || pgagroal_json_is_command_object_faulty(json)) + { + goto error; + } + + // now get the output and start printing it + cJSON* output = pgagroal_json_extract_command_output_object(json); + + // files + cJSON* files = cJSON_GetObjectItemCaseSensitive(output, "files"); + if (!files) + { + goto error; + } + + cJSON* files_array = cJSON_GetObjectItemCaseSensitive(files, JSON_TAG_ARRAY_NAME); + cJSON* current; + cJSON_ArrayForEach(current, files_array) + { + // the current JSON object is made by two different values + printf("%-25s : %s\n", + cJSON_GetObjectItemCaseSensitive(current, "description")->valuestring, + cJSON_GetObjectItemCaseSensitive(current, "path")->valuestring); + } + +error: + cJSON_Delete(json); + return 1; +} diff --git a/src/libpgagroal/utils.c b/src/libpgagroal/utils.c index 896e8ca1..d24064fa 100644 --- a/src/libpgagroal/utils.c +++ b/src/libpgagroal/utils.c @@ -1077,3 +1077,34 @@ parse_command_simple(int argc, { return parse_command(argc, argv, offset, command, subcommand, NULL, NULL, NULL, NULL); } + +/** + * Given a server state, it returns a string that + * described the state in a human-readable form. + * + * If the state cannot be determined, the numeric + * form of the state is returned as a string. + * + * @param state the value of the sate for the server + * @returns the string representing the state + */ +char* +pgagroal_server_state_as_string(signed char state) +{ + char* buf; + + switch (state) + { + case SERVER_NOTINIT: return "Not init"; + case SERVER_NOTINIT_PRIMARY: return "Not init (primary)"; + case SERVER_PRIMARY: return "Primary"; + case SERVER_REPLICA: return "Replica"; + case SERVER_FAILOVER: return "Failover"; + case SERVER_FAILED: return "Failed"; + default: + buf = malloc(5); + memset(buf, 0, 5); + snprintf(buf, 5, "%d", state); + return buf; + } +} From 20a1650cef5dc42fbafe23501f3d5fc61058dde8 Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Fri, 16 Feb 2024 13:39:44 +0100 Subject: [PATCH 15/24] [#399] Fix segmentation fault in `conf get` JSON format The `conf get` implementation in JSON format was trying to extract the command status after having freed the JSON object. This commit changes the `pgagroal_json_print_and_free_json_object()` function making it returning the status code for the command contained (possibly) in the JSON object. In this way, it is possible to call the fuction in a way like: int status = pgagroal_json_print_and_free_json_object( json ); and get back the status leaving the function to free the object. Close #399 --- src/include/json.h | 7 ++++++- src/libpgagroal/json.c | 4 +++- src/libpgagroal/management.c | 4 +--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/include/json.h b/src/include/json.h index 6f6c1761..2ad78590 100644 --- a/src/include/json.h +++ b/src/include/json.h @@ -155,9 +155,14 @@ pgagroal_json_is_command_name_equals_to(cJSON* json, char* command_name); * when there is the need to print out the information * contained in a json object. * + * Since the JSON object will be invalidated, the method + * returns the status of the JSON command within it + * to be used. + * * @param json the json object to print + * @return the command status within the JSON object */ -void +int pgagroal_json_print_and_free_json_object(cJSON* json); /** diff --git a/src/libpgagroal/json.c b/src/libpgagroal/json.c index 3f50109f..766bcc51 100644 --- a/src/libpgagroal/json.c +++ b/src/libpgagroal/json.c @@ -260,9 +260,11 @@ pgagroal_json_get_command_object_status(cJSON* json) } -void +int pgagroal_json_print_and_free_json_object(cJSON* json) { + int status = pgagroal_json_command_object_exit_status(json); printf("%s\n", cJSON_Print(json)); cJSON_Delete(json); + return status; } diff --git a/src/libpgagroal/management.c b/src/libpgagroal/management.c index f624b3c1..789b6736 100644 --- a/src/libpgagroal/management.c +++ b/src/libpgagroal/management.c @@ -1812,8 +1812,7 @@ pgagroal_management_read_config_get(int socket, char* config_key, char* expected if (output_format == COMMAND_OUTPUT_FORMAT_JSON) { - pgagroal_json_print_and_free_json_object(json); - goto end; + return pgagroal_json_print_and_free_json_object(json); } // if here, print out in text format @@ -1829,7 +1828,6 @@ pgagroal_management_read_config_get(int socket, char* config_key, char* expected printf("%s\n", value->valuestring); } -end: return pgagroal_json_command_object_exit_status(json); error: From 610e7b8515c462b30d3d7637ccf357908eeb0fd4 Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Fri, 16 Feb 2024 16:33:25 +0100 Subject: [PATCH 16/24] [401] Change JSON based command return paths Fixes the return path of the management functions that use cJSON objects to ensure that all the objects are freed appropriately. Also changes the return value to reflect the command exit status as provided by the JSON object. Close #401 --- src/libpgagroal/management.c | 69 ++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/src/libpgagroal/management.c b/src/libpgagroal/management.c index 789b6736..62615bd9 100644 --- a/src/libpgagroal/management.c +++ b/src/libpgagroal/management.c @@ -582,15 +582,13 @@ pgagroal_management_read_status(SSL* ssl, int socket, char output_format) // print out the command answer if (output_format == COMMAND_OUTPUT_FORMAT_JSON) { - pgagroal_json_print_and_free_json_object(json); + return pgagroal_json_print_and_free_json_object(json); } else { - pgagroal_management_json_print_status_details(json); + return pgagroal_management_json_print_status_details(json); } - return 0; - error: pgagroal_log_warn("pgagroal_management_read_status: command error [%s]", (json == NULL ? "" : pgagroal_json_get_command_object_status(json))); @@ -936,15 +934,13 @@ pgagroal_management_read_details(SSL* ssl, int socket, char output_format) // print out the command answer if (output_format == COMMAND_OUTPUT_FORMAT_JSON) { - pgagroal_json_print_and_free_json_object(json); + return pgagroal_json_print_and_free_json_object(json); } else { - pgagroal_management_json_print_status_details(json); + return pgagroal_management_json_print_status_details(json); } - return 0; - error: pgagroal_log_warn("pgagroal_management_read_details: command error [%s]", (json == NULL ? "" : pgagroal_json_get_command_object_status(json))); @@ -1103,7 +1099,7 @@ pgagroal_management_read_isalive(SSL* ssl, int socket, int* status, char output_ cJSON_AddStringToObject(output, "message", "unknown"); } - pgagroal_json_print_and_free_json_object(json); + return pgagroal_json_print_and_free_json_object(json); } @@ -1804,15 +1800,21 @@ pgagroal_management_read_config_get(int socket, char* config_key, char* expected { cJSON* json = pgagroal_managment_json_read_config_get(socket, config_key, expected_value); + int status = EXIT_STATUS_OK; - if (!json) + if (!json || pgagroal_json_is_command_object_faulty(json)) { goto error; } + // extract the command status + status = pgagroal_json_command_object_exit_status(json); + if (output_format == COMMAND_OUTPUT_FORMAT_JSON) { - return pgagroal_json_print_and_free_json_object(json); + pgagroal_json_print_and_free_json_object(json); + json = NULL; + goto end; } // if here, print out in text format @@ -1828,13 +1830,20 @@ pgagroal_management_read_config_get(int socket, char* config_key, char* expected printf("%s\n", value->valuestring); } - return pgagroal_json_command_object_exit_status(json); + goto end; error: pgagroal_log_warn("pgagroal_management_read_config_get : error retrieving configuration for <%s> : %s", config_key, strerror(errno)); errno = 0; - return EXIT_STATUS_DATA_ERROR; + status = EXIT_STATUS_DATA_ERROR; +end: + if (json) + { + cJSON_Delete(json); + } + + return status; } int @@ -1987,15 +1996,13 @@ pgagroal_management_read_conf_ls(SSL* ssl, int socket, char output_format) // print out the command answer if (output_format == COMMAND_OUTPUT_FORMAT_JSON) { - pgagroal_json_print_and_free_json_object(json); + return pgagroal_json_print_and_free_json_object(json); } else { - pgagroal_management_json_print_conf_ls(json); + return pgagroal_management_json_print_conf_ls(json); } - return 0; - error: pgagroal_log_warn("pgagroal_management_read_conf_ls: read: %d %s", socket, strerror(errno)); errno = 0; @@ -2176,11 +2183,12 @@ int pgagroal_management_json_print_status_details(cJSON* json) { bool is_command_details = false; /* is this command 'status details' ? */ + int status = EXIT_STATUS_OK; // sanity check if (!json || pgagroal_json_is_command_object_faulty(json)) { - return 1; + goto error; } // the command must be 'status' or 'status details' @@ -2292,11 +2300,15 @@ pgagroal_management_json_print_status_details(cJSON* json) } +error: + status = 1; end: - return 0; + if (json) + { + cJSON_Delete(json); + } -error: - return 1; + return status; } @@ -2429,6 +2441,8 @@ pgagroal_management_json_read_conf_ls(SSL* ssl, int socket) static int pgagroal_management_json_print_conf_ls(cJSON* json) { + int status = EXIT_STATUS_OK; + // sanity check if (!json || pgagroal_json_is_command_object_faulty(json)) { @@ -2455,7 +2469,16 @@ pgagroal_management_json_print_conf_ls(cJSON* json) cJSON_GetObjectItemCaseSensitive(current, "path")->valuestring); } + status = pgagroal_json_command_object_exit_status(json); + goto end; + error: - cJSON_Delete(json); - return 1; + status = EXIT_STATUS_DATA_ERROR; +end: + if (json) + { + cJSON_Delete(json); + } + + return status; } From ec23d77e4522fd18cd527578984ea6619f80ab30 Mon Sep 17 00:00:00 2001 From: jesperpedersen Date: Fri, 16 Feb 2024 08:00:35 -0500 Subject: [PATCH 17/24] [#404] Support client certificates --- doc/CONFIGURATION.md | 3 +++ src/include/pgagroal.h | 15 ++++++----- src/libpgagroal/configuration.c | 45 ++++++++++++++++++++++++++++++++- src/libpgagroal/security.c | 15 +++++------ 4 files changed, 62 insertions(+), 16 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index 109309f3..72c234b7 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -106,6 +106,9 @@ There can be up to `64` host sections, each with an unique name and different co | port | | Int | Yes | The port of the PostgreSQL instance | | primary | | Bool | No | Identify the instance as primary (hint) | | tls | `off` | Bool | No | Enable Transport Layer Security (TLS) support (Experimental - no pooling) | +| tls_cert_file | | String | No | Certificate file for TLS. This file must be owned by either the user running pgagroal or root. | +| tls_key_file | | String | No | Private key file for TLS. This file must be owned by either the user running pgagroal or root. Additionally permissions must be at least `0640` when owned by root or `0600` otherwise. | +| tls_ca_file | | String | No | Certificate Authority (CA) file for TLS. This file must be owned by either the user running pgagroal or root. | Note, that if `host` starts with a `/` it represents a path and `pgagroal` will connect using a Unix Domain Socket. diff --git a/src/include/pgagroal.h b/src/include/pgagroal.h index c4dff957..b76c72cf 100644 --- a/src/include/pgagroal.h +++ b/src/include/pgagroal.h @@ -252,12 +252,15 @@ extern void* prometheus_cache_shmem; */ struct server { - char name[MISC_LENGTH]; /**< The name of the server */ - char host[MISC_LENGTH]; /**< The host name of the server */ - int port; /**< The port of the server */ - bool tls; /**< Use TLS if possible */ - atomic_schar state; /**< The state of the server */ - int lineno; /**< The line number within the configuration file */ + char name[MISC_LENGTH]; /**< The name of the server */ + char host[MISC_LENGTH]; /**< The host name of the server */ + int port; /**< The port of the server */ + bool tls; /**< Use TLS if possible */ + char tls_cert_file[MISC_LENGTH]; /**< TLS certificate path */ + char tls_key_file[MISC_LENGTH]; /**< TLS key path */ + char tls_ca_file[MISC_LENGTH]; /**< TLS CA certificate path */ + atomic_schar state; /**< The state of the server */ + int lineno; /**< The line number within the configuration file */ } __attribute__ ((aligned (64))); /** @struct diff --git a/src/libpgagroal/configuration.c b/src/libpgagroal/configuration.c index 0665882a..2cdc64e7 100644 --- a/src/libpgagroal/configuration.c +++ b/src/libpgagroal/configuration.c @@ -2524,6 +2524,7 @@ restart_server(struct server* src, struct server* dst) restart_string(restart_message, dst->host, src->host, false); snprintf(restart_message, sizeof(restart_message), "Server <%s>, parameter ", src->name); restart_int(restart_message, dst->port, src->port); + /* TODO - TLS */ return 1; } @@ -3163,7 +3164,6 @@ pgagroal_write_config_value(char* buffer, char* config_key, size_t buffer_size) else if (!strncmp(key, "pipeline", MISC_LENGTH)) { return to_pipeline(buffer, config->pipeline); - } else if (!strncmp(key, "failover_script", MISC_LENGTH)) { @@ -3345,6 +3345,22 @@ pgagroal_write_server_config_value(char* buffer, char* server_name, char* config return to_bool(buffer, primary); } + else if (!strncmp(config_key, "tls", MISC_LENGTH)) + { + return to_bool(buffer, config->servers[server_index].tls); + } + else if (!strncmp(config_key, "tls_cert_file", MISC_LENGTH)) + { + return to_string(buffer, config->servers[server_index].tls_cert_file, buffer_size); + } + else if (!strncmp(config_key, "tls_key_file", MISC_LENGTH)) + { + return to_string(buffer, config->servers[server_index].tls_key_file, buffer_size); + } + else if (!strncmp(config_key, "tls_ca_file", MISC_LENGTH)) + { + return to_string(buffer, config->servers[server_index].tls_ca_file, buffer_size); + } else { goto error; @@ -3976,6 +3992,15 @@ pgagroal_apply_main_configuration(struct configuration* config, } memcpy(config->tls_ca_file, value, max); } + else if (key_in_section("tls_ca_file", section, key, false, &unknown)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(srv->tls_ca_file, value, max); + } else if (key_in_section("tls_cert_file", section, key, true, &unknown)) { max = strlen(value); @@ -3985,6 +4010,15 @@ pgagroal_apply_main_configuration(struct configuration* config, } memcpy(config->tls_cert_file, value, max); } + else if (key_in_section("tls_cert_file", section, key, false, &unknown)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(srv->tls_cert_file, value, max); + } else if (key_in_section("tls_key_file", section, key, true, &unknown)) { max = strlen(value); @@ -3994,6 +4028,15 @@ pgagroal_apply_main_configuration(struct configuration* config, } memcpy(config->tls_key_file, value, max); } + else if (key_in_section("tls_key_file", section, key, false, &unknown)) + { + max = strlen(value); + if (max > MISC_LENGTH - 1) + { + max = MISC_LENGTH - 1; + } + memcpy(srv->tls_key_file, value, max); + } else if (key_in_section("blocking_timeout", section, key, true, &unknown)) { diff --git a/src/libpgagroal/security.c b/src/libpgagroal/security.c index 0020a0d8..40487747 100644 --- a/src/libpgagroal/security.c +++ b/src/libpgagroal/security.c @@ -125,7 +125,7 @@ static int create_ssl_ctx(bool client, SSL_CTX** ctx); static int create_ssl_client(SSL_CTX* ctx, char* key, char* cert, char* root, int socket, SSL** ssl); static int create_ssl_server(SSL_CTX* ctx, int socket, SSL** ssl); static int establish_client_tls_connection(int server, int fd, SSL** ssl); -static int create_client_tls_connection(int fd, SSL** ssl); +static int create_client_tls_connection(int fd, SSL** ssl, char* tls_key_file, char* tls_cert_file, char* tls_ca_file); static int auth_query(SSL* c_ssl, int client_fd, int slot, char* username, char* database, int hba_method); static int auth_query_get_connection(char* username, char* password, char* database, int* server_fd, SSL** server_ssl); @@ -4484,7 +4484,7 @@ create_ssl_client(SSL_CTX* ctx, char* key, char* cert, char* root, int socket, S goto error; } - if (have_cert && strlen(key) > 0) + if (have_cert && key != NULL && strlen(key) > 0) { if (SSL_use_PrivateKey_file(s, key, SSL_FILETYPE_PEM) != 1) { @@ -5716,7 +5716,6 @@ auth_query_client_scram256(SSL* c_ssl, int client_fd, char* username, char* shad static int establish_client_tls_connection(int server, int fd, SSL** ssl) { - bool use_ssl = false; struct configuration* config = NULL; struct message* ssl_msg = NULL; struct message* msg = NULL; @@ -5724,9 +5723,7 @@ establish_client_tls_connection(int server, int fd, SSL** ssl) config = (struct configuration*)shmem; - use_ssl = config->servers[server].tls; - - if (use_ssl) + if (config->servers[server].tls) { status = pgagroal_create_ssl_message(&ssl_msg); if (status != MESSAGE_STATUS_OK) @@ -5748,7 +5745,7 @@ establish_client_tls_connection(int server, int fd, SSL** ssl) if (msg->kind == 'S') { - create_client_tls_connection(fd, ssl); + create_client_tls_connection(fd, ssl, config->servers[server].tls_key_file, config->servers[server].tls_cert_file, config->servers[server].tls_ca_file); } } @@ -5766,7 +5763,7 @@ establish_client_tls_connection(int server, int fd, SSL** ssl) } static int -create_client_tls_connection(int fd, SSL** ssl) +create_client_tls_connection(int fd, SSL** ssl, char* tls_key_file, char* tls_cert_file, char* tls_ca_file) { SSL_CTX* ctx = NULL; SSL* s = NULL; @@ -5780,7 +5777,7 @@ create_client_tls_connection(int fd, SSL** ssl) } /* Create SSL structure */ - if (create_ssl_client(ctx, NULL, NULL, NULL, fd, &s)) + if (create_ssl_client(ctx, tls_key_file, tls_cert_file, tls_ca_file, fd, &s)) { pgagroal_log_error("Client failed"); goto error; From 1dd4d1238a0cbb25298c9e392561f081e78d793f Mon Sep 17 00:00:00 2001 From: jesperpedersen Date: Thu, 22 Feb 2024 09:05:54 -0500 Subject: [PATCH 18/24] Update dependencies --- pgagroal.spec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pgagroal.spec b/pgagroal.spec index 23861721..af99bc9f 100644 --- a/pgagroal.spec +++ b/pgagroal.spec @@ -7,8 +7,8 @@ URL: https://github.com/agroal/pgagroal Source0: https://github.com/agroal/pgagroal/archive/%{version}.tar.gz BuildRequires: gcc cmake make python3-docutils -BuildRequires: libev libev-devel openssl openssl-devel systemd systemd-devel -Requires: libev openssl systemd +BuildRequires: libev libev-devel openssl openssl-devel systemd systemd-devel libatomic cjson cjson-devel +Requires: libev openssl systemd libatomic cjson %description pgagroal is a high-performance connection pool for PostgreSQL. From 03ce33fdd552a6fc869288bfa2570250129a34d2 Mon Sep 17 00:00:00 2001 From: jesperpedersen Date: Thu, 22 Feb 2024 12:51:25 -0500 Subject: [PATCH 19/24] Next is 1.7.0 --- CMakeLists.txt | 2 +- doc/GETTING_STARTED.md | 56 ++++++++++++++++++++++++++++-------------- pgagroal.spec | 2 +- 3 files changed, 39 insertions(+), 21 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a60633b4..122f0dd2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.14.0) set(VERSION_MAJOR "1") -set(VERSION_MINOR "6") +set(VERSION_MINOR "7") set(VERSION_PATCH "0") set(VERSION_STRING ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}) diff --git a/doc/GETTING_STARTED.md b/doc/GETTING_STARTED.md index 4c2321bf..0a19437f 100644 --- a/doc/GETTING_STARTED.md +++ b/doc/GETTING_STARTED.md @@ -4,7 +4,7 @@ First of all, make sure that `pgagroal` is installed and in your path by using `pgagroal -?`. You should see ``` -pgagroal 1.6.0 +pgagroal 1.7.0 High-performance connection pool for PostgreSQL Usage: @@ -124,39 +124,57 @@ the `SIGTERM` signal to the process using `kill `. You can see the commands it supports by using `pgagroal-cli -?` which will give ``` -pgagroal-cli 1.6.0 +pgagroal-cli 1.7.0 Command line utility for pgagroal Usage: - pgagroal-cli [ -c CONFIG_FILE ] [ COMMAND ] + pgagroal-cli [ OPTIONS ] [ COMMAND ] Options: -c, --config CONFIG_FILE Set the path to the pgagroal.conf file + Default: /etc/pgagroal/pgagroal.conf -h, --host HOST Set the host name -p, --port PORT Set the port number -U, --user USERNAME Set the user name -P, --password PASSWORD Set the password -L, --logfile FILE Set the log file + -F, --format text|json Set the output format -v, --verbose Output text string of result -V, --version Display version information -?, --help Display help Commands: - flush-idle Flush idle connections - flush-gracefully Flush all connections gracefully - flush-all Flush all connections. USE WITH CAUTION ! - is-alive Is pgagroal alive - enable Enable a database - disable Disable a database - gracefully Stop pgagroal gracefully - stop Stop pgagroal - cancel-shutdown Cancel the graceful shutdown - status Status of pgagroal - details Detailed status of pgagroal - switch-to Switch to another primary - reload Reload the configuration - reset Reset the Prometheus statistics - reset-server Reset the state of a server + flush [mode] [database] Flush connections according to . + Allowed modes are: + - 'gracefully' (default) to flush all connections gracefully + - 'idle' to flush only idle connections + - 'all' to flush all connections. USE WITH CAUTION! + If no name is specified, applies to all databases. + ping Verifies if pgagroal is up and running + enable [database] Enables the specified databases (or all databases) + disable [database] Disables the specified databases (or all databases) + shutdown [mode] Stops pgagroal pooler. The can be: + - 'gracefully' (default) waits for active connections to quit + - 'immediate' forces connections to close and terminate + - 'cancel' avoid a previously issued 'shutdown gracefully' + status [details] Status of pgagroal, with optional details + switch-to Switches to the specified primary server + conf Manages the configuration (e.g., reloads the configuration + The subcommand can be: + - 'reload' to issue a configuration reload; + - 'get' to obtain information about a runtime configuration value; + conf get + - 'set' to modify a configuration value; + conf set ; + - 'ls' lists the configuration files used. + clear Resets either the Prometheus statistics or the specified server. + can be + - 'server' (default) followed by a server name + - a server name on its own + - 'prometheus' to reset the Prometheus metrics + +pgagroal: +Report bugs: ``` This tool can be used on the machine running `pgagroal` to flush connections. @@ -193,7 +211,7 @@ registration with `pgagroal`. You can see the commands it supports by using `pgagroal-admin -?` which will give ``` -pgagroal-admin 1.6.0 +pgagroal-admin 1.7.0 Administration utility for pgagroal Usage: diff --git a/pgagroal.spec b/pgagroal.spec index af99bc9f..d19f98c0 100644 --- a/pgagroal.spec +++ b/pgagroal.spec @@ -1,5 +1,5 @@ Name: pgagroal -Version: 1.6.0 +Version: 1.7.0 Release: 1%{dist} Summary: High-performance connection pool for PostgreSQL License: BSD From b189574fe18d45a5cd126ed9ca1be4d1065b41be Mon Sep 17 00:00:00 2001 From: jesperpedersen Date: Thu, 22 Feb 2024 13:01:38 -0500 Subject: [PATCH 20/24] <> -> [] --- src/cli.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/cli.c b/src/cli.c index 0ce08efc..cd4758ec 100644 --- a/src/cli.c +++ b/src/cli.c @@ -116,31 +116,31 @@ usage(void) printf(" -?, --help Display help\n"); printf("\n"); printf("Commands:\n"); - printf(" flush [mode] [database] Flush connections according to .\n"); + printf(" flush [mode] [database] Flush connections according to [mode].\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(" If no [database] name is specified, applies to all databases.\n"); printf(" ping Verifies if pgagroal is up and running\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(" shutdown [mode] Stops pgagroal pooler. The [mode] 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 [details] Status of pgagroal, with optional details\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(" switch-to [server] Switches to the specified primary server\n"); + printf(" conf [action[ Manages the configuration (e.g., reloads the configuration\n"); + printf(" The subcommand [action] 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(" - 'ls' lists the configuration files used.\n"); - printf(" clear Resets either the Prometheus statistics or the specified server.\n"); - printf(" can be\n"); + printf(" clear [what] Resets either the Prometheus statistics or the specified server.\n"); + printf(" [what] 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"); From dc8abb70665c914d1ebf8a7e23c874164006450b Mon Sep 17 00:00:00 2001 From: jesperpedersen Date: Thu, 22 Feb 2024 13:07:06 -0500 Subject: [PATCH 21/24] Fix flush command --- doc/GETTING_STARTED.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/GETTING_STARTED.md b/doc/GETTING_STARTED.md index 0a19437f..5f695947 100644 --- a/doc/GETTING_STARTED.md +++ b/doc/GETTING_STARTED.md @@ -182,7 +182,7 @@ This tool can be used on the machine running `pgagroal` to flush connections. To flush all idle connections you would use ``` -pgagroal-cli -c pgagroal.conf flush-idle +pgagroal-cli -c pgagroal.conf flush idle ``` To stop pgagroal you would use From a9c4c1306ba8aa15d189dac5dc6fb847c4e1c9a5 Mon Sep 17 00:00:00 2001 From: Christoph Berg Date: Fri, 23 Feb 2024 15:57:34 +0100 Subject: [PATCH 22/24] Fix sprintf of size_t This failed on 32-bit platforms. --- src/libpgagroal/prometheus.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libpgagroal/prometheus.c b/src/libpgagroal/prometheus.c index a836d9b8..e7582a01 100644 --- a/src/libpgagroal/prometheus.c +++ b/src/libpgagroal/prometheus.c @@ -2101,7 +2101,7 @@ send_chunk(int client_fd, char* data) return MESSAGE_STATUS_ERROR; } - sprintf(m, "%lX\r\n", strlen(data)); + sprintf(m, "%zX\r\n", strlen(data)); m = pgagroal_append(m, data); m = pgagroal_append(m, "\r\n"); From 3b565188fc619cf0b635147ce0097282cedae156 Mon Sep 17 00:00:00 2001 From: jesperpedersen Date: Fri, 1 Mar 2024 09:54:15 -0500 Subject: [PATCH 23/24] Revert part of b189574 --- src/cli.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cli.c b/src/cli.c index cd4758ec..c9bdc6c6 100644 --- a/src/cli.c +++ b/src/cli.c @@ -130,17 +130,17 @@ usage(void) printf(" - 'immediate' forces connections to close and terminate\n"); printf(" - 'cancel' avoid a previously issued 'shutdown gracefully'\n"); printf(" status [details] Status of pgagroal, with optional details\n"); - printf(" switch-to [server] Switches to the specified primary server\n"); - printf(" conf [action[ Manages the configuration (e.g., reloads the configuration\n"); - printf(" The subcommand [action] can be:\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(" - 'ls' lists the configuration files used.\n"); - printf(" clear [what] Resets either the Prometheus statistics or the specified server.\n"); - printf(" [what] can be\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"); From d9f9253504605194b6bac1dd18491f132059b145 Mon Sep 17 00:00:00 2001 From: Henrique de Carvalho Date: Sat, 2 Mar 2024 02:55:16 -0300 Subject: [PATCH 24/24] Fix SegFault in pgagroal-cli pgagroal-cli was raising segmentation fault when no command is specified, but a password is specified. --- AUTHORS | 3 ++- src/cli.c | 22 ++++++---------------- 2 files changed, 8 insertions(+), 17 deletions(-) diff --git a/AUTHORS b/AUTHORS index 3707d5a4..a81701ca 100644 --- a/AUTHORS +++ b/AUTHORS @@ -8,4 +8,5 @@ Luca Ferrari Nikita Bugrovsky Lawrence Wu Yongting You <2010youy01@gmail.com> -Ashutosh Sharma \ No newline at end of file +Ashutosh Sharma +Henrique de Carvalho \ No newline at end of file diff --git a/src/cli.c b/src/cli.c index c9bdc6c6..8d7a6ca1 100644 --- a/src/cli.c +++ b/src/cli.c @@ -163,7 +163,6 @@ main(int argc, char** argv) char* password = NULL; bool verbose = false; char* logfile = NULL; - bool do_free = true; int c; int option_index = 0; size_t size; @@ -218,7 +217,11 @@ main(int argc, char** argv) username = optarg; break; case 'P': - password = optarg; + password = strdup(optarg); + if (password == NULL) + { + errx(1, "Error allocating memory for password"); + } break; case 'L': logfile = optarg; @@ -533,20 +536,10 @@ main(int argc, char** argv) /* Password */ if (password == NULL) { - if (password != NULL) - { - free(password); - password = NULL; - } - printf("Password : "); password = pgagroal_get_password(); printf("\n"); } - else - { - do_free = false; - } for (int i = 0; i < strlen(password); i++) { @@ -676,10 +669,7 @@ main(int argc, char** argv) pgagroal_stop_logging(); pgagroal_destroy_shared_memory(shmem, size); - if (do_free) - { - free(password); - } + free(password); if (verbose) {