Skip to content

Commit

Permalink
[#244] Allows spaces and comments in configuration parameters
Browse files Browse the repository at this point in the history
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
<tab> ; 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
  • Loading branch information
fluca1978 authored and jesperpedersen committed May 25, 2022
1 parent 3929b53 commit 856f03a
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 11 deletions.
19 changes: 16 additions & 3 deletions doc/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand All @@ -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 |
Expand Down
111 changes: 103 additions & 8 deletions src/libpgagroal/configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -1879,15 +1879,48 @@ 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 <key> = <value>.
*
* 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:
* <code>
* foo = bar
* foo=bar
* foo= bar
* foo = "bar"
* foo = 'bar'
* foo = "#bar"
* foo = '#bar'
* foo = bar # bar set!
* foo = bar# bar set!
* </code>
*
* @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;
int offset = 0;
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++;

Expand All @@ -1903,17 +1936,60 @@ 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)
{
v = malloc((c - offset) + 1);
memset(v, 0, (c - offset) + 1);
memcpy(v, str + offset, (c - offset));
*value = v;
return 0;
}
}
error:
return 1;
}

static int
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}

/**
Expand Down

0 comments on commit 856f03a

Please sign in to comment.