Add configuration file include directive

To include the contents of another configuration file simply do e.g.:

[include raspberrypi.conf]

Also, included file can include other files which can include other
files etc.

This feature is useful for managing many configuration files and sharing
configuration files with others.
This commit is contained in:
Martin Lund 2024-07-18 21:01:26 +02:00
parent 14963032c3
commit 725423c50c
4 changed files with 144 additions and 19 deletions

View file

@ -74,6 +74,7 @@ when used in combination with [tmux](https://tmux.github.io).
* Configuration file support
* Support for configuration profiles
* Activate configuration profiles by name or pattern
* Support for including other configuration files
* Redirect I/O of shell command to serial device
* Redirect I/O to UNIX socket or IPv4/v6 network socket
* Useful for scripting or TTY sharing

10
TODO
View file

@ -1,13 +1,3 @@
* Add support for nested configuration files
Add "include" directive to make it possible to include a configuration file
from another. For example:
[include work/config]
The feature should support including files that include other files etc.
(multi level nesting).
* Porting layer to support native win32 builds.
Some of the work that needs to be done:

View file

@ -581,6 +581,9 @@ Run script on connect
.IP "\fBexec"
Execute shell command with I/O redirected to device
.PP
It is possible to include the content of other configuration files using the
include directive like so: "[include <file>]".
.SH "CONFIGURATION FILE EXAMPLES"

View file

@ -26,6 +26,7 @@
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <libgen.h>
#include <errno.h>
#include <regex.h>
#include <glib.h>
@ -35,9 +36,14 @@
#include "misc.h"
#define CONFIG_GROUP_NAME_DEFAULT "default"
#define CONFIG_GROUP_INCLUDE_PREFIX "include "
#define MAX_LINE_LENGTH 1024
struct config_t config = {};
static void config_file_load(const char *filename, GString *buffer, bool test);
static void config_file_process(const char *filename, GString *buffer, GList **included_files, bool test);
static void config_get_string(GKeyFile *key_file, gchar *group, gchar *key, char **dest, char *allowed_string, ...)
{
(void)dest;
@ -347,9 +353,11 @@ static int config_file_resolve(void)
void config_file_show_profiles(void)
{
GKeyFile *keyfile;
GString *config_buffer;
GError *error = NULL;
GKeyFile *keyfile;
// Reset configuration
memset(&config, 0, sizeof(struct config_t));
// Find config file
@ -359,13 +367,16 @@ void config_file_show_profiles(void)
return;
}
keyfile = g_key_file_new();
// Load content of configuration file into buffer
config_buffer = g_string_new(NULL);
config_file_load(config.path, config_buffer, false);
if (!g_key_file_load_from_file(keyfile, config.path, G_KEY_FILE_NONE, &error))
// Load configuration
keyfile = g_key_file_new();
if (g_key_file_load_from_data(keyfile, config_buffer->str, config_buffer->len, G_KEY_FILE_NONE, &error) == false)
{
tio_error_print("Failure loading file: %s", error->message);
g_error_free(error);
return;
goto error_load;
}
// Get all group names
@ -379,11 +390,20 @@ void config_file_show_profiles(void)
{
continue;
}
// Skip group with include directive
if (strncmp(group[i], CONFIG_GROUP_INCLUDE_PREFIX, strlen(CONFIG_GROUP_INCLUDE_PREFIX)) == 0)
{
continue;
}
printf("%s ", group[i]);
}
g_strfreev(group);
error_load:
g_key_file_free(keyfile);
g_string_free(config_buffer, TRUE);
}
static void replace_substring(char *str, const char *substr, const char *replacement)
@ -483,6 +503,95 @@ error:
return NULL;
}
static void config_file_process_line(const char *line, GString *buffer, GList **included_files, bool test)
{
if (strncmp(line, "[include ", 9) == 0 && line[strlen(line) - 2] == ']')
{
char include_filename[MAX_LINE_LENGTH];
// Construct the format string safely
char format_string[50];
snprintf(format_string, sizeof(format_string), "[include %%%ds]", MAX_LINE_LENGTH - 1);
int ret = sscanf(line, format_string, include_filename);
if (ret != 1)
{
return;
}
// Remove the trailing ']' character
include_filename[strlen(include_filename) - 1] = '\0';
if (g_list_find_custom(*included_files, include_filename, (GCompareFunc)strcmp) != NULL)
{
// Already included, avoid recursion
return;
}
// Add to included files list
*included_files = g_list_append(*included_files, g_strdup(include_filename));
// Process the included file
config_file_process(include_filename, buffer, included_files, test);
}
else
{
// Normal line, add to buffer
g_string_append(buffer, line);
}
}
static void config_file_process(const char *filename, GString *buffer, GList **included_files, bool test)
{
if (test)
{
// Test that configuration file can be parsed
GError *error = NULL;
GKeyFile *keyfile = g_key_file_new();
if (g_key_file_load_from_file(keyfile, filename, G_KEY_FILE_NONE, &error) == false)
{
tio_error_print("Failure loading file %s: %s", filename, error->message);
g_key_file_free(keyfile);
g_error_free(error);
exit(EXIT_FAILURE);
}
}
FILE *file = fopen(filename, "r");
if (file)
{
char line[MAX_LINE_LENGTH];
while (fgets(line, sizeof(line), file))
{
config_file_process_line(line, buffer, included_files, test);
}
fclose(file);
}
}
static void config_file_load(const char *filename, GString *buffer, bool test)
{
char current_dir[PATH_MAX] = ".";
char *config_file_dir = dirname(strdup(config.path));
GList *included_files = NULL;
getcwd(current_dir, PATH_MAX);
// Change to the directory of the configuration file
chdir(config_file_dir);
config_file_process(filename, buffer, &included_files, test);
// Restore current directory
chdir(current_dir);
// Free memory
g_list_free_full(included_files, g_free);
free(config_file_dir);
}
void config_file_parse(void)
{
// Find config file
@ -497,12 +606,18 @@ void config_file_parse(void)
return;
}
GString *config_buffer = g_string_new(NULL);
GKeyFile *keyfile = g_key_file_new();
GList *included_files = NULL;
GError *error = NULL;
if (g_key_file_load_from_file(keyfile, config.path, G_KEY_FILE_NONE, &error) == false)
config_file_load(config.path, config_buffer, true);
if (g_key_file_load_from_data(keyfile, config_buffer->str, config_buffer->len, G_KEY_FILE_NONE, &error) == false)
{
tio_error_print("Failure loading file %s: %s", config.path, error->message);
g_string_free(config_buffer, TRUE);
g_key_file_free(keyfile);
g_error_free(error);
exit(EXIT_FAILURE);
}
@ -574,7 +689,10 @@ void config_file_parse(void)
g_strfreev(group);
}
// Cleanup
g_key_file_free(keyfile);
g_string_free(config_buffer, TRUE);
g_list_free_full(included_files, g_free);
atexit(&config_exit);
}
@ -614,10 +732,14 @@ void config_list_targets(void)
keyfile = g_key_file_new();
if (!g_key_file_load_from_file(keyfile, config.path, G_KEY_FILE_NONE, &error))
GString *config_buffer = g_string_new(NULL);
config_file_load(config.path, config_buffer, false);
if (g_key_file_load_from_data(keyfile, config_buffer->str, config_buffer->len, G_KEY_FILE_NONE, &error) == false)
{
g_error_free(error);
return;
goto cleanup;
}
// Get all group names
@ -626,7 +748,7 @@ void config_list_targets(void)
if (num_groups == 0)
{
return;
goto cleanup;
}
printf("\nConfiguration profiles (%s)\n", config.path);
@ -640,6 +762,13 @@ void config_list_targets(void)
{
continue;
}
// Skip group with include directive
if (strncmp(group[i], CONFIG_GROUP_INCLUDE_PREFIX, strlen(CONFIG_GROUP_INCLUDE_PREFIX)) == 0)
{
continue;
}
printf("%-19s ", group[i]);
if (j++ % 4 == 0)
{
@ -652,5 +781,7 @@ void config_list_targets(void)
}
g_strfreev(group);
cleanup:
g_key_file_free(keyfile);
g_string_free(config_buffer, TRUE);
}