Telodendria/src/TelodendriaConfig.c
Jordan Bancino cdd7808642 Prototype the configuration file parser.
Right now there's a nasty memory bug I need to fix. Will have to run this
through valgrind.
2022-08-09 20:05:41 -04:00

317 lines
8.6 KiB
C

/*
* 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 <TelodendriaConfig.h>
#include <Config.h>
#include <HashMap.h>
#include <Log.h>
#include <Array.h>
#include <Util.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
static int
IsInteger(char *str)
{
while (*str)
{
if (!isdigit(*str))
{
return 0;
}
str++;
}
return 1;
}
#define GET_DIRECTIVE(name) \
directive = (ConfigDirective *) HashMapGet(config, name); \
if (!directive) { \
Log(lc, LOG_ERROR, "Missing required configuration directive: '%s'.", name); \
goto error; \
} \
children = ConfigChildrenGet(directive); \
value = ConfigValuesGet(directive); \
#define ASSERT_NO_CHILDREN(name) if (children) { \
Log(lc, LOG_ERROR, "Unexpected child values in directive: '%s'.", name); \
goto error; \
}
#define ASSERT_VALUES(name, expected) if (ArraySize(value) != expected) { \
Log(lc, LOG_ERROR, \
"Wrong value count in directive '%s': got '%d', but expected '%d'.", \
name, ArraySize(value), expected); \
goto error; \
}
#define COPY_VALUE(into, index) into = UtilStringDuplicate(ArrayGet(value, index))
TelodendriaConfig *
TelodendriaConfigParse(HashMap * config, LogConfig * lc)
{
TelodendriaConfig *tConfig;
ConfigDirective *directive;
Array *value;
HashMap *children;
if (!config || !lc)
{
return NULL;
}
tConfig = calloc(1, sizeof(tConfig));
if (!tConfig)
{
return NULL;
}
directive = (ConfigDirective *) HashMapGet(config, "listen");
children = ConfigChildrenGet(directive);
value = ConfigValuesGet(directive);
if (!directive)
{
Log(lc, LOG_WARNING, "No 'listen' directive specified; using defaults, which may change.");
tConfig->listenHost = UtilStringDuplicate("localhost");
tConfig->listenPort = UtilStringDuplicate("8008");
}
else
{
ASSERT_NO_CHILDREN("listen");
ASSERT_VALUES("listen", 2);
COPY_VALUE(tConfig->listenHost, 0);
COPY_VALUE(tConfig->listenPort, 1);
}
GET_DIRECTIVE("server-name");
ASSERT_NO_CHILDREN("server-name");
ASSERT_VALUES("server-name", 1);
COPY_VALUE(tConfig->serverName, 0);
GET_DIRECTIVE("chroot");
ASSERT_NO_CHILDREN("chroot");
ASSERT_VALUES("chroot", 1);
COPY_VALUE(tConfig->chroot, 0);
GET_DIRECTIVE("id");
ASSERT_NO_CHILDREN("id");
ASSERT_VALUES("id", 2);
COPY_VALUE(tConfig->uid, 0);
COPY_VALUE(tConfig->gid, 1);
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));
}
else
{
Log(lc, LOG_ERROR,
"Expected integer for directive 'threads', "
"but got '%s'.", ArrayGet(value, 0));
goto error;
}
GET_DIRECTIVE("federation");
ASSERT_NO_CHILDREN("federation");
ASSERT_VALUES("federation", 1);
if (strcmp(ArrayGet(value, 0), "true") == 0)
{
tConfig->flags |= TELODENDRIA_FEDERATION;
}
else if (strcmp(ArrayGet(value, 0), "false") != 0)
{
Log(lc, LOG_ERROR,
"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)
{
tConfig->flags |= TELODENDRIA_REGISTRATION;
}
else if (strcmp(ArrayGet(value, 0), "false") != 0)
{
Log(lc, LOG_ERROR,
"Expected boolean value for directive 'registration', "
"but got '%s'.", ArrayGet(value, 0));
goto error;
}
GET_DIRECTIVE("log");
ASSERT_VALUES("log", 1);
if (children)
{
ConfigDirective *cDirective;
char *cVal;
size_t size;
cDirective = HashMapGet(children, "level");
if (cDirective)
{
size = ArraySize(ConfigValuesGet(cDirective));
if (size > 1)
{
Log(lc, LOG_ERROR, "Expected 1 value for log.level, got %d.", size);
goto error;
}
cVal = ArrayGet(ConfigValuesGet(cDirective), 0);
if (strcmp(cVal, "message") == 0)
{
LogConfigLevelSet(lc, LOG_MESSAGE);
}
else if (strcmp(cVal, "debug") == 0)
{
LogConfigLevelSet(lc, LOG_DEBUG);
}
else if (strcmp(cVal, "task") == 0)
{
LogConfigLevelSet(lc, LOG_TASK);
}
else if (strcmp(cVal, "warning") == 0)
{
LogConfigLevelSet(lc, LOG_WARNING);
}
else if (strcmp(cVal, "error") == 0)
{
LogConfigLevelSet(lc, LOG_ERROR);
}
else
{
Log(lc, LOG_ERROR, "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_ERROR, "Expected 1 value for log.level, got %d.", size);
goto error;
}
cVal = ArrayGet(ConfigValuesGet(cDirective), 0);
if (strcmp(cVal, "none") == 0)
{
LogConfigTimeStampFormatSet(lc, NULL);
}
else if (strcmp(cVal, "default") != 0)
{
LogConfigTimeStampFormatSet(lc, UtilStringDuplicate(cVal));
}
}
cDirective = HashMapGet(children, "color");
if (cDirective)
{
size = ArraySize(ConfigValuesGet(cDirective));
if (size > 1)
{
Log(lc, LOG_ERROR, "Expected 1 value for log.level, got %d.", size);
goto error;
}
cVal = ArrayGet(ConfigValuesGet(cDirective), 0);
if (strcmp(cVal, "false") == 0)
{
LogConfigFlagClear(lc, LOG_FLAG_COLOR);
}
else if (strcmp(cVal, "true") != 0)
{
Log(lc, LOG_ERROR, "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)
{
FILE *out = fopen(ArrayGet(value, 0), "w");
if (!out)
{
Log(lc, LOG_ERROR, "Unable to open log file '%s' for writing.",
ArrayGet(value, 0));
goto error;
}
Log(lc, LOG_DEBUG, "Redirecting output to '%s'.", ArrayGet(value, 0));
LogConfigOutputSet(lc, out);
}
tConfig->logConfig = lc;
return tConfig;
error:
TelodendriaConfigFree(tConfig);
return NULL;
}
#undef GET_DIRECTIVE
#undef ASSERT_NO_CHILDREN
#undef ASSERT_VALUES
void
TelodendriaConfigFree(TelodendriaConfig * tConfig)
{
if (!tConfig)
{
return;
}
free(tConfig->listenHost);
free(tConfig->listenPort);
free(tConfig->serverName);
free(tConfig->chroot);
free(tConfig->uid);
free(tConfig->gid);
free(tConfig->dataDir);
LogConfigFree(tConfig->logConfig);
free(tConfig);
}