forked from lda/telodendria
Convert configuration file to JSON
This commit is contained in:
parent
c0534b0e05
commit
fb5a8e4587
8 changed files with 260 additions and 1154 deletions
3
TODO.txt
3
TODO.txt
|
@ -28,9 +28,6 @@ Due: January 1, 2023
|
|||
[x] Array
|
||||
[x] Base64
|
||||
[x] CanonicalJson
|
||||
[ ] Config
|
||||
[ ] API (Config.3)
|
||||
[ ] File format (Config.5)
|
||||
[x] HashMap
|
||||
[ ] Http
|
||||
[ ] HttpServer
|
||||
|
|
|
@ -1,20 +1,14 @@
|
|||
#
|
||||
# Telodendria development configuration file.
|
||||
#
|
||||
|
||||
server-name "localhost";
|
||||
base-url "http://localhost:8008";
|
||||
|
||||
# Make this directory if Telodendria complains that it's missing.
|
||||
data-dir "./data";
|
||||
|
||||
federation "true";
|
||||
registration "true";
|
||||
log "stdout" {
|
||||
# level "debug";
|
||||
timestampFormat "none";
|
||||
color "true";
|
||||
};
|
||||
threads "4";
|
||||
max-connections "32";
|
||||
max-cache "1K";
|
||||
{
|
||||
"serverName": "localhost",
|
||||
"baseUrl": "http://localhost:8008",
|
||||
"dataDir": "./data",
|
||||
"federation": true,
|
||||
"registration": true,
|
||||
"threads": 2,
|
||||
"log": {
|
||||
"output": "stdout",
|
||||
"level": "debug",
|
||||
"timestampFormat": "none",
|
||||
"color": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,33 +1,13 @@
|
|||
#
|
||||
# Telodendria configuration file.
|
||||
#
|
||||
# The following man pages document the configuration:
|
||||
#
|
||||
# - telodendria.conf(5)
|
||||
# - Config(5)
|
||||
#
|
||||
# Alternatively, find the man pages online at the
|
||||
# following URL:
|
||||
#
|
||||
# https://telodendria.io/#documentation
|
||||
#
|
||||
|
||||
listen "8008";
|
||||
|
||||
server-name "example.com";
|
||||
base-url "https://matrix.example.com";
|
||||
identity-server "https://identity.example.com";
|
||||
|
||||
data-dir "/var/telodendria";
|
||||
|
||||
federation "true";
|
||||
registration "false";
|
||||
|
||||
log "file" {
|
||||
level "message";
|
||||
timestampFormat "default";
|
||||
};
|
||||
|
||||
threads "4";
|
||||
max-connections "32";
|
||||
max-cache "512M";
|
||||
{
|
||||
"serverName": "example.com",
|
||||
"baseUrl": "https://matrix.example.com",
|
||||
"identityServer": "https://identity.example.com",
|
||||
"dataDir": "/var/telodendria",
|
||||
"federation": true,
|
||||
"registration": false,
|
||||
"threads": 4,
|
||||
"maxCache": 512000000,
|
||||
"log": {
|
||||
"output": "file"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.Dd $Mdocdate: November 8 2022 $
|
||||
.Dd $Mdocdate: December 9 2022 $
|
||||
.Dt TELODENDRIA.CONF 5
|
||||
.Os Telodendria Project
|
||||
.Sh NAME
|
||||
|
@ -16,21 +16,13 @@ option, and is typically located at
|
|||
.Pa /etc/telodendria.conf
|
||||
.sp
|
||||
.Nm
|
||||
uses OpenBSD-style syntax, though it is a little more rigid in its
|
||||
parser. All values must be surrounded by quotes, and each directive
|
||||
must be ended with a semicolon.
|
||||
.Sh MACROS
|
||||
Macros can be defined that will later be expanded in context.
|
||||
Macro names must start with a letter, digit, or underscore, and may
|
||||
contain only those characters. Macros are not expanded inside quotes.
|
||||
.sp
|
||||
For example:
|
||||
.Bd -literal -offset indent
|
||||
macro1 = "value1";
|
||||
directive $macro1;
|
||||
.Ed
|
||||
.Sh GLOBAL OPTIONS
|
||||
Here are the settings that can be set globally:
|
||||
uses JSON for its configuration file syntax, which should be
|
||||
familiar. Very early versions of
|
||||
.Nm
|
||||
used a custom OpenBSD-style configuration file, but this was
|
||||
not as versatile or familiar as JSON.
|
||||
.Sh DIRECTIVES
|
||||
Here are the top-level directives:
|
||||
.Bl -tag -width Ds
|
||||
.It Ic listen Ar port
|
||||
The port to listen on. Telodendria will bind to all interfaces, but it
|
||||
|
@ -47,7 +39,7 @@ the internet.
|
|||
.Ar port
|
||||
should be a decimal port number. This directive is entirely optional. If
|
||||
it is omitted, then Telodendria will listen on port 8008 by default.
|
||||
.It Ic server-name Ar name
|
||||
.It Ic serverName Ar name
|
||||
Configure the domain name of your homeserver. Note that Matrix servers
|
||||
cannot be migrated to other domains, so once this is set, it should never
|
||||
change unless you want unexpected things to happen, or you want to start
|
||||
|
@ -55,7 +47,7 @@ over.
|
|||
.Ar name
|
||||
should be a DNS name that can be publically resolved. This directive
|
||||
is required.
|
||||
.It Ic base-url Ar url
|
||||
.It Ic baseUrl Ar url
|
||||
Set the server's base URL.
|
||||
.Ar url
|
||||
should be a valid URL, complete with the protocol. It does not need to
|
||||
|
@ -69,16 +61,16 @@ manifest.
|
|||
.Pp
|
||||
This directive is optional. If it is not specified, it is automatically
|
||||
deduced from the server name.
|
||||
.It Ic identity-server Ar url
|
||||
.It Ic identityServer Ar url
|
||||
The identity server that clients should use to perform identity lookups.
|
||||
.Pp
|
||||
.Ar url
|
||||
follows the same rules as
|
||||
.Ic base-url .
|
||||
.Ic baseUrl .
|
||||
.Pp
|
||||
This directive is optional. If it is not specified, it is automatically
|
||||
set to be the same as the base URL.
|
||||
.It Ic id Ar uid Ar gid
|
||||
.It Ic runAs Ar uidObj
|
||||
The effective UNIX user and group to drop to after binding to the socket
|
||||
and changing the filesystem root for the process. This only works if
|
||||
Telodendria is running as the root user, and is used as a security mechanism.
|
||||
|
@ -86,7 +78,20 @@ If this option is set and Telodendria is started as a non-priviledged user,
|
|||
then a warning is printed to the log if that user does not match what's
|
||||
specified here. This directive is optional, but should be used as a sanity
|
||||
check, if nothing more, to make sure the permissions are working properly.
|
||||
.It Ic data-dir Ar directory
|
||||
.Pp
|
||||
This directive takes an object with the following directives:
|
||||
.Bl -tag -width Ds
|
||||
.It Ic uid Ar user
|
||||
The UNIX username to drop to. If
|
||||
.Ic runAs
|
||||
is specified, this directive is required.
|
||||
.It Ic gid Ar group
|
||||
The UNIX group to drop to. This directive is optional; if it is not
|
||||
specified, then the value of
|
||||
.Ic uid
|
||||
is copied.
|
||||
.El
|
||||
.It Ic dataDir Ar directory
|
||||
The data directory into which Telodendria will write all user and event
|
||||
information. Telodendria doesn't use a database like other Matrix homeserver
|
||||
implementations; it uses a flat-file directory structure, similar to how an
|
||||
|
@ -122,27 +127,27 @@ to run their own homeserver, you can aset this to
|
|||
which will allow anyone to create an account. Telodendria should be capable of handling
|
||||
a large amount of users without difficulty or security issues. This directive is
|
||||
required.
|
||||
.It Ic log Ar stdout|file|syslog
|
||||
The log configuration. Telodendria uses its own logging facility, which can output
|
||||
logs to standard output, a file, or the syslog. If set to
|
||||
.It Ic log Ar logObj
|
||||
The log file configuration. Telodendria uses its own logging facility, which can
|
||||
output logs to standard output, a file, or the syslog. This directive is required,
|
||||
and it takes an object with the following directives:
|
||||
.Bl -tag -width Ds
|
||||
.It Ic output Ar stdout|file|syslog
|
||||
The lot output destination. If set to
|
||||
.Ar file ,
|
||||
Telodendria will log to
|
||||
.Pa telodendria.log
|
||||
inside the
|
||||
.Ic data-dir .
|
||||
.Pp
|
||||
A number of child directives can
|
||||
be added to this directive to customize the log output:
|
||||
.Bl -tag -width Ds
|
||||
.It Ic level Ar error|warning|task|message|debug
|
||||
.Ic dataDir .
|
||||
.It Ic level Ar error|warning|notice|message|debug
|
||||
The level of messages to log at. Each level shows all the levels above it. For
|
||||
example, setting the level to
|
||||
.Ar error
|
||||
will show only errors, while setting the level to
|
||||
.Ar warning
|
||||
will show warnings and errors.
|
||||
.Ar task
|
||||
shows tasks, warnings, and errors, and so on. The
|
||||
.Ar notice
|
||||
shows notices, warnings, and errors, and so on. The
|
||||
.Ar debug
|
||||
level shows all messages.
|
||||
.It Ic timestampFormat Ar format|none|default
|
||||
|
@ -171,11 +176,11 @@ less than the total CPU core count, to prevent overloading the system. The most
|
|||
efficient number of threads ultimately depends on the configuration of the
|
||||
machine running Telodendria, so you may just have to play around with different
|
||||
values here to see which gives the best performance.
|
||||
.It Ic max-connections Ar count
|
||||
.It Ic maxConnections Ar count
|
||||
The maximum number of simultanious connections to allow to the daemon. This option
|
||||
prevents the daemon from allocating large amounts of memory in the even that it
|
||||
undergoes a denial of service attack. It typically does not need to be adjusted.
|
||||
.It Ic max-cache Ar bytes
|
||||
.It Ic maxCache Ar bytes
|
||||
The maximum size of the cache. Telodendria relies heavily on caching to speed
|
||||
things up. The cache grows as data is loaded from the data directory. All cache
|
||||
is stored in memory. This option limits the size of the memory cache. If you have
|
||||
|
|
544
src/Config.c
544
src/Config.c
|
@ -1,544 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
#include <Config.h>
|
||||
|
||||
#include <Memory.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef CONFIG_BUFFER_BLOCK
|
||||
#define CONFIG_BUFFER_BLOCK 32
|
||||
#endif
|
||||
|
||||
struct ConfigDirective
|
||||
{
|
||||
Array *values;
|
||||
HashMap *children;
|
||||
};
|
||||
|
||||
struct ConfigParseResult
|
||||
{
|
||||
unsigned int ok:1;
|
||||
union
|
||||
{
|
||||
size_t lineNumber;
|
||||
HashMap *confMap;
|
||||
} data;
|
||||
};
|
||||
|
||||
typedef enum ConfigToken
|
||||
{
|
||||
TOKEN_UNKNOWN,
|
||||
TOKEN_NAME,
|
||||
TOKEN_MACRO_ASSIGNMENT,
|
||||
TOKEN_VALUE,
|
||||
TOKEN_SEMICOLON,
|
||||
TOKEN_BLOCK_OPEN,
|
||||
TOKEN_BLOCK_CLOSE,
|
||||
TOKEN_MACRO,
|
||||
TOKEN_EOF
|
||||
} ConfigToken;
|
||||
|
||||
typedef struct ConfigParserState
|
||||
{
|
||||
FILE *stream;
|
||||
unsigned int line;
|
||||
|
||||
char *token;
|
||||
size_t tokenSize;
|
||||
size_t tokenLen;
|
||||
ConfigToken tokenType;
|
||||
|
||||
HashMap *macroMap;
|
||||
|
||||
} ConfigParserState;
|
||||
|
||||
unsigned int
|
||||
ConfigParseResultOk(ConfigParseResult * result)
|
||||
{
|
||||
return result ? result->ok : 0;
|
||||
}
|
||||
|
||||
size_t
|
||||
ConfigParseResultLineNumber(ConfigParseResult * result)
|
||||
{
|
||||
return result && !result->ok ? result->data.lineNumber : 0;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
ConfigParseResultGet(ConfigParseResult * result)
|
||||
{
|
||||
return result && result->ok ? result->data.confMap : NULL;
|
||||
}
|
||||
|
||||
void
|
||||
ConfigParseResultFree(ConfigParseResult * result)
|
||||
{
|
||||
/*
|
||||
* Note that if the parse was valid, the hash map
|
||||
* needs to be freed separately.
|
||||
*/
|
||||
Free(result);
|
||||
}
|
||||
|
||||
Array *
|
||||
ConfigValuesGet(ConfigDirective * directive)
|
||||
{
|
||||
return directive ? directive->values : NULL;
|
||||
}
|
||||
|
||||
HashMap *
|
||||
ConfigChildrenGet(ConfigDirective * directive)
|
||||
{
|
||||
return directive ? directive->children : NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
ConfigDirectiveFree(ConfigDirective * directive)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (!directive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < ArraySize(directive->values); i++)
|
||||
{
|
||||
Free(ArrayGet(directive->values, i));
|
||||
}
|
||||
|
||||
ArrayFree(directive->values);
|
||||
|
||||
ConfigFree(directive->children);
|
||||
|
||||
Free(directive);
|
||||
}
|
||||
|
||||
void
|
||||
ConfigFree(HashMap * conf)
|
||||
{
|
||||
char *key;
|
||||
void *value;
|
||||
|
||||
while (HashMapIterate(conf, &key, &value))
|
||||
{
|
||||
ConfigDirectiveFree((ConfigDirective *) value);
|
||||
Free(key);
|
||||
}
|
||||
|
||||
HashMapFree(conf);
|
||||
}
|
||||
|
||||
static ConfigParserState *
|
||||
ConfigParserStateCreate(FILE * stream)
|
||||
{
|
||||
ConfigParserState *state = Malloc(sizeof(ConfigParserState));
|
||||
|
||||
if (!state)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
state->macroMap = HashMapCreate();
|
||||
|
||||
if (!state->macroMap)
|
||||
{
|
||||
Free(state);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
state->stream = stream;
|
||||
state->line = 1;
|
||||
state->token = NULL;
|
||||
state->tokenSize = 0;
|
||||
state->tokenLen = 0;
|
||||
state->tokenType = TOKEN_UNKNOWN;
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
static void
|
||||
ConfigParserStateFree(ConfigParserState * state)
|
||||
{
|
||||
char *key;
|
||||
void *value;
|
||||
|
||||
if (!state)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Free(state->token);
|
||||
|
||||
while (HashMapIterate(state->macroMap, &key, &value))
|
||||
{
|
||||
Free(key);
|
||||
Free(value);
|
||||
}
|
||||
|
||||
HashMapFree(state->macroMap);
|
||||
|
||||
Free(state);
|
||||
}
|
||||
|
||||
static int
|
||||
ConfigIsNameChar(int c)
|
||||
{
|
||||
return isdigit(c) || isalpha(c) || (c == '-' || c == '_');
|
||||
}
|
||||
|
||||
static char
|
||||
ConfigConsumeWhitespace(ConfigParserState * state)
|
||||
{
|
||||
int c;
|
||||
|
||||
while (isspace(c = fgetc(state->stream)))
|
||||
{
|
||||
if (c == '\n')
|
||||
{
|
||||
state->line++;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static void
|
||||
ConfigConsumeLine(ConfigParserState * state)
|
||||
{
|
||||
while (fgetc(state->stream) != '\n');
|
||||
state->line++;
|
||||
}
|
||||
|
||||
static void
|
||||
ConfigTokenSeek(ConfigParserState * state)
|
||||
{
|
||||
int c;
|
||||
|
||||
/* If we already hit EOF, don't do anything */
|
||||
if (state->tokenType == TOKEN_EOF)
|
||||
{
|
||||
return;
|
||||
}
|
||||
while ((c = ConfigConsumeWhitespace(state)) == '#')
|
||||
{
|
||||
ConfigConsumeLine(state);
|
||||
}
|
||||
|
||||
/*
|
||||
* After all whitespace and comments are consumed, identify the
|
||||
* token by looking at the next character
|
||||
*/
|
||||
|
||||
if (feof(state->stream))
|
||||
{
|
||||
state->tokenType = TOKEN_EOF;
|
||||
return;
|
||||
}
|
||||
if (ConfigIsNameChar(c))
|
||||
{
|
||||
state->tokenLen = 0;
|
||||
|
||||
/* Read the key/macro into state->token */
|
||||
if (!state->token)
|
||||
{
|
||||
state->tokenSize = CONFIG_BUFFER_BLOCK;
|
||||
state->token = Malloc(CONFIG_BUFFER_BLOCK);
|
||||
}
|
||||
state->token[state->tokenLen] = c;
|
||||
state->tokenLen++;
|
||||
|
||||
while (ConfigIsNameChar((c = fgetc(state->stream))))
|
||||
{
|
||||
state->token[state->tokenLen] = c;
|
||||
state->tokenLen++;
|
||||
|
||||
if (state->tokenLen >= state->tokenSize)
|
||||
{
|
||||
state->tokenSize += CONFIG_BUFFER_BLOCK;
|
||||
state->token = Realloc(state->token,
|
||||
state->tokenSize);
|
||||
}
|
||||
}
|
||||
|
||||
state->token[state->tokenLen] = '\0';
|
||||
state->tokenLen++;
|
||||
|
||||
if (!isspace(c))
|
||||
{
|
||||
state->tokenType = TOKEN_UNKNOWN;
|
||||
}
|
||||
else
|
||||
{
|
||||
state->tokenType = TOKEN_NAME;
|
||||
|
||||
if (c == '\n')
|
||||
{
|
||||
state->line++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case '=':
|
||||
state->tokenType = TOKEN_MACRO_ASSIGNMENT;
|
||||
break;
|
||||
case '"':
|
||||
state->tokenLen = 0;
|
||||
state->tokenType = TOKEN_VALUE;
|
||||
|
||||
/* read the value into state->curtok */
|
||||
while ((c = fgetc(state->stream)) != '"')
|
||||
{
|
||||
if (c == '\n')
|
||||
{
|
||||
state->line++;
|
||||
}
|
||||
/*
|
||||
* End of the stream reached without finding
|
||||
* a closing quote
|
||||
*/
|
||||
if (feof(state->stream))
|
||||
{
|
||||
state->tokenType = TOKEN_EOF;
|
||||
break;
|
||||
}
|
||||
state->token[state->tokenLen] = c;
|
||||
state->tokenLen++;
|
||||
|
||||
if (state->tokenLen >= state->tokenSize)
|
||||
{
|
||||
state->tokenSize += CONFIG_BUFFER_BLOCK;
|
||||
state->token = Realloc(state->token,
|
||||
state->tokenSize);
|
||||
}
|
||||
}
|
||||
state->token[state->tokenLen] = '\0';
|
||||
state->tokenLen++;
|
||||
break;
|
||||
case ';':
|
||||
state->tokenType = TOKEN_SEMICOLON;
|
||||
break;
|
||||
case '{':
|
||||
state->tokenType = TOKEN_BLOCK_OPEN;
|
||||
break;
|
||||
case '}':
|
||||
state->tokenType = TOKEN_BLOCK_CLOSE;
|
||||
break;
|
||||
case '$':
|
||||
state->tokenLen = 0;
|
||||
/* read the macro name into state->curtok */
|
||||
while (ConfigIsNameChar(c = fgetc(state->stream)))
|
||||
{
|
||||
state->token[state->tokenLen] = c;
|
||||
state->tokenLen++;
|
||||
|
||||
if (state->tokenLen >= state->tokenSize)
|
||||
{
|
||||
state->tokenSize += CONFIG_BUFFER_BLOCK;
|
||||
state->token = Realloc(state->token,
|
||||
state->tokenSize);
|
||||
}
|
||||
}
|
||||
state->token[state->tokenLen] = '\0';
|
||||
state->tokenLen++;
|
||||
state->tokenType = TOKEN_MACRO;
|
||||
|
||||
ungetc(c, state->stream);
|
||||
break;
|
||||
default:
|
||||
state->tokenType = TOKEN_UNKNOWN;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Resize curtok to only use the bytes it needs */
|
||||
if (state->tokenLen)
|
||||
{
|
||||
state->tokenSize = state->tokenLen;
|
||||
state->token = Realloc(state->token, state->tokenSize);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
ConfigExpect(ConfigParserState * state, ConfigToken tokenType)
|
||||
{
|
||||
return state->tokenType == tokenType;
|
||||
}
|
||||
|
||||
|
||||
static HashMap *
|
||||
ConfigParseBlock(ConfigParserState * state, int level)
|
||||
{
|
||||
HashMap *block = HashMapCreate();
|
||||
|
||||
ConfigTokenSeek(state);
|
||||
|
||||
while (ConfigExpect(state, TOKEN_NAME))
|
||||
{
|
||||
char *name = Malloc(state->tokenLen + 1);
|
||||
|
||||
strcpy(name, state->token);
|
||||
|
||||
ConfigTokenSeek(state);
|
||||
if (ConfigExpect(state, TOKEN_VALUE) || ConfigExpect(state, TOKEN_MACRO))
|
||||
{
|
||||
ConfigDirective *directive;
|
||||
|
||||
directive = Malloc(sizeof(ConfigDirective));
|
||||
directive->children = NULL;
|
||||
directive->values = ArrayCreate();
|
||||
|
||||
while (ConfigExpect(state, TOKEN_VALUE) ||
|
||||
ConfigExpect(state, TOKEN_MACRO))
|
||||
{
|
||||
|
||||
char *dval;
|
||||
char *dvalCpy;
|
||||
|
||||
if (ConfigExpect(state, TOKEN_VALUE))
|
||||
{
|
||||
dval = state->token;
|
||||
}
|
||||
else if (ConfigExpect(state, TOKEN_MACRO))
|
||||
{
|
||||
dval = HashMapGet(state->macroMap, state->token);
|
||||
if (!dval)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dval = NULL; /* Should never happen */
|
||||
}
|
||||
|
||||
/* dval is a pointer which is overwritten with the next
|
||||
* token. */
|
||||
dvalCpy = Malloc(strlen(dval) + 1);
|
||||
strcpy(dvalCpy, dval);
|
||||
|
||||
ArrayAdd(directive->values, dvalCpy);
|
||||
ConfigTokenSeek(state);
|
||||
}
|
||||
|
||||
if (ConfigExpect(state, TOKEN_BLOCK_OPEN))
|
||||
{
|
||||
/* token_seek(state); */
|
||||
directive->children = ConfigParseBlock(state, level + 1);
|
||||
if (!directive->children)
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Append this directive to the current block,
|
||||
* overwriting a directive at this level with the same name.
|
||||
*
|
||||
* Note that if a value already exists with this name, it is
|
||||
* returned by HashMapSet() and then immediately passed to
|
||||
* ConfigDirectiveFree(). If the value does not exist, then
|
||||
* NULL is sent to ConfigDirectiveFree(), making it a no-op.
|
||||
*/
|
||||
ConfigDirectiveFree(HashMapSet(block, name, directive));
|
||||
}
|
||||
else if (ConfigExpect(state, TOKEN_MACRO_ASSIGNMENT))
|
||||
{
|
||||
ConfigTokenSeek(state);
|
||||
if (ConfigExpect(state, TOKEN_VALUE))
|
||||
{
|
||||
char *valueCopy = Malloc(strlen(state->token) + 1);
|
||||
|
||||
strcpy(valueCopy, state->token);
|
||||
Free(HashMapSet(state->macroMap, name, valueCopy));
|
||||
ConfigTokenSeek(state);
|
||||
}
|
||||
else
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (!ConfigExpect(state, TOKEN_SEMICOLON))
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
ConfigTokenSeek(state);
|
||||
}
|
||||
|
||||
if (ConfigExpect(state, level ? TOKEN_BLOCK_CLOSE : TOKEN_EOF))
|
||||
{
|
||||
ConfigTokenSeek(state);
|
||||
return block;
|
||||
}
|
||||
else
|
||||
{
|
||||
goto error;
|
||||
}
|
||||
|
||||
error:
|
||||
/* Only free the very top level, because this will recurse */
|
||||
if (!level)
|
||||
{
|
||||
ConfigFree(block);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ConfigParseResult *
|
||||
ConfigParse(FILE * stream)
|
||||
{
|
||||
ConfigParseResult *result;
|
||||
HashMap *conf;
|
||||
ConfigParserState *state;
|
||||
|
||||
result = Malloc(sizeof(ConfigParseResult));
|
||||
state = ConfigParserStateCreate(stream);
|
||||
conf = ConfigParseBlock(state, 0);
|
||||
|
||||
if (!conf)
|
||||
{
|
||||
result->ok = 0;
|
||||
result->data.lineNumber = state->line;
|
||||
}
|
||||
else
|
||||
{
|
||||
result->ok = 1;
|
||||
result->data.confMap = conf;
|
||||
}
|
||||
|
||||
ConfigParserStateFree(state);
|
||||
return result;
|
||||
}
|
|
@ -35,7 +35,7 @@
|
|||
#include <TelodendriaConfig.h>
|
||||
#include <Log.h>
|
||||
#include <HashMap.h>
|
||||
#include <Config.h>
|
||||
#include <Json.h>
|
||||
#include <HttpServer.h>
|
||||
#include <Matrix.h>
|
||||
#include <Db.h>
|
||||
|
@ -136,7 +136,6 @@ main(int argc, char **argv)
|
|||
|
||||
/* Config file */
|
||||
FILE *configFile = NULL;
|
||||
ConfigParseResult *configParseResult = NULL;
|
||||
HashMap *config = NULL;
|
||||
|
||||
/* Program configuration */
|
||||
|
@ -237,29 +236,25 @@ main(int argc, char **argv)
|
|||
|
||||
Log(lc, LOG_NOTICE, "Processing configuration file '%s'.", configArg);
|
||||
|
||||
configParseResult = ConfigParse(configFile);
|
||||
if (!ConfigParseResultOk(configParseResult))
|
||||
config = JsonDecode(configFile);
|
||||
fclose(configFile);
|
||||
|
||||
if (!config)
|
||||
{
|
||||
Log(lc, LOG_ERR, "Syntax error on line %d.",
|
||||
ConfigParseResultLineNumber(configParseResult));
|
||||
Log(lc, LOG_ERR, "Syntax error in configuration file.");
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
config = ConfigParseResultGet(configParseResult);
|
||||
ConfigParseResultFree(configParseResult);
|
||||
|
||||
fclose(configFile);
|
||||
|
||||
tConfig = TelodendriaConfigParse(config, lc);
|
||||
JsonFree(config);
|
||||
|
||||
if (!tConfig)
|
||||
{
|
||||
exit = EXIT_FAILURE;
|
||||
goto finish;
|
||||
}
|
||||
|
||||
ConfigFree(config);
|
||||
|
||||
if (flags & ARG_CONFIGTEST)
|
||||
{
|
||||
Log(lc, LOG_INFO, "Configuration is OK.");
|
||||
|
@ -277,7 +272,10 @@ main(int argc, char **argv)
|
|||
unveil(NULL, NULL); /* Done with unveil(), so disable it */
|
||||
#endif
|
||||
|
||||
if (!tConfig->logTimestamp || strcmp(tConfig->logTimestamp, "default") != 0)
|
||||
{
|
||||
LogConfigTimeStampFormatSet(lc, tConfig->logTimestamp);
|
||||
}
|
||||
|
||||
if (tConfig->flags & TELODENDRIA_LOG_COLOR)
|
||||
{
|
||||
|
|
|
@ -23,61 +23,179 @@
|
|||
*/
|
||||
#include <TelodendriaConfig.h>
|
||||
#include <Memory.h>
|
||||
#include <Config.h>
|
||||
#include <Json.h>
|
||||
#include <HashMap.h>
|
||||
#include <Log.h>
|
||||
#include <Array.h>
|
||||
#include <Util.h>
|
||||
#include <Db.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
static int
|
||||
IsInteger(char *str)
|
||||
{
|
||||
while (*str)
|
||||
{
|
||||
if (!isdigit((unsigned char) *str))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
str++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#define GET_DIRECTIVE(name) \
|
||||
directive = (ConfigDirective *) HashMapGet(config, name); \
|
||||
if (!directive) { \
|
||||
Log(lc, LOG_ERR, "Missing required configuration directive: '%s'.", name); \
|
||||
#define CONFIG_REQUIRE(key, type) \
|
||||
value = HashMapGet(config, key); \
|
||||
if (!value) \
|
||||
{ \
|
||||
Log(lc, LOG_ERR, "Missing required " key " directive."); \
|
||||
goto error; \
|
||||
} \
|
||||
children = ConfigChildrenGet(directive); \
|
||||
value = ConfigValuesGet(directive); \
|
||||
|
||||
#define ASSERT_NO_CHILDREN(name) if (children) { \
|
||||
Log(lc, LOG_ERR, "Unexpected child values in directive: '%s'.", name); \
|
||||
if (JsonValueType(value) == JSON_NULL) \
|
||||
{ \
|
||||
Log(lc, LOG_ERR, "Missing value for " key " directive."); \
|
||||
goto error; \
|
||||
} \
|
||||
if (JsonValueType(value) != type) \
|
||||
{ \
|
||||
Log(lc, LOG_ERR, "Expected " key " to be of type " #type); \
|
||||
goto error; \
|
||||
}
|
||||
|
||||
#define ASSERT_VALUES(name, expected) if (ArraySize(value) != expected) { \
|
||||
Log(lc, LOG_ERR, \
|
||||
"Wrong value count in directive '%s': got '%d', but expected '%d'.", \
|
||||
name, ArraySize(value), expected); \
|
||||
#define CONFIG_COPY_STRING(into) \
|
||||
into = UtilStringDuplicate(JsonValueAsString(value));
|
||||
|
||||
#define CONFIG_OPTIONAL_STRING(into, key, default) \
|
||||
value = HashMapGet(config, key); \
|
||||
if (value && JsonValueType(value) != JSON_NULL) \
|
||||
{ \
|
||||
if (JsonValueType(value) != JSON_STRING) \
|
||||
{ \
|
||||
Log(lc, LOG_ERR, "Expected " key " to be of type JSON_STRING"); \
|
||||
goto error; \
|
||||
} \
|
||||
into = UtilStringDuplicate(JsonValueAsString(value)); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
Log(lc, LOG_INFO, "Using default value " #default " for " key "."); \
|
||||
into = default ? UtilStringDuplicate(default) : NULL; \
|
||||
}
|
||||
|
||||
#define COPY_VALUE(into, index) into = UtilStringDuplicate(ArrayGet(value, index))
|
||||
#define CONFIG_OPTIONAL_INTEGER(into, key, default) \
|
||||
value = HashMapGet(config, key); \
|
||||
if (value && JsonValueType(value) != JSON_NULL) \
|
||||
{ \
|
||||
if (JsonValueType(value) != JSON_INTEGER) \
|
||||
{ \
|
||||
Log(lc, LOG_ERR, "Expected " key " to be of type JSON_INTEGER"); \
|
||||
goto error; \
|
||||
} \
|
||||
into = JsonValueAsInteger(value); \
|
||||
} \
|
||||
else \
|
||||
{ \
|
||||
Log(lc, LOG_INFO, "Using default value " #default " for " key "."); \
|
||||
into = default; \
|
||||
}
|
||||
|
||||
int
|
||||
ConfigParseRunAs(LogConfig * lc, TelodendriaConfig * tConfig, HashMap * config)
|
||||
{
|
||||
JsonValue *value;
|
||||
|
||||
CONFIG_REQUIRE("uid", JSON_STRING);
|
||||
CONFIG_COPY_STRING(tConfig->uid);
|
||||
|
||||
CONFIG_OPTIONAL_STRING(tConfig->gid, "gid", tConfig->uid);
|
||||
|
||||
return 1;
|
||||
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
ConfigParseLog(LogConfig * lc, TelodendriaConfig * tConfig, HashMap * config)
|
||||
{
|
||||
JsonValue *value;
|
||||
char *str;
|
||||
|
||||
CONFIG_REQUIRE("output", JSON_STRING);
|
||||
str = JsonValueAsString(value);
|
||||
|
||||
if (strcmp(str, "stdout") == 0)
|
||||
{
|
||||
tConfig->flags |= TELODENDRIA_LOG_STDOUT;
|
||||
}
|
||||
else if (strcmp(str, "file") == 0)
|
||||
{
|
||||
tConfig->flags |= TELODENDRIA_LOG_FILE;
|
||||
}
|
||||
else if (strcmp(str, "syslog") == 0)
|
||||
{
|
||||
tConfig->flags |= TELODENDRIA_LOG_SYSLOG;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(lc, LOG_ERR, "Invalid value for log.output: '%s'.", str);
|
||||
goto error;
|
||||
}
|
||||
|
||||
CONFIG_OPTIONAL_STRING(str, "level", "message");
|
||||
|
||||
if (strcmp(str, "message") == 0)
|
||||
{
|
||||
tConfig->logLevel = LOG_INFO;
|
||||
}
|
||||
else if (strcmp(str, "debug") == 0)
|
||||
{
|
||||
tConfig->logLevel = LOG_DEBUG;
|
||||
}
|
||||
else if (strcmp(str, "notice") == 0)
|
||||
{
|
||||
tConfig->logLevel = LOG_NOTICE;
|
||||
}
|
||||
else if (strcmp(str, "warning") == 0)
|
||||
{
|
||||
tConfig->logLevel = LOG_WARNING;
|
||||
}
|
||||
else if (strcmp(str, "error") == 0)
|
||||
{
|
||||
tConfig->logLevel = LOG_ERR;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(lc, LOG_ERR, "Invalid value for log.level: '%s'.", tConfig->logLevel);
|
||||
goto error;
|
||||
}
|
||||
|
||||
Free(str);
|
||||
|
||||
CONFIG_OPTIONAL_STRING(tConfig->logTimestamp, "timestampFormat", "default");
|
||||
|
||||
if (strcmp(tConfig->logTimestamp, "none") == 0)
|
||||
{
|
||||
Free(tConfig->logTimestamp);
|
||||
tConfig->logTimestamp = NULL;
|
||||
}
|
||||
|
||||
value = HashMapGet(config, "color");
|
||||
if (value && JsonValueType(value) != JSON_NULL)
|
||||
{
|
||||
if (JsonValueType(value) != JSON_BOOLEAN)
|
||||
{
|
||||
Log(lc, LOG_ERR, "Expected type JSON_BOOLEAN for log.color.");
|
||||
goto error;
|
||||
}
|
||||
|
||||
if (JsonValueAsBoolean(value))
|
||||
{
|
||||
tConfig->flags |= TELODENDRIA_LOG_COLOR;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
error:
|
||||
return 0;
|
||||
}
|
||||
|
||||
TelodendriaConfig *
|
||||
TelodendriaConfigParse(HashMap * config, LogConfig * lc)
|
||||
{
|
||||
TelodendriaConfig *tConfig;
|
||||
|
||||
ConfigDirective *directive;
|
||||
Array *value;
|
||||
HashMap *children;
|
||||
JsonValue *value;
|
||||
|
||||
if (!config || !lc)
|
||||
{
|
||||
|
@ -92,41 +210,15 @@ TelodendriaConfigParse(HashMap * config, LogConfig * lc)
|
|||
|
||||
memset(tConfig, 0, sizeof(TelodendriaConfig));
|
||||
|
||||
directive = (ConfigDirective *) HashMapGet(config, "listen");
|
||||
children = ConfigChildrenGet(directive);
|
||||
value = ConfigValuesGet(directive);
|
||||
CONFIG_OPTIONAL_INTEGER(tConfig->listenPort, "listen", 8008);
|
||||
|
||||
if (!directive)
|
||||
CONFIG_REQUIRE("serverName", JSON_STRING);
|
||||
CONFIG_COPY_STRING(tConfig->serverName);
|
||||
|
||||
value = HashMapGet(config, "baseUrl");
|
||||
if (value)
|
||||
{
|
||||
tConfig->listenPort = 8008;
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT_NO_CHILDREN("listen");
|
||||
ASSERT_VALUES("listen", 1);
|
||||
|
||||
tConfig->listenPort = (unsigned short) atoi(ArrayGet(value, 0));
|
||||
if (!tConfig->listenPort)
|
||||
{
|
||||
Log(lc, LOG_ERR, "Expected numeric value for listen port, got '%s'.", ArrayGet(value, 1));
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
GET_DIRECTIVE("server-name");
|
||||
ASSERT_NO_CHILDREN("server-name");
|
||||
ASSERT_VALUES("server-name", 1);
|
||||
COPY_VALUE(tConfig->serverName, 0);
|
||||
|
||||
directive = (ConfigDirective *) HashMapGet(config, "base-url");
|
||||
children = ConfigChildrenGet(directive);
|
||||
value = ConfigValuesGet(directive);
|
||||
|
||||
if (directive)
|
||||
{
|
||||
ASSERT_NO_CHILDREN("base-url");
|
||||
ASSERT_VALUES("base-url", 1);
|
||||
COPY_VALUE(tConfig->baseUrl, 0);
|
||||
CONFIG_COPY_STRING(tConfig->baseUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -134,273 +226,65 @@ TelodendriaConfigParse(HashMap * config, LogConfig * lc)
|
|||
tConfig->baseUrl = Malloc(strlen(tConfig->serverName) + 10);
|
||||
if (!tConfig->baseUrl)
|
||||
{
|
||||
Log(lc, LOG_ERR, "Error allocating memory for default config value 'base-url'.");
|
||||
Log(lc, LOG_ERR, "Error allocating memory for default config value 'baseUrl'.");
|
||||
goto error;
|
||||
}
|
||||
|
||||
sprintf(tConfig->baseUrl, "https://%s", tConfig->serverName);
|
||||
}
|
||||
|
||||
directive = (ConfigDirective *) HashMapGet(config, "identity-server");
|
||||
children = ConfigChildrenGet(directive);
|
||||
value = ConfigValuesGet(directive);
|
||||
CONFIG_OPTIONAL_STRING(tConfig->identityServer, "identityServer", NULL);
|
||||
|
||||
if (directive)
|
||||
value = HashMapGet(config, "runAs");
|
||||
if (value && JsonValueType(value) != JSON_NULL)
|
||||
{
|
||||
ASSERT_NO_CHILDREN("identity-server");
|
||||
ASSERT_VALUES("identity-server", 1);
|
||||
COPY_VALUE(tConfig->identityServer, 0);
|
||||
}
|
||||
else
|
||||
if (JsonValueType(value) == JSON_OBJECT)
|
||||
{
|
||||
Log(lc, LOG_WARNING, "Identity server not specified. No identity server will be advertised.");
|
||||
tConfig->identityServer = NULL;
|
||||
}
|
||||
|
||||
directive = (ConfigDirective *) HashMapGet(config, "id");
|
||||
children = ConfigChildrenGet(directive);
|
||||
value = ConfigValuesGet(directive);
|
||||
|
||||
ASSERT_NO_CHILDREN("id");
|
||||
|
||||
if (directive)
|
||||
if (!ConfigParseRunAs(lc, tConfig, JsonValueAsObject(value)))
|
||||
{
|
||||
|
||||
switch (ArraySize(value))
|
||||
{
|
||||
case 1:
|
||||
Log(lc, LOG_WARNING, "No run group specified; assuming it's the same as the user.");
|
||||
COPY_VALUE(tConfig->uid, 0);
|
||||
tConfig->gid = UtilStringDuplicate(tConfig->uid);
|
||||
break;
|
||||
case 2:
|
||||
COPY_VALUE(tConfig->uid, 0);
|
||||
COPY_VALUE(tConfig->gid, 1);
|
||||
break;
|
||||
default:
|
||||
Log(lc, LOG_ERR,
|
||||
"Wrong value count in directive 'id': got '%d', but expected 1 or 2.",
|
||||
ArraySize(value));
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
tConfig->uid = NULL;
|
||||
tConfig->gid = NULL;
|
||||
}
|
||||
|
||||
GET_DIRECTIVE("data-dir");
|
||||
ASSERT_NO_CHILDREN("data-dir");
|
||||
ASSERT_VALUES("data-dir", 1);
|
||||
COPY_VALUE(tConfig->dataDir, 0);
|
||||
|
||||
GET_DIRECTIVE("threads");
|
||||
ASSERT_NO_CHILDREN("threads");
|
||||
ASSERT_VALUES("threads", 1);
|
||||
|
||||
if (IsInteger(ArrayGet(value, 0)))
|
||||
{
|
||||
tConfig->threads = atoi(ArrayGet(value, 0));
|
||||
if (!tConfig->threads)
|
||||
{
|
||||
Log(lc, LOG_ERR, "threads must be greater than zero");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(lc, LOG_ERR,
|
||||
"Expected integer for directive 'threads', "
|
||||
"but got '%s'.", ArrayGet(value, 0));
|
||||
goto error;
|
||||
}
|
||||
|
||||
directive = (ConfigDirective *) HashMapGet(config, "max-connections");
|
||||
if (!directive)
|
||||
{
|
||||
Log(lc, LOG_WARNING, "max-connections not specified; using defaults, which may change");
|
||||
tConfig->maxConnections = 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
ASSERT_NO_CHILDREN("max-connections");
|
||||
ASSERT_VALUES("max-connections", 1);
|
||||
if (IsInteger(ArrayGet(value, 0)))
|
||||
{
|
||||
tConfig->maxConnections = atoi(ArrayGet(value, 0));
|
||||
if (!tConfig->maxConnections)
|
||||
{
|
||||
Log(lc, LOG_ERR, "max-connections must be greater than zero.");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(lc, LOG_ERR, "Expected integer for max-connections, got '%s'", ArrayGet(value, 0));
|
||||
Log(lc, LOG_ERR, "Config directive 'runAs' should be a JSON object");
|
||||
Log(lc, LOG_ERR, "that contains a 'uid' and 'gid'.");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
GET_DIRECTIVE("max-cache");
|
||||
ASSERT_NO_CHILDREN("max-cache");
|
||||
ASSERT_VALUES("max-cache", 1);
|
||||
tConfig->maxCache = UtilParseBytes(ArrayGet(value, 0));
|
||||
CONFIG_REQUIRE("dataDir", JSON_STRING);
|
||||
CONFIG_COPY_STRING(tConfig->dataDir);
|
||||
|
||||
GET_DIRECTIVE("federation");
|
||||
ASSERT_NO_CHILDREN("federation");
|
||||
ASSERT_VALUES("federation", 1);
|
||||
CONFIG_OPTIONAL_INTEGER(tConfig->threads, "threads", 1);
|
||||
CONFIG_OPTIONAL_INTEGER(tConfig->maxConnections, "maxConnections", 32);
|
||||
CONFIG_OPTIONAL_INTEGER(tConfig->maxCache, "maxCache", DB_MIN_CACHE);
|
||||
|
||||
if (strcmp(ArrayGet(value, 0), "true") == 0)
|
||||
CONFIG_REQUIRE("federation", JSON_BOOLEAN);
|
||||
if (JsonValueAsBoolean(value))
|
||||
{
|
||||
tConfig->flags |= TELODENDRIA_FEDERATION;
|
||||
}
|
||||
else if (strcmp(ArrayGet(value, 0), "false") != 0)
|
||||
{
|
||||
Log(lc, LOG_ERR,
|
||||
"Expected boolean value for directive 'federation', "
|
||||
"but got '%s'.", ArrayGet(value, 0));
|
||||
goto error;
|
||||
}
|
||||
|
||||
GET_DIRECTIVE("registration");
|
||||
ASSERT_NO_CHILDREN("registration");
|
||||
ASSERT_VALUES("registration", 1);
|
||||
if (strcmp(ArrayGet(value, 0), "true") == 0)
|
||||
CONFIG_REQUIRE("registration", JSON_BOOLEAN);
|
||||
if (JsonValueAsBoolean(value))
|
||||
{
|
||||
tConfig->flags |= TELODENDRIA_REGISTRATION;
|
||||
}
|
||||
else if (strcmp(ArrayGet(value, 0), "false") != 0)
|
||||
{
|
||||
Log(lc, LOG_ERR,
|
||||
"Expected boolean value for directive 'registration', "
|
||||
"but got '%s'.", ArrayGet(value, 0));
|
||||
goto error;
|
||||
}
|
||||
|
||||
GET_DIRECTIVE("log");
|
||||
ASSERT_VALUES("log", 1);
|
||||
|
||||
if (children)
|
||||
CONFIG_REQUIRE("log", JSON_OBJECT);
|
||||
if (!ConfigParseLog(lc, tConfig, JsonValueAsObject(value)))
|
||||
{
|
||||
ConfigDirective *cDirective;
|
||||
char *cVal;
|
||||
size_t size;
|
||||
|
||||
cDirective = HashMapGet(children, "level");
|
||||
if (cDirective)
|
||||
{
|
||||
size = ArraySize(ConfigValuesGet(cDirective));
|
||||
if (size > 1)
|
||||
{
|
||||
Log(lc, LOG_ERR, "Expected 1 value for log.level, got %d.", size);
|
||||
goto error;
|
||||
}
|
||||
|
||||
cVal = ArrayGet(ConfigValuesGet(cDirective), 0);
|
||||
if (strcmp(cVal, "message") == 0)
|
||||
{
|
||||
tConfig->logLevel = LOG_INFO;
|
||||
}
|
||||
else if (strcmp(cVal, "debug") == 0)
|
||||
{
|
||||
tConfig->logLevel = LOG_DEBUG;
|
||||
}
|
||||
else if (strcmp(cVal, "task") == 0)
|
||||
{
|
||||
tConfig->logLevel = LOG_NOTICE;
|
||||
}
|
||||
else if (strcmp(cVal, "warning") == 0)
|
||||
{
|
||||
tConfig->logLevel = LOG_WARNING;
|
||||
}
|
||||
else if (strcmp(cVal, "error") == 0)
|
||||
{
|
||||
tConfig->logLevel = LOG_ERR;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(lc, LOG_ERR, "Invalid value for log.level: '%s'.", cVal);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
cDirective = HashMapGet(children, "timestampFormat");
|
||||
if (cDirective)
|
||||
{
|
||||
size = ArraySize(ConfigValuesGet(cDirective));
|
||||
if (size > 1)
|
||||
{
|
||||
Log(lc, LOG_ERR, "Expected 1 value for log.level, got %d.", size);
|
||||
goto error;
|
||||
}
|
||||
|
||||
cVal = ArrayGet(ConfigValuesGet(cDirective), 0);
|
||||
|
||||
if (strcmp(cVal, "none") == 0)
|
||||
{
|
||||
tConfig->logTimestamp = NULL;
|
||||
}
|
||||
else if (strcmp(cVal, "default") != 0)
|
||||
{
|
||||
tConfig->logTimestamp = UtilStringDuplicate(cVal);
|
||||
}
|
||||
}
|
||||
|
||||
cDirective = HashMapGet(children, "color");
|
||||
if (cDirective)
|
||||
{
|
||||
size = ArraySize(ConfigValuesGet(cDirective));
|
||||
if (size > 1)
|
||||
{
|
||||
Log(lc, LOG_ERR, "Expected 1 value for log.level, got %d.", size);
|
||||
goto error;
|
||||
}
|
||||
|
||||
cVal = ArrayGet(ConfigValuesGet(cDirective), 0);
|
||||
|
||||
if (strcmp(cVal, "true") == 0)
|
||||
{
|
||||
tConfig->flags |= TELODENDRIA_LOG_COLOR;
|
||||
}
|
||||
else if (strcmp(cVal, "false") != 0)
|
||||
{
|
||||
Log(lc, LOG_ERR, "Expected boolean value for log.color, got '%s'.", cVal);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the actual log output file last */
|
||||
if (strcmp(ArrayGet(value, 0), "stdout") == 0)
|
||||
{
|
||||
tConfig->flags |= TELODENDRIA_LOG_STDOUT;
|
||||
}
|
||||
else if (strcmp(ArrayGet(value, 0), "file") == 0)
|
||||
{
|
||||
tConfig->flags |= TELODENDRIA_LOG_FILE;
|
||||
}
|
||||
else if (strcmp(ArrayGet(value, 0), "syslog") == 0)
|
||||
{
|
||||
tConfig->flags |= TELODENDRIA_LOG_SYSLOG;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log(lc, LOG_ERR, "Unknown log value '%s', expected 'stdout', 'file', or 'syslog'.",
|
||||
ArrayGet(value, 0));
|
||||
goto error;
|
||||
}
|
||||
|
||||
return tConfig;
|
||||
|
||||
error:
|
||||
TelodendriaConfigFree(tConfig);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#undef GET_DIRECTIVE
|
||||
#undef ASSERT_NO_CHILDREN
|
||||
#undef ASSERT_VALUES
|
||||
|
||||
void
|
||||
TelodendriaConfigFree(TelodendriaConfig * tConfig)
|
||||
{
|
||||
|
|
|
@ -1,208 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation files
|
||||
* (the "Software"), to deal in the Software without restriction,
|
||||
* including without limitation the rights to use, copy, modify, merge,
|
||||
* publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be
|
||||
* included in all copies or portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Config.h: A heavily-modified version of Conifer2, a configuration
|
||||
* file format specification and C parsing library written by Jordan
|
||||
* Bancino. This library differs from Conifer2 in that the function
|
||||
* naming convention has been updated to be consistent with Telodendria,
|
||||
* and the underlying data structures have been overhauled to use the
|
||||
* data structure libraries provided by Telodendria.
|
||||
*
|
||||
* Conifer2 was originally a learning project. It was very thoroughly
|
||||
* debugged, however, and the configuration syntax was elegant,
|
||||
* certainly more elegant than using JSON for a configuration file,
|
||||
* so it was chosen to be the format for Telodendria's configuration
|
||||
* file. The original Conifer2 project is now dead; Conifer2 lives on
|
||||
* only as Telodendria's Config parsing library.
|
||||
*/
|
||||
#ifndef TELODENDRIA_CONFIG_H
|
||||
#define TELODENDRIA_CONFIG_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include <HashMap.h>
|
||||
#include <Array.h>
|
||||
|
||||
/*
|
||||
* A configuration directive is a single key that may have at least one
|
||||
* value, and any number of children.
|
||||
*/
|
||||
typedef struct ConfigDirective ConfigDirective;
|
||||
|
||||
/*
|
||||
* The parser returns a parse result object. This stores whether or
|
||||
* not the parse was successful, and then also additional information
|
||||
* about the parse, such as the line number on which parsing failed,
|
||||
* or the collection of directives if the parsing succeeded.
|
||||
*
|
||||
* There are a number of ConfigParseResult methods that can be used
|
||||
* to query the result of parsing.
|
||||
*/
|
||||
typedef struct ConfigParseResult ConfigParseResult;
|
||||
|
||||
/*
|
||||
* Parse a configuration file, and generate the structures needed to
|
||||
* make it easy to read.
|
||||
*
|
||||
* Params:
|
||||
*
|
||||
* (FILE *) The input stream to read from.
|
||||
*
|
||||
* Return: A ConfigParseResult, which can be used to check whether or
|
||||
* not the parsing was successful. If the parsing was sucessful, then
|
||||
* this object contains the root directive, which can be used to
|
||||
* retrieve configuration values out of. If the parsing failed, then
|
||||
* this object contains the line number at which the parsing was
|
||||
* aborted.
|
||||
*/
|
||||
extern ConfigParseResult *
|
||||
ConfigParse(FILE *);
|
||||
|
||||
/*
|
||||
* Get whether or not a parse result indicates that parsing was
|
||||
* successful or not. This function should be used to determine what
|
||||
* to do next. If the parsing failed, your program should terminate
|
||||
* with an error, otherwise, you can proceed to parse the configuration
|
||||
* file.
|
||||
*
|
||||
* Params:
|
||||
*
|
||||
* (ConfigParseResult *) The output of ConfigParse() to check.
|
||||
*
|
||||
* Return: 0 if the configuration file is malformed, or otherwise
|
||||
* could not be parsed. Any non-zero return value indicates that the
|
||||
* configuration file was successfully parsed.
|
||||
*/
|
||||
extern unsigned int
|
||||
ConfigParseResultOk(ConfigParseResult *);
|
||||
|
||||
/*
|
||||
* If, and only if, the configuration file parsing failed, then this
|
||||
* function can be used to get the line number it failed at. Typically,
|
||||
* this will be reported to the user and then the program will be
|
||||
* terminated.
|
||||
*
|
||||
* Params:
|
||||
*
|
||||
* (ConfigParseResult *) The output of ConfigParse() to get the
|
||||
* line number from.
|
||||
*
|
||||
* Return: The line number on which the configuration file parser
|
||||
* choked, or 0 if the parsing was actually successful.
|
||||
*/
|
||||
extern size_t
|
||||
ConfigParseResultLineNumber(ConfigParseResult *);
|
||||
|
||||
/*
|
||||
* Convert a ConfigParseResult into a HashMap containing the entire
|
||||
* configuration file, if, and only if, the parsing was successful.
|
||||
*
|
||||
* Params:
|
||||
*
|
||||
* (ConfigParseResult *) The output of ConfigParse() to get the
|
||||
* actual configuration data from.
|
||||
*
|
||||
* Return: A HashMap containing all the configuration data, or NULL
|
||||
* if the parsing was not successful. This HashMap is a map of string
|
||||
* keys to ConfigDirective objects. Use the standard HashMap methods
|
||||
* to get ConfigDirectives, and then use the ConfigDirective functions
|
||||
* to get information out of them.
|
||||
*/
|
||||
extern HashMap *
|
||||
ConfigParseResultGet(ConfigParseResult *);
|
||||
|
||||
/*
|
||||
* Free the memory being used by the given ConfigParseResult. Note that
|
||||
* it is safe to free the ConfigParseResult immediately after you have
|
||||
* retrieved either the line number or the configuration data from it.
|
||||
* Freeing the parse result does not free the configuration data.
|
||||
*
|
||||
* Params:
|
||||
*
|
||||
* (ConfigParseResult *) The output of ConfigParse() to free. This
|
||||
* object will be invalidated, but pointers to
|
||||
* the actual configuration data will still be
|
||||
* valid.
|
||||
*/
|
||||
extern void
|
||||
ConfigParseResultFree(ConfigParseResult *);
|
||||
|
||||
/*
|
||||
* Get an array of values associated with the given configuration
|
||||
* directive. Directives can have any number of values, which are
|
||||
* made accessible via the Array API.
|
||||
*
|
||||
* Params:
|
||||
*
|
||||
* (ConfigDirective *) The configuration directive to get the values
|
||||
* for.
|
||||
*
|
||||
* Return: An array that contains at least 1 value. Configuration files
|
||||
* cannot have value-less directives. If the passed directive is NULL,
|
||||
* or there is an error allocating memory for an array, then NULL is
|
||||
* returned.
|
||||
*/
|
||||
extern Array *
|
||||
ConfigValuesGet(ConfigDirective *);
|
||||
|
||||
/*
|
||||
* Get a map of children associated with the given configuration
|
||||
* directive. Configuration files can recurse with no practical limit,
|
||||
* so directives can have any number of child directives.
|
||||
*
|
||||
* Params:
|
||||
*
|
||||
* (ConfigDirective *) The configuratio ndirective to get the
|
||||
* children of.
|
||||
*
|
||||
* Return: A HashMap containing child directives, or NULL if the passed
|
||||
* directive is NULL or has no children.
|
||||
*/
|
||||
extern HashMap *
|
||||
ConfigChildrenGet(ConfigDirective *);
|
||||
|
||||
/*
|
||||
* Free all the memory associated with the given configuration hash
|
||||
* map. Note: this will free *everything*. All Arrays, HashMaps,
|
||||
* ConfigDirectives, and even strings will be invalidated. As such,
|
||||
* this should be done after you either copy the values you want, or
|
||||
* are done using them. It is highly recommended to use this function
|
||||
* near the end of your program's execution during cleanup, otherwise
|
||||
* copy any values you need into your own buffers.
|
||||
*
|
||||
* Note that this should only be run on the root configuration object,
|
||||
* not any children. Running on children will produce undefined
|
||||
* behavior. This function is recursive; it will get all the children
|
||||
* under it.
|
||||
*
|
||||
* Params:
|
||||
*
|
||||
* (HashMap *) The configuration data to free.
|
||||
*
|
||||
*/
|
||||
extern void
|
||||
ConfigFree(HashMap *);
|
||||
|
||||
#endif /* TELODENDRIA_CONFIG_H */
|
Loading…
Reference in a new issue