File util-linux-lib-configs.patch of Package util-linux
From 4109f4bfefff9e6cd65815399af3eab2b0a59104 Mon Sep 17 00:00:00 2001
From: Stefan Schubert <schubi@suse.de>
Date: Wed, 17 Sep 2025 13:40:29 +0200
Subject: [PATCH] libcommon: added lib "configs" for parsing configuration
files in the correct order
---
include/Makemodule.am | 3 +-
include/configs.h | 92 ++++++++++++
include/pathnames.h | 10 +-
lib/Makemodule.am | 3 +-
lib/configs.c | 330 ++++++++++++++++++++++++++++++++++++++++++
lib/meson.build | 1 +
6 files changed, 433 insertions(+), 6 deletions(-)
create mode 100644 include/configs.h
create mode 100644 lib/configs.c
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 <err.h>
+#include <errno.h>
+#include <sys/syslog.h>
+#include <sys/stat.h>
+#include <stdbool.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#if defined(HAVE_SCANDIRAT) && defined(HAVE_OPENAT)
+#include <dirent.h>
+#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')
--
2.48.1