From 856f03a27dcc50996c4c4439105e4683eda1d52a Mon Sep 17 00:00:00 2001 From: Luca Ferrari Date: Wed, 25 May 2022 04:26:13 -0400 Subject: [PATCH] [#244] Allows spaces and comments in configuration parameters This commit refactors the `extract_key_value()` function to handle quoted strings as values. A quoted string must start with either '"' or '\''. Everything between the quotes is handled as a value. If quotes do not match (e.g., "foo') the value is not set, so the line is ignored at all. Quoted strings can contain also spaces and comment charaters. Therefore, the following is a good string: log_line_prefix = "PGAGROAL #%Y-%m-%d-%H:%M:%S" Also, quotes can be nested: log_line_prefix = "'PGAGROAL' #%Y-%m-%d-%H:%M:%S" Values that do not need a quoting (because of spaces or comment signs) can be quoted as well, and that will not affect the value parsing. The following two lines are the same: log_rotation_size = 1M log_rotation_size = "1M" The `extract_key_value()` function now returns also an integer to indicate if it has parsed the key and the value correctly. The return value is not used because there is already a check on the validity of both key and value in `pgagroal_read_configuration()`, but to be coherent the function returns a status. This commit also allows to use comment-only line that begin with spaces or tabs, therefore the following are all ignored lines. The `is_comment_line()` function has therefore been refactored so that the following is a valid snippet of configuration: ; comment ; comment ; comment Last, a comment can be placed on the right side of a configuration option, so log_line_prefix = "PGAGROAL #%Y-%m-%d-%H:%M:%S" # the prefix is a valid line. The trick here is that any space after the option makes the rest of the line ignored. But now, even without spaces the comment is ignored: log_type = create; overwrites the file Updated also the documentation to reflect the changes. Close #244 --- doc/CONFIGURATION.md | 19 +++++- src/libpgagroal/configuration.c | 111 +++++++++++++++++++++++++++++--- 2 files changed, 119 insertions(+), 11 deletions(-) diff --git a/doc/CONFIGURATION.md b/doc/CONFIGURATION.md index f93fbc9c..471bcfea 100644 --- a/doc/CONFIGURATION.md +++ b/doc/CONFIGURATION.md @@ -13,10 +13,23 @@ instance. All properties are in the format `key = value`. -The characters `#` and `;` can be used for comments; must be the first character on the line. +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`. -See a [sample](./etc/pgagroal.conf) configuration for running `pgagroal` on `localhost`. +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: + +``` +# This line is ignored since it starts with '#' +# and so is this one +log_line_prefix = "PGAGROAL #%Y-%m-%d-%H:%M:%S" # quoted because it contains spaces +log_type= console#log to stdout +pipeline = 'performance' # no need to quote since it does not contain any spaces + +``` + +See a more complete [sample](./etc/pgagroal.conf) configuration for running `pgagroal` on `localhost`. ## [pgagroal] @@ -32,7 +45,7 @@ See a [sample](./etc/pgagroal.conf) configuration for running `pgagroal` on `loc | log_path | pgagroal.log | String | No | The log file location. Can be a strftime(3) compatible string. | | log_rotation_age | -1 | String | No | The age that will trigger a log file rotation. If expressed as a positive number, is managed as seconds. Supports suffixes: 'S' (seconds, the default), 'M' (minutes), 'H' (hours), 'D' (days), 'W' (weeks) | | log_rotation_size | -1 | String | No | The size of the log file that will trigger a log rotation. Supports suffixes: 'B' (bytes), the default if omitted, 'K' (kilobytes), 'M' (megabytes), 'G' (gigabytes). | -| log_line_prefix | %Y-%m-%d %H:%M:%S | String | No | A strftime(3) compatible string to use as prefix for every log line. Must not contain any space. | +| log_line_prefix | %Y-%m-%d %H:%M:%S | String | No | A strftime(3) compatible string to use as prefix for every log line. Must be quoted if contains spaces. | | log_mode | append | String | No | Append to or create the log file (append, create) | | log_connections | `off` | Bool | No | Log connects | | log_disconnections | `off` | Bool | No | Log disconnects | diff --git a/src/libpgagroal/configuration.c b/src/libpgagroal/configuration.c index 70b2427b..6054ea71 100644 --- a/src/libpgagroal/configuration.c +++ b/src/libpgagroal/configuration.c @@ -53,7 +53,7 @@ #define LINE_LENGTH 512 -static void extract_key_value(char* str, char** key, char** value); +static int extract_key_value(char* str, char** key, char** value); static int as_int(char* str, int* i); static int as_bool(char* str, bool* b); static int as_logging_type(char* str); @@ -1879,7 +1879,36 @@ pgagroal_reload_configuration(void) return 1; } -static void +/** + * Given a line of text extracts the key part and the value. + * Valid lines must have the form = . + * + * The key must be unquoted and cannot have any spaces + * in front of it. + * + * Comments on the right side of a value are allowed. + * + * The value can be quoted, and this allows for inserting spaces + * and comment signs. Quotes are '""' and '\''. + * Example of valid lines are: + * + * foo = bar + * foo=bar + * foo= bar + * foo = "bar" + * foo = 'bar' + * foo = "#bar" + * foo = '#bar' + * foo = bar # bar set! + * foo = bar# bar set! + * + * + * @param str the line of text incoming from the configuration file + * @param key the pointer to where to store the key extracted from the line + * @param value the pointer to where to store the value (unquoted) + * @returns 1 if unable to parse the line, 0 if everything is ok + */ +static int extract_key_value(char* str, char** key, char** value) { int c = 0; @@ -1887,7 +1916,11 @@ extract_key_value(char* str, char** key, char** value) int length = strlen(str); char* k; char* v; + char quoting_begin = '\0'; + char quoting_end = '\0'; + // the key does not allow spaces and is whatever is + // on the left of the '=' while (str[c] != ' ' && str[c] != '=' && c < length) c++; @@ -1903,8 +1936,48 @@ extract_key_value(char* str, char** key, char** value) offset = c; - while (str[c] != ' ' && str[c] != '\r' && str[c] != '\n' && c < length) + // the value of the parameter starts from offset 'offset' + while (str[c] != '\r' && str[c] != '\n' && c < length) + { + if (str[c] == '\'' || str[c] == '"') + { + if (quoting_begin == '\0') + { + quoting_begin = str[c]; + offset = c + 1; // start at the very first character after the quote + } + else if (str[c] == quoting_begin && quoting_end == '\0') + { + quoting_end = str[c]; + // end at the last character before the quote + break; + } + } + else if (str[c] == '#' || str[c] == ';') + { + if (quoting_begin == '\0' || (quoting_begin != '\0' && quoting_end != '\0')) + { + // a comment outside of quoted string, ignore anything else + break; + } + } + else if (str[c] == ' ') + { + if (quoting_begin == '\0' || (quoting_begin != '\0' && quoting_end != '\0')) + { + // space outside a quoted string, stop here + break; + } + } + c++; + } + + // quotes must be the same! + if (quoting_begin != '\0' && quoting_begin != quoting_end) + { + goto error; + } if (c <= length) { @@ -1912,8 +1985,11 @@ extract_key_value(char* str, char** key, char** value) memset(v, 0, (c - offset) + 1); memcpy(v, str + offset, (c - offset)); *value = v; + return 0; } } +error: + return 1; } static int @@ -2880,7 +2956,7 @@ key_in_section( char* wanted, char* section, char* key, bool global, bool* unkno { return true; } - else if (!global && strlen( section ) > 0) + else if (!global && strlen(section) > 0) { return true; } @@ -2896,16 +2972,35 @@ key_in_section( char* wanted, char* section, char* key, bool global, bool* unkno } /** - * Function to see if the specified line starts with a comment - * symbol. + * Function to see if the specified line is a comment line + * and has to be ignored. + * A comment line is a line that starts with '#' or ';' or + * with spaces (or tabs) and a comment sign. * * @param line the line read from the file - * @return true if the line starts with a comment symbol + * @return true if the line is a full comment line */ static bool is_comment_line(char* line) { - return line[0] == '#' || line[0] == ';'; + int c = 0; + int length = strlen( line ); + + while (c < length) + { + if (line[c] == '#' || line[c] == ';') + { + return true; + } + else if (line[c] != ' ' && line[c] != '\t') + { + break; + } + + c++; + } + + return false; } /**