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, &current, &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

openSUSE Build Service is sponsored by