diff --git a/README.md b/README.md index e434a64..5521df0 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/TODO b/TODO index 7557ff3..962ad71 100644 --- a/TODO +++ b/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. Some of the work that needs to be done: diff --git a/man/tio.1.in b/man/tio.1.in index 67c1600..114a97d 100644 --- a/man/tio.1.in +++ b/man/tio.1.in @@ -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 ]". .SH "CONFIGURATION FILE EXAMPLES" diff --git a/src/configfile.c b/src/configfile.c index 460e476..53545a4 100644 --- a/src/configfile.c +++ b/src/configfile.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -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); }