mirror of
https://github.com/tio/tio.git
synced 2026-05-01 14:57:59 +02:00
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:
parent
14963032c3
commit
725423c50c
4 changed files with 144 additions and 19 deletions
|
|
@ -74,6 +74,7 @@ when used in combination with [tmux](https://tmux.github.io).
|
||||||
* Configuration file support
|
* Configuration file support
|
||||||
* Support for configuration profiles
|
* Support for configuration profiles
|
||||||
* Activate configuration profiles by name or pattern
|
* 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 of shell command to serial device
|
||||||
* Redirect I/O to UNIX socket or IPv4/v6 network socket
|
* Redirect I/O to UNIX socket or IPv4/v6 network socket
|
||||||
* Useful for scripting or TTY sharing
|
* Useful for scripting or TTY sharing
|
||||||
|
|
|
||||||
10
TODO
10
TODO
|
|
@ -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.
|
* Porting layer to support native win32 builds.
|
||||||
|
|
||||||
Some of the work that needs to be done:
|
Some of the work that needs to be done:
|
||||||
|
|
|
||||||
|
|
@ -581,6 +581,9 @@ Run script on connect
|
||||||
.IP "\fBexec"
|
.IP "\fBexec"
|
||||||
Execute shell command with I/O redirected to device
|
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"
|
.SH "CONFIGURATION FILE EXAMPLES"
|
||||||
|
|
||||||
|
|
|
||||||
149
src/configfile.c
149
src/configfile.c
|
|
@ -26,6 +26,7 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
#include <libgen.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <regex.h>
|
#include <regex.h>
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
|
@ -35,9 +36,14 @@
|
||||||
#include "misc.h"
|
#include "misc.h"
|
||||||
|
|
||||||
#define CONFIG_GROUP_NAME_DEFAULT "default"
|
#define CONFIG_GROUP_NAME_DEFAULT "default"
|
||||||
|
#define CONFIG_GROUP_INCLUDE_PREFIX "include "
|
||||||
|
#define MAX_LINE_LENGTH 1024
|
||||||
|
|
||||||
struct config_t config = {};
|
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, ...)
|
static void config_get_string(GKeyFile *key_file, gchar *group, gchar *key, char **dest, char *allowed_string, ...)
|
||||||
{
|
{
|
||||||
(void)dest;
|
(void)dest;
|
||||||
|
|
@ -347,9 +353,11 @@ static int config_file_resolve(void)
|
||||||
|
|
||||||
void config_file_show_profiles(void)
|
void config_file_show_profiles(void)
|
||||||
{
|
{
|
||||||
GKeyFile *keyfile;
|
GString *config_buffer;
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
|
GKeyFile *keyfile;
|
||||||
|
|
||||||
|
// Reset configuration
|
||||||
memset(&config, 0, sizeof(struct config_t));
|
memset(&config, 0, sizeof(struct config_t));
|
||||||
|
|
||||||
// Find config file
|
// Find config file
|
||||||
|
|
@ -359,13 +367,16 @@ void config_file_show_profiles(void)
|
||||||
return;
|
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);
|
g_error_free(error);
|
||||||
return;
|
goto error_load;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all group names
|
// Get all group names
|
||||||
|
|
@ -379,11 +390,20 @@ void config_file_show_profiles(void)
|
||||||
{
|
{
|
||||||
continue;
|
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]);
|
printf("%s ", group[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_strfreev(group);
|
g_strfreev(group);
|
||||||
|
error_load:
|
||||||
g_key_file_free(keyfile);
|
g_key_file_free(keyfile);
|
||||||
|
g_string_free(config_buffer, TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void replace_substring(char *str, const char *substr, const char *replacement)
|
static void replace_substring(char *str, const char *substr, const char *replacement)
|
||||||
|
|
@ -483,6 +503,95 @@ error:
|
||||||
return NULL;
|
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)
|
void config_file_parse(void)
|
||||||
{
|
{
|
||||||
// Find config file
|
// Find config file
|
||||||
|
|
@ -497,12 +606,18 @@ void config_file_parse(void)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GString *config_buffer = g_string_new(NULL);
|
||||||
GKeyFile *keyfile = g_key_file_new();
|
GKeyFile *keyfile = g_key_file_new();
|
||||||
|
GList *included_files = NULL;
|
||||||
GError *error = 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);
|
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);
|
g_error_free(error);
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
|
@ -574,7 +689,10 @@ void config_file_parse(void)
|
||||||
g_strfreev(group);
|
g_strfreev(group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
g_key_file_free(keyfile);
|
g_key_file_free(keyfile);
|
||||||
|
g_string_free(config_buffer, TRUE);
|
||||||
|
g_list_free_full(included_files, g_free);
|
||||||
|
|
||||||
atexit(&config_exit);
|
atexit(&config_exit);
|
||||||
}
|
}
|
||||||
|
|
@ -614,10 +732,14 @@ void config_list_targets(void)
|
||||||
|
|
||||||
keyfile = g_key_file_new();
|
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);
|
g_error_free(error);
|
||||||
return;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all group names
|
// Get all group names
|
||||||
|
|
@ -626,7 +748,7 @@ void config_list_targets(void)
|
||||||
|
|
||||||
if (num_groups == 0)
|
if (num_groups == 0)
|
||||||
{
|
{
|
||||||
return;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("\nConfiguration profiles (%s)\n", config.path);
|
printf("\nConfiguration profiles (%s)\n", config.path);
|
||||||
|
|
@ -640,6 +762,13 @@ void config_list_targets(void)
|
||||||
{
|
{
|
||||||
continue;
|
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]);
|
printf("%-19s ", group[i]);
|
||||||
if (j++ % 4 == 0)
|
if (j++ % 4 == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -652,5 +781,7 @@ void config_list_targets(void)
|
||||||
}
|
}
|
||||||
|
|
||||||
g_strfreev(group);
|
g_strfreev(group);
|
||||||
|
cleanup:
|
||||||
g_key_file_free(keyfile);
|
g_key_file_free(keyfile);
|
||||||
|
g_string_free(config_buffer, TRUE);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue