Replace inih with glib key file parser

After including the use of glib we might as well replace inih
with the glib key file parser.

All configuraiton file parsing has been reworked and also the options
parsing has been cleaned up, resulting in better and stricter
configuration file and option value checks.

Compared to old, configuration files now requires any default
configurations to be put in a group/section named [default].

Configuration file keywords such as "enable", "disable", "on",
"off", "yes", "no", "0", "1" have been retired. Now only "true" and
"false" apply to boolean configuration options. This is done to simplify
things and avoid any confusion.

The pattern option feature has been reworked so now the user can now
access the full match string and any matching subexpression using the
%mN syntax.

For example:

[usb devices]
pattern = usb([0-9]*)
device = /dev/ttyUSB%m1

Then when using tio:
$ tio usb12

   %m0 = 'usb12'  // Full match string
   %m1 = 12       // First match subexpression

Which results in device = /dev/ttyUSB12
This commit is contained in:
Martin Lund 2024-05-02 17:21:10 +02:00
parent 68d3b845b2
commit 65c5a068d8
23 changed files with 969 additions and 723 deletions

View file

@ -32,10 +32,11 @@
#include <errno.h>
#include <getopt.h>
#include <termios.h>
#include <assert.h>
#include <limits.h>
#include <unistd.h>
#include <regex.h>
#include <ini.h>
#include <glib.h>
#include "options.h"
#include "configfile.h"
#include "misc.h"
@ -46,505 +47,555 @@
#include "timestamp.h"
#include "alert.h"
struct config_t
#define CONFIG_GROUP_NAME_DEFAULT "default"
struct config_t config = {};
static void config_get_string(GKeyFile *key_file, gchar *group, gchar *key, char **dest, char *allowed_string, ...)
{
const char *user;
(void)dest;
GError *error = NULL;
bool mismatch = true;
char *path;
char *section_name;
char *match;
char *target;
char *flow;
char *parity;
char *log_filename;
char *socket;
char *map;
char *script;
char *script_filename;
bool script_run;
char *exclude_devices;
char *exclude_drivers;
char *exclude_tids;
};
static struct config_t c;
static int get_match(const char *input, const char *pattern, char **match)
{
int ret;
int len = 0;
regex_t re;
regmatch_t m[2];
char err[128];
/* compile a regex with the pattern */
ret = regcomp(&re, pattern, REG_EXTENDED);
if (ret)
gchar *string = g_key_file_get_string(key_file, group, key, &error);
if (error != NULL)
{
regerror(ret, &re, err, sizeof(err));
tio_error_printf("Regex failure: %s", err);
return ret;
}
/* try to match on input */
ret = regexec(&re, input, 2, m, 0);
if (!ret)
{
len = m[1].rm_eo - m[1].rm_so;
}
regfree(&re);
if (len)
{
asprintf(match, "%s", &input[m[1].rm_so]);
}
return len;
}
static bool read_boolean(const char *value, const char *name)
{
const char *true_values[] = { "true", "enable", "on", "yes", "1", NULL };
const char *false_values[] = { "false", "disable", "off", "no", "0", NULL };
for (int i = 0; true_values[i] != NULL; i++)
if (strcmp(value, true_values[i]) == 0)
return true;
for (int i = 0; false_values[i] != NULL; i++)
if (strcmp(value, false_values[i]) == 0)
return false;
tio_error_printf("Invalid value '%s' for option '%s' in configuration file",
value, name);
exit(EXIT_FAILURE);
}
static long read_integer(const char *value, const char *name, long min_value, long max_value)
{
errno = 0;
char *endptr;
long result = strtol(value, &endptr, 10);
if (errno || endptr == value || *endptr != '\0' || result < min_value || result > max_value)
{
tio_error_printf("Invalid value '%s' for option '%s' in configuration file",
value, name);
if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)
{
// Key not found - ignore key
g_error_free(error);
return;
}
tio_error_print("%s: %s", config.path, error->message);
g_error_free(error);
exit(EXIT_FAILURE);
}
return result;
va_list args;
const char* current_arg = allowed_string;
va_start(args, allowed_string);
if (current_arg == NULL)
{
mismatch = false;
}
// Iterate through variable arguments
while (current_arg != NULL)
{
if (strcmp(string, current_arg) == 0)
{
mismatch = false;
break;
}
current_arg = va_arg(args, const char *);
}
if (mismatch)
{
tio_error_print("%s: Invalid %s value '%s' in %s profile", config.path, key, string, group);
exit(EXIT_FAILURE);
}
va_end(args);
*dest = string;
}
/**
* data_handler() - walk config file to load parameters matching user input
*
* INIH handler used to get all parameters from a given section
*/
static int data_handler(void *user, const char *section, const char *name,
const char *value)
static void config_get_integer(GKeyFile *key_file, gchar *group, gchar *key, int *dest, int min, int max)
{
UNUSED(user);
(void)dest;
GError *error = NULL;
// If section matches current section being parsed
if (!strcmp(section, c.section_name))
int value = g_key_file_get_integer(key_file, group, key, &error);
if (error != NULL)
{
// Set configuration parameter if found
if (!strcmp(name, "device") || !strcmp(name, "tty"))
if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)
{
asprintf(&c.target, value, c.match);
option.target = c.target;
// Key not found - ignore key
g_error_free(error);
return;
}
else if (!strcmp(name, "baudrate"))
{
option.baudrate = read_integer(value, name, 0, LONG_MAX);
}
else if (!strcmp(name, "databits"))
{
option.databits = read_integer(value, name, 5, 8);
}
else if (!strcmp(name, "flow"))
{
asprintf(&c.flow, "%s", value);
option.flow = c.flow;
}
else if (!strcmp(name, "stopbits"))
{
option.stopbits = read_integer(value, name, 1, 2);
}
else if (!strcmp(name, "parity"))
{
asprintf(&c.parity, "%s", value);
option.parity = c.parity;
}
else if (!strcmp(name, "output-delay"))
{
option.output_delay = read_integer(value, name, 0, LONG_MAX);
}
else if (!strcmp(name, "output-line-delay"))
{
option.output_line_delay = read_integer(value, name, 0, LONG_MAX);
}
else if (!strcmp(name, "line-pulse-duration"))
{
line_pulse_duration_option_parse(value);
}
else if (!strcmp(name, "no-reconnect"))
{
option.no_reconnect = read_boolean(value, name);
}
else if (!strcmp(name, "auto-connect"))
{
option.auto_connect = auto_connect_option_parse(value);
}
else if (!strcmp(name, "log"))
{
option.log = read_boolean(value, name);
}
else if (!strcmp(name, "log-file"))
{
asprintf(&c.log_filename, "%s", value);
option.log_filename = c.log_filename;
}
else if (!strcmp(name, "log-append"))
{
option.log_append = read_boolean(value, name);
}
else if (!strcmp(name, "log-strip"))
{
option.log_strip = read_boolean(value, name);
}
else if (!strcmp(name, "local-echo"))
{
option.local_echo = read_boolean(value, name);
}
else if (!strcmp(name, "input-mode"))
{
option.input_mode = input_mode_option_parse(value);
}
else if (!strcmp(name, "output-mode"))
{
option.output_mode = output_mode_option_parse(value);
}
else if (!strcmp(name, "timestamp"))
{
option.timestamp = read_boolean(value, name) ?
TIMESTAMP_24HOUR : TIMESTAMP_NONE;
}
else if (!strcmp(name, "timestamp-format"))
{
option.timestamp = timestamp_option_parse(value);
}
else if (!strcmp(name, "timestamp-timeout"))
{
option.timestamp_timeout = read_integer(value, name, 0, LONG_MAX);
}
else if (!strcmp(name, "map"))
{
asprintf(&c.map, "%s", value);
option.map = c.map;
}
else if (!strcmp(name, "color"))
{
if (!strcmp(value, "list"))
{
// Ignore
return 0;
}
else if (!strcmp(value, "none"))
{
option.color = -1; // No color
return 0;
}
else if (!strcmp(value, "bold"))
{
option.color = 256; // Bold
return 0;
}
tio_error_print("%s: %s", config.path, error->message);
g_error_free(error);
exit(EXIT_FAILURE);
}
option.color = atoi(value);
if ((option.color < 0) || (option.color > 255))
{
option.color = -1; // No color
}
}
else if (!strcmp(name, "socket"))
if ((value < min) || (value > max))
{
tio_error_print("%s: Invalid %s value '%d' in %s profile", config.path, key, value, group);
exit(EXIT_FAILURE);
}
*dest = value;
}
static void config_get_bool(GKeyFile *key_file, gchar *group, gchar *key, bool *dest)
{
(void)dest;
GError *error = NULL;
bool value = g_key_file_get_boolean(key_file, group, key, &error);
if (error != NULL)
{
if (error->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND)
{
asprintf(&c.socket, "%s", value);
option.socket = c.socket;
// Key not found - ignore key
g_error_free(error);
return;
}
else if (!strcmp(name, "prefix-ctrl-key"))
tio_error_print("%s: %s", config.path, error->message);
g_error_free(error);
exit(EXIT_FAILURE);
}
*dest = value;
}
static void config_parse_keys(GKeyFile *key_file, char *group)
{
char *string = NULL;
bool boolean = false;
config_get_string(key_file, group, "device", &config.device, NULL);
config_get_integer(key_file, group, "baudrate", &option.baudrate, 0, INT_MAX);
config_get_integer(key_file, group, "databits", &option.databits, 5, 8);
config_get_string(key_file, group, "flow", &string, "none", "hard", "soft", NULL);
if (string != NULL)
{
option_parse_flow(string, &option.flow);
g_free((void *)string);
string = NULL;
}
config_get_integer(key_file, group, "stopbits", &option.stopbits, 1, 2);
config_get_string(key_file, group, "parity", &string, "odd", "even", "none", "mark", "space", NULL);
if (string != NULL)
{
option_parse_parity(string, &option.parity);
g_free((void *)string);
string = NULL;
}
config_get_integer(key_file, group, "output-delay", &option.output_delay, 0, INT_MAX);
config_get_integer(key_file, group, "output-line-delay", &option.output_line_delay, 0, INT_MAX);
config_get_string(key_file, group, "line-pulse-duration", &string, NULL);
if (string != NULL)
{
option_parse_line_pulse_duration(string);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "auto-connect", &string, "new", "latest", "direct", NULL);
if (string != NULL)
{
option_parse_auto_connect(string, &option.auto_connect);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "exclude-devices", &option.exclude_devices, NULL);
config_get_string(key_file, group, "exclude-drivers", &option.exclude_devices, NULL);
config_get_string(key_file, group, "exclude-tids", &option.exclude_devices, NULL);
config_get_bool(key_file, group, "no-reconnect", &option.no_reconnect);
config_get_bool(key_file, group, "local-echo", &option.no_reconnect);
config_get_string(key_file, group, "input-mode", &string, "normal", "hex", "line", NULL);
if (string != NULL)
{
option_parse_input_mode(string, &option.input_mode);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "output-mode", &string, NULL);
if (string != NULL)
{
option_parse_output_mode(string, &option.output_mode);
g_free((void *)string);
string = NULL;
}
config_get_bool(key_file, group, "timestamp", &boolean);
if (boolean == true)
{
option.timestamp = TIMESTAMP_24HOUR;
}
config_get_string(key_file, group, "timestamp-format", &string, "24hour", "24hour-start", "24hour-delta", "iso8601", NULL);
if (string != NULL)
{
option_parse_timestamp(string, &option.timestamp);
g_free((void *)string);
string = NULL;
}
config_get_integer(key_file, group, "timestamp-timeout", &option.timestamp_timeout, 0, INT_MAX);
config_get_bool(key_file, group, "log", &option.log);
config_get_string(key_file, group, "log-file", &option.log_filename, NULL);
config_get_bool(key_file, group, "log-append", &option.log_append);
config_get_bool(key_file, group, "log-strip", &option.log_strip);
config_get_string(key_file, group, "map", &option.map, NULL);
config_get_string(key_file, group, "color", &string, NULL);
if (string != NULL)
{
if (strcmp(string, "list") == 0)
{
if (!strcmp(value, "none"))
{
option.prefix_enabled = false;
}
else if (ctrl_key_code(value[0]) > 0)
{
option.prefix_code = ctrl_key_code(value[0]);
option.prefix_key = value[0];
}
// Ignore
}
else if (!strcmp(name, "rs-485"))
else if (strcmp(string, "none") == 0)
{
option.rs485 = read_boolean(value, name);
option.color = -1; // No color
}
else if (!strcmp(name, "rs-485-config"))
else if (strcmp(string, "bold") == 0)
{
rs485_parse_config(value);
}
else if (!strcmp(name, "alert"))
{
option.alert = alert_option_parse(value);
}
else if (!strcmp(name, "mute"))
{
option.mute = read_boolean(value, name);
}
else if (!strcmp(name, "pattern"))
{
// Do nothing
}
else if (!strcmp(name, "script"))
{
asprintf(&c.script, "%s", value);
option.script = c.script;
}
else if (!strcmp(name, "script-file"))
{
asprintf(&c.script_filename, "%s", value);
option.script_filename = c.script_filename;
}
else if (!strcmp(name, "script-run"))
{
option.script_run = script_run_option_parse(value);
}
else if (!strcmp(name, "exclude-devices"))
{
c.exclude_devices = strdup(value);
option.exclude_devices = c.exclude_devices;
}
else if (!strcmp(name, "exclude-drivers"))
{
c.exclude_drivers = strdup(value);
option.exclude_drivers = c.exclude_drivers;
}
else if (!strcmp(name, "exclude-tids"))
{
c.exclude_tids = strdup(value);
option.exclude_tids = c.exclude_tids;
option.color = 256; // Bold
}
else
{
tio_warning_printf("Unknown option '%s' in configuration file, ignored", name);
option.color = atoi(string);
if ((option.color < 0) || (option.color > 255))
{
tio_error_print("%s: Invalid color value in %s profile", config.path, group);
exit(EXIT_FAILURE);
}
}
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "socket", &option.socket, NULL);
config_get_bool(key_file, group, "rs-485", &option.rs485);
config_get_string(key_file, group, "rs-385-config", &string, NULL);
if (string != NULL)
{
rs485_parse_config(string);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "alert", &string, "bell", "blink", "none", NULL);
if (string != NULL)
{
option_parse_alert(string, &option.alert);
g_free((void *)string);
string = NULL;
}
config_get_bool(key_file, group, "mute", &option.mute);
config_get_string(key_file, group, "script", &option.script, NULL);
config_get_string(key_file, group, "script-file", &option.script_filename, NULL);
config_get_string(key_file, group, "script-run", &string, NULL);
if (string != NULL)
{
option_parse_script_run(string, &option.script_run);
g_free((void *)string);
string = NULL;
}
config_get_string(key_file, group, "prefix-ctrl-key", &string, NULL);
if (string != NULL)
{
if (strcmp(string, "none") == 0)
{
option.prefix_enabled = false;
}
else if (strlen(string) >= 2)
{
tio_error_print("%s: Invalid prefix-ctrl-key value in %s profile", config.path, group);
exit(EXIT_FAILURE);
}
else if (ctrl_key_code(string[0]) > 0)
{
option.prefix_enabled = true;
option.prefix_code = ctrl_key_code(string[0]);
option.prefix_key = string[0];
}
else
{
tio_error_print("%s: Invalid prefix-ctrl-key value in %s profile", config.path, group);
exit(EXIT_FAILURE);
}
g_free((void *)string);
string = NULL;
}
return 0;
}
/**
* section_pattern_search_handler() - walk config file to find section matching user input
*
* INIH handler used to resolve the section matching the user's input.
* This will look for the pattern element of each section and try to match it
* with the user input.
*/
static int section_pattern_search_handler(void *user, const char *section, const char *varname,
const char *varval)
{
UNUSED(user);
if (strcmp(varname, "pattern"))
return 0;
if (!strcmp(varval, c.user))
{
/* pattern matches as plain text */
asprintf(&c.section_name, "%s", section);
}
else if (get_match(c.user, varval, &c.match) > 0)
{
/* pattern matches as regex */
asprintf(&c.section_name, "%s", section);
}
return 0;
}
/**
* section_pattern_search_handler() - walk config file to find section matching user input
*
* INIH handler used to resolve the section matching the user's input.
* This will try to match the user input against a section with the name of the user input.
*/
static int section_name_search_handler(void *user, const char *section, const char *varname,
const char *varval)
{
UNUSED(user);
UNUSED(varname);
UNUSED(varval);
if (!strcmp(section, c.user))
{
/* section name matches as plain text */
asprintf(&c.section_name, "%s", section);
}
return 0;
}
static int section_name_print_handler(void *user, const char *section, const char *varname,
const char *varval)
{
UNUSED(user);
UNUSED(varname);
UNUSED(varval);
static char *section_previous = "";
if (strcmp(section, section_previous) != 0)
{
printf("%s ", section);
section_previous = strdup(section);
}
return 0;
}
static int resolve_config_file(void)
static int config_file_resolve(void)
{
char *xdg = getenv("XDG_CONFIG_HOME");
if (xdg)
{
if (asprintf(&c.path, "%s/tio/config", xdg) != -1)
if (asprintf(&config.path, "%s/tio/config", xdg) != -1)
{
if (access(c.path, F_OK) == 0)
if (access(config.path, F_OK) == 0)
{
return 0;
}
free(c.path);
free(config.path);
}
}
char *home = getenv("HOME");
if (home)
{
if (asprintf(&c.path, "%s/.config/tio/config", home) != -1)
if (asprintf(&config.path, "%s/.config/tio/config", home) != -1)
{
if (access(c.path, F_OK) == 0)
if (access(config.path, F_OK) == 0)
{
return 0;
}
free(c.path);
free(config.path);
}
if (asprintf(&c.path, "%s/.tioconfig", home) != -1)
if (asprintf(&config.path, "%s/.tioconfig", home) != -1)
{
if (access(c.path, F_OK) == 0)
if (access(config.path, F_OK) == 0)
{
return 0;
}
free(c.path);
free(config.path);
}
}
c.path = NULL;
config.path = NULL;
return -EINVAL;
}
void config_file_show_profiles(void)
{
memset(&c, 0, sizeof(struct config_t));
memset(&config, 0, sizeof(struct config_t));
// Find config file
if (resolve_config_file() != 0)
if (config_file_resolve() != 0)
{
// None found - stop parsing
return;
}
ini_parse(c.path, section_name_print_handler, NULL);
GKeyFile *keyfile;
GError *error = NULL;
keyfile = g_key_file_new();
if (!g_key_file_load_from_file(keyfile, config.path, G_KEY_FILE_NONE, &error))
{
tio_error_print("Failure loading file: %s", error->message);
g_error_free(error);
return;
}
// Get all group names
gsize num_groups;
gchar **group = g_key_file_get_groups(keyfile, &num_groups);
for (gsize i = 0; i < num_groups; i++)
{
// Skip default group
if (strcmp(group[i], CONFIG_GROUP_NAME_DEFAULT) == 0)
{
continue;
}
}
g_strfreev(group);
g_key_file_free(keyfile);
}
static void replace_substring(char *str, const char *substr, const char *replacement)
{
char *pos = strstr(str, substr);
if (pos != NULL)
{
int substrLen = strlen(substr);
int replacementLen = strlen(replacement);
memmove(pos + replacementLen, pos + substrLen, strlen(pos + substrLen) + 1);
memcpy(pos, replacement, replacementLen);
}
}
static char *match_and_replace(const char *str, const char *pattern, char *device)
{
char replacement_str[PATH_MAX] = {};
char m_key[13] = {};
regex_t regex;
assert(str != NULL);
assert(pattern != NULL);
assert(device != NULL);
char *string = malloc(strlen(device) + PATH_MAX);
if (string == NULL)
{
return NULL;
}
strcpy(string, device);
/* Find matches of pattern in str. For each match, replace any '%mN' in the
* copy of the device string with the corresponding match subexpression and
* return the new formed device string.
*
* Note: %m0 = Full match expression.
* %m1 = First subexpression
* %m2 = Second subexpression
* %m3 = etc..
*/
if (regcomp(&regex, pattern, REG_EXTENDED) != 0)
{
// Failure to compile regular expression
return NULL;
}
regmatch_t matches[regex.re_nsub + 1];
int status = regexec(&regex, str, regex.re_nsub + 1, matches, REG_EXTENDED);
if (status == 0)
{
tio_debug_printf("Full match: ");
int j = 0;
for (int i = matches[0].rm_so; i < matches[0].rm_eo; i++)
{
tio_debug_printf_raw("%c", str[i]);
replacement_str[j++] = str[i];
}
replacement_str[j] = '\0';
replace_substring(string, "%m0", replacement_str);
tio_debug_printf_raw("\n");
for (int i = 1; i < ((int)regex.re_nsub + 1) && matches[i].rm_so != -1; i++)
{
tio_debug_printf("Subexpression %d match: ", i);
int k = 0;
for (int j = matches[i].rm_so; j < matches[i].rm_eo; j++)
{
tio_debug_printf_raw("%c", str[j]);
replacement_str[k++] = str[j];
}
replacement_str[k] = '\0';
sprintf(m_key, "%%m%d", i);
replace_substring(string, m_key, replacement_str);
tio_debug_printf_raw("\n");
}
}
else if (status == REG_NOMATCH)
{
goto error;
}
else
{
char error_message[100];
regerror(status, &regex, error_message, sizeof(error_message));
tio_error_print("Regex match failed: %s", error_message);
goto error;
}
regfree(&regex);
return string;
error:
regfree(&regex);
return NULL;
}
void config_file_parse(void)
{
int ret;
memset(&c, 0, sizeof(struct config_t));
// Find config file
if (resolve_config_file() != 0)
if (config_file_resolve() != 0)
{
// None found - stop parsing
return;
}
// Set user input which may be tty device or profile or tid
c.user = option.target;
if (!c.user)
if (option.target == NULL)
{
return;
}
// Parse default (unnamed) settings
asprintf(&c.section_name, "%s", "");
ret = ini_parse(c.path, data_handler, NULL);
if (ret < 0)
GKeyFile *keyfile = g_key_file_new();
GError *error = NULL;
if (g_key_file_load_from_file(keyfile, config.path, G_KEY_FILE_NONE, &error) == false)
{
tio_error_printf("Unable to parse configuration file (%d)", ret);
tio_error_print("Failure loading file %s: %s", config.path, error->message);
g_error_free(error);
exit(EXIT_FAILURE);
}
free(c.section_name);
c.section_name = NULL;
// Find matching section
ret = ini_parse(c.path, section_pattern_search_handler, NULL);
if (!c.section_name)
// Parse default group/section
if (g_key_file_has_group(keyfile, CONFIG_GROUP_NAME_DEFAULT))
{
ret = ini_parse(c.path, section_name_search_handler, NULL);
if (!c.section_name)
config_parse_keys(keyfile, CONFIG_GROUP_NAME_DEFAULT);
}
// Parse target
if (g_key_file_has_group(keyfile, option.target))
{
config.active_group = strdup(option.target);
config_parse_keys(keyfile, option.target);
}
else
{
// Find group by pattern
gsize num_groups;
gchar **group = g_key_file_get_groups(keyfile, &num_groups);
for (gsize i = 0; i < num_groups; i++)
{
tio_debug_printf("Unable to match user input to configuration section (%d)", ret);
return;
// Skip default group
if (strcmp(group[i], CONFIG_GROUP_NAME_DEFAULT) == 0)
{
continue;
}
// Lookup 'pattern' key
GError *error = NULL;
gchar *pattern = g_key_file_get_string(keyfile, group[i], "pattern", &error);
if (error != NULL)
{
g_error_free(error);
continue;
}
// Lookup 'device' key
gchar *device = g_key_file_get_string(keyfile, group[i], "device", &error);
if (error != NULL)
{
g_error_free(error);
continue;
}
// Match pattern against target and replace any sub expression
// matches (%mN) in device string and return resulting string
// representing the new pattern based string.
config.device = match_and_replace(option.target, pattern, device);
if (config.device != NULL)
{
// Match found - save device
char *device = strdup(config.device);
// Parse found group
config_parse_keys(keyfile, group[i]);
// Update configuration
config.active_group = strdup(group[i]);
config.device = device;
break;
}
}
g_strfreev(group);
}
// Parse settings of found section (profile)
ret = ini_parse(c.path, data_handler, NULL);
if (ret < 0)
{
tio_error_printf("Unable to parse configuration file (%d)", ret);
exit(EXIT_FAILURE);
}
g_key_file_free(keyfile);
atexit(&config_exit);
}
void config_exit(void)
{
free(c.target);
free(c.flow);
free(c.parity);
free(c.log_filename);
free(c.map);
free(c.match);
free(c.section_name);
free(c.path);
free(config.active_group);
free(config.path);
free(config.device);
}
void config_file_print(void)
{
if (c.path != NULL)
if (config.path != NULL)
{
tio_printf(" Active configuration file: %s", c.path);
if (c.section_name != NULL)
tio_printf(" Active configuration file: %s", config.path);
if (config.active_group != NULL)
{
tio_printf(" Active configuration profile: %s", c.section_name);
tio_printf(" Active configuration profile: %s", config.active_group);
}
}
}