From: Stefan Schubert Date: Wed, 17 Sep 2025 11:40:29 +0000 (+0200) Subject: libcommon: added lib "configs" for parsing configuration files in the correct order X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=4109f4bfefff9e6cd65815399af3eab2b0a59104;p=thirdparty%2Futil-linux.git libcommon: added lib "configs" for parsing configuration files in the correct order --- diff --git a/include/Makemodule.am b/include/Makemodule.am index 59ecc793f..bc2c73415 100644 --- a/include/Makemodule.am +++ b/include/Makemodule.am @@ -83,4 +83,5 @@ dist_noinst_HEADERS += \ include/ttyutils.h \ include/widechar.h \ include/xalloc.h \ - include/xxhash.h + include/xxhash.h \ + include/configs.h diff --git a/include/configs.h b/include/configs.h new file mode 100644 index 000000000..783c10e30 --- /dev/null +++ b/include/configs.h @@ -0,0 +1,92 @@ +/* + * No copyright is claimed. This code is in the public domain; do with + * it what you wish. + * + * Evaluting a list of configuration filenames which have to be handled/parsed. + * The order of this file list has been defined by + * https://github.com/uapi-group/specifications/blob/main/specs/configuration_files_specification.md + */ + +#ifndef UTIL_LINUX_CONFIGS_H +#define UTIL_LINUX_CONFIGS_H + +#include "list.h" + +/** + * ul_configs_file_list - Evaluting a list of sorted configuration filenames which have to be handled + * in the correct order. + * + * @file_list: List of filenames which have to be parsed in that order + * @project: name of the project used as subdirectory, can be NULL + * @etc_subdir: absolute directory path for user changed configuration files, can be NULL (default "/etc"). + * @usr_subdir: absolute directory path of vendor defined settings (often "/usr/lib"). + * @config_name: basename of the configuration file. If it is NULL, drop-ins without a main configuration file will be parsed only. + * @config_suffix: suffix of the configuration file. Can also be NULL. + * + * Returns the length of the file_list, or -ENOMEM, or -ENOTEMPTY if config_name is NULL + * + * Example: + * int count = 0; + * struct list_head *file_list; + * + * count = ul_configs_file_list(&file_list, + * "foo", + * "/etc", + * "/usr/lib", + * "example", + * "conf"); + * + * The order of this file list has been defined by + * https://github.com/uapi-group/specifications/blob/main/specs/configuration_files_specification.md + * + */ +int ul_configs_file_list(struct list_head *file_list, + const char *project, + const char *etc_subdir, + const char *usr_subdir, + const char *config_name, + const char *config_suffix); + + +/** + * ul_configs_free_list - Freeing configuration list. + * + * @file_list: List of filenames which has to be freed. + * + */ +void ul_configs_free_list(struct list_head *file_list); + + +/** + * ul_configs_next_filename - Going through the file list which has to be handled/parsed. + * + * @file_list: List of filenames which have to be handled. + * @current_entry: Current list entry. Has to be initialized with NULL for the first call. + * @name: Returned file name for each call. + * + * Returns 0 on success, <0 on error and 1 if the end of the list has been reached. + * + * Example: + * int count = 0; + * struct list_head *file_list = NULL; + * struct list_head *current = NULL; + * char *name = NULL; + * + * count = ul_configs_file_list(&file_list, + * "foo", + * "/etc", + * "/usr/lib", + * "example", + * "conf"); + * + * while (ul_configs_next_filename(&file_list, ¤t, &name) == 0) + * printf("filename: %s\n", name); + * + * ul_configs_free_list(&file_list); + * + */ +int ul_configs_next_filename(struct list_head *file_list, + struct list_head **current_entry, + char **name); + +#endif diff --git a/include/pathnames.h b/include/pathnames.h index 0f9944f89..298d94973 100644 --- a/include/pathnames.h +++ b/include/pathnames.h @@ -72,10 +72,12 @@ #endif #define _PATH_ISSUE_FILENAME "issue" -#define _PATH_ISSUE_DIRNAME _PATH_ISSUE_FILENAME ".d" - -#define _PATH_ISSUE "/etc/" _PATH_ISSUE_FILENAME -#define _PATH_ISSUEDIR "/etc/" _PATH_ISSUE_DIRNAME +#define _PATH_ETC_ISSUEDIR "/etc" +#ifdef USE_VENDORDIR +# define _PATH_USR_ISSUEDIR _PATH_VENDORDIR +#else +# define _PATH_USR_ISSUEDIR "/usr/lib" +#endif #define _PATH_OS_RELEASE_ETC "/etc/os-release" #define _PATH_OS_RELEASE_USR "/usr/lib/os-release" diff --git a/lib/Makemodule.am b/lib/Makemodule.am index a60810d7d..84ab3e3ae 100644 --- a/lib/Makemodule.am +++ b/lib/Makemodule.am @@ -40,7 +40,8 @@ libcommon_la_SOURCES = \ lib/strv.c \ lib/timeutils.c \ lib/ttyutils.c \ - lib/xxhash.c + lib/xxhash.c \ + lib/configs.c if LINUX libcommon_la_SOURCES += \ diff --git a/lib/configs.c b/lib/configs.c new file mode 100644 index 000000000..b038844d2 --- /dev/null +++ b/lib/configs.c @@ -0,0 +1,330 @@ +/* + * configs_file.c instantiates functions defined and described in configs_file.h + */ +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(HAVE_SCANDIRAT) && defined(HAVE_OPENAT) +#include +#endif +#include "configs.h" +#include "list.h" +#include "fileutils.h" + +#define DEFAULT_ETC_SUBDIR "/etc" + +struct file_element { + struct list_head file_list; + char *filename; +}; + +/* Checking for main configuration file + * + * Returning absolute path or NULL if not found + * The return value has to be freed by the caller. + */ +static char *main_configs(const char *root, + const char *project, + const char *config_name, + const char *config_suffix) +{ + bool found = false; + char *path = NULL; + struct stat st; + + if (config_suffix) { + if (asprintf(&path, "%s/%s/%s.%s", root, project, config_name, config_suffix) < 0) + return NULL; + if (stat(path, &st) == 0) { + found = true; + } else { + free(path); + path = NULL; + } + } + if (!found) { + /* trying filename without suffix */ + if (asprintf(&path, "%s/%s/%s", root, project, config_name) < 0) + return NULL; + if (stat(path, &st) != 0) { + /* not found */ + free(path); + path = NULL; + } + } + return path; +} + +static struct file_element *new_list_entry(const char *filename) +{ + struct file_element *file_element = calloc(1, sizeof(*file_element)); + + if (file_element == NULL) + return NULL; + + INIT_LIST_HEAD(&file_element->file_list); + + if (filename != NULL) { + file_element->filename = strdup(filename); + if (file_element->filename == NULL) { + free(file_element); + return NULL; + } + } else { + file_element->filename = NULL; + } + + return file_element; +} + +#if defined(HAVE_SCANDIRAT) && defined(HAVE_OPENAT) + +static int filter(const struct dirent *d) +{ +#ifdef _DIRENT_HAVE_D_TYPE + if (d->d_type != DT_UNKNOWN && d->d_type != DT_REG && + d->d_type != DT_LNK) + return 0; +#endif + if (*d->d_name == '.') + return 0; + + /* Accept this */ + return 1; +} + +static int read_dir(struct list_head *file_list, + const char *project, + const char *root, + const char *config_name, + const char *config_suffix) +{ + bool found = false; + char *dirname = NULL; + char *filename = NULL; + struct stat st; + int dd = 0, nfiles = 0, i; + int counter = 0; + struct dirent **namelist = NULL; + struct file_element *entry = NULL; + + if (config_suffix) { + if (asprintf(&dirname, "%s/%s/%s.%s.d", + root, project, config_name, config_suffix) < 0) + return -ENOMEM; + if (stat(dirname, &st) == 0) { + found = true; + } else { + free(dirname); + dirname = NULL; + } + } + if (!found) { + /* trying path without suffix */ + if (asprintf(&dirname, "%s/%s/%s.d", root, project, config_name) < 0) + return -ENOMEM; + if (stat(dirname, &st) != 0) { + /* not found */ + free(dirname); + dirname = NULL; + } + } + + if (dirname==NULL) + goto finish; + + dd = open(dirname, O_RDONLY|O_CLOEXEC|O_DIRECTORY); + if (dd < 0) + goto finish; + + nfiles = scandirat(dd, ".", &namelist, filter, alphasort); + if (nfiles <= 0) + goto finish; + + for (i = 0; i < nfiles; i++) { + struct dirent *d = namelist[i]; + size_t namesz = strlen(d->d_name); + if (config_suffix && strlen(config_suffix) > 0 && + (!namesz || namesz < strlen(config_suffix) + 1 || + strcmp(d->d_name + (namesz - strlen(config_suffix)), config_suffix) != 0)) { + /* filename does not have requested suffix */ + continue; + } + + if (asprintf(&filename, "%s/%s", dirname, d->d_name) < 0) { + counter = -ENOMEM; + break; + } + entry = new_list_entry(filename); + free(filename); + if (entry == NULL) { + counter = -ENOMEM; + break; + } + + list_add_tail(&entry->file_list, file_list); + counter++; + } + +finish: + for (i = 0; i < nfiles; i++) + free(namelist[i]); + free(namelist); + free(dirname); + if (dd > 0) + close(dd); + return counter; +} + +#endif + +static void free_list_entry(struct file_element *element) +{ + free(element->filename); + free(element); +} + + +int ul_configs_file_list(struct list_head *file_list, + const char *project, + const char *etc_subdir, + const char *usr_subdir, + const char *config_name, + const char *config_suffix) +{ + char *filename = NULL, *usr_basename = NULL, *etc_basename = NULL; + struct list_head etc_file_list; + struct list_head usr_file_list; + struct list_head *etc_entry = NULL, *usr_entry = NULL; + struct file_element *add_element = NULL, *usr_element = NULL, *etc_element = NULL; + int counter = 0; + + INIT_LIST_HEAD(file_list); + + if (!config_name){ + return -ENOTEMPTY; + } + + /* Default is /etc */ + if (!etc_subdir) + etc_subdir = DEFAULT_ETC_SUBDIR; + + if (!usr_subdir) + usr_subdir = ""; + + if (!project) + project = ""; + + /* Evaluating first "main" file which has to be parsed */ + /* in the following order : /etc /run /usr */ + filename = main_configs(etc_subdir, project, config_name, config_suffix); + if (filename == NULL) + filename = main_configs(_PATH_RUNSTATEDIR, project, config_name, config_suffix); + if (filename == NULL) + filename = main_configs(usr_subdir, project, config_name, config_suffix); + if (filename != NULL) { + add_element = new_list_entry(filename); + free(filename); + if (add_element == NULL) + return -ENOMEM; + list_add_tail(&add_element->file_list, file_list); + counter++; + } + + INIT_LIST_HEAD(&etc_file_list); + INIT_LIST_HEAD(&usr_file_list); + +#if defined(HAVE_SCANDIRAT) && defined(HAVE_OPENAT) + int ret_usr = 0, ret_etc = 0; + ret_etc = read_dir(&etc_file_list, + project, + etc_subdir, + config_name, + config_suffix); + ret_usr = read_dir(&usr_file_list, + project, + usr_subdir, + config_name, + config_suffix); + if (ret_etc == -ENOMEM || ret_usr == -ENOMEM) { + counter = -ENOMEM; + goto finish; + } +#endif + + list_for_each(etc_entry, &etc_file_list) { + etc_element = list_entry(etc_entry, struct file_element, file_list); + etc_basename = ul_basename(etc_element->filename); + list_for_each(usr_entry, &usr_file_list) { + usr_element = list_entry(usr_entry, struct file_element, file_list); + usr_basename = ul_basename(usr_element->filename); + if (strcmp(usr_basename, etc_basename) <= 0) { + if (strcmp(usr_basename, etc_basename) < 0) { + add_element = new_list_entry(usr_element->filename); + if (add_element == NULL) { + counter = -ENOMEM; + goto finish; + } + list_add_tail(&add_element->file_list, file_list); + counter++; + } + list_del(&usr_element->file_list); + } else { + break; + } + } + add_element = new_list_entry(etc_element->filename); + if (add_element == NULL) { + counter = -ENOMEM; + goto finish; + } + list_add_tail(&add_element->file_list, file_list); + counter++; + } + + /* taking the rest of /usr */ + list_for_each(usr_entry, &usr_file_list) { + usr_element = list_entry(usr_entry, struct file_element, file_list); + add_element = new_list_entry(usr_element->filename); + if (add_element == NULL) { + counter = -ENOMEM; + goto finish; + } + list_add_tail(&add_element->file_list, file_list); + counter++; + } + +finish: + ul_configs_free_list(&etc_file_list); + ul_configs_free_list(&usr_file_list); + + return counter; +} + +void ul_configs_free_list(struct list_head *file_list) +{ + list_free(file_list, struct file_element, file_list, free_list_entry); +} + +int ul_configs_next_filename(struct list_head *file_list, + struct list_head **current_entry, + char **name) +{ + struct file_element *element = NULL; + + if (*current_entry == file_list) + return 1; + + if (*current_entry == NULL) + *current_entry = file_list; + element = list_entry(*current_entry, struct file_element, file_list); + *name = element->filename; + *current_entry = (*current_entry)->next; + + return 0; +} diff --git a/lib/meson.build b/lib/meson.build index d62af238b..70e2703d5 100644 --- a/lib/meson.build +++ b/lib/meson.build @@ -29,6 +29,7 @@ lib_common_sources = ''' timeutils.c ttyutils.c xxhash.c + configs.c '''.split() idcache_c = files('idcache.c')