diff --git a/0001-include-Include-unistd.h-in-pidfd-utils.h-for-syscal.patch b/0001-include-Include-unistd.h-in-pidfd-utils.h-for-syscal.patch deleted file mode 100644 index 8a61064..0000000 --- a/0001-include-Include-unistd.h-in-pidfd-utils.h-for-syscal.patch +++ /dev/null @@ -1,32 +0,0 @@ -From: Xi Ruoyao -Date: Sun, 31 Mar 2024 00:42:03 +0800 -Subject: include: Include in pidfd-utils.h for syscall() -Git-repo: https://github.com/util-linux/util-linux.git -Git-commit: 10add327c608b11b3d70215048aade4d1797b1fd -Patch-mainline: yes -References: kernel 6.9 - -In Glibc, only contains SYS_* macros and the syscall() -function is in . So include it. - -Signed-off-by: Xi Ruoyao -Signed-off-by: Jiri Slaby ---- - include/pidfd-utils.h | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/include/pidfd-utils.h b/include/pidfd-utils.h -index ff0bc4c7..0ee55f3b 100644 ---- a/include/pidfd-utils.h -+++ b/include/pidfd-utils.h -@@ -7,6 +7,7 @@ - - #ifdef HAVE_SYS_SYSCALL_H - # include -+# include - - /* - * If the kernel headers are too old to provide the syscall numbers, let's --- -2.45.0 - diff --git a/0002-lsfd-Refactor-the-pidfd-logic-into-lsfd-pidfd.c.patch b/0002-lsfd-Refactor-the-pidfd-logic-into-lsfd-pidfd.c.patch deleted file mode 100644 index 9ee6f85..0000000 --- a/0002-lsfd-Refactor-the-pidfd-logic-into-lsfd-pidfd.c.patch +++ /dev/null @@ -1,319 +0,0 @@ -From: Xi Ruoyao -Date: Wed, 3 Apr 2024 15:29:34 +0800 -Subject: lsfd: Refactor the pidfd logic into lsfd-pidfd.c -Git-repo: https://github.com/util-linux/util-linux.git -Git-commit: bf6645dc1edef09ad378cc5b9eb2c93861408735 -Patch-mainline: yes -References: kernel 6.9 - -We'll reuse these logic for pidfd support on Linux >= 6.9. This should -be a no-functional change. - -Besides moving the code, this change also renames anon_pidfd_data to -pidfd_data, and removes a redundant nullity check for free (because -free(NULL) will just do nothing per the C standard). - -Signed-off-by: Xi Ruoyao -Signed-off-by: Jiri Slaby ---- - misc-utils/Makemodule.am | 4 +- - misc-utils/lsfd-pidfd.c | 95 ++++++++++++++++++++++++++++++++++++++++ - misc-utils/lsfd-pidfd.h | 37 ++++++++++++++++ - misc-utils/lsfd-unkn.c | 71 +++++------------------------- - misc-utils/meson.build | 1 + - 5 files changed, 147 insertions(+), 61 deletions(-) - create mode 100644 misc-utils/lsfd-pidfd.c - create mode 100644 misc-utils/lsfd-pidfd.h - -diff --git a/misc-utils/Makemodule.am b/misc-utils/Makemodule.am -index 9edf3d98..7622a5d7 100644 ---- a/misc-utils/Makemodule.am -+++ b/misc-utils/Makemodule.am -@@ -298,7 +298,9 @@ lsfd_SOURCES = \ - misc-utils/lsfd-sock.h \ - misc-utils/lsfd-sock-xinfo.c \ - misc-utils/lsfd-unkn.c \ -- misc-utils/lsfd-fifo.c -+ misc-utils/lsfd-fifo.c \ -+ misc-utils/lsfd-pidfd.h \ -+ misc-utils/lsfd-pidfd.c - lsfd_LDADD = $(LDADD) $(MQ_LIBS) libsmartcols.la libcommon.la - lsfd_CFLAGS = $(AM_CFLAGS) -I$(ul_libsmartcols_incdir) - endif -diff --git a/misc-utils/lsfd-pidfd.c b/misc-utils/lsfd-pidfd.c -new file mode 100644 -index 00000000..430a8028 ---- /dev/null -+++ b/misc-utils/lsfd-pidfd.c -@@ -0,0 +1,95 @@ -+/* -+ * lsfd-pidfd.c - handle pidfd (from anon_inode or pidfs) -+ * -+ * Copyright (C) 2024 Xi Ruoyao -+ * -+ * Refactored and moved out from lsfd-unkn.c (originally authored by -+ * Masatake YAMATO ). -+ * -+ * This program is free software; you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License as published by -+ * the Free Software Foundation; either version 2 of the License, or -+ * (at your option) any later version. -+ * -+ * This program is distributed in the hope that it would be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program; if not, write to the Free Software Foundation, -+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -+ */ -+ -+#include -+ -+#include "strutils.h" -+#include "xalloc.h" -+ -+#include "lsfd.h" -+#include "lsfd-pidfd.h" -+ -+int pidfd_handle_fdinfo(struct pidfd_data *data, const char *key, -+ const char *value) -+{ -+ if (strcmp(key, "Pid") == 0) { -+ uint64_t pid; -+ int rc = ul_strtou64(value, &pid, 10); -+ -+ if (rc < 0) -+ return 0; /* ignore -- parse failed */ -+ -+ data->pid = (pid_t)pid; -+ return 1; -+ } else if (strcmp(key, "NSpid") == 0) { -+ data->nspid = xstrdup(value); -+ return 1; -+ } -+ -+ return 0; -+} -+ -+char *pidfd_get_name(struct pidfd_data *data) -+{ -+ char *str = NULL; -+ char *comm = NULL; -+ struct proc *proc = get_proc(data->pid); -+ -+ if (proc) -+ comm = proc->command; -+ -+ xasprintf(&str, "pid=%d comm=%s nspid=%s", -+ data->pid, -+ comm ? comm : "", -+ data->nspid ? data->nspid : ""); -+ return str; -+} -+ -+bool pidfd_fill_column(struct pidfd_data *data, int column_id, char **str) -+{ -+ switch(column_id) { -+ case COL_PIDFD_COMM: { -+ struct proc *pidfd_proc = get_proc(data->pid); -+ char *pidfd_comm = NULL; -+ -+ if (pidfd_proc) -+ pidfd_comm = pidfd_proc->command; -+ if (pidfd_comm) { -+ *str = xstrdup(pidfd_comm); -+ return true; -+ } -+ break; -+ } -+ case COL_PIDFD_NSPID: -+ if (data->nspid) { -+ *str = xstrdup(data->nspid); -+ return true; -+ } -+ break; -+ case COL_PIDFD_PID: -+ xasprintf(str, "%d", (int)data->pid); -+ return true; -+ } -+ -+ return false; -+} -diff --git a/misc-utils/lsfd-pidfd.h b/misc-utils/lsfd-pidfd.h -new file mode 100644 -index 00000000..2f65d3b3 ---- /dev/null -+++ b/misc-utils/lsfd-pidfd.h -@@ -0,0 +1,37 @@ -+/* -+ * lsfd-pidfd.h - handle pidfd (from anon_inode or pidfs) -+ * -+ * Copyright (C) 2024 Xi Ruoyao -+ * -+ * This program is free software; you can redistribute it and/or modify -+ * it under the terms of the GNU General Public License as published by -+ * the Free Software Foundation; either version 2 of the License, or -+ * (at your option) any later version. -+ * -+ * This program is distributed in the hope that it would be useful, -+ * but WITHOUT ANY WARRANTY; without even the implied warranty of -+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -+ * GNU General Public License for more details. -+ * -+ * You should have received a copy of the GNU General Public License -+ * along with this program; if not, write to the Free Software Foundation, -+ * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -+ */ -+ -+#include -+#include -+ -+struct pidfd_data { -+ pid_t pid; -+ char *nspid; -+}; -+ -+int pidfd_handle_fdinfo(struct pidfd_data *, const char *, const char *); -+char *pidfd_get_name(struct pidfd_data *); -+bool pidfd_fill_column(struct pidfd_data *, int, char **); -+ -+static inline void __attribute__((nonnull(1))) -+pidfd_free(struct pidfd_data *data) -+{ -+ free(data->nspid); -+} -diff --git a/misc-utils/lsfd-unkn.c b/misc-utils/lsfd-unkn.c -index 8f6e9084..8e257f47 100644 ---- a/misc-utils/lsfd-unkn.c -+++ b/misc-utils/lsfd-unkn.c -@@ -28,6 +28,7 @@ - #include "timeutils.h" - - #include "lsfd.h" -+#include "lsfd-pidfd.h" - - #define offsetofend(TYPE, MEMBER) \ - (offsetof(TYPE, MEMBER) + sizeof_member(TYPE, MEMBER)) -@@ -183,10 +184,6 @@ static int unkn_handle_fdinfo(struct file *file, const char *key, const char *va - /* - * pidfd - */ --struct anon_pidfd_data { -- pid_t pid; -- char *nspid; --}; - - static bool anon_pidfd_probe(const char *str) - { -@@ -195,51 +192,28 @@ static bool anon_pidfd_probe(const char *str) - - static char *anon_pidfd_get_name(struct unkn *unkn) - { -- char *str = NULL; -- struct anon_pidfd_data *data = (struct anon_pidfd_data *)unkn->anon_data; -+ struct pidfd_data *data = (struct pidfd_data *)unkn->anon_data; - -- char *comm = NULL; -- struct proc *proc = get_proc(data->pid); -- if (proc) -- comm = proc->command; -- -- xasprintf(&str, "pid=%d comm=%s nspid=%s", -- data->pid, -- comm? comm: "", -- data->nspid? data->nspid: ""); -- return str; -+ return pidfd_get_name(data); - } - - static void anon_pidfd_init(struct unkn *unkn) - { -- unkn->anon_data = xcalloc(1, sizeof(struct anon_pidfd_data)); -+ unkn->anon_data = xcalloc(1, sizeof(struct pidfd_data)); - } - - static void anon_pidfd_free(struct unkn *unkn) - { -- struct anon_pidfd_data *data = (struct anon_pidfd_data *)unkn->anon_data; -+ struct pidfd_data *data = (struct pidfd_data *)unkn->anon_data; - -- if (data->nspid) -- free(data->nspid); -+ pidfd_free(data); - free(data); - } - - static int anon_pidfd_handle_fdinfo(struct unkn *unkn, const char *key, const char *value) - { -- if (strcmp(key, "Pid") == 0) { -- uint64_t pid; -- -- int rc = ul_strtou64(value, &pid, 10); -- if (rc < 0) -- return 0; /* ignore -- parse failed */ -- ((struct anon_pidfd_data *)unkn->anon_data)->pid = (pid_t)pid; -- return 1; -- } else if (strcmp(key, "NSpid") == 0) { -- ((struct anon_pidfd_data *)unkn->anon_data)->nspid = xstrdup(value); -- return 1; -- -- } -- return 0; -+ return pidfd_handle_fdinfo((struct pidfd_data *)unkn->anon_data, -+ key, value); - } - - static bool anon_pidfd_fill_column(struct proc *proc __attribute__((__unused__)), -@@ -249,32 +223,9 @@ static bool anon_pidfd_fill_column(struct proc *proc __attribute__((__unused__) - size_t column_index __attribute__((__unused__)), - char **str) - { -- struct anon_pidfd_data *data = (struct anon_pidfd_data *)unkn->anon_data; -- -- switch(column_id) { -- case COL_PIDFD_COMM: { -- struct proc *pidfd_proc = get_proc(data->pid); -- char *pidfd_comm = NULL; -- if (pidfd_proc) -- pidfd_comm = pidfd_proc->command; -- if (pidfd_comm) { -- *str = xstrdup(pidfd_comm); -- return true; -- } -- break; -- } -- case COL_PIDFD_NSPID: -- if (data->nspid) { -- *str = xstrdup(data->nspid); -- return true; -- } -- break; -- case COL_PIDFD_PID: -- xasprintf(str, "%d", (int)data->pid); -- return true; -- } -- -- return false; -+ return pidfd_fill_column((struct pidfd_data *)unkn->anon_data, -+ column_id, -+ str); - } - - static const struct anon_ops anon_pidfd_ops = { -diff --git a/misc-utils/meson.build b/misc-utils/meson.build -index 847b1012..68ea9777 100644 ---- a/misc-utils/meson.build -+++ b/misc-utils/meson.build -@@ -56,6 +56,7 @@ lsfd_sources = files ( - 'lsfd-sock-xinfo.c', - 'lsfd-unkn.c', - 'lsfd-fifo.c', -+ 'lsfd-pidfd.c', - ) - - uuidgen_sources = files( --- -2.45.0 - diff --git a/0003-lsfd-Support-pidfs.patch b/0003-lsfd-Support-pidfs.patch deleted file mode 100644 index a914b3d..0000000 --- a/0003-lsfd-Support-pidfs.patch +++ /dev/null @@ -1,186 +0,0 @@ -From: Xi Ruoyao -Date: Wed, 3 Apr 2024 15:46:57 +0800 -Subject: lsfd: Support pidfs -Git-repo: https://github.com/util-linux/util-linux.git -Git-commit: b1a48efd173c7f37d8df39a84eb25b4440335661 -Patch-mainline: yes -References: kernel 6.9 - -In Linux 6.9 pidfds are moved from the anonymous inode infrastructure to -a tiny pseudo filesystem named pidfs. Recognize it properly. - -Fixes #2865. - -Signed-off-by: Xi Ruoyao -Signed-off-by: Jiri Slaby ---- - misc-utils/lsfd-file.c | 96 ++++++++++++++++++++++++++++++++++++++++++ - misc-utils/lsfd.c | 3 ++ - misc-utils/lsfd.h | 7 ++- - 3 files changed, 105 insertions(+), 1 deletion(-) - -diff --git a/misc-utils/lsfd-file.c b/misc-utils/lsfd-file.c -index 9b91462d..3f330146 100644 ---- a/misc-utils/lsfd-file.c -+++ b/misc-utils/lsfd-file.c -@@ -45,6 +45,8 @@ - #include "procfs.h" - - #include "lsfd.h" -+#include "lsfd-pidfd.h" -+#include "pidfd-utils.h" - - static struct idcache *username_cache; - -@@ -492,6 +494,22 @@ static unsigned long get_minor_for_mqueue(void) - return minor(sb.st_dev); - } - -+static unsigned long get_minor_for_pidfs(void) -+{ -+ int fd = pidfd_open(getpid(), 0); -+ struct stat sb; -+ unsigned long ret = 0; -+ -+ if (fd < 0) -+ return 0; -+ -+ if (fstat(fd, &sb) == 0 && (sb.st_mode & S_IFMT) == S_IFREG) -+ ret = minor(sb.st_dev); -+ -+ close(fd); -+ return ret; -+} -+ - static void file_class_initialize(void) - { - unsigned long m; -@@ -510,6 +528,10 @@ static void file_class_initialize(void) - m = get_minor_for_mqueue(); - if (m) - add_nodev(m, "mqueue"); -+ -+ m = get_minor_for_pidfs(); -+ if (m) -+ add_nodev(m, "pidfs"); - } - - static void file_class_finalize(void) -@@ -783,3 +805,77 @@ const struct file_class mqueue_file_class = { - .fill_column = mqueue_file_fill_column, - .get_ipc_class = mqueue_file_get_ipc_class, - }; -+ -+struct pidfs_file { -+ struct file file; -+ struct pidfd_data data; -+}; -+ -+static void init_pidfs_file_content(struct file *file) -+{ -+ struct pidfs_file *pidfs_file = (struct pidfs_file *)file; -+ -+ memset(&pidfs_file->data, 0, sizeof(pidfs_file->data)); -+} -+ -+static int pidfs_file_handle_fdinfo(struct file *file, const char *key, const char *value) -+{ -+ struct pidfs_file *pidfs_file = (struct pidfs_file *)file; -+ -+ return pidfd_handle_fdinfo(&pidfs_file->data, key, value); -+} -+ -+static void pidfs_file_free_content(struct file *file) -+{ -+ struct pidfs_file *pidfs_file = (struct pidfs_file *)file; -+ -+ pidfd_free(&pidfs_file->data); -+} -+ -+static bool pidfs_file_fill_column(struct proc *proc __attribute__((__unused__)), -+ struct file *file, -+ struct libscols_line *ln, -+ int column_id, -+ size_t column_index) -+{ -+ struct pidfs_file *pidfs_file = (struct pidfs_file *)file; -+ char *buf = NULL; -+ -+ switch(column_id) { -+ case COL_TYPE: -+ if (scols_line_set_data(ln, column_index, "pidfd")) -+ err(EXIT_FAILURE, _("failed to add output data")); -+ return true; -+ case COL_NAME: -+ buf = pidfd_get_name(&pidfs_file->data); -+ break; -+ default: -+ if (!pidfd_fill_column(&pidfs_file->data, column_id, &buf)) -+ return false; -+ } -+ -+ if (buf && -+ scols_line_refer_data(ln, column_index, buf)) -+ err(EXIT_FAILURE, _("failed to add output data")); -+ -+ return true; -+} -+ -+const struct file_class pidfs_file_class = { -+ .super = &file_class, -+ .size = sizeof(struct pidfs_file), -+ .initialize_content = init_pidfs_file_content, -+ .handle_fdinfo = pidfs_file_handle_fdinfo, -+ .fill_column = pidfs_file_fill_column, -+ .free_content = pidfs_file_free_content, -+}; -+ -+bool is_pidfs_dev(dev_t dev) -+{ -+ const char *fs = get_nodev_filesystem(minor(dev)); -+ -+ if (fs && (strcmp (fs, "pidfs") == 0)) -+ return true; -+ -+ return false; -+} -diff --git a/misc-utils/lsfd.c b/misc-utils/lsfd.c -index 98820ee8..01e88d51 100644 ---- a/misc-utils/lsfd.c -+++ b/misc-utils/lsfd.c -@@ -683,6 +683,9 @@ static const struct file_class *stat2class(struct stat *sb) - if (is_mqueue_dev(dev)) - return &mqueue_file_class; - -+ if (is_pidfs_dev(dev)) -+ return &pidfs_file_class; -+ - return &file_class; - default: - break; -diff --git a/misc-utils/lsfd.h b/misc-utils/lsfd.h -index e646758c..f0f17d5b 100644 ---- a/misc-utils/lsfd.h -+++ b/misc-utils/lsfd.h -@@ -228,7 +228,7 @@ struct file_class { - }; - - extern const struct file_class file_class, cdev_class, bdev_class, sock_class, unkn_class, fifo_class, -- nsfs_file_class, mqueue_file_class; -+ nsfs_file_class, mqueue_file_class, pidfs_file_class; - - /* - * IPC -@@ -307,4 +307,9 @@ bool is_mqueue_dev(dev_t dev); - */ - bool is_multiplexed_by_eventpoll(int fd, struct list_head *eventpolls); - -+/* -+ * Pidfs -+ */ -+bool is_pidfs_dev(dev_t dev); -+ - #endif /* UTIL_LINUX_LSFD_H */ --- -2.45.0 - diff --git a/0004-lsfd-test-Adapt-test-cases-for-pidfs.patch b/0004-lsfd-test-Adapt-test-cases-for-pidfs.patch deleted file mode 100644 index 1d8b72e..0000000 --- a/0004-lsfd-test-Adapt-test-cases-for-pidfs.patch +++ /dev/null @@ -1,142 +0,0 @@ -From: Xi Ruoyao -Date: Thu, 28 Mar 2024 04:01:02 +0800 -Subject: lsfd: test: Adapt test cases for pidfs -Git-repo: https://github.com/util-linux/util-linux.git -Git-commit: 04db2ba9008e9635286b1aafc8ecd9533a0a91bf -Patch-mainline: yes -References: kernel 6.9 - -On Linux >= 6.9, pidfds are from pidfs instead of anonymous inode. -Thus: - -STTYPE is REG on Linux >= 6.9, UNKN on Linux < 6.9. - -KNAME is pidfd:[inode number] on Linux >= 6.9, anon_inode:[pidfd] on -Linux < 6.9. - -And ainode_class test cannot work on Linux >= 6.9, just skip this sub -test if STTYPE is REG. - -Signed-off-by: Xi Ruoyao -Signed-off-by: Jiri Slaby ---- - tests/expected/lsfd/column-name-pidfd | 2 +- - tests/expected/lsfd/column-type-pidfd | 2 +- - tests/expected/lsfd/mkfds-pidfd | 2 +- - tests/ts/lsfd/column-ainodeclass | 8 ++++++++ - tests/ts/lsfd/column-name | 7 +++++++ - tests/ts/lsfd/column-type | 7 +++++++ - tests/ts/lsfd/mkfds-pidfd | 4 ++++ - 7 files changed, 29 insertions(+), 3 deletions(-) - -diff --git a/tests/expected/lsfd/column-name-pidfd b/tests/expected/lsfd/column-name-pidfd -index 10e3c5e7..68787d69 100644 ---- a/tests/expected/lsfd/column-name-pidfd -+++ b/tests/expected/lsfd/column-name-pidfd -@@ -1,2 +1,2 @@ --3 anon_inode:[pidfd] pid=1 comm= nspid=1 -+3 [KNAME] pid=1 comm= nspid=1 - pidfd:ASSOC,KNAME,NAME: 0 -diff --git a/tests/expected/lsfd/column-type-pidfd b/tests/expected/lsfd/column-type-pidfd -index 6c9a9632..a4379807 100644 ---- a/tests/expected/lsfd/column-type-pidfd -+++ b/tests/expected/lsfd/column-type-pidfd -@@ -1,2 +1,2 @@ --3 UNKN pidfd -+3 [STTYPE] pidfd - pidfd:ASSOC,STTYPE,TYPE: 0 -diff --git a/tests/expected/lsfd/mkfds-pidfd b/tests/expected/lsfd/mkfds-pidfd -index 94846992..bce4dd42 100644 ---- a/tests/expected/lsfd/mkfds-pidfd -+++ b/tests/expected/lsfd/mkfds-pidfd -@@ -1,2 +1,2 @@ --3 UNKN anon_inodefs pid=1 comm=systemd nspid=1 systemd 1 -+3 [STTYPE] [SOURCE] pid=1 comm=systemd nspid=1 systemd 1 - ASSOC,STTYPE,SOURCE,NAME,PIDFD.COMM,PIDFD.PID: 0 -diff --git a/tests/ts/lsfd/column-ainodeclass b/tests/ts/lsfd/column-ainodeclass -index 6829494f..ab2abebd 100755 ---- a/tests/ts/lsfd/column-ainodeclass -+++ b/tests/ts/lsfd/column-ainodeclass -@@ -42,10 +42,18 @@ for C in pidfd inotify; do - fi - wait "${MKFDS_PID}" - } > "$TS_OUTPUT" 2>&1 -+ - if [ "$C-$?" == "pidfd-$TS_EXIT_NOTSUPP" ]; then - ts_skip_subtest "pidfd_open(2) is not available" - continue - fi -+ -+ STTYPE="$(head -n1 "$TS_OUTPUT" | awk '{print $2}')" -+ if [ "$C-$STTYPE" == "pidfd-REG" ]; then -+ ts_skip_subtest "pidfd is from pidfs instead of anon inode" -+ continue -+ fi -+ - ts_finalize_subtest - done - -diff --git a/tests/ts/lsfd/column-name b/tests/ts/lsfd/column-name -index 8bf8f421..9c67de88 100755 ---- a/tests/ts/lsfd/column-name -+++ b/tests/ts/lsfd/column-name -@@ -64,10 +64,17 @@ for C in ro-regular-file pidfd socketpair; do - fi - } > "$TS_OUTPUT" 2>&1 - wait "${MKFDS_PID}" -+ - if [ "$C-$?" == "pidfd-$TS_EXIT_NOTSUPP" ]; then - ts_skip_subtest "pidfd_open(2) is not available" - continue - fi -+ -+ case $C in -+ pidfd) -+ sed -i -E 's/(pidfd|anon_inode):\[[a-zA-Z]+\]/[KNAME]/' "$TS_OUTPUT" -+ esac -+ - ts_finalize_subtest - done - -diff --git a/tests/ts/lsfd/column-type b/tests/ts/lsfd/column-type -index 77bc5c94..1b8aa8c6 100755 ---- a/tests/ts/lsfd/column-type -+++ b/tests/ts/lsfd/column-type -@@ -50,10 +50,17 @@ for C in ro-regular-file pidfd inotify socketpair; do - fi - wait "${MKFDS_PID}" - } > "$TS_OUTPUT" 2>&1 -+ - if [ "$C-$?" == "pidfd-$TS_EXIT_NOTSUPP" ]; then - ts_skip_subtest "pidfd_open(2) is not available" - continue - fi -+ -+ case $C in -+ pidfd) -+ sed -i -E 's/UNKN|REG/[STTYPE]/' "$TS_OUTPUT" -+ esac -+ - ts_finalize_subtest - done - -diff --git a/tests/ts/lsfd/mkfds-pidfd b/tests/ts/lsfd/mkfds-pidfd -index c0fae4f7..9b0ff33c 100755 ---- a/tests/ts/lsfd/mkfds-pidfd -+++ b/tests/ts/lsfd/mkfds-pidfd -@@ -44,8 +44,12 @@ EXPR="(PID != ${TARGET}) and (FD == 3) and (PIDFD.PID == ${TARGET})" - fi - wait ${MKFDS_PID} - } > $TS_OUTPUT 2>&1 -+ - if [ "$?" == "$TS_EXIT_NOTSUPP" ]; then - ts_skip "pidfd_open(2) is not available" - fi - -+sed -i -E -e 's/UNKN|REG/[STTYPE]/' -e 's/pidfs|anon_inodefs/[SOURCE]/' \ -+ $TS_OUTPUT -+ - ts_finalize --- -2.45.0 - diff --git a/libmount-print-a-blacklist-hint-for-unknown-filesyst.patch b/libmount-print-a-blacklist-hint-for-unknown-filesyst.patch index d5a3e6c..29cb3b9 100644 --- a/libmount-print-a-blacklist-hint-for-unknown-filesyst.patch +++ b/libmount-print-a-blacklist-hint-for-unknown-filesyst.patch @@ -14,12 +14,11 @@ Signed-off-by: Martin Wilck libmount/src/context_mount.c | 41 ++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) -diff --git a/libmount/src/context_mount.c b/libmount/src/context_mount.c -index f914c9b..a48483f 100644 ---- a/libmount/src/context_mount.c -+++ b/libmount/src/context_mount.c -@@ -1423,6 +1423,32 @@ done: - return rc; +diff -ur util-linux-2.41.orig/libmount/src/context_mount.c util-linux-2.41/libmount/src/context_mount.c +--- util-linux-2.41.orig/libmount/src/context_mount.c 2025-03-04 19:14:02.587173600 +0100 ++++ util-linux-2.41/libmount/src/context_mount.c 2025-04-08 14:03:59.173505340 +0200 +@@ -1469,6 +1469,32 @@ + } } +/* @@ -51,7 +50,7 @@ index f914c9b..a48483f 100644 int mnt_context_get_mount_excode( struct libmnt_context *cxt, int rc, -@@ -1670,10 +1696,17 @@ int mnt_context_get_mount_excode( +@@ -1772,10 +1798,17 @@ case ENODEV: if (!buf) break; @@ -73,6 +72,3 @@ index f914c9b..a48483f 100644 snprintf(buf, bufsz, _("unknown filesystem type")); break; --- -2.19.2 - diff --git a/util-linux-2.40.1.tar.sign b/util-linux-2.40.1.tar.sign deleted file mode 100644 index 4ca2db7..0000000 --- a/util-linux-2.40.1.tar.sign +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN PGP SIGNATURE----- - -iQIzBAABCAAdFiEEsMZNFDAcxu+u32Dk5LcdXuw5woQFAmY4lxMACgkQ5LcdXuw5 -woSckw//ZmoxCcjdDBv5LkAK0Yl5EMKZYzkvqswsp/Uwllgevb/FcCGwZ+F49RM3 -H8F5Kj5CIJW9wh2UNWxaMm4MxcGd3eG2NEwgYh8RiXSS84zEL78yaIJLyFlPziMp -kZb0fpc2HpyCzDrvtlw6S5TGkUUx2uFqTQQG7GFd6TOlbPpGp4OpqY3adf85mwmr -NKy0XbYLo6sqGENz6Uklbf5Qhc19UEEc1jScOYNpkuCxhRQguxq3Jc6W8A8DQkjB -EWaw9Q1XYDV3Bnd40F0K+bo3CYB6z3pMM51NlsYxV0fKt/SoEvJw3S7u3VcbtJWn -S6xl6q/sMINDrMIpSL5PAAg5yoBNIIoWW7OxSh0nv6ctp9dmln0sKBtXNr09zTWT -q9mLUt7LwXB6LT3XI64262vF21bKq/hQO8IObsx8vfPSlp1SyGq7VqGh6QVbkjx6 -2vA7ueV5jfIKiAlZAcXz6NHjwxcBqYq7wASeUEmYPgh5lb4HBOuOgatYBNAmQoxp -2t5AXPPhI/u95GRa4WntpgqlQm/1CKA9kHiezvO4P0lnpUluT24DXclMTcq4xaiN -ci+dzVF2EPRoPJgYy9crKNsr7dftVtvAVXIRk3UaTwDe1o7OK4vSykDc0PSNxsHQ -mMabBuFt17oweonotSOZ8z7blHnCVnzZWVxyIxC3Pw/0ChHFAkw= -=68JD ------END PGP SIGNATURE----- diff --git a/util-linux-2.40.1.tar.xz b/util-linux-2.40.1.tar.xz deleted file mode 100644 index ea93fed..0000000 --- a/util-linux-2.40.1.tar.xz +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:59e676aa53ccb44b6c39f0ffe01a8fa274891c91bef1474752fad92461def24f -size 8823784 diff --git a/util-linux-2.41.1.tar.sign b/util-linux-2.41.1.tar.sign new file mode 100644 index 0000000..0993aa8 --- /dev/null +++ b/util-linux-2.41.1.tar.sign @@ -0,0 +1,16 @@ +-----BEGIN PGP SIGNATURE----- + +iQIzBAABCAAdFiEEsMZNFDAcxu+u32Dk5LcdXuw5woQFAmhaXXIACgkQ5LcdXuw5 +woTvGg/8DVhCA5WWXbRh+fxsTwb2vIWPgBpjV+wUqxiNeijPXDK7tDcNF9bHvXy7 +X7ajs4y+ULzw2hBpBmI/k/Oi/enDYNo+Q76du6POjJkasgO0bY9kGVVFhd61BFfk +86yY0uOud3n/F/ht1rR3TdFaatA2QtK7AqGmh3xpQh7ELJ8v7uNT/uyU7ZtKJXT3 +uEa217G1OYxxJlmLmob0jWYYuyjtg0nB/Xw4MiXrTqQbD4K0304eDsYu10DaRPW6 +ZNEjnCYEaPPkZ1xH3L29ZZlrwZ47ApN52dsTRzoymQGhak5UsKvcL7pWAa3gIlk9 +m3cL6TnuFXemBF6FqdAu2+xs1gX6zzPW8+2iff+9PrvODXSZPNBamwsVY6Ae39g9 +8lRQH1RpyXdIB+wg3ItgaGxTYz6aEJU9nCsE2uHQF/IRSdEHsqG5rc00ax0h4Ihk +M+3z94Wuo+8NceY7kIM/lkH5sCnVV4WcpGImA3kvaVDkjYhb8XK8Yb3KFR8RE/rH +LOCRavkKXaKT4CaS+SLvOZoFTTLciw7oNqecopSsREPk0y4HKbPdfRrfcS38m4ex +HXp9yI2Rl7vThtDuNi0Bcp6GmLo4cgWgHBYSNZcnGH88LJk2Ya7Yamg+tUeynGCI +6RvQxxfDT8mMf5deEiiLgG9RxjSVlA53dIZf1tB1RUjjh61aLdQ= +=mm3H +-----END PGP SIGNATURE----- diff --git a/util-linux-2.41.1.tar.xz b/util-linux-2.41.1.tar.xz new file mode 100644 index 0000000..6197ecb --- /dev/null +++ b/util-linux-2.41.1.tar.xz @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:be9ad9a276f4305ab7dd2f5225c8be1ff54352f565ff4dede9628c1aaa7dec57 +size 9606156 diff --git a/util-linux-agetty-netlink.patch b/util-linux-agetty-netlink.patch new file mode 100644 index 0000000..cea20f8 --- /dev/null +++ b/util-linux-agetty-netlink.patch @@ -0,0 +1,639 @@ +From bf7c46ef9158f3baae6b637ebb73a24d8460d394 Mon Sep 17 00:00:00 2001 +From: Stanislav Brabec +Date: Wed, 9 Jul 2025 14:35:28 +0200 +Subject: [PATCH 2/2] agetty: Implement netlink based IP processing + +The current \4 and \6 issue file escapes implementation is inferior. It +uses get getifaddrs() to get a list of IP addresses. This function does not +provide enough information to discriminate between stable IP addresses and +ephemeral addresses. As a result, especially \6 often gives unreliable +results. + +The code is actually unable to get list of all interfaces, so a proper out +of the box IP address reporting depends on external tools that generate +issue file with the interfaces list. + +The netlink messages are already used, but only as a change notifier. The +contents is not used, even if it contains exact information about the +change. As a result, change processing is triggered even for unrelated +network changes like IPv6 router advertisement. + +The new implementation uses the new netaddrq library. It reports more +reliable results especially for IPv6. + +Additionally, two new escapes are implemented: + +\a Report all interfaces and assigned addresses that are considered as +reliable. + +\A Report all interfaces and all assigned addresses. + +TODO: + +To prevent overflooding of the console, the list is currently limited to 12 +interfaces. It would be nice to make it configurable. + +Two pass processing of issue files. First pass just collects IP protocols +and list of interfaces (in future interface patterns). Now it always +processes both IPv4 and IPv6 on all interfaces. Not so bad, as \a is smart +enough to display just the useful part. + +Maybe implement more options and formatting support for \a and \A. + +Maybe implement interface filter globs or regexps for \a and \A. Still not +so bad, as \a automatically skips interfaces without reliable addresses +(e. g. lo or TUN). + +Signed-off-by: Stanislav Brabec +--- + term-utils/agetty.8.adoc | 6 + + term-utils/agetty.c | 417 ++++++++++++++++++++++----------------- + 2 files changed, 246 insertions(+), 177 deletions(-) + +diff --git a/term-utils/agetty.8.adoc b/term-utils/agetty.8.adoc +index a33f12a3f..6670498f5 100644 +--- a/term-utils/agetty.8.adoc ++++ b/term-utils/agetty.8.adoc +@@ -253,6 +253,12 @@ Insert the IPv4 address of the specified network interface (for example: \4\{eth + 6 or 6{_interface_}:: + The same as \4 but for IPv6. + ++a:: ++Insert list of "good" IP addresses for all interfaces. It prints best candidates for remote login IP addresses: global and site addresses; if not available, temporary address with the longest lifetime, if not available, link address. Note that link addresses are printed with local interface name, but they has to be done with the interface name on the machine where they will be used. ++ ++A:: ++Insert list of all IP addresses for all interfaces. ++ + b:: + Insert the baudrate of the current line. + +diff --git a/term-utils/agetty.c b/term-utils/agetty.c +index 5e564c4f0..c37417e1e 100644 +--- a/term-utils/agetty.c ++++ b/term-utils/agetty.c +@@ -32,10 +32,7 @@ + #include + #include + #include +-#include + #include +-#include +-#include + #include + + #include "strutils.h" +@@ -50,6 +47,9 @@ + #include "env.h" + #include "path.h" + #include "fileutils.h" ++#ifdef AGETTY_RELOAD ++#include "netaddrq.h" ++#endif + + #include "logindefs.h" + +@@ -144,7 +144,6 @@ + # define AGETTY_RELOAD_FILENAME "/run/agetty.reload" /* trigger file */ + # define AGETTY_RELOAD_FDNONE -2 /* uninitialized fd */ + static int inotify_fd = AGETTY_RELOAD_FDNONE; +-static int netlink_fd = AGETTY_RELOAD_FDNONE; + static uint32_t netlink_groups; + #endif + +@@ -154,6 +153,7 @@ struct issue { + size_t mem_sz; + + #ifdef AGETTY_RELOAD ++ struct ul_nl_data nl; + char *mem_old; + #endif + unsigned int do_tcsetattr : 1, +@@ -364,6 +364,7 @@ int main(int argc, char **argv) + }; + struct issue issue = { + .mem = NULL, ++ .nl.fd = -1 + }; + char *login_argv[LOGIN_ARGV_MAX + 1]; + int login_argc = 0; +@@ -1603,81 +1604,7 @@ done: + } + + #ifdef AGETTY_RELOAD +-static void open_netlink(void) +-{ +- struct sockaddr_nl addr = { 0, }; +- int sock; +- +- if (netlink_fd != AGETTY_RELOAD_FDNONE) +- return; +- +- sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); +- if (sock >= 0) { +- addr.nl_family = AF_NETLINK; +- addr.nl_pid = getpid(); +- addr.nl_groups = netlink_groups; +- if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) +- close(sock); +- else +- netlink_fd = sock; +- } +-} +- +-static int process_netlink_msg(int *triggered) +-{ +- char buf[4096]; +- struct sockaddr_nl snl; +- struct nlmsghdr *h; +- int rc; +- +- struct iovec iov = { +- .iov_base = buf, +- .iov_len = sizeof(buf) +- }; +- struct msghdr msg = { +- .msg_name = &snl, +- .msg_namelen = sizeof(snl), +- .msg_iov = &iov, +- .msg_iovlen = 1, +- .msg_control = NULL, +- .msg_controllen = 0, +- .msg_flags = 0 +- }; +- +- rc = recvmsg(netlink_fd, &msg, MSG_DONTWAIT); +- if (rc < 0) { +- if (errno == EWOULDBLOCK || errno == EAGAIN) +- return 0; +- +- /* Failure, just stop listening for changes */ +- close(netlink_fd); +- netlink_fd = AGETTY_RELOAD_FDNONE; +- return 0; +- } +- +- for (h = (struct nlmsghdr *)buf; NLMSG_OK(h, (unsigned int)rc); h = NLMSG_NEXT(h, rc)) { +- if (h->nlmsg_type == NLMSG_DONE || +- h->nlmsg_type == NLMSG_ERROR) { +- close(netlink_fd); +- netlink_fd = AGETTY_RELOAD_FDNONE; +- return 0; +- } +- +- *triggered = 1; +- break; +- } +- +- return 1; +-} +- +-static int process_netlink(void) +-{ +- int triggered = 0; +- while (process_netlink_msg(&triggered)); +- return triggered; +-} +- +-static int wait_for_term_input(int fd) ++static int wait_for_term_input(struct issue *ie, int fd) + { + char buffer[sizeof(struct inotify_event) + NAME_MAX + 1]; + fd_set rfds; +@@ -1711,9 +1638,9 @@ static int wait_for_term_input(int fd) + FD_SET(inotify_fd, &rfds); + nfds = max(nfds, inotify_fd); + } +- if (netlink_fd >= 0) { +- FD_SET(netlink_fd, &rfds); +- nfds = max(nfds, netlink_fd); ++ if (ie->nl.fd >= 0) { ++ FD_SET(ie->nl.fd, &rfds); ++ nfds = max(nfds, ie->nl.fd); + } + + /* If waiting fails, just fall through, presumably reading input will fail */ +@@ -1725,9 +1652,10 @@ static int wait_for_term_input(int fd) + + } + +- if (netlink_fd >= 0 && FD_ISSET(netlink_fd, &rfds)) { +- if (!process_netlink()) +- continue; ++ if (ie->nl.fd >= 0 && FD_ISSET(ie->nl.fd, &rfds)) { ++ /* We are ignoring errors here to prevent unability of ++ * further processing. */ ++ ul_nl_process(&(ie->nl), UL_NL_ASYNC, UL_NL_ONESHOT); + + /* Just drain the inotify buffer */ + } else if (inotify_fd >= 0 && FD_ISSET(inotify_fd, &rfds)) { +@@ -1937,11 +1865,44 @@ static void eval_issue_file(struct issue *ie, + struct options *op, + struct termios *tp) + { +-#ifdef AGETTY_RELOAD +- netlink_groups = 0; +-#endif + if (!(op->flags & F_ISSUE)) + goto done; ++ ++#ifdef AGETTY_RELOAD ++/* TODO: ++ * Two pass processing for eval_issue_file() ++ * Implement pass 1: Just evaluate list of netlink_groups (IP protocols) and ++ * intefaces to monitor. ++ * That is why again label is here: netlink_groups will be re-evaluated and ++ * dump will be performed again. ++ */ ++ /* netlink_groups = 0; */ ++ netlink_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; ++ ++ /* Already initialized? */ ++ if (ie->nl.fd >= 0) ++ goto skip; ++ /* Prepare netlink. */ ++ ul_nl_init(&(ie->nl)); ++ if ((ul_netaddrq_init(&(ie->nl), NULL, NULL, (void *)ie))) ++ goto skip; ++ ++ /* Open netlink and create address list. */ ++ if (ul_nl_open(&(ie->nl), ++ RTMGRP_LINK | netlink_groups)) ++ goto skip; ++ if (ul_nl_request_dump(&(ie->nl), RTM_GETADDR)) ++ goto error; ++ if (ul_nl_process(&(ie->nl), UL_NL_SYNC, UL_NL_LOOP) != UL_NL_DONE) ++ goto error; ++ goto skip; ++error: ++ /* In case of any error, the addrq list is just empty, and we can use ++ * the code without any error checking. */ ++ ul_nl_close(&(ie->nl)); ++ ie->nl.fd = -1; ++skip: ++#endif + /* + * The custom issue file or directory list specified by: + * agetty --issue-file +@@ -1986,11 +1947,6 @@ static void eval_issue_file(struct issue *ie, + issuedir_read(ie, _PATH_SYSCONFSTATICDIR "/" _PATH_ISSUE_DIRNAME, op, tp); + + done: +- +-#ifdef AGETTY_RELOAD +- if (netlink_groups != 0) +- open_netlink(); +-#endif + if (ie->output) { + fclose(ie->output); + ie->output = NULL; +@@ -2002,7 +1958,7 @@ done: + */ + static void show_issue(struct options *op) + { +- struct issue ie = { .output = NULL }; ++ struct issue ie = { .output = NULL, .nl.fd = -1 }; + struct termios tp; + + memset(&tp, 0, sizeof(struct termios)); +@@ -2032,13 +1988,19 @@ again: + puts(_("[press ENTER to login]")); + #ifdef AGETTY_RELOAD + /* reload issue */ +- if (!wait_for_term_input(STDIN_FILENO)) { ++ if (!wait_for_term_input(ie, STDIN_FILENO)) { + eval_issue_file(ie, op, tp); + if (issue_is_changed(ie)) { + if ((op->flags & F_VCONSOLE) + && (op->flags & F_NOCLEAR) == 0) + termio_clear(STDOUT_FILENO); +- goto again; ++ { ++ /* TODO: Close to set netlink_groups again using pass 1 */ ++ /* if (ie->nl.fd >= 0) ul_nl_close(&(ie->nl)); ++ * ie->nl.fd = -1; */ ++ ++ goto again; ++ } + } + } + #endif +@@ -2168,7 +2130,7 @@ static char *get_logname(struct issue *ie, struct options *op, struct termios *t + + no_reload: + #ifdef AGETTY_RELOAD +- if (!wait_for_term_input(STDIN_FILENO)) { ++ if (!wait_for_term_input(ie, STDIN_FILENO)) { + /* refresh prompt -- discard input data, clear terminal + * and call do_prompt() again + */ +@@ -2177,6 +2139,8 @@ static char *get_logname(struct issue *ie, struct options *op, struct termios *t + eval_issue_file(ie, op, tp); + if (!issue_is_changed(ie)) + goto no_reload; ++ /* if (ie->nl.fd >= 0) ul_nl_close(&(ie->nl)); ++ * ie->nl.fd = -1; */ + tcflush(STDIN_FILENO, TCIFLUSH); + if ((op->flags & F_VCONSOLE) + && (op->flags & F_NOCLEAR) == 0) +@@ -2576,92 +2540,170 @@ static void log_warn(const char *fmt, ...) + va_end(ap); + } + +-static void print_addr(struct issue *ie, sa_family_t family, void *addr) ++static void print_iface_best(struct issue *ie, ++ const char *ifname, ++ uint8_t ifa_family) + { +- char buff[INET6_ADDRSTRLEN + 1]; ++ struct ul_netaddrq_ip *best[__ULNETLINK_RATING_MAX]; ++ struct ul_netaddrq_iface *ifaceq; ++ struct list_head *l; ++ enum ul_netaddrq_ip_rating threshold; + +- inet_ntop(family, addr, buff, sizeof(buff)); +- fprintf(ie->output, "%s", buff); +-} ++ if (!ie->nl.data_addr) ++ return; /* error: init failed */ + +-/* +- * Prints IP for the specified interface (@iface), if the interface is not +- * specified then prints the "best" one (UP, RUNNING, non-LOOPBACK). If not +- * found the "best" interface then prints at least host IP. +- */ +-static void output_iface_ip(struct issue *ie, +- struct ifaddrs *addrs, +- const char *iface, +- sa_family_t family) +-{ +- struct ifaddrs *p; +- struct addrinfo hints, *info = NULL; +- char *host = NULL; +- void *addr = NULL; ++ if ((ifaceq = ul_netaddrq_iface_by_name(&(ie->nl), ifname))) ++ { ++ memset(best, 0, sizeof(best)); ++ if (ifa_family == AF_INET) ++ l = &(ifaceq->ip_quality_list_4); ++ else ++ /* if (ifa_family == AF_INET6) */ ++ l = &(ifaceq->ip_quality_list_6); + +- if (!addrs) +- return; ++ threshold = ++ ul_netaddrq_iface_bestaddr(l, &best); ++ if (best[threshold]) ++ fputs(ul_nl_addr_ntop_address(best[threshold]->addr), ++ ie->output); ++ } ++} + +- for (p = addrs; p; p = p->ifa_next) { ++static void print_addrq_bestofall(struct issue *ie, ++ uint8_t ifa_family) ++{ ++ struct ul_netaddrq_iface *best_ifaceq; ++ enum ul_netaddrq_ip_rating threshold; ++ const char *best_ipp; + +- if (!p->ifa_name || +- !p->ifa_addr || +- p->ifa_addr->sa_family != family) +- continue; ++ if (!ie->nl.data_addr) ++ return; /* error: init failed */ + +- if (iface) { +- /* Filter out by interface name */ +- if (strcmp(p->ifa_name, iface) != 0) +- continue; +- } else { +- /* Select the "best" interface */ +- if ((p->ifa_flags & IFF_LOOPBACK) || +- !(p->ifa_flags & IFF_UP) || +- !(p->ifa_flags & IFF_RUNNING)) +- continue; +- } ++ best_ipp = ul_netaddrq_get_best_ipp(&(ie->nl), ifa_family, ++ &threshold, &best_ifaceq); ++ if (best_ipp) ++ fputs(best_ipp, ie->output); ++} + +- addr = NULL; +- switch (p->ifa_addr->sa_family) { +- case AF_INET: +- addr = &((struct sockaddr_in *) p->ifa_addr)->sin_addr; +- break; +- case AF_INET6: +- addr = &((struct sockaddr_in6 *) p->ifa_addr)->sin6_addr; +- break; ++static void dump_iface_good(struct issue *ie, ++ struct ul_netaddrq_iface *ifaceq) ++{ ++ struct ul_netaddrq_ip *best4[__ULNETLINK_RATING_MAX]; ++ struct ul_netaddrq_ip *best6[__ULNETLINK_RATING_MAX]; ++ struct list_head *li; ++ enum ul_netaddrq_ip_rating threshold = __ULNETLINK_RATING_MAX - 1; ++ enum ul_netaddrq_ip_rating fthreshold; /* per family threshold */ ++ bool first = true; ++ ++ memset(best4, 0, sizeof(best4)); ++ threshold = ul_netaddrq_iface_bestaddr(&(ifaceq->ip_quality_list_4), ++ &best4); ++ memset(best6, 0, sizeof(best6)); ++ fthreshold = ul_netaddrq_iface_bestaddr(&(ifaceq->ip_quality_list_6), ++ &best6); ++ if (fthreshold < threshold) ++ threshold = fthreshold; ++ ++ list_for_each(li, &(ifaceq->ip_quality_list_4)) ++ { ++ struct ul_netaddrq_ip *ipq; ++ ++ ipq = list_entry(li, struct ul_netaddrq_ip, entry); ++ if (threshold <= ULNETLINK_RATING_SCOPE_LINK && ++ ( ipq->quality <= threshold || ++ /* Consider site addresses equally good as global */ ++ ipq->quality == ULNETLINK_RATING_SCOPE_SITE) && ++ best4[threshold]) ++ { ++ if (first) ++ { ++ fprintf(ie->output, "%s: ", ifaceq->ifname); ++ first = false; ++ } ++ else ++ fprintf(ie->output, " "); ++ /* Write only the longest living temporary address */ ++ if (threshold == ULNETLINK_RATING_F_TEMPORARY) ++ { ++ fputs(ul_nl_addr_ntop_address(best4[ULNETLINK_RATING_F_TEMPORARY]->addr), ++ ie->output); ++ goto temp_cont4; ++ } ++ else ++ fputs(ul_nl_addr_ntop_address(ipq->addr), ++ ie->output); + } ++ temp_cont4:; ++ } + +- if (addr) { +- print_addr(ie, family, addr); +- return; ++ list_for_each(li, &(ifaceq->ip_quality_list_6)) ++ { ++ struct ul_netaddrq_ip *ipq; ++ ++ ipq = list_entry(li, struct ul_netaddrq_ip, entry); ++ if (threshold <= ULNETLINK_RATING_SCOPE_LINK && ++ ( ipq->quality <= threshold || ++ /* Consider site addresses equally good as global */ ++ ipq->quality == ULNETLINK_RATING_SCOPE_SITE) && ++ best6[threshold]) ++ { ++ if (first) ++ { ++ fprintf(ie->output, "%s: ", ifaceq->ifname); ++ first = false; ++ } ++ else ++ fprintf(ie->output, " "); ++ /* Write only the longest living temporary address */ ++ if (threshold == ULNETLINK_RATING_F_TEMPORARY) ++ { ++ fputs(ul_nl_addr_ntop_address(best6[ULNETLINK_RATING_F_TEMPORARY]->addr), ++ ie->output); ++ goto temp_cont6; ++ } ++ else ++ fputs(ul_nl_addr_ntop_address(ipq->addr), ++ ie->output); + } ++ temp_cont6:; + } ++ if (!first) ++ fputs("\n", ie->output); ++} + +- if (iface) +- return; ++static void dump_iface_all(struct issue *ie, ++ struct ul_netaddrq_iface *ifaceq) ++{ ++ struct list_head *li; ++ struct ul_netaddrq_ip *ipq; ++ bool first = true; + +- /* Hmm.. not found the best interface, print host IP at least */ +- memset(&hints, 0, sizeof(hints)); +- hints.ai_family = family; +- if (family == AF_INET6) +- hints.ai_flags = AI_V4MAPPED; +- +- host = xgethostname(); +- if (host && getaddrinfo(host, NULL, &hints, &info) == 0 && info) { +- switch (info->ai_family) { +- case AF_INET: +- addr = &((struct sockaddr_in *) info->ai_addr)->sin_addr; +- break; +- case AF_INET6: +- addr = &((struct sockaddr_in6 *) info->ai_addr)->sin6_addr; +- break; ++ list_for_each(li, &(ifaceq->ip_quality_list_4)) ++ { ++ ipq = list_entry(li, struct ul_netaddrq_ip, entry); ++ if (first) ++ { ++ fprintf(ie->output, "%s: ", ifaceq->ifname); ++ first = false; + } +- if (addr) +- print_addr(ie, family, addr); +- +- freeaddrinfo(info); ++ else ++ fprintf(ie->output, " "); ++ fputs(ul_nl_addr_ntop_address(ipq->addr), ie->output); ++ } ++ list_for_each(li, &(ifaceq->ip_quality_list_6)) ++ { ++ ipq = list_entry(li, struct ul_netaddrq_ip, entry); ++ if (first) ++ { ++ fprintf(ie->output, "%s: ", ifaceq->ifname); ++ first = false; ++ } ++ else ++ fprintf(ie->output, " "); ++ fputs(ul_nl_addr_ntop_address(ipq->addr), ie->output); + } +- free(host); ++ if (!first) ++ fputs("\n", ie->output); + } + + /* +@@ -2860,26 +2902,47 @@ static void output_special_char(struct issue *ie, + case '4': + case '6': + { +- sa_family_t family = c == '4' ? AF_INET : AF_INET6; +- struct ifaddrs *addrs = NULL; +- char iface[128]; +- +- if (getifaddrs(&addrs)) +- break; ++ char iface[IF_NAMESIZE]; ++ uint8_t ifa_family = c == '4' ? AF_INET : AF_INET6; + + if (get_escape_argument(fp, iface, sizeof(iface))) +- output_iface_ip(ie, addrs, iface, family); ++ print_iface_best(ie, iface, ifa_family); + else +- output_iface_ip(ie, addrs, NULL, family); +- +- freeifaddrs(addrs); ++ print_addrq_bestofall(ie, ifa_family); + ++ /* TODO: Move to pass 1 */ + if (c == '4') + netlink_groups |= RTMGRP_IPV4_IFADDR; + else + netlink_groups |= RTMGRP_IPV6_IFADDR; + break; + } ++ case 'a': ++ { ++ struct list_head *li; ++ struct ul_netaddrq_iface *ifaceq; ++ ++ list_for_each_netaddrq_iface(li, &(ie->nl)) ++ { ++ ifaceq = list_entry(li, struct ul_netaddrq_iface, entry); ++ ++ dump_iface_good(ie, ifaceq); ++ } ++ } ++ break; ++ case 'A': ++ { ++ struct list_head *li; ++ struct ul_netaddrq_iface *ifaceq; ++ ++ list_for_each_netaddrq_iface(li, &(ie->nl)) ++ { ++ ifaceq = list_entry(li, struct ul_netaddrq_iface, entry); ++ ++ dump_iface_all(ie, ifaceq); ++ } ++ } ++ break; + #endif + default: + putc(c, ie->output); +-- +2.48.1 + diff --git a/util-linux-agetty-ssh-host-keys.patch b/util-linux-agetty-ssh-host-keys.patch new file mode 100644 index 0000000..765353a --- /dev/null +++ b/util-linux-agetty-ssh-host-keys.patch @@ -0,0 +1,111 @@ +From 275a215e3ee02d8240d22b2bc61abcdd6b24c35f Mon Sep 17 00:00:00 2001 +From: Stanislav Brabec +Date: Fri, 8 Aug 2025 02:39:28 +0200 +Subject: [PATCH] agetty: Implement \k: print ssh host keys + +Implement new keyword \k that will print all ssh host keys. + +Signed-off-by: Stanislav Brabec +--- + term-utils/agetty.8.adoc | 3 +++ + term-utils/agetty.c | 56 ++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 59 insertions(+) + +diff --git a/term-utils/agetty.8.adoc b/term-utils/agetty.8.adoc +index 6670498f5..f84ec2e1e 100644 +--- a/term-utils/agetty.8.adoc ++++ b/term-utils/agetty.8.adoc +@@ -304,6 +304,9 @@ Insert the string "1 user" or " users" where is the number of current use + v:: + Insert the version of the OS, that is, the build-date and such. + ++k:: ++Print host ssh keys. ++ + An example. On my system, the following _/etc/issue_ file: + + .... +diff --git a/term-utils/agetty.c b/term-utils/agetty.c +index c37417e1e..d53882ef7 100644 +--- a/term-utils/agetty.c ++++ b/term-utils/agetty.c +@@ -33,6 +33,7 @@ + #include + #include + #include ++#include + #include + + #include "strutils.h" +@@ -2706,6 +2707,58 @@ static void dump_iface_all(struct issue *ie, + fputs("\n", ie->output); + } + ++#define SSH_KEYGEN_BUFFER_SIZE 512 ++void print_ssh_keys(FILE *fd) { ++ glob_t glob_result; ++ int rc; ++ ++ rc = glob("/etc/ssh/ssh_host_*_key.pub", 0, NULL, &glob_result); ++ if (rc != 0) { ++ if (rc == GLOB_NOMATCH) { ++ fprintf(fd, _("No SSH host keys found.\n")); ++ } ++ globfree(&glob_result); ++ return; ++ } ++ ++ for (size_t i = 0; i < glob_result.gl_pathc; i++) { ++ int pipefd[2]; ++ pid_t pid; ++ ++ if (pipe(pipefd) == -1) { ++ continue; ++ } ++ pid = fork(); ++ if (pid == -1) { ++ close(pipefd[0]); ++ close(pipefd[1]); ++ continue; ++ } ++ if (pid == 0) { ++ char *argv[] = {"ssh-keygen", "-l", "-f", glob_result.gl_pathv[i], NULL}; ++ ++ close(pipefd[0]); ++ dup2(pipefd[1], STDOUT_FILENO); ++ close(pipefd[1]); ++ execvp("ssh-keygen", argv); ++ return; ++ } else { ++ char buffer[SSH_KEYGEN_BUFFER_SIZE]; ++ ++ close(pipefd[1]); ++ wait(NULL); ++ if (fgets(buffer, sizeof(buffer), fdopen(pipefd[0], "r")) != NULL) { ++ char field2[SSH_KEYGEN_BUFFER_SIZE], field4[SSH_KEYGEN_BUFFER_SIZE]; ++ if (sscanf(buffer, "%*s %s %*s %s", field2, field4) == 2) { ++ fprintf(fd, _("SSH host key: %s %s\n"), field2, field4); ++ } ++ } ++ close(pipefd[0]); ++ } ++ } ++ globfree(&glob_result); ++ return; ++} + /* + * parses \x{argument}, if not argument specified then returns NULL, the @fd + * has to point to one char after the sequence (it means '{'). +@@ -2944,6 +2997,9 @@ static void output_special_char(struct issue *ie, + } + break; + #endif ++ case 'k': ++ print_ssh_keys(ie->output); ++ break; + default: + putc(c, ie->output); + break; +-- +2.48.1 + diff --git a/util-linux-lib-netlink.patch b/util-linux-lib-netlink.patch new file mode 100644 index 0000000..d4902e9 --- /dev/null +++ b/util-linux-lib-netlink.patch @@ -0,0 +1,1601 @@ +From 02b917ba6fa43908a39f15c9496de04910044c0e Mon Sep 17 00:00:00 2001 +From: Stanislav Brabec +Date: Wed, 9 Jul 2025 14:29:10 +0200 +Subject: [PATCH 1/2] New netlink library + +To support netlink and IP address processing, two new library files were +added: + +netlink: Generic netlink message processing code converting netlink +messages to calls of callbacks with a pre-processed data. + +netaddrq: A code that gets and maintains linked list of the current +interfaces and assigned IP addresses. It also provides a rating of IP +addresses based on its "quality", i. e. type of address, validity, lifetime +etc. + +Signed-off-by: Stanislav Brabec +--- + include/Makemodule.am | 2 + + include/netaddrq.h | 124 ++++++++ + include/netlink.h | 171 ++++++++++ + lib/Makemodule.am | 11 + + lib/meson.build | 2 + + lib/netaddrq.c | 716 ++++++++++++++++++++++++++++++++++++++++++ + lib/netlink.c | 465 +++++++++++++++++++++++++++ + 7 files changed, 1491 insertions(+) + create mode 100644 include/netaddrq.h + create mode 100644 include/netlink.h + create mode 100644 lib/netaddrq.c + create mode 100644 lib/netlink.c + +diff --git a/include/Makemodule.am b/include/Makemodule.am +index bdf87e221..4e310f0c4 100644 +--- a/include/Makemodule.am ++++ b/include/Makemodule.am +@@ -48,6 +48,8 @@ dist_noinst_HEADERS += \ + include/monotonic.h \ + include/mount-api-utils.h \ + include/namespace.h \ ++ include/netaddrq.h \ ++ include/netlink.h \ + include/nls.h \ + include/optutils.h \ + include/pager.h \ +diff --git a/include/netaddrq.h b/include/netaddrq.h +new file mode 100644 +index 000000000..6d5e655f5 +--- /dev/null ++++ b/include/netaddrq.h +@@ -0,0 +1,124 @@ ++/* ++ * Netlink address quality rating list builder ++ * ++ * Copyright (C) 2025 Stanislav Brabec ++ * ++ * This program is freely distributable. ++ * ++ * This set of netlink callbacks kernel and creates ++ * and/or maintains a linked list of requested type. Using callback fuctions ++ * and custom data, it could be used for arbitraty purpose. ++ * ++ */ ++ ++#ifndef UTIL_LINUX_NETADDRQ_H ++#define UTIL_LINUX_NETADDRQ_H ++ ++#include "netlink.h" ++ ++/* Specific return code */ ++#define UL_NL_IFACES_MAX 64 /* ADDR: Too many interfaces */ ++ ++/* Network address "quality". Higher means worse. */ ++enum ul_netaddrq_ip_rating { ++ ULNETLINK_RATING_SCOPE_UNIVERSE, ++ ULNETLINK_RATING_SCOPE_SITE, ++ ULNETLINK_RATING_F_TEMPORARY, ++ ULNETLINK_RATING_SCOPE_LINK, ++ ULNETLINK_RATING_BAD, ++ __ULNETLINK_RATING_MAX ++}; ++ ++/* Data structure in ul_nl_data You can use callback_pre for filtering events ++ * you want to get into the list, callback_post to check the processed data or ++ * use the list after processing ++ */ ++struct ul_netaddrq_data { ++ ul_nl_callback callback_pre; /* Function to process ul_netaddrq_data */ ++ ul_nl_callback callback_post; /* Function to process ul_netaddrq_data */ ++ void *callback_data; /* Arbitrary data for callback */ ++ struct list_head ifaces; /* The intefaces list */ ++ /* ifaces_change_* has to be changed by userspace when processed. */ ++ bool ifaces_change_4; /* Any changes in the IPv4 list? */ ++ bool ifaces_change_6; /* Any changes in the IPv6 list? */ ++ int nifaces; /* interface count */ ++ bool overflow; /* Too many interfaces? */ ++}; ++ ++/* List item for particular interface contains interface specific data and ++ * heads of two lists, one per each address family */ ++struct ul_netaddrq_iface { ++ struct list_head entry; ++ uint32_t ifa_index; ++ char *ifname; ++ struct list_head ip_quality_list_4; ++ struct list_head ip_quality_list_6; ++}; ++ ++/* Macro casting generic ul_nl_data->data_addr to struct ul_netaddrq_data */ ++#define UL_NETADDRQ_DATA(nl) ((struct ul_netaddrq_data*)((nl)->data_addr)) ++ ++/* list_for_each macro for intercaces */ ++#define list_for_each_netaddrq_iface(li, nl) list_for_each(li, &(UL_NETADDRQ_DATA(nl)->ifaces)) ++ ++/* List item for for a particular address contains information for IP quality ++ * evaluation and a copy of generic ul_nl_addr data */ ++struct ul_netaddrq_ip { ++ struct list_head entry; ++ enum ul_netaddrq_ip_rating quality; ++ struct ul_nl_addr *addr; ++}; ++ ++/* Initialize ul_nl_data for use with netlink-addr-quality ++ * callback: Process the data after updating the tree. If NULL, it just ++ * updates the tree and everything has to be processed outside. ++ */ ++int ul_netaddrq_init(struct ul_nl_data *nl, ul_nl_callback callback_pre, ++ ul_nl_callback callback_post, void *data); ++ ++/* Get best rating value from the ul_netaddrq_ip list ++ * ipq_list: List of IP addresses of a particular interface and family ++ * returns: ++ * best array: best ifa_valid lifetime seen per quality rating ++ * return value: best rating seen ++ * Note: It can be needed to call it twice: once for ip_quality_list_4, once ++ * for ip_quality_list_6. ++ */ ++enum ul_netaddrq_ip_rating ++ul_netaddrq_iface_bestaddr(struct list_head *ipq_list, ++ struct ul_netaddrq_ip *(*best)[__ULNETLINK_RATING_MAX]); ++ ++/* Get best rating value from the ifaces list (i. e. best address of all ++ * interfaces) ++ * returns: ++ * best_iface: interface where the best address was seen ++ * best array: best ifa_valid lifetime seen per quality rating ++ * return value: best rating seen ++ * Note: It can be needed to call it twice: once for ip_quality_list_4, once ++ * for ip_quality_list_6. ++ */ ++enum ul_netaddrq_ip_rating ++ul_netaddrq_bestaddr(struct ul_nl_data *nl, ++ struct ul_netaddrq_iface **best_iface, ++ struct ul_netaddrq_ip *(*best)[__ULNETLINK_RATING_MAX], ++ uint8_t ifa_family); ++ ++/* Get best rating value from the ul_netaddrq_ip list as a string ++ * ipq_list: List of IP addresses of a particular interface and family ++ * returns: ++ * return value: The best address as a string ++ * threshold: The best rating ever seen. ++ * best_ifaceq: The best rated interfece ever seen. ++ * Note: It can be needed to call it twice: once for AF_INET, once ++ * for AF_INET6. ++ */ ++const char *ul_netaddrq_get_best_ipp(struct ul_nl_data *nl, ++ uint8_t ifa_family, ++ enum ul_netaddrq_ip_rating *threshold, ++ struct ul_netaddrq_iface **best_ifaceq); ++ ++/* Find interface by name */ ++struct ul_netaddrq_iface *ul_netaddrq_iface_by_name(const struct ul_nl_data *nl, ++ const char *ifname); ++ ++#endif /* UTIL_LINUX_NETADDRQ_H */ +diff --git a/include/netlink.h b/include/netlink.h +new file mode 100644 +index 000000000..3d7c3da04 +--- /dev/null ++++ b/include/netlink.h +@@ -0,0 +1,171 @@ ++/* ++ * Netlink message processing ++ * ++ * Copyright (C) 2025 Stanislav Brabec ++ * ++ * This program is freely distributable. ++ * ++ * This set of functions processes netlink messages from the kernel socket, ++ * joins message parts into a single structure and calls callback. ++ * ++ * To do something useful, callback for a selected message type has to be ++ * defined. Using callback fuctions and custom data, it could be used for ++ * arbitraty purpose. ++ * ++ * The code is incomplete. More could be implemented as needed by its use ++ * cases. ++ * ++ */ ++ ++#ifndef UTIL_LINUX_NETLINK_H ++#define UTIL_LINUX_NETLINK_H ++ ++#include ++#include ++#include ++#include ++#include ++#include "list.h" ++ ++/* Return codes */ ++/* 0 means OK. ++ * Negative return codes indicate fatal errors. ++ */ ++ ++#define UL_NL_WOULDBLOCK 1 /* no data are ready (for asynchronous mode) */ ++#define UL_NL_DONE 2 /* processing reached NLMSG_DONE (for ++ * ul_nl_request_dump() */ ++#define UL_NL_RETURN 3 /* callback initiated immediate return; if you use ++ * it, keep in mind that further processing could ++ * reach unprocessed NLMSG_DONE */ ++#define UL_NL_SOFT_ERROR 4 /* soft error, indicating a race condition or ++ * message relating to events before program ++ * start); could be optionally ignored */ ++ ++struct ul_nl_data; ++ ++/* The callback of the netlink message header. ++ * Return code: Normally returns UL_NL_OK. In other cases, ++ * ul_nl_process() immediately exits with an error. ++ * Special return codes: ++ * UL_NL_RETURN: stopping further processing that does not mean an error ++ * (example: There was found interface or IP we were waiting for.) ++ * See nlmsghdr to see, what you can process here. ++ */ ++typedef int (*ul_nl_callback)(struct ul_nl_data *nl); ++ ++/* Structure for ADDR messages collects information from a single ifaddsmsg ++ * structure and all optional rtattr structures into a single structure ++ * containing all useful data. */ ++struct ul_nl_addr { ++/* values from ifaddrmsg or rtattr */ ++ uint8_t ifa_family; ++ uint8_t ifa_scope; ++ uint8_t ifa_index; ++ uint32_t ifa_flags; ++ void *ifa_address; /* IFA_ADDRESS */ ++ int ifa_address_len; /* size of IFA_ADDRESS data */ ++ void *ifa_local; /* IFA_LOCAL */ ++ int ifa_local_len; /* size of IFA_LOCAL data */ ++ char *ifname; /* interface from ifa_index as string */ ++ void *address; /* IFA_LOCAL, if defined, otherwise ++ * IFA_ADDRESS. This is what you want it most ++ * cases. See comment in linux/if_addr.h. */ ++ int address_len; /* size of address data */ ++ uint32_t ifa_prefered; /* ifa_prefered from IFA_CACHEINFO */ ++ uint32_t ifa_valid; /* ifa_valid from IFA_CACHEINFO */ ++ /* More can be implemented in future. */ ++}; ++ ++/* Values for rtm_event */ ++#define UL_NL_RTM_DEL false /* processing RTM_DEL_* */ ++#define UL_NL_RTM_NEW true /* processing RTM_NEW_* */ ++/* Checks for rtm_event */ ++#define UL_NL_IS_RTM_DEL(nl) (!(nl->rtm_event)) /* is it RTM_DEL_*? */ ++#define UL_NL_IS_RTM_NEW(nl) (nl->rtm_event) /* is it RTM_NEW_*? */ ++ ++struct ul_nl_data { ++ /* "static" part of the structure, filled once and kept */ ++ ul_nl_callback callback_addr; /* Function to process ul_nl_addr */ ++ void *data_addr; /* Arbitrary data of callback_addr */ ++ int fd; /* netlink socket FD, may be used externally ++ * for select() */ ++ ++ /* volatile part of the structure, filled by the current message */ ++ bool rtm_event; /* UL_NL_RTM_DEL or UL_NL_RTM_NEW */ ++ bool dumping; /* Dump in progress */ ++ ++ /* volatile part of the structure that depends on message typ */ ++ union { ++ /* ADDR */ ++ struct ul_nl_addr addr; ++ /* More can be implemented in future (LINK, ROUTE etc.). */ ++ }; ++}; ++ ++/* Initialize ul_nl_data structure */ ++void ul_nl_init(struct ul_nl_data *nl); ++ ++/* Open a netlink connection. ++ * nl_groups: Applies for monitoring. In case of ul_nl_request_dump(), ++ * use its argument to select one. ++ * ++ * Close and open vs. initial open with parameters? ++ * ++ * If we use single open with parameters, we can get mixed output due to race ++ * window between opening the socket and sending dump request. ++ * ++ * If we use close/open, we get a race window that could contain unprocessed ++ * events. ++ */ ++int ul_nl_open(struct ul_nl_data *nl, uint32_t nl_groups); ++ ++/* Close a netlink connection. */ ++int ul_nl_close(struct ul_nl_data *nl); ++ ++/* Synchronously sends dump request of a selected nlmsg_type. It does not ++ * perform any further actions. The result is returned through the callback. ++ * ++ * Under normal conditions, use ul_nl_process(nl, false, true); for processing ++ * the reply ++ */ ++int ul_nl_request_dump(struct ul_nl_data *nl, uint16_t nlmsg_type); ++ ++/* Values for async */ ++#define UL_NL_SYNC false /* synchronous mode */ ++#define UL_NL_ASYNC true /* asynchronous mode */ ++#define UL_NL_ONESHOT false /* return after processing message */ ++#define UL_NL_LOOP true /* wait for NLMSG_DONE */ ++/* Process netlink messages. ++ * async: If true, return UL_NL_WOULDBLOCK immediately if there is no data ++ * ready. If false, wait for a message. ++ * loop: If true, run in a loop until NLMSG_DONE is received. Returns after ++ * finishing a reply from ul_nl_request_dump(), otherwise it acts as an ++ * infinite loop. If false, it returns after processing one message. ++ */ ++int ul_nl_process(struct ul_nl_data *nl, bool async, bool loop); ++ ++/* Duplicate ul_nl_addr structure to a newly allocated memory */ ++struct ul_nl_addr *ul_nl_addr_dup (struct ul_nl_addr *addr); ++ ++/* Deallocate ul_nl_addr structure */ ++void ul_nl_addr_free (struct ul_nl_addr *addr); ++ ++/* Convert ul_nl_addr to string. ++ addr: ul_nl_addr structure ++ id: Which of 3 possible addresses should be converted? ++ * Returns static string, valid to next call. ++ */ ++#define UL_NL_ADDR_ADDRESS offsetof(struct ul_nl_addr, address) ++#define UL_NL_ADDR_IFA_ADDRESS offsetof(struct ul_nl_addr, ifa_address) ++#define UL_NL_ADDR_IFA_LOCAL offsetof(struct ul_nl_addr, ifa_local) ++/* Warning: id must be one of above. No checks are performed */ ++const char *ul_nl_addr_ntop (const struct ul_nl_addr *addr, int addrid); ++#define ul_nl_addr_ntop_address(addr)\ ++ ul_nl_addr_ntop(addr, UL_NL_ADDR_ADDRESS) ++#define ul_nl_addr_ntop_ifa_address(addr)\ ++ ul_nl_addr_ntop(addr, UL_NL_ADDR_IFA_ADDRESS) ++ #define ul_nl_addr_ntop_ifa_local(addr)\ ++ ul_nl_addr_ntop(addr, UL_NL_ADDR_IFA_LOCAL) ++ ++#endif /* UTIL_LINUX_NETLINK_H */ +diff --git a/lib/Makemodule.am b/lib/Makemodule.am +index bf24b6bee..4af6589d1 100644 +--- a/lib/Makemodule.am ++++ b/lib/Makemodule.am +@@ -1,4 +1,5 @@ + # ++ + # Use only LGPL or Public domain (preferred) code in libcommon, otherwise add + # your lib/file.c directly to the _SOURCES= of the target binary. + # +@@ -30,6 +31,8 @@ libcommon_la_SOURCES = \ + lib/mbsalign.c \ + lib/mbsedit.c\ + lib/md5.c \ ++ lib/netaddrq.c \ ++ lib/netlink.c \ + lib/pwdutils.c \ + lib/randutils.c \ + lib/sha1.c \ +@@ -91,6 +94,8 @@ check_PROGRAMS += \ + test_ismounted \ + test_pwdutils \ + test_mangle \ ++ test_netlink \ ++ test_netaddrq \ + test_randutils \ + test_remove_env \ + test_strutils \ +@@ -138,6 +143,12 @@ test_ismounted_LDADD = libcommon.la $(LDADD) + test_mangle_SOURCES = lib/mangle.c + test_mangle_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_MANGLE + ++test_netlink_SOURCES = lib/netlink.c ++test_netlink_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_NETLINK ++ ++test_netaddrq_SOURCES = lib/netaddrq.c lib/netlink.c ++test_netaddrq_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_NETADDRQ ++ + test_strutils_SOURCES = lib/strutils.c + test_strutils_CFLAGS = $(AM_CFLAGS) -DTEST_PROGRAM_STRUTILS + +diff --git a/lib/meson.build b/lib/meson.build +index 25febbc19..8734108a3 100644 +--- a/lib/meson.build ++++ b/lib/meson.build +@@ -17,6 +17,8 @@ lib_common_sources = ''' + mbsalign.c + mbsedit.c + md5.c ++ netaddrq.c ++ netlink.c + procfs.c + pwdutils.c + randutils.c +diff --git a/lib/netaddrq.c b/lib/netaddrq.c +new file mode 100644 +index 000000000..67a43cb85 +--- /dev/null ++++ b/lib/netaddrq.c +@@ -0,0 +1,716 @@ ++/* ++ * Netlink address quality rating list builder ++ * ++ * Copyright (C) 2025 Stanislav Brabec ++ * ++ * This program is freely distributable. ++ * ++ * This set of netlink callbacks kernel and creates ++ * and/or maintains a linked list of requested type. Using callback fuctions ++ * and custom data, it could be used for arbitraty purpose. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include "netaddrq.h" ++#include "list.h" ++#include "debug.h" ++ ++/* Maximal number of interfaces. The algorithm has a quadratic complexity, ++ * don't overflood it. */ ++const int max_ifaces = 12; ++ ++/* ++ * Debug stuff (based on include/debug.h) ++ */ ++#define ULNETADDRQ_DEBUG_HELP (1 << 0) ++#define ULNETADDRQ_DEBUG_INIT (1 << 1) ++#define ULNETADDRQ_DEBUG_ADDRQ (1 << 2) ++#define ULNETADDRQ_DEBUG_LIST (1 << 3) ++#define ULNETADDRQ_DEBUG_BEST (1 << 4) ++ ++#define ULNETADDRQ_DEBUG_ALL 0x1F ++ ++static UL_DEBUG_DEFINE_MASK(netaddrq); ++UL_DEBUG_DEFINE_MASKNAMES(netaddrq) = ++{ ++ { "all", ULNETADDRQ_DEBUG_ALL, "complete adddress processing" }, ++ { "help", ULNETADDRQ_DEBUG_HELP, "this help" }, ++ { "addrq", ULNETADDRQ_DEBUG_ADDRQ, "address rating" }, ++ { "list", ULNETADDRQ_DEBUG_LIST, "list processing" }, ++ { "best", ULNETADDRQ_DEBUG_BEST, "searching best address" }, ++ ++ { NULL, 0 } ++}; ++ ++#define DBG(m, x) __UL_DBG(netaddrq, ULNETADDRQ_DEBUG_, m, x) ++#define ON_DBG(m, x) __UL_DBG_CALL(netaddrq, ULNETADDRQ_DEBUG_, m, x) ++ ++#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(netaddrq) ++#include "debugobj.h" ++ ++static void netaddrq_init_debug(void) ++{ ++ if (netaddrq_debug_mask) ++ return; ++ ++ __UL_INIT_DEBUG_FROM_ENV(netaddrq, ULNETADDRQ_DEBUG_, 0, ++ ULNETADDRQ_DEBUG); ++ ++ ON_DBG(HELP, ul_debug_print_masks("ULNETADDRQ_DEBUG", ++ UL_DEBUG_MASKNAMES(netaddrq))); ++} ++ ++static inline enum ul_netaddrq_ip_rating ++evaluate_ip_quality(struct ul_nl_addr *addr) { ++ enum ul_netaddrq_ip_rating quality; ++ ++ switch (addr->ifa_scope) { ++ case RT_SCOPE_UNIVERSE: ++ quality = ULNETLINK_RATING_SCOPE_UNIVERSE; ++ break; ++ case RT_SCOPE_LINK: ++ quality = ULNETLINK_RATING_SCOPE_LINK; ++ break; ++ case RT_SCOPE_SITE: ++ quality = ULNETLINK_RATING_SCOPE_SITE; ++ break; ++ default: ++ quality = ULNETLINK_RATING_BAD; ++ break; ++ } ++ if (addr->ifa_flags & IFA_F_TEMPORARY) { ++ if (quality <= ULNETLINK_RATING_F_TEMPORARY) ++ quality = ULNETLINK_RATING_F_TEMPORARY; ++ } ++ return quality; ++} ++ ++#define DBG_CASE(x) case x: str = #x; break ++#define DBG_CASE_DEF8(x) default: snprintf(strx+2, 3, "%02hhx", x); str = strx; break ++static char *ip_rating_as_string(enum ul_netaddrq_ip_rating q) ++{ ++ char *str; ++ static char strx[5] = "0x"; ++ switch (q) { ++ DBG_CASE(ULNETLINK_RATING_SCOPE_UNIVERSE); ++ DBG_CASE(ULNETLINK_RATING_SCOPE_SITE); ++ DBG_CASE(ULNETLINK_RATING_F_TEMPORARY); ++ DBG_CASE(ULNETLINK_RATING_SCOPE_LINK); ++ DBG_CASE(ULNETLINK_RATING_BAD); ++ DBG_CASE_DEF8(q); ++ } ++ return str; ++} ++ ++/* Netlink callback evaluating the address quality and building the list of ++ * interface lists */ ++static int callback_addrq(struct ul_nl_data *nl) { ++ struct ul_netaddrq_data *addrq = UL_NETADDRQ_DATA(nl); ++ struct list_head *li, *ipq_list; ++ struct ul_netaddrq_iface *ifaceq = NULL; ++ struct ul_netaddrq_ip *ipq = NULL; ++ int rc; ++ bool *ifaces_change; ++ ++ DBG(LIST, ul_debugobj(addrq, "callback_addrq() for %s on %s", ++ ul_nl_addr_ntop_address(&(nl->addr)), ++ nl->addr.ifname)); ++ if (addrq->callback_pre) ++ { ++ DBG(LIST, ul_debugobj(addrq, "callback_pre")); ++ if ((rc = (*(addrq->callback_pre))(nl))) ++ DBG(LIST, ul_debugobj(nl, "callback_pre rc != 0")); ++ } ++ ++ /* Search for interface in ifaces */ ++ addrq->nifaces = 0; ++ ++ list_for_each(li, &(addrq->ifaces)) { ++ struct ul_netaddrq_iface *ifaceqq; ++ ifaceqq = list_entry(li, struct ul_netaddrq_iface, entry); ++ if (ifaceqq->ifa_index == nl->addr.ifa_index) { ++ ifaceq = ifaceqq; ++ DBG(LIST, ul_debugobj(ifaceq, ++ "%s found in addrq", ++ nl->addr.ifname)); ++ break; ++ } ++ addrq->nifaces++; ++ } ++ ++ if (ifaceq == NULL) { ++ if (nl->rtm_event) { ++ if (addrq->nifaces >= max_ifaces) { ++ DBG(LIST, ul_debugobj(addrq, ++ "too many interfaces")); ++ addrq->overflow = true; ++ return UL_NL_IFACES_MAX; ++ } ++ DBG(LIST, ul_debugobj(addrq, ++ "new ifa_index in addrq")); ++ if (!(ifaceq = malloc(sizeof(struct ul_netaddrq_iface)))) ++ return -ENOMEM; ++ INIT_LIST_HEAD(&(ifaceq->ip_quality_list_4)); ++ INIT_LIST_HEAD(&(ifaceq->ip_quality_list_6)); ++ ifaceq->ifa_index = nl->addr.ifa_index; ++ if (!(ifaceq->ifname = strdup(nl->addr.ifname))) ++ { ++ DBG(LIST, ul_debugobj(addrq, ++ "malloc() 2 failed")); ++ free(ifaceq); ++ return -1; ++ } ++ list_add_tail(&(ifaceq->entry), &(addrq->ifaces)); ++ DBG(LIST, ul_debugobj(ifaceq, ++ "new interface")); ++ } else { ++ /* Should never happen. */ ++ DBG(LIST, ul_debugobj(ifaceq, ++ "interface not found")); ++ return UL_NL_SOFT_ERROR; ++ } ++ } ++ if (nl->addr.ifa_family == AF_INET) { ++ ipq_list = &(ifaceq->ip_quality_list_4); ++ ifaces_change = &(addrq->ifaces_change_4); ++ } else { ++ /* if (nl->addr.ifa_family == AF_INET6) */ ++ ipq_list = &(ifaceq->ip_quality_list_6); ++ ifaces_change = &(addrq->ifaces_change_6); ++ } ++ ++ list_for_each(li, ipq_list) { ++ ipq = list_entry(li, struct ul_netaddrq_ip, entry); ++ if (ipq->addr->address_len == nl->addr.address_len) ++ if (!memcmp(ipq->addr->address, nl->addr.address, ++ nl->addr.address_len)) ++ break; ++ } ++ if (ipq == NULL) { ++ DBG(LIST, ul_debugobj(ipq_list, ++ "address not found in the list")); ++ } ++ ++ /* From now on, rc is return code */ ++ rc = 0; ++ if (UL_NL_IS_RTM_NEW(nl)) { ++ struct ul_nl_addr *addr; ++ ++ addr = ul_nl_addr_dup(&(nl->addr)); ++ if (!addr) { ++ rc = -ENOMEM; ++ goto error; ++ } ++ if (ipq == NULL) { ++ if (!(ipq = malloc(sizeof(struct ul_netaddrq_ip)))) ++ { ++ rc = -ENOMEM; ++ ul_nl_addr_free(addr); ++ goto error; ++ } ++ ipq->addr = addr; ++ list_add_tail(&(ipq->entry), ipq_list); ++ DBG(LIST, ul_debugobj(ipq, "new address")); ++ *ifaces_change = true; ++ } else { ++ DBG(LIST, ul_debugobj(addrq, "updating address data")); ++ ul_nl_addr_free(ipq->addr); ++ ipq->addr = addr; ++ } ++ ipq->quality = evaluate_ip_quality(addr); ++ DBG(ADDRQ, ++ ul_debugobj(addrq, "%s rating: %s", ++ ul_nl_addr_ntop_address(&(nl->addr)), ++ ip_rating_as_string(ipq->quality))); ++ } else { ++ /* UL_NL_RTM_DEL */ ++ if (ipq == NULL) ++ { ++ /* Should not happen. */ ++ DBG(LIST, ul_debugobj(nl, ++ "UL_NL_RTM_DEL: unknown address")); ++ return UL_NL_SOFT_ERROR; ++ } ++ /* Delist the address */ ++ DBG(LIST, ul_debugobj(ipq, "removing address")); ++ *ifaces_change = true; ++ list_del(&(ipq->entry)); ++ ul_nl_addr_free(ipq->addr); ++ free(ipq); ++ error: ++ if (list_empty(&(ifaceq->ip_quality_list_4)) && ++ list_empty(&(ifaceq->ip_quality_list_6))) { ++ DBG(LIST, ++ ul_debugobj(ifaceq, ++ "deleted last address, removing interface")); ++ list_del(&(ifaceq->entry)); ++ addrq->nifaces--; ++ free(ifaceq->ifname); ++ free(ifaceq); ++ } ++ } ++ if (!rc && addrq->callback_post) ++ { ++ DBG(LIST, ul_debugobj(addrq, "callback_post")); ++ if ((rc = (*(addrq->callback_post))(nl))) ++ DBG(LIST, ul_debugobj(nl, "callback_post rc != 0")); ++ } ++ return rc; ++} ++ ++/* Initialize ul_nl_data for use with netlink-addr-quality */ ++int ul_netaddrq_init(struct ul_nl_data *nl, ul_nl_callback callback_pre, ++ ul_nl_callback callback_post, void *data) ++{ ++ struct ul_netaddrq_data *addrq; ++ ++ netaddrq_init_debug(); ++ if (!(nl->data_addr = calloc(1, sizeof(struct ul_netaddrq_data)))) ++ return -ENOMEM; ++ nl->callback_addr = callback_addrq; ++ addrq = UL_NETADDRQ_DATA(nl); ++ addrq->callback_pre = callback_pre; ++ addrq->callback_post = callback_post; ++ addrq->callback_data = data; ++ INIT_LIST_HEAD(&(addrq->ifaces)); ++ DBG(LIST, ul_debugobj(addrq, "callback initialized")); ++ return 0; ++} ++ ++enum ul_netaddrq_ip_rating ++ul_netaddrq_iface_bestaddr(struct list_head *ipq_list, ++ struct ul_netaddrq_ip *(*best)[__ULNETLINK_RATING_MAX]) ++{ ++ struct list_head *li; ++ struct ul_netaddrq_ip *ipq; ++ enum ul_netaddrq_ip_rating threshold; ++ ++ threshold = ULNETLINK_RATING_BAD; ++ list_for_each(li, ipq_list) ++ { ++ ipq = list_entry(li, struct ul_netaddrq_ip, entry); ++ ++ if (!(*best)[ipq->quality] || ++ ipq->addr->ifa_valid > ++ (*best)[ipq->quality]->addr->ifa_valid) ++ { ++ DBG(BEST, ++ ul_debugobj((*best), "%s -> best[%s]", ++ ul_nl_addr_ntop_address(ipq->addr), ++ ip_rating_as_string(ipq->quality))); ++ (*best)[ipq->quality] = ipq; ++ } ++ ++ if (ipq->quality < threshold) ++ { ++ threshold = ipq->quality; ++ DBG(BEST, ++ ul_debug("threshold %s", ip_rating_as_string(threshold))); ++ ++ } ++ } ++ return threshold; ++} ++ ++enum ul_netaddrq_ip_rating ++ul_netaddrq_bestaddr(struct ul_nl_data *nl, ++ struct ul_netaddrq_iface **best_ifaceq, ++ struct ul_netaddrq_ip *(*best)[__ULNETLINK_RATING_MAX], ++ uint8_t ifa_family) ++{ ++ struct ul_netaddrq_data *addrq = UL_NETADDRQ_DATA(nl); ++ struct list_head *li; ++ struct ul_netaddrq_iface *ifaceq; ++ size_t ipqo; ++ enum ul_netaddrq_ip_rating threshold; ++ ++ if (ifa_family == AF_INET) { ++ ipqo = offsetof(struct ul_netaddrq_iface, ip_quality_list_4); ++ } else { ++ /* if (ifa_family == AF_INET6) */ ++ ipqo = offsetof(struct ul_netaddrq_iface, ip_quality_list_6); ++ } ++ ++ threshold = ULNETLINK_RATING_BAD; ++ list_for_each(li, &(addrq->ifaces)) ++ { ++ struct list_head *ipq_list; ++ enum ul_netaddrq_ip_rating t; ++ ++ ifaceq = list_entry(li, struct ul_netaddrq_iface, entry); ++ ++ ipq_list = (struct list_head*)((char*)ifaceq + ipqo); ++ ++ t = ul_netaddrq_iface_bestaddr(ipq_list, best); ++ if (t < threshold) ++ { ++ DBG(BEST, ++ ul_debugobj(*best, "best iface %s, threshold %hhd", ++ ifaceq->ifname, t)); ++ *best_ifaceq = ifaceq; ++ threshold = t; ++ } ++ } ++ return threshold; ++} ++ ++const char *ul_netaddrq_get_best_ipp(struct ul_nl_data *nl, ++ uint8_t ifa_family, ++ enum ul_netaddrq_ip_rating *threshold, ++ struct ul_netaddrq_iface **best_ifaceq) ++{ ++ struct ul_netaddrq_ip *best[__ULNETLINK_RATING_MAX]; ++ ++ memset(best, 0, sizeof(best)); ++ *threshold = ul_netaddrq_bestaddr(nl, best_ifaceq, &best, ifa_family); ++ if (best[*threshold]) ++ return ul_nl_addr_ntop_address(best[*threshold]->addr); ++ return NULL; ++} ++ ++struct ul_netaddrq_iface *ul_netaddrq_iface_by_name(const struct ul_nl_data *nl, ++ const char *ifname) ++{ ++ struct list_head *li; ++ struct ul_netaddrq_iface *ifaceq; ++ ++ list_for_each_netaddrq_iface(li, nl) ++ { ++ ifaceq = list_entry(li, struct ul_netaddrq_iface, entry); ++ ++ if (!strcmp(ifaceq->ifname, ifname)) ++ return ifaceq; ++ } ++ return NULL; ++} ++ ++#ifdef TEST_PROGRAM_NETADDRQ ++/* This test program shows several possibilities for cherry-picking of IP ++ * addresses based on its rating. But there are many more possibilities in the ++ * criteria selection. ADDRQ_MODE_GOOD is the most smart one. */ ++enum addrq_print_mode { ++ ADDRQ_MODE_BESTOFALL, /* Best address of all interfaces */ ++ ADDRQ_MODE_BEST, /* Best address per interface */ ++ ADDRQ_MODE_GOOD, /* All global or site addresses, if none, the ++ * longest living temporary, if none, link */ ++ ADDRQ_MODE_ALL /* All available addresses */ ++}; ++ ++/* In our example addrq->callback_data is a simple FILE *. In more complex ++ * programs it could be a pointer to an arbitrary struct */ ++#define netout (FILE *)(UL_NETADDRQ_DATA(nl)->callback_data) ++ ++/* This example uses separate threshold for IPv4 and IPv6, so the best IPv4 and ++ * best IPv6 addresses are printed. */ ++static void dump_iface_best(struct ul_nl_data *nl, ++ struct ul_netaddrq_iface *ifaceq) ++{ ++ struct ul_netaddrq_ip *best[__ULNETLINK_RATING_MAX]; ++ enum ul_netaddrq_ip_rating threshold; ++ bool first = true; ++ ++ memset(best, 0, sizeof(best)); ++ threshold = ul_netaddrq_iface_bestaddr(&(ifaceq->ip_quality_list_4), ++ &best); ++ if (best[threshold]) ++ { ++ fprintf(netout, "%s IPv4: %s", (first ? "best address" : " "), ++ ul_nl_addr_ntop_address(best[threshold]->addr)); ++ first = false; ++ } ++ memset(best, 0, sizeof(best)); ++ threshold = ul_netaddrq_iface_bestaddr(&(ifaceq->ip_quality_list_6), ++ &best); ++ if (best[threshold]) ++ { ++ fprintf(netout, "%s IPv6: %s", (first ? "best address" : " "), ++ ul_nl_addr_ntop_address(best[threshold]->addr)); ++ first = false; ++ } ++ if (!first) ++ fprintf(netout, " on interface %s\n", ++ ifaceq->ifname); ++} ++ ++/* This example uses common threshold for IPv4 and IPv6, so e. g. worse rated ++ * IPv6 are completely ignored. */ ++static void dump_iface_good(struct ul_nl_data *nl, ++ struct ul_netaddrq_iface *ifaceq) ++{ ++ struct ul_netaddrq_ip *best4[__ULNETLINK_RATING_MAX]; ++ struct ul_netaddrq_ip *best6[__ULNETLINK_RATING_MAX]; ++ struct list_head *li; ++ enum ul_netaddrq_ip_rating threshold = __ULNETLINK_RATING_MAX - 1; ++ enum ul_netaddrq_ip_rating fthreshold; /* per family threshold */ ++ bool first = true; ++ ++ memset(best4, 0, sizeof(best4)); ++ threshold = ul_netaddrq_iface_bestaddr(&(ifaceq->ip_quality_list_4), ++ &best4); ++ memset(best6, 0, sizeof(best6)); ++ fthreshold = ul_netaddrq_iface_bestaddr(&(ifaceq->ip_quality_list_6), ++ &best6); ++ if (fthreshold < threshold) ++ threshold = fthreshold; ++ ++ list_for_each(li, &(ifaceq->ip_quality_list_4)) ++ { ++ struct ul_netaddrq_ip *ipq; ++ ++ ipq = list_entry(li, struct ul_netaddrq_ip, entry); ++ if (threshold <= ULNETLINK_RATING_SCOPE_LINK && ++ ( ipq->quality <= threshold || ++ /* Consider site addresses equally good as global */ ++ ipq->quality == ULNETLINK_RATING_SCOPE_SITE) && ++ best4[threshold]) ++ { ++ if (first) ++ { ++ fprintf(netout, "%s: ", ifaceq->ifname); ++ first = false; ++ } ++ else ++ fprintf(netout, " "); ++ /* Write only the longest living temporary address */ ++ if (threshold == ULNETLINK_RATING_F_TEMPORARY) ++ { ++ fputs(ul_nl_addr_ntop_address(best4[ULNETLINK_RATING_F_TEMPORARY]->addr), ++ netout); ++ goto temp_cont4; ++ } ++ else ++ fputs(ul_nl_addr_ntop_address(ipq->addr), ++ netout); ++ } ++ temp_cont4:; ++ } ++ ++ list_for_each(li, &(ifaceq->ip_quality_list_6)) ++ { ++ struct ul_netaddrq_ip *ipq; ++ ++ ipq = list_entry(li, struct ul_netaddrq_ip, entry); ++ if (threshold <= ULNETLINK_RATING_SCOPE_LINK && ++ ( ipq->quality <= threshold || ++ /* Consider site addresses equally good as global */ ++ ipq->quality == ULNETLINK_RATING_SCOPE_SITE) && ++ best6[threshold]) ++ { ++ if (first) ++ { ++ fprintf(netout, "%s: ", ifaceq->ifname); ++ first = false; ++ } ++ else ++ fprintf(netout, " "); ++ /* Write only the longest living temporary address */ ++ if (threshold == ULNETLINK_RATING_F_TEMPORARY) ++ { ++ fputs(ul_nl_addr_ntop_address(best6[ULNETLINK_RATING_F_TEMPORARY]->addr), ++ netout); ++ goto temp_cont6; ++ } ++ else ++ fputs(ul_nl_addr_ntop_address(ipq->addr), ++ netout); ++ } ++ temp_cont6:; ++ } ++ if (!first) ++ fputs("\n", netout); ++} ++ ++static void dump_iface_all(struct ul_nl_data *nl, ++ struct ul_netaddrq_iface *ifaceq) ++{ ++ struct list_head *li; ++ struct ul_netaddrq_ip *ipq; ++ bool first = true; ++ ++ list_for_each(li, &(ifaceq->ip_quality_list_4)) ++ { ++ ipq = list_entry(li, struct ul_netaddrq_ip, entry); ++ if (first) ++ { ++ fprintf(netout, "%s: ", ifaceq->ifname); ++ first = false; ++ } ++ else ++ fprintf(netout, " "); ++ fputs(ul_nl_addr_ntop_address(ipq->addr), netout); ++ } ++ list_for_each(li, &(ifaceq->ip_quality_list_6)) ++ { ++ ipq = list_entry(li, struct ul_netaddrq_ip, entry); ++ if (first) ++ { ++ fprintf(netout, "%s: ", ifaceq->ifname); ++ first = false; ++ } ++ else ++ fprintf(netout, " "); ++ fputs(ul_nl_addr_ntop_address(ipq->addr), netout); ++ } ++ if (!first) ++ fputs("\n", netout); ++} ++ ++static void dump_addrq(struct ul_nl_data *nl, enum addrq_print_mode c) { ++ struct list_head *li; ++ struct ul_netaddrq_iface *ifaceq; ++ enum ul_netaddrq_ip_rating threshold; ++ ++ switch(c) ++ { ++ case ADDRQ_MODE_BESTOFALL: ++ { ++ struct ul_netaddrq_iface *best_ifaceq; ++ const char *best_ipp; ++ ++ best_ipp = ul_netaddrq_get_best_ipp(nl, AF_INET, ++ &threshold, &best_ifaceq); ++ if (best_ipp) ++ fprintf(netout, "best IPv4 address: %s on %s\n", ++ best_ipp, best_ifaceq->ifname); ++ best_ipp = ul_netaddrq_get_best_ipp(nl, AF_INET6, ++ &threshold, &best_ifaceq); ++ if (best_ipp) ++ fprintf(netout, "best IPv6 address: %s on %s\n", ++ best_ipp, best_ifaceq->ifname); ++ } ++ break; ++ case ADDRQ_MODE_BEST: ++ { ++ list_for_each_netaddrq_iface(li, nl) ++ { ++ ifaceq = list_entry(li, struct ul_netaddrq_iface, entry); ++ ++ dump_iface_best(nl, ifaceq); ++ } ++ } ++ break; ++ case ADDRQ_MODE_GOOD: ++ { ++ list_for_each_netaddrq_iface(li, nl) ++ { ++ ifaceq = list_entry(li, struct ul_netaddrq_iface, entry); ++ ++ dump_iface_good(nl, ifaceq); ++ } ++ } ++ break; ++ case ADDRQ_MODE_ALL: ++ list_for_each_netaddrq_iface(li, nl) ++ { ++ ifaceq = list_entry(li, struct ul_netaddrq_iface, entry); ++ ++ dump_iface_all(nl,ifaceq); ++ } ++ break; ++ } ++} ++ ++static int callback_post(struct ul_nl_data *nl) ++{ ++ /* If not processing dump, process the change immediatelly by the ++ * callback. */ ++ if (!nl->dumping) ++ { ++ /* If there is no change in the list, do nothing */ ++ if (!(UL_NETADDRQ_DATA(nl)->ifaces_change_4 || ++ UL_NETADDRQ_DATA(nl)->ifaces_change_6)) ++ { ++ fputs("\n\nNo changes in the address list.\n", netout); ++ return 0; ++ } ++ UL_NETADDRQ_DATA(nl)->ifaces_change_4 = false; ++ UL_NETADDRQ_DATA(nl)->ifaces_change_6 = false; ++ fputs("\n\nNetwork change detected:\n", netout); ++ fputs("\nbest address:\n", netout); ++ dump_addrq(nl, ADDRQ_MODE_BESTOFALL); ++ ++ fputs("\nbest addresses dump:\n", netout); ++ dump_addrq(nl, ADDRQ_MODE_BEST); ++ ++ fputs("\ngood addresses dump:\n", netout); ++ dump_addrq(nl, ADDRQ_MODE_GOOD); ++ ++ fputs("\nall addresses dump:\n", netout); ++ dump_addrq(nl, ADDRQ_MODE_ALL); ++ } ++ return 0; ++} ++ ++int main(int argc __attribute__((__unused__)), char *argv[] __attribute__((__unused__))) ++{ ++ int rc = 1; ++ int ulrc; ++ struct ul_nl_data nl; ++ FILE *out = stdout; ++ struct ul_netaddrq_iface *ifaceq; ++ const char *ifname = "eth0"; ++ ++ /* Prepare netlink. */ ++ ul_nl_init(&nl); ++ if ((ul_netaddrq_init(&nl, NULL, callback_post, (void *)out))) ++ return -1; ++ ++ /* Dump addresses */ ++ if (ul_nl_open(&nl, ++ RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR)) ++ return -1; ++ if (ul_nl_request_dump(&nl, RTM_GETADDR)) ++ goto error; ++ if (ul_nl_process(&nl, UL_NL_SYNC, UL_NL_LOOP) != UL_NL_DONE) ++ goto error; ++ fputs("RTM_GETADDR dump finished.", out); ++ ++ /* Example of several types of dump */ ++ fputs("\nbest address:\n", out); ++ dump_addrq(&nl, ADDRQ_MODE_BESTOFALL); ++ ++ fputs("\nbest addresses dump:\n", out); ++ dump_addrq(&nl, ADDRQ_MODE_BEST); ++ ++ fputs("\ngood addresses dump:\n", out); ++ dump_addrq(&nl, ADDRQ_MODE_GOOD); ++ ++ fputs("\nall addresses dump:\n", out); ++ dump_addrq(&nl, ADDRQ_MODE_ALL); ++ ++ fputs("\naddresses for interface ", out); ++ if ((ifaceq = ul_netaddrq_iface_by_name(&nl, ifname))) ++ dump_iface_all(&nl, ifaceq); ++ else ++ fprintf(out, "%s not found.", ifname); ++ ++ /* Monitor further changes */ ++ fputs("\nGoing to monitor mode.\n", out); ++ ++ /* In this example UL_NL_RETURN never appears, as callback does ++ * not use it. */ ++ ++ /* There are two different ways to create the loop: ++ * ++ * 1) Use UL_NL_LOOP and process the result in addrq->callback_post ++ * (optionally excluding events with nl->dumping set. (We can ++ * process dump output in the callback as well, but in many cases, ++ * single run after finishing the dump is a better solution than ++ * processing it after each message. ++ * ++ * 2) Make a loop, use UL_NL_ONESHOT, keep addrq->callback_post empty ++ * and process the result in the loop. ++ */ ++ ulrc = ul_nl_process(&nl, UL_NL_SYNC, UL_NL_LOOP); ++ if (!ulrc || ulrc == UL_NL_RETURN) ++ rc = 0; ++error: ++ if ((ul_nl_close(&nl))) ++ rc = 1; ++ return rc; ++} ++#endif /* TEST_PROGRAM_NETADDRQ */ +diff --git a/lib/netlink.c b/lib/netlink.c +new file mode 100644 +index 000000000..fbe04dd4c +--- /dev/null ++++ b/lib/netlink.c +@@ -0,0 +1,465 @@ ++/* ++ * Netlink message processing ++ * ++ * Copyright (C) 2025 Stanislav Brabec ++ * ++ * This program is freely distributable. ++ * ++ * This set of functions processes netlink messages from kernel and creates ++ * and/or maintains a linked list of requested type. Using callback fuctions ++ * and custom data, it could be used for arbitraty purpose. ++ * ++ * The code here just processes the netlink stream. To do something useful, ++ * callback for a selected message type has to be defined. ++ */ ++ ++#include ++#include ++#include ++#include "netlink.h" ++#include "debug.h" ++#include "nls.h" ++ ++/* ++ * Debug stuff (based on include/debug.h) ++ */ ++#define ULNETLINK_DEBUG_HELP (1 << 0) ++#define ULNETLINK_DEBUG_INIT (1 << 1) ++#define ULNETLINK_DEBUG_NLMSG (1 << 2) ++#define ULNETLINK_DEBUG_ADDR (1 << 3) ++ ++#define ULNETLINK_DEBUG_ALL 0x0F ++ ++static UL_DEBUG_DEFINE_MASK(netlink); ++UL_DEBUG_DEFINE_MASKNAMES(netlink) = ++{ ++ { "all", ULNETLINK_DEBUG_ALL, "complete netlink debugging" }, ++ { "help", ULNETLINK_DEBUG_HELP, "this help" }, ++ { "nlmsg", ULNETLINK_DEBUG_NLMSG, "netlink message debugging" }, ++ { "addr", ULNETLINK_DEBUG_ADDR, "netlink address processing" }, ++ ++ { NULL, 0 } ++}; ++ ++#define DBG(m, x) __UL_DBG(netlink, ULNETLINK_DEBUG_, m, x) ++#define ON_DBG(m, x) __UL_DBG_CALL(netlink, ULNETLINK_DEBUG_, m, x) ++ ++#define UL_DEBUG_CURRENT_MASK UL_DEBUG_MASK(netlink) ++#include "debugobj.h" ++ ++static void netlink_init_debug(void) ++{ ++ if (netlink_debug_mask) ++ return; ++ ++ __UL_INIT_DEBUG_FROM_ENV(netlink, ULNETLINK_DEBUG_, 0, ULNETLINK_DEBUG); ++ ++ ON_DBG(HELP, ul_debug_print_masks("ULNETLINK_DEBUG", ++ UL_DEBUG_MASKNAMES(netlink))); ++} ++ ++void ul_nl_init(struct ul_nl_data *nl) { ++ netlink_init_debug(); ++ memset(nl, 0, sizeof(struct ul_nl_data)); ++} ++ ++int ul_nl_request_dump(struct ul_nl_data *nl, uint16_t nlmsg_type) { ++ struct { ++ struct nlmsghdr nh; ++ struct rtgenmsg g; ++ } req; ++ ++ memset(&req, 0, sizeof(req)); ++ req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.g)); ++ req.nh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; ++ req.nh.nlmsg_type = nlmsg_type; ++ req.g.rtgen_family = AF_NETLINK; ++ nl->dumping = true; ++ DBG(NLMSG, ul_debugobj(nl, "sending dump request")); ++ if (send(nl->fd, &req, req.nh.nlmsg_len, 0) < 0) ++ return -1; ++ return 0; ++} ++ ++#define DBG_CASE(x) case x: str = #x; break ++#define DBG_CASE_DEF8(x) default: snprintf(strx+2, 3, "%02hhx", x); str = strx; break ++static void dbg_addr(struct ul_nl_data *nl) ++{ ++ char *str; ++ char strx[5] = "0x"; ++ switch (nl->addr.ifa_family) { ++ DBG_CASE(AF_INET); ++ DBG_CASE(AF_INET6); ++ DBG_CASE_DEF8(nl->addr.ifa_family); ++ } ++ DBG(ADDR, ul_debug(" ifa_family: %s", str)); ++ switch (nl->addr.ifa_scope) { ++ DBG_CASE(RT_SCOPE_UNIVERSE); ++ DBG_CASE(RT_SCOPE_SITE); ++ DBG_CASE(RT_SCOPE_LINK); ++ DBG_CASE(RT_SCOPE_HOST); ++ DBG_CASE(RT_SCOPE_NOWHERE); ++ DBG_CASE_DEF8(nl->addr.ifa_scope); ++ } ++ DBG(ADDR, ul_debug(" ifa_scope: %s", str)); ++ DBG(ADDR, ul_debug(" interface: %s (ifa_index %u)", ++ nl->addr.ifname, nl->addr.ifa_index)); ++ DBG(ADDR, ul_debug(" ifa_flags: 0x%02x", nl->addr.ifa_flags)); ++} ++ ++/* Expecting non-zero nl->callback_addr! */ ++static int process_addr(struct ul_nl_data *nl, struct nlmsghdr *nh) ++{ ++ struct ifaddrmsg *ifaddr; ++ struct rtattr *attr; ++ int len; ++ static char ifname[IF_NAMESIZE]; ++ bool has_local_address = false; ++ ++ DBG(ADDR, ul_debugobj(nh, "processing nlmsghdr")); ++ memset(&(nl->addr), 0, sizeof(struct ul_nl_addr)); ++ ++ /* Process ifaddrmsg. */ ++ ifaddr = NLMSG_DATA(nh); ++ ++ nl->addr.ifa_family = ifaddr->ifa_family; ++ nl->addr.ifa_scope = ifaddr->ifa_scope; ++ nl->addr.ifa_index = ifaddr->ifa_index; ++ if ((if_indextoname(ifaddr->ifa_index, ifname))) ++ nl->addr.ifname = ifname; ++ else ++ { ++ /* There can be race, we do not return error here */ ++ /* FIXME I18N: *"unknown"* is too generic. Use context. */ ++ /* TRANSLATORS: unknown network interface, maximum 15 ++ * (IF_NAMESIZE-1) bytes */ ++ nl->addr.ifname = _("unknown"); ++ } ++ nl->addr.ifa_flags = (uint32_t)(ifaddr->ifa_flags); ++ /* If IFA_CACHEINFO is not present, suppose permanent addresses. */ ++ nl->addr.ifa_valid = UINT32_MAX; ++ ON_DBG(ADDR, dbg_addr(nl)); ++ ++ /* Process rtattr. */ ++ len = nh->nlmsg_len - NLMSG_LENGTH(sizeof(*ifaddr)); ++ for (attr = IFA_RTA(ifaddr); RTA_OK(attr, len); ++ attr = RTA_NEXT(attr, len)) { ++ /* Proces most common rta attributes */ ++ DBG(ADDR, ul_debugobj(attr, "processing rtattr")); ++ switch (attr->rta_type) { ++ case IFA_ADDRESS: ++ nl->addr.ifa_address = RTA_DATA(attr); ++ nl->addr.ifa_address_len = RTA_PAYLOAD(attr); ++ if (!has_local_address) { ++ nl->addr.address = RTA_DATA(attr); ++ nl->addr.address_len = RTA_PAYLOAD(attr); ++ } ++ DBG(ADDR, ++ ul_debug(" IFA_ADDRESS%s: %s", ++ (has_local_address ? "" : ++ " (setting address)"), ++ ul_nl_addr_ntop_ifa_address(&(nl->addr)))); ++ break; ++ case IFA_LOCAL: ++ /* Point to Point interface listens has local address ++ * and listens there */ ++ has_local_address = true; ++ nl->addr.ifa_local = nl->addr.address = RTA_DATA(attr); ++ nl->addr.ifa_local_len = ++ nl->addr.address_len = RTA_PAYLOAD(attr); ++ DBG(ADDR, ++ ul_debug(" IFA_LOCAL (setting address): %s", ++ ul_nl_addr_ntop_ifa_local(&(nl->addr)))); ++ break; ++ case IFA_CACHEINFO: ++ { ++ struct ifa_cacheinfo *ci = ++ (struct ifa_cacheinfo *)RTA_DATA(attr); ++ nl->addr.ifa_valid = ci->ifa_valid; ++ DBG(ADDR, ++ ul_debug(" IFA_CACHEINFO: ifa_prefered = %u," ++ "ifa_valid = %u", ++ nl->addr.ifa_prefered, ++ nl->addr.ifa_valid)); ++ } ++ break; ++ case IFA_FLAGS: ++ nl->addr.ifa_flags = *(uint32_t *)(RTA_DATA(attr)); ++ DBG(ADDR, ul_debug(" IFA_FLAGS: 0x%08x", ++ nl->addr.ifa_flags)); ++ break; ++ default: ++ DBG(ADDR, ul_debug(" rta_type = 0x%04x", ++ attr->rta_type)); ++ break; ++ } ++ } ++ DBG(NLMSG, ul_debugobj(nl, "callback %p", nl->callback_addr)); ++ return (*(nl->callback_addr))(nl); ++} ++ ++static int process_msg(struct ul_nl_data *nl, struct nlmsghdr *nh) ++{ ++ int rc = 0; ++ ++ nl->rtm_event = UL_NL_RTM_DEL; ++ switch (nh->nlmsg_type) { ++ case RTM_NEWADDR: ++ nl->rtm_event = UL_NL_RTM_NEW; ++ /* fallthrough */ ++ case RTM_DELADDR: ++ /* If callback_addr is not set, skip process_addr */ ++ DBG(NLMSG, ul_debugobj(nl, "%s", ++ (UL_NL_IS_RTM_DEL(nl) ? ++ "RTM_DELADDR" : "RTM_NEWADDR"))); ++ if (nl->callback_addr) ++ rc = process_addr(nl, nh); ++ break; ++ /* More can be implemented in future (RTM_NEWLINK, RTM_DELLINK etc.). */ ++ default: ++ DBG(NLMSG, ul_debugobj(nl, "nlmsg_type = %hu", nh->nlmsg_type)); ++ break; ++ ++ } ++ return rc; ++} ++ ++int ul_nl_process(struct ul_nl_data *nl, bool async, bool loop) ++{ ++ char buf[BUFSIZ]; ++ struct sockaddr_nl snl; ++ struct nlmsghdr *nh; ++ int rc; ++ uint32_t len; ++ ++ struct iovec iov = { ++ .iov_base = buf, ++ .iov_len = sizeof(buf) ++ }; ++ struct msghdr msg = { ++ .msg_name = &snl, ++ .msg_namelen = sizeof(snl), ++ .msg_iov = &iov, ++ .msg_iovlen = 1, ++ .msg_control = NULL, ++ .msg_controllen = 0, ++ .msg_flags = 0 ++ }; ++ ++ while (1) { ++ DBG(NLMSG, ul_debugobj(nl, "waiting for message")); ++ rc = recvmsg(nl->fd, &msg, (loop ? 0 : ++ (async ? MSG_DONTWAIT : 0))); ++ DBG(NLMSG, ul_debugobj(nl, "got message")); ++ if (rc < 0) { ++ if (errno == EWOULDBLOCK || errno == EAGAIN) { ++ DBG(NLMSG, ++ ul_debugobj(nl, "no data")); ++ return UL_NL_WOULDBLOCK; ++ } ++ /* Failure, just stop listening for changes */ ++ nl->dumping = false; ++ DBG(NLMSG, ul_debugobj(nl, "error")); ++ return rc; ++ } ++ else ++ len = rc; ++ ++ for (nh = (struct nlmsghdr *)buf; ++ NLMSG_OK(nh, len); ++ nh = NLMSG_NEXT(nh, len)) { ++ ++ if (nh->nlmsg_type == NLMSG_ERROR) { ++ DBG(NLMSG, ul_debugobj(nl, "NLMSG_ERROR")); ++ nl->dumping = false; ++ return -1; ++ } ++ if (nh->nlmsg_type == NLMSG_DONE) { ++ DBG(NLMSG, ++ ul_debugobj(nl, "NLMSG_DONE")); ++ nl->dumping = false; ++ return UL_NL_DONE; ++ } ++ ++ rc = process_msg(nl, nh); ++ if (rc) ++ { ++ DBG(NLMSG, ++ ul_debugobj(nl, ++ "process_msg() returned %d", ++ rc)); ++ return rc; ++ } ++ } ++ if (!loop) ++ return 0; ++ DBG(NLMSG, ul_debugobj(nl, "looping until NLMSG_DONE")); ++ } ++} ++ ++int ul_nl_open(struct ul_nl_data *nl, uint32_t nl_groups) ++{ ++ struct sockaddr_nl addr = { 0, }; ++ int sock; ++ int rc; ++ ++ DBG(NLMSG, ul_debugobj(nl, "opening socket")); ++ sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); ++ if (sock < 0) ++ return sock; ++ addr.nl_family = AF_NETLINK; ++ addr.nl_pid = getpid(); ++ addr.nl_groups = nl_groups; ++ if ((rc = bind(sock, (struct sockaddr *)&addr, sizeof(addr))) >= 0) ++ { ++ nl->fd = sock; ++ return 0; ++ } ++ else ++ { ++ close(sock); ++ return rc; ++ } ++} ++ ++int ul_nl_close(struct ul_nl_data *nl) { ++ DBG(NLMSG, ul_debugobj(nl, "closing socket")); ++ return close(nl->fd); ++} ++ ++struct ul_nl_addr *ul_nl_addr_dup (struct ul_nl_addr *addr) { ++ struct ul_nl_addr *newaddr; ++ newaddr = calloc(1, sizeof(struct ul_nl_addr)); ++ if (!newaddr) ++ goto error; ++ memcpy(newaddr, addr, sizeof(struct ul_nl_addr)); ++ if (addr->ifa_address_len) { ++ newaddr->ifa_address = malloc(addr->ifa_address_len); ++ if (!newaddr->ifa_address) ++ goto error; ++ memcpy(newaddr->ifa_address, addr->ifa_address, ++ addr->ifa_address_len); ++ } ++ if (addr->ifa_local_len) { ++ newaddr->ifa_local = malloc(addr->ifa_local_len); ++ if (!newaddr->ifa_local) ++ goto error; ++ memcpy(newaddr->ifa_local, addr->ifa_local, ++ addr->ifa_local_len); ++ } ++ if (&(addr->ifa_address) == &(addr->ifa_local)) ++ newaddr->address = newaddr->ifa_local; ++ else ++ newaddr->address = newaddr->ifa_address; ++ if (!(newaddr->ifname = strdup(addr->ifname))) ++ goto error; ++ return newaddr; ++error: ++ ul_nl_addr_free(newaddr); ++ return NULL; ++} ++ ++void ul_nl_addr_free (struct ul_nl_addr *addr) { ++ if (addr) { ++ free(addr->ifa_address); ++ free(addr->ifa_local); ++ free(addr->ifname); ++ free(addr); ++ } ++} ++ ++const char *ul_nl_addr_ntop (const struct ul_nl_addr *addr, int addrid) { ++ const void **ifa_addr = (const void **)((const char *)addr + addrid); ++ /* (INET6_ADDRSTRLEN-1) + (IF_NAMESIZE-1) + strlen("%") + 1 */ ++ static char addr_str[INET6_ADDRSTRLEN+IF_NAMESIZE]; ++ ++ if (addr->ifa_family == AF_INET) ++ return inet_ntop(AF_INET, ++ *ifa_addr, addr_str, sizeof(addr_str)); ++ else { ++ /* if (addr->ifa_family == AF_INET6) */ ++ if (addr->ifa_scope == RT_SCOPE_LINK) { ++ char *p; ++ ++ inet_ntop(AF_INET6, ++ *ifa_addr, addr_str, sizeof(addr_str)); ++ p = addr_str; ++ while (*p) p++; ++ *p++ = '%'; ++ strncpy(p, addr->ifname, IF_NAMESIZE); ++ return addr_str; ++ } else ++ return inet_ntop(AF_INET6, ++ *ifa_addr, addr_str, sizeof(addr_str)); ++ } ++} ++ ++#ifdef TEST_PROGRAM_NETLINK ++#include ++ ++static int callback_addr(struct ul_nl_data *nl) { ++ char *str; ++ ++ printf("%s address:\n", ((nl->rtm_event ? "Add" : "Delete"))); ++ printf(" interface: %s\n", nl->addr.ifname); ++ if (nl->addr.ifa_family == AF_INET) ++ printf(" IPv4 %s\n", ++ ul_nl_addr_ntop_address(&(nl->addr))); ++ else ++ /* if (nl->addr.ifa_family == AF_INET) */ ++ printf(" IPv6 %s\n", ++ ul_nl_addr_ntop_address(&(nl->addr))); ++ switch (nl->addr.ifa_scope) { ++ case RT_SCOPE_UNIVERSE: str = "global"; break; ++ case RT_SCOPE_SITE: str = "site"; break; ++ case RT_SCOPE_LINK: str = "link"; break; ++ case RT_SCOPE_NOWHERE: str = "nowhere"; break; ++ default: str = "other"; break; ++ } ++ printf(" scope: %s\n", str); ++ if (nl->addr.ifa_valid != UINT32_MAX) ++ printf(" valid: %u\n", nl->addr.ifa_valid); ++ else ++ printf(" valid: forever\n"); ++ return 0; ++} ++ ++int main(int argc __attribute__((__unused__)), ++ char *argv[] __attribute__((__unused__))) ++{ ++ int rc; ++ struct ul_nl_data nl; ++ ++ /* Prepare netlink. */ ++ ul_nl_init(&nl); ++ nl.callback_addr = callback_addr; ++ ++ /* Dump addresses */ ++ if ((rc = ul_nl_open(&nl, 0))) ++ return 1; ++ if (ul_nl_request_dump(&nl, RTM_GETADDR)) ++ goto error; ++ if (ul_nl_process(&nl, UL_NL_SYNC, UL_NL_LOOP) != UL_NL_DONE) ++ goto error; ++ puts("RTM_GETADDR dump finished."); ++ ++ /* Close and later open. See note in the ul_nl_open() docs. */ ++ if ((rc = ul_nl_close(&nl)) < 0) ++ goto error; ++ ++ /* Monitor further changes */ ++ puts("Going to monitor mode."); ++ if ((rc = ul_nl_open(&nl, RTMGRP_LINK | ++ RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR))) ++ goto error; ++ /* In this example UL_NL_ABORT never appears, as callback does ++ * not use it. */ ++ rc = ul_nl_process(&nl, UL_NL_SYNC, UL_NL_LOOP); ++ if (rc == UL_NL_RETURN) ++ rc = 0; ++error: ++ if ((ul_nl_close(&nl))) ++ rc = 1; ++ return rc; ++} ++#endif /* TEST_PROGRAM_NETLINK */ +-- +2.48.1 + diff --git a/util-linux-login_defs-check.sh b/util-linux-login_defs-check.sh index bc0b921..ec43bf2 100644 --- a/util-linux-login_defs-check.sh +++ b/util-linux-login_defs-check.sh @@ -15,7 +15,7 @@ echo -n "Checking login.defs variables in util-linux... " >&2 sed -n 's/^.*logindefs_setenv*("[A-Z0-9_]*", "\([A-Z0-9_]*\)".*$/\1/p' ) | LC_ALL=C sort -u >util-linux-login_defs-vars.lst -if test $(sha1sum util-linux-login_defs-vars.lst | sed 's/ .*$//') != 8516ca34e059b2dee1d4421a3ab07e88331b1674 ; then +if test $(sha1sum util-linux-login_defs-vars.lst | sed 's/ .*$//') != 713b442bf6d16353b7f74538ece165b424f90932 ; then echo "does not match!" >&2 echo "Checksum is: $(sha1sum util-linux-login_defs-vars.lst | sed 's/ .*$//')" >&2 @@ -32,10 +32,13 @@ If it is false positive: - The same fix is needed in shadow package in shadow-login_defs-check.sh. If it is true positive: -- Check-out shadow package and call shadow-login_defs-check.sh. -- Compare its output shadow-login_defs-check-util-linux.lst with - util-linux-login_defs-vars.lst in the util-linux build directory. + +- Check-out shadow package and call shadow-login_defs-check.sh. If it + fails, check the output. - Update shadow shadow-login_defs-util-linux.patch, if needed. +- Verify that the new variable is included in FOREIGNDEFS in lib/getdef.c. + If not, add it to shadow-login_defs-util-linux.patch and send the chunk + to the upstream. - If shadow-login_defs-util-linux.patch was updated, update login_defs-support-for-util-linux symbol version in both shadow and util-linux spec files accordingly. diff --git a/util-linux-rpmlintrc b/util-linux-rpmlintrc deleted file mode 100644 index fc942ab..0000000 --- a/util-linux-rpmlintrc +++ /dev/null @@ -1,7 +0,0 @@ -# False positives. Libraries outside LD_LIBRARY_PATH use RPATH to find libraries, not ldconfig. -addFilter("library-without-ldconfig-postin /usr/libexec/build/staging/.*") -addFilter("library-without-ldconfig-postun /usr/libexec/build/staging/.*") -# Not applicable for multi flavor build. -addFilter("invalid-spec-name") -# Not important, and it simplifies packaging. -addFilter("non-etc-or-var-file-marked-as-conffile /usr/libexec/build/staging/.*") diff --git a/util-linux.changes b/util-linux.changes index b85a22f..a6d2332 100644 --- a/util-linux.changes +++ b/util-linux.changes @@ -1,3 +1,302 @@ +------------------------------------------------------------------- +Mon Aug 11 23:54:47 UTC 2025 - Stanislav Brabec + +- Implement escape code for printing of ssh host keys in agetty + issue file (util-linux-agetty-ssh-host-keys.patch. +- Include fixes from + https://github.com/util-linux/util-linux/pull/3649 (jsc#PED-8734, + util-linux-lib-netlink.patch, util-linux-agetty-netlink.patch). + +------------------------------------------------------------------- +Thu Jul 24 10:35:23 UTC 2025 - Dr. Werner Fink + +- For bash 5.3 add (SIG)INT tests/expected/kill/decode as ignored + signal for asynchronous coprocesses (boo#1246830) + +------------------------------------------------------------------- +Thu Jul 10 02:39:17 UTC 2025 - Stanislav Brabec + +- agetty: Implement netlink based IP address detection and issue + reload. It makes possible to identify IP addresses usability and + prefer stable global addresses over ephemeral or link-local + addresses. New issue keywords \a and \A were added. (boo#1139983, + jsc#PED-8734, util-linux-lib-netlink.patch, + util-linux-agetty-netlink.patch) + +------------------------------------------------------------------- +Tue Jun 24 22:41:18 UTC 2025 - Stanislav Brabec + +- Update to version 2.41.1: + * cfdisk: fix memory leak and possible NULL dereference + * fdisk: fix possible memory leak + * findmnt: fix -k option parsing regression (boo#1242705, + drop util-linux-libblkid-econf-parse.patch) + * hardlink: fix performance regression + * include/cctype: fix string comparison + * libblkid: + * Fix crash while parsing config with libeconf + * befs fix underflow + * avoid strcasecmp() for ASCII-only strings + * libblkid/src/topology/dm: fix fscanf return value check to + match expected number of parsed items + * libmount: + * (subdir) restrict for real mounts only + * (subdir) remove unused code + * avoid calling memset() unnecessarily + * fix --no-canonicalize regression (boo#1244251, + drop libmount-fix-no-canonicalize-regression.patch) + * lsblk: + * use ID_PART_ENTRY_SCHEME as fallback for PTTYPE + * avoid strcasecmp() for ASCII-only strings + * lscpu: + * fix possible buffer overflow in cpuinfo parser + * Fix loongarch op-mode output with recent kernel + * lsfd: + * scan the protocol field of /proc/net/packet as a hex number + * fix the description for PACKET.PROTOCOL column + * lsns: + * enhance compilation without USE_NS_GET_API + * fix undefined reference to add_namespace_for_nsfd #3483 + * more: + * fix broken ':!command' command key + * fix implicit previous shell_line execution #3508 + * tests: (test_mkfds::mapped-packet-socket) add a new parameter, + protocol + * treewide: + * add ul_ to parse_timestamp() function name + (drop util-linux-rename-common-symbols-4.patch) + * add ul_ to parse_switch() function name + (drop util-linux-rename-common-symbols-3.patch) + * add ul_ to parse_size() function name + (drop util-linux-rename-common-symbols-2.patch) + * add ul_ to parse_range() function name + (drop util-linux-rename-common-symbols-1.patch) + * fix optional arguments usage + * avoid strcasecmp() for ASCII-only strings + * Wipefs: improve --all descriptions for whole-disks + * Misc: Do not call exit() on code ending in shared libraries + * Other fixes. For complete list see + https://kernel.org/pub/linux/utils/util-linux/v2.41/v2.41.1-ReleaseNotes +- Fix problem with uname26 listed twice. + +------------------------------------------------------------------- +Tue Jun 10 11:16:10 UTC 2025 - Nicolas Belouin + +- Fix libmount --no-canonicalize regression (boo#1244251, + gh#util-linux/util-linux#3479, + libmount-fix-no-canonicalize-regression.patch). + +------------------------------------------------------------------- +Thu May 29 15:21:59 UTC 2025 - Stanislav Brabec + +- Add ul_ prefix to functions with common names. Fixes btrfsprogs + build failure (gh#util-linux/util-linux#3603, + util-linux-rename-common-symbols-1.patch, + util-linux-rename-common-symbols-2.patch, + util-linux-rename-common-symbols-3.patch, + util-linux-rename-common-symbols-4.patch). + +------------------------------------------------------------------- +Tue May 20 14:27:14 UTC 2025 - Stanislav Brabec + +- Fix segfault of findmnt (boo#1242705, + gh#util-linux/util-linux#3574, + util-linux-libblkid-econf-parse.patch). + +------------------------------------------------------------------- +Thu Apr 17 18:33:03 UTC 2025 - Stanislav Brabec + +- Enable mountfd support again (jsc#PED-9752). + BREAKING CHANGE + Mountfd is nearly completely compatible with the old mount. There + is a special case that cannot be handled by mountfd, and it needs + to be handled by applications: + Mountfd discriminates between physical mount layer and virtual + mount layer. Once the physical mount layer is read-only, + read-write mount on the virtual layer is not possible. + If the first mount is read only, then the physical filesystem is + mounted read-only, and later mount of the same file system as + read-write is not possible. To solve this problem, the first + mount needs to be read-only only on the virtual layer, keeping + the physical layer read-write. + The user space fix is simple: + Instead of + mount -oro + use + mount -oro=vfs + This will keep the physical layer read-write, but the virtual + file system layer (and the user space access) will be read-only. + +------------------------------------------------------------------- +Mon Apr 7 21:00:58 UTC 2025 - Stanislav Brabec + +- Update to version 2.41: + * agetty: Fixed an issue where issue files were not being printed + from additional locations, such as /run or /usr/lib. This + change now allows for the use of local information from /etc, + in addition to generated files from /run and + distribution-specific files from /usr/lib. + * cfdisk and sfdisk: Added support for the --sector-size command + line option. + * sfdisk: Added a new option, --discard-free. + * fdisk: Added a new command, 'T', to discard sectors. + * chrt: The --sched-runtime now supports SCHED_{OTHER,BATCH} + policies. + * column: Can now handle ANSI SGR colors inside OSC 8 hyperlink + escape codes and sequences. + * enosys: Can now dump defined filters. + * libmount: + * Added experimental support for statmount() and listmount() + syscalls. + * This new functionality can be accessed using "findmnt + --kernel=listmount". + * Added a new mount option, + X-mount.nocanonicalize[=source|target]. + * Added new mount extensions to the "ro" flag (ro[=vfs,fs]). + * Added a new option, X-mount.noloop, to disable automatic loop + device creation. + * Now supports bind symlinks over symlinks. + * Reads all kernel info/warning/error messages from new API + syscalls (and mount(8) prints them). + * libuuid: Now supports RFC9562 UUIDs. + * findmnt, lsblk, and lsfd: Added a new --hyperlink command line + option to print paths as terminal hyperlinks. + * findmnt: Can now address filesystems using --id and --uniq-id + (requires listmount() kernel support). + * flock: Added support for the --fcntl command line option. + * hardlink: Can now prioritize specified trees on the command + line using --prioritize-trees. + * Can exclude sub-trees using --exclude-subtree or keep them in + the current mount using --mount. + * Duplicates can now be printed using --list-duplicates. + * hwclock: Added a new --param-index option to address position + for RTC_PARAM_{GET,SET} ioctls. + * kill: Can now decode signal masks (e.g. as used in /proc) to + signal names. + * libblkid: Made many changes to improve detection, including + exfat, GPT, LUKS2, bitlocker, etc. + * login: Added support for LOGIN_ENV_SAFELIST in /etc/login.def. + * lsfd: Now supports pidfs and AF_VSOCK sockets. + * lsipc, ipcmk, ipcrm: Now supports POSIX ipc. + * lslogins: Now supports lastlog2. + * lsns: Added support for the --filter option. + * build by meson: Now supports translated man pages and has fixed + many bugs. + * mkswap: The option --file should now be usable on btrfs. + * nsenter: Improved support for pidfd and can now join target + process's socket net namespace. + * scriptlive: Added a new option, --echo . + * zramctl: Now supports COMP-RATIO and --algorithm-params. + * Many other new features and fixes. For complete list see + https://kernel.org/pub/linux/utils/util-linux/v2.41/v2.41-ReleaseNotes +- Update util-linux-login_defs-check.sh: + * Make instructions up to date. + * Update checksum reflecting the shadow update. +- Refresh libmount-print-a-blacklist-hint-for-unknown-filesyst.patch. + +------------------------------------------------------------------- +Mon Feb 24 17:16:49 UTC 2025 - Jan Engelhardt + +- Delete /usr/sbin/rc* symlinks +- Drop bashisms from build recipe + +------------------------------------------------------------------- +Tue Jan 28 23:05:38 UTC 2025 - Stanislav Brabec + +- Create and own directories /etc/blkid.conf.d and + /usr/etc/blkid.conf.d (boo#1235887#c3). +- Add missingok for /etc/blkid.conf. + +------------------------------------------------------------------- +Mon Jan 20 18:12:46 UTC 2025 - Stanislav Brabec + +- Move blkid.conf to /usr/etc (boo#1235887). + +------------------------------------------------------------------- +Tue Jan 14 22:29:47 UTC 2025 - Stanislav Brabec + +- Update to version 2.40.4: + * agetty: Prevent cursor escape (bsc#1194818, drop + util-linux-agetty-prevent-cursor-escape.patch) + add "systemd" to --version output\ + * chcpu(8): Document CPU deconfiguring behavior + * fdisk: SGI fixes + * hardlink: fix memory corruption + * hardlink.1 directory|file is mandatory + * lib/env: fix env_list_setenv() for strings without '=' + * libblkid: + (exfat) validate fields used by prober + (gpt) use blkid_probe_verify_csum() for partition array + checksum + add FSLASTBLOCK for swaparea + bitlocker fix version on big-endian systems + * libfdisk: make sure libblkid uses the same sector size + * libmount: + extract common error handling function + propagate first error of multiple filesystem types + * logger: correctly format tv_usec + * lscpu: Skip aarch64 decode path for rest of the architectures + (bsc#1229476, drop util-linux-lscpu-skip-aarch64-decode.patch) + * lsns: ignore ESRCH errors reported when accessing files under + /proc + * mkswap: set selinux label also when creating file + * more: make sure we have data on stderr + * nsenter: support empty environ + * umount, losetup: Document loop destroy behavior + (bsc#1159034, drop + util-linux-umount-losetup-lazy-destruction.patch, + util-linux-umount-losetup-lazy-destruction-generated.patch). + * uuidd: fix /var/lib/libuuid mode uuidd-tmpfiles.conf + fix /var/lib/libuuid mode uuidd-tmpfiles.conf + * Many other fixes, improvements and code cleanup. For the + complete list see + https://mirrors.edge.kernel.org/pub/linux/utils/util-linux/v2.40/v2.40.3-ReleaseNotes + https://mirrors.edge.kernel.org/pub/linux/utils/util-linux/v2.40/v2.40.4-ReleaseNotes + - Refresh util-linux.keyring. Key validity was extended. + +------------------------------------------------------------------- +Sun Nov 17 21:06:01 UTC 2024 - Stanislav Brabec + +- Skip aarch64 decode path for rest of the architectures + (bsc#1229476, util-linux-lscpu-skip-aarch64-decode.patch). +- agetty: Prevent login cursor escape (bsc#1194818, + util-linux-agetty-prevent-cursor-escape.patch). +- Document unexpected side effects of lazy destruction + (bsc#1159034, util-linux-umount-losetup-lazy-destruction.patch, + util-linux-umount-losetup-lazy-destruction-generated.patch). + +------------------------------------------------------------------- +Mon Oct 21 23:25:19 UTC 2024 - Stanislav Brabec + +- Disable mountfd API again. + (https://github.com/util-linux/util-linux/issues/3158) + +------------------------------------------------------------------- +Mon Aug 5 22:14:13 UTC 2024 - Stanislav Brabec + +- Update to version 2.40.2: + * cfdisk: fix possible integer overflow + * libmount: improving robustness in reading kernel messages, + add pidfs to pseudo fs list + * lscpu: New Arm Cortex part numbers + fix hang of lscpu -e (bsc#1225598) + * lsfd: Refactor the pidfd logic, support pidfs + (obsoletes + 0001-include-Include-unistd.h-in-pidfd-utils.h-for-syscal.patch, + 0002-lsfd-Refactor-the-pidfd-logic-into-lsfd-pidfd.c.patch, + 0003-lsfd-Support-pidfs.patch, + 0004-lsfd-test-Adapt-test-cases-for-pidfs.patch) + * mkswap.8.adoc: update note regarding swapfile creation + * setpgid: make -f work + * Many other fixes, improvements and code cleanup. For the + complete list see + https://mirrors.edge.kernel.org/pub/linux/utils/util-linux/v2.40/v2.40.2-ReleaseNotes +- Enable kernel mountfd API, as it should be already stable + (PED-9752). +- Move autoreconf back to %build. +- Add devel dependencies. +- Remove util-linux-rpmlintrc. It is no more needed with multibuild. + ------------------------------------------------------------------- Tue Jul 16 13:25:03 UTC 2024 - Valentin Lefebvre @@ -80,7 +379,7 @@ Thu Apr 4 12:27:18 UTC 2024 - Thorsten Kukuk * blockdev: add support for BLKGETZONESZ * cfdisk: ask y/n before wipe * cfdisk: properly handle out-of-order partitions during resize - * chcpu: document limitations of -g + * chcpu: document limitations of -g (bsc#1218609) * chsh: use libeconf to read /etc/shells * column: fix -l * column: fix memory leak @@ -103,10 +402,12 @@ Thu Apr 4 12:27:18 UTC 2024 - Thorsten Kukuk * lsblk: add --highlight * lsblk: add --list-columns * lsclocks: new command to show clocks - * lscpu: even more Arm part numbers + * lscpu: even more Arm part numbers (bsc#1223605) * mkfs.minix: handle 64bit time on 32bit system * mkswap: implement --file * mkswap: implement --offset + * more: clean processes not cleaned up after failed SSH session + using up 100% CPU (bsc#1220117) * mount: add --map-users and --map-groups convenience options * nsenter: add option `-c` to join the cgroup of target process * setarch: add riscv64/riscv32 support @@ -114,6 +415,8 @@ Thu Apr 4 12:27:18 UTC 2024 - Thorsten Kukuk * uuidd: add cont_clock persistence * uuidgen: add option --count * wall: query logind for list of users with tty (#2088) + properly neutralize escape sequences (bsc#1221831, + CVE-2024-28085) * write: query logind for list of users with tty (#2088) * libuuid: improved support for 64-bit time - skip-lsfd-tests-PR2888.patch: skip some lsfd tests which OBS does @@ -233,6 +536,7 @@ Fri Jul 28 14:47:15 UTC 2023 - Goldwyn Rodrigues - Re-add 0001-Revert-libblkid-try-LUKS2-first-when-probing.patch because the patch is not in 2.39.1 + (bsc#1224393, boo#1213227, boo#1213361) ------------------------------------------------------------------- Wed Jul 26 01:22:20 UTC 2023 - Neil Brown diff --git a/util-linux.keyring b/util-linux.keyring index a34bbee..658fd17 100644 --- a/util-linux.keyring +++ b/util-linux.keyring @@ -1,9 +1,10 @@ -pub 4096R/EC39C284 2011-10-10 [expires: 2016-10-08] -uid Karel Zak -sub 4096R/7BA16CAC 2011-10-10 [expires: 2016-10-08] +pub rsa4096 2011-10-10 [SC] [expires: 2033-05-14] + B0C64D14301CC6EFAEDF60E4E4B71D5EEC39C284 +uid [ unknown] Karel Zak +sub rsa4096 2011-10-10 [E] [expires: 2033-05-14] + FC0214B4E2D01655F5898228ED846C667BA16CAC -----BEGIN PGP PUBLIC KEY BLOCK----- -Version: GnuPG v2.0.20 (GNU/Linux) mQINBE6StA4BEACp9++Y+DgbBloJEuVhsDjDIvAR1n/aHPDyPQQzg/DkKtR3BXHn dGfTL9/DR8y9YzLNwUf2lWsEAvwHZ2XfUTp5S5nVbgpAB0/Q2ebP0TnkNYaRkxq7 @@ -16,41 +17,42 @@ Q+MQyA1L5FifkpA7+akITF5luOqUb2TToEBLiF/nn8y0sIUa/HGgcUrK2N9E1VNJ tcIt/z0sZJUHYC+EBh/G0UNt9tRwPdnUks5sua1sCquXnkd9IS0Kr3Kq/C6JOKzz UDGdFKVc6wExf70hX5h0g1kkypyjNwipGSdk+qVXO0IF/tKMToa8WZqoK3enzryI Kmdq7IQ0ThdTTTC1ctVk4367/30prpNHF4/642G0OOiQCzWBrb0V217HvQARAQAB -tBtLYXJlbCBaYWsgPGt6YWtAcmVkaGF0LmNvbT6JAj4EEwECACgFAk6StA4CGwMF -CQlmAYAGCwkIBwMCBhUIAgkKCwQWAgMBAh4BAheAAAoJEOS3HV7sOcKETI8QAI0U -StG6dv1l9kqkmFpXPZJ75hf5SJA69+upcKeTg9BXKrEqjZLeyEn1OVPmfVGwWpz8 -SRbiYcHh8AhJaggAxKcIgQ/sAUBkmrTP6RyYEQUV6vFW5qv7dcEOs46d+LE/Wkxs -ymC2FSXxYOFiw7z27gkXQYq/IkdwfhRLFD6aD5egxcBVl91ZlRyklvPPW7qo046B -MWh2LaCVowYg+33GjS4A4JcF+tGkWZc0yqANwov93uhY0VXEdDsT4YWrTVdNDI3/ -lZ5u5k9sEUGR03oV336M/j0qNtMfAG5iDt2PFrzhJZcGcQPiGd/DeYuJeLVCd8Nl -jAwsnfGqu9VITgaDOreLbpSTNSj8egPqOoUBAGncMSfdiQ2ZEhluPyCTwspDy5Pa -dESyk0q2Z6tKG9ae01g/RzfTGCVN4GxkORPxcEHloa4XM3C9FBohM1LTWmsvnNXe -cPdbPyCoiFKio/yFZDt1CYDkols4uf/0ztCSH6pI874sCmYJVkYev2W9RCxE4aYK -KMb8XCUPec8L4C1mpmBiej0NT/d9GHQUnJUpRD4EG7UBxMwNWflhO4P4Q81uM0Kr -RMht0lS0EylZXuQPPG5C1nMsa1+eN0wjSTpy7232PTCxu+bhxA1HzvWXYuueUmp2 -QuV8PyA1lsfYI7PEgk2skfAvbP5vJszorklo2hVGuQINBE6StA4BEADG5Hind61Y -qoXXHotraJO2ejsPiy3BxSZTQet+IJO5tyURSXVIv+ZuV/MBRS/88fkBL2nHpK5b -BtJT11D2ZESmziZWGgMtZRV4va3fh3GaMeVdi5pXpmPZp4fBc60F3iCKfd1V8/1a -zwicZtdhTphkc6O7ETCr240OrJoOgvilbpv8WuVwhjfEOL2DwKITK6tzba1VScXi -ehDhhTssP14RQiH/OcMFuiHCHJeHQOH9ku4fzqT2/lxxSo4kMWKR2VslW17f3Zr3 -Zvrbi/b8UE/3T/RsoaQn2ml9BfDiMgNwT4l2ILlE7HpZMfD2WAP6itGHolcdbhNa -jxAMHdP5t64zSdwKmB8AbuIo7nbMKuJMiPdkOS/8x3YHRle4WEEeRWTEcqyzqkMq -MCqKLxc4SCuSMv+ingDrHr+d5usuMlQjT8c71PIipl9OpM8Jkl8CI2ToVF20wijY -Oof4T/jjObYiZk1KcqqKhQzMXEhKCt9hK5AaKMq5BiublS/Q5EXpzcRgVmG+SMHd -hUNLN7gilFx5939Ev+36TNE/f66r9aiF+WbiI1V1JGs0LYVyFzwmFMCgQUsnyqyA -RNREnLysdLE98PDSO2ESxu9BO7kTvlP0q5p+MKQiYj/s5wSqXw8EDCSBH9u0/FQi -gyV0a+J70WZZNpdi5wq+qVZ16LENQdxtKwARAQABiQIlBBgBAgAPBQJOkrQOAhsM -BQkJZgGAAAoJEOS3HV7sOcKEtCYP/3ji7Kt4+M0N6IOkh7wHfWk3HLqBa1XOD1Oz -X+rp79L1cDK8J1XUHoW/84bsS8Y3NsXlIej1wLOcaH0HOpEsPzqoqP1JxGilRkAu -Yazt3WhqdM2FcOQNEnuk66F8HnN/mD4vLzxdxuPlRtlCruUcDZlZlyzpywk6B7Gi -cVfh1CvUJsDA++aOlgYIHB4Z6nSJWYp64z+5QAVToBHzI7ywVyWTJbjO1RCR5QsV -fPD07p1deSW96QhqLSb2wQfk93I4YGshaVPwG01ZamxPEbspKqrEIG+5S6E4q/B+ -VF0zj5GU7jt/6M4qFzKbaY+vxsaqjgCOCPL5bCz5RFTHdtEmC/cmsvVbYuBD/5UY -D3JbyXt7KSG/a5Oel4ynK1pRQbnS6eNcGQqZPUop4PBouRKnUqv8uzljaiL9Wm3G -Hv9tn1L6ly86VcLt1ALTVuqwm5ci1fDVbddSliPv5atWNJ+So2MfEg3qbCzEC8Is -JNsVd4N6fSctYfFvBxsPjy7fw1iEqKq7SzTlHMO5hiKpS+8HSRVv6djHlj3aWtgy -u+BTXT/tRQ6c3TlZadqoyumX1U+Tflb6qMyJaZPsqv3bsOpXwjLAVfT4nPRXqbN6 -WWUhdompzuZufyCCL9Tc6lPDgVmuWyycHk4gbdfERodk4SEYJ0cEwFbl+GjL9XFZ -VeljfCzq -=8wc5 +tBtLYXJlbCBaYWsgPGt6YWtAcmVkaGF0LmNvbT6JAlUEEwEIAD8CGwMGCwkIBwMC +BhUIAgkKCwQWAgMBAh4BAheAFiEEsMZNFDAcxu+u32Dk5LcdXuw5woQFAmRkpsEF +CSid9bMACgkQ5LcdXuw5woSvFA/+LYBDPSubYZF4lS8lNlWwRNe1gP9VrCkaF5mb +Psx7cV8eHQICR/QJD7WDSIHlnfaBCmNelwGRc76PB23Huvq8xwvTYiQDWdqIN4Vl +gDLqSganq53poN1BNBFRP6ubsdGjHlSYH+ygf8XXL/h+/b9Ud61eb43XQ5sUhFQU +kEZEiJ26rCwOQWnlyJBMzo0axwW55qKoMmNjcwtRrB5kW7QDZ8BHVZiEqVrjJxQh +0NpwrjlFdRZ8Ak+/3Iz1UOIJl+p0+5sjx6iY7Xy7oKP28msdLcqRy/o58A+IHGQ1 +Y5DeeosNkatnT9+7TQdAxJdJzuLpH9WTmXyIoG1DP8p2A2fjvLpYMEomWKYBb6ci +1MZaI1Kdw9BX3xfaI0OhRDArLN0Nu630jTLafwf+jv8OtNEtb2BnbHWIrQw0rRc7 +877VRu7wmyTroj5IJthuEVr+NWw9RqCMljO/SDoR3GzE+2xSvKQlZU//vIBdHiVV +u4IbKhHlYELFMiqmNWoqlTFwstpP+TgYYtwf0Xuhv/1Y7PezI8Zvp8/pjYwmjCg6 +CdtTZMSonFBmMuA5hA/5Eg2KEwplHUfz/XgCe4wNv21tnvUY7QuHU2ZvlInEFACM +/4L1cIz7/PmQke7z7WvXP0d2pCMtfTra0RqH1nneNgq0VAhGnyI5qpK+n+hSYTqV +3VQNNQi5Ag0ETpK0DgEQAMbkeKd3rViqhdcei2tok7Z6Ow+LLcHFJlNB634gk7m3 +JRFJdUi/5m5X8wFFL/zx+QEvacekrlsG0lPXUPZkRKbOJlYaAy1lFXi9rd+HcZox +5V2LmlemY9mnh8FzrQXeIIp93VXz/VrPCJxm12FOmGRzo7sRMKvbjQ6smg6C+KVu +m/xa5XCGN8Q4vYPAohMrq3NtrVVJxeJ6EOGFOyw/XhFCIf85wwW6IcIcl4dA4f2S +7h/OpPb+XHFKjiQxYpHZWyVbXt/dmvdm+tuL9vxQT/dP9GyhpCfaaX0F8OIyA3BP +iXYguUTselkx8PZYA/qK0YeiVx1uE1qPEAwd0/m3rjNJ3AqYHwBu4ijudswq4kyI +92Q5L/zHdgdGV7hYQR5FZMRyrLOqQyowKoovFzhIK5Iy/6KeAOsev53m6y4yVCNP +xzvU8iKmX06kzwmSXwIjZOhUXbTCKNg6h/hP+OM5tiJmTUpyqoqFDMxcSEoK32Er +kBooyrkGK5uVL9DkRenNxGBWYb5Iwd2FQ0s3uCKUXHn3f0S/7fpM0T9/rqv1qIX5 +ZuIjVXUkazQthXIXPCYUwKBBSyfKrIBE1EScvKx0sT3w8NI7YRLG70E7uRO+U/Sr +mn4wpCJiP+znBKpfDwQMJIEf27T8VCKDJXRr4nvRZlk2l2LnCr6pVnXosQ1B3G0r +ABEBAAGJAjwEGAEIACYCGwwWIQSwxk0UMBzG767fYOTktx1e7DnChAUCZGSnpQUJ +KJ32lwAKCRDktx1e7DnChJ0gD/wOsq5mWpoSvBmhI/Xa4WOl2D9ltYYJxrQpdTd+ +IhUuQUCZle/z6dSO2jsrpcvnwn3OHBQguvX8vzBr2qFeQDjrAbZ4V76teY2Zg+5o +HFy21TlCFrNeFLrk4m8PyCrfQVoQ55ESLPUBp0qK5VLzKjFAHD7+Z90bEie6IGUc +rJyUokTNc7hSWsQk96rAENE6cxCgsEGZhtsS41iBZ4lkhkaVfpPj/yxiiOFw101N +G1PXq+EAJ6iaIvBbNiy+AI9EFVAkX6uMM8INely0HAn/H3/hc/xADiUFolPiIEwK +E04L/8KHVH5vn007kUeMD4DecvL+8XYyT18+jH/Hvpai610uWwfBP50HoKa9A8JD +ppBZaqBGzeNvCMUWU6rxyLoNOeJduUwFTh3mUesBoF7Iqdpe1CphuUlOKWUYY6Jq +ZEZ7oQN630z0QsLzr6YZinnnfMbO9xNktfJPBMju6UPmOHfYI/zJFRe5VTqvG8w3 +SxJVriqs75jFpGqSC/a7IcW3j2FeVQ66sAcik4XRA9JO7SpsTJtebAw0tQ8nIkIj +ekmNJnNAlIKOnisKca9QRzuUn5HNPl8UDeN9KjxsFkmDMEkRSuijpLEFe+66bkjP +NdEeAGQJbiXWb1z9vHHQpDPKMXhK18D5PBnLDIl8iFnpDE+M088Xnavf9eYapj2k +Zh9rvA== +=cevX -----END PGP PUBLIC KEY BLOCK----- diff --git a/util-linux.spec b/util-linux.spec index 5322172..e8fc51a 100644 --- a/util-linux.spec +++ b/util-linux.spec @@ -1,7 +1,7 @@ # # spec file for package util-linux # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) 2025 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -85,19 +85,18 @@ Group: Development/Languages/Python %endif # ulbuild == python -Version: 2.40.1 +Version: 2.41.1 Release: 0 License: GPL-2.0-or-later URL: https://www.kernel.org/pub/linux/utils/util-linux/ -Source: https://www.kernel.org/pub/linux/utils/util-linux/v2.40/util-linux-%{version}.tar.xz +Source: https://www.kernel.org/pub/linux/utils/util-linux/v2.41/util-linux-%{version}.tar.xz Source2: util-linux-login_defs-check.sh -Source3: util-linux-rpmlintrc Source7: baselibs.conf Source8: login.pamd Source9: remote.pamd Source10: su.pamd Source11: su.default -Source12: https://www.kernel.org/pub/linux/utils/util-linux/v2.39/util-linux-%{version}.tar.sign +Source12: https://www.kernel.org/pub/linux/utils/util-linux/v2.41/util-linux-%{version}.tar.sign Source13: %{_name}.keyring Source14: runuser.pamd Source15: runuser-l.pamd @@ -111,10 +110,12 @@ Patch2: Add-documentation-on-blacklisted-modules-to-mount-8-.patch # PATCH-FIX-SUSE util-linux-bash-completion-su-chsh-l.patch bsc1172427 -- Fix "su -s" bash completion. Patch3: util-linux-bash-completion-su-chsh-l.patch Patch5: static_lib.patch -Patch6: 0001-include-Include-unistd.h-in-pidfd-utils.h-for-syscal.patch -Patch7: 0002-lsfd-Refactor-the-pidfd-logic-into-lsfd-pidfd.c.patch -Patch8: 0003-lsfd-Support-pidfs.patch -Patch9: 0004-lsfd-test-Adapt-test-cases-for-pidfs.patch +# PATCH-FEATURE-UPSTREAM util-linux-lib-netlink.patch boo1139983 jsc#PED-8734 sbrabec@suse.com -- Implement netlink based IP address detection and issue reload. +Patch6: util-linux-lib-netlink.patch +# PATCH-FEATURE-UPSTREAM util-linux-agetty-netlink.patch boo1139983 jsc#PED-8734 sbrabec@suse.com -- Implement netlink based IP address detection and issue reload. +Patch7: util-linux-agetty-netlink.patch +# PATCH-FEATURE-OPENSUSE util-linux-agetty-ssh-host-keys.patch sbrabec@suse.com -- Implement escape code for printing of ssh host keys in agetty issue file. +Patch8: util-linux-agetty-ssh-host-keys.patch BuildRequires: audit-devel BuildRequires: bc BuildRequires: binutils-devel @@ -190,7 +191,8 @@ Supplements: filesystem(minix) # All login.defs variables require support from shadow side. # Upgrade this symbol version only if new variables appear! # Verify by shadow-login_defs-check.sh from shadow source package. -Recommends: login_defs-support-for-util-linux >= 2.37 +# Use downstream version. Upstream may accept the patch later. +Recommends: login_defs-support-for-util-linux >= 4.17.4 %endif # ulsubset == core @@ -280,7 +282,10 @@ Library for filesystem detection. Summary: Development files for the filesystem detection library License: LGPL-2.1-or-later Group: Development/Libraries/C and C++ +Requires: glibc-devel +Requires: libblkid-devel Requires: libfdisk1 = %{version} +Requires: libuuid-devel %description -n libfdisk-devel Files needed to develop applications using the library for filesystem @@ -290,7 +295,10 @@ detection. Summary: Development files for the filesystem detection library License: LGPL-2.1-or-later Group: Development/Libraries/C and C++ +Requires: libblkid-devel-static +Requires: libeconf-devel Requires: libfdisk-devel = %{version} +Requires: libuuid-devel-static %description -n libfdisk-devel-static Files needed to develop applications using the library for filesystem @@ -309,7 +317,10 @@ mount(8) and /usr/sbin/mount. helpers. Summary: Development files for libmount License: LGPL-2.1-or-later Group: Development/Libraries/C and C++ +Requires: libblkid-devel +Requires: libeconf-devel Requires: libmount1 = %{version} +Requires: libselinux-devel %description -n libmount-devel Files to develop applications using the libmount library. @@ -318,6 +329,7 @@ Files to develop applications using the libmount library. Summary: Development files for libmount License: LGPL-2.1-or-later Group: Development/Libraries/C and C++ +Requires: libblkid-devel-static Requires: libmount-devel = %{version} %description -n libmount-devel-static @@ -364,6 +376,7 @@ A library to generate universally unique IDs (UUIDs). Summary: Development files for libuuid License: BSD-3-Clause Group: Development/Libraries/C and C++ +Requires: glibc-devel Requires: libuuid1 = %{version} %description -n libuuid-devel @@ -467,9 +480,18 @@ cp -a %{S:2} . %autopatch -p1 # This test randomly fails or keeps hanging task inside build chroot (tested on 2.38). rm tests/ts/lsns/ioctl_ns -AUTOPOINT=true GTKDOCIZE=true autoreconf -vfi +# The bash-5.3 and up ignores SIGINT as well for async (co)processes, +# rational https://lists.gnu.org/archive/html/bug-bash/2023-01/msg00050.html +if test -n "$BASH_VERSION" +then + if test ${BASH_VERSINFO[0]} -gt 5 -o \( ${BASH_VERSINFO[0]} -eq 5 -a ${BASH_VERSINFO[1]} -gt 2 \) + then + sed -ri '/^Ignored:/{ s/(HUP)/\1 INT/ }' tests/expected/kill/decode + fi +fi %build +AUTOPOINT=true GTKDOCIZE=true autoreconf -vfi %global _lto_cflags %{_lto_cflags} -ffat-lto-objects export SUID_CFLAGS="-fpie" export SUID_LDFLAGS="-pie" @@ -479,38 +501,38 @@ export CXXFLAGS="%{optflags} -D_GNU_SOURCE" # Here we define a build function. For the base build, we use it as it # is. For python build, we use it repeatedly for all flavors. -function configure_and_build() { +configure_and_build() { # configure options depending on ulbuild and ulsubset values configure_options="" # libmagic is only used for determining in more(1) whether or not a file # is binary. but it has builtin code that is doing the same with a simpler # check and the libmagic database dependency is rather large (9MB+) -configure_options+="--without-libmagic " +configure_options="$configure_options --without-libmagic " %if "%ulbuild" == "python" %define _configure ../configure -configure_options+="--disable-all-programs " -configure_options+="--with-python " -configure_options+="--enable-pylibmount " -configure_options+="--enable-libmount " -configure_options+="--enable-libblkid " +configure_options="$configure_options --disable-all-programs " +configure_options="$configure_options --with-python " +configure_options="$configure_options --enable-pylibmount " +configure_options="$configure_options --enable-libmount " +configure_options="$configure_options --enable-libblkid " %endif # ulbuild == python %if "%ulbuild" == "base" -configure_options+="--enable-all-programs " -configure_options+="--without-python " +configure_options="$configure_options --enable-all-programs " +configure_options="$configure_options --without-python " %endif # ulbuild == base %if "%ulsubset" == "core" -configure_options+="--without-systemd --disable-liblastlog2" +configure_options="$configure_options --without-systemd --disable-liblastlog2" %endif # ulsubset == core %if "%ulsubset" == "systemd" -configure_options+="--with-systemd " +configure_options="$configure_options --with-systemd " %endif # ulsubset == systemd @@ -553,10 +575,9 @@ configure_options+="--with-systemd " --disable-pg\ --enable-fs-paths-default="/sbin:/usr/sbin"\ --enable-static\ - --with-vendordir=%{_distconfdir} \ - --disable-libmount-mountfd-support \ + --with-vendordir=%{_distconfdir}\ $configure_options -make %{?_smp_mflags} +%make_build } ################ @@ -607,8 +628,10 @@ fi ################ %if "%ulbuild" == "base" %make_install -mkdir -p %{buildroot}{%{_distconfdir}/default,%{_pam_vendordir},%{_sysconfdir}/issue.d} -install -m 644 %{SOURCE51} %{buildroot}%{_sysconfdir}/blkid.conf +mkdir -p "%{buildroot}/%{_distconfdir}/default" "%{buildroot}/%{_pam_vendordir}" "%{buildroot}/%{_sysconfdir}/issue.d" +install -m 644 %{SOURCE51} %{buildroot}%{_distconfdir}/blkid.conf +touch %{buildroot}%{_sysconfdir}/blkid.conf +mkdir %{buildroot}%{_sysconfdir}/blkid.conf.d %{buildroot}%{_distconfdir}/blkid.conf.d install -m 644 %{SOURCE8} %{buildroot}%{_pam_vendordir}/login install -m 644 %{SOURCE9} %{buildroot}%{_pam_vendordir}/remote %if 0%{?suse_version} <= 1500 @@ -662,10 +685,10 @@ rm -f %{buildroot}%{_mandir}/man8/fdisk.8* # create list of setarch(8) symlinks find %{buildroot}%{_mandir}/man8 -regextype posix-egrep \ - -regex ".*(linux32|linux64|s390|s390x|i386|ppc|ppc64|ppc32|sparc|sparc64|sparc32|sparc32bash|mips|mips64|mips32|ia64|x86_64|parisc|parisc32|parisc64|uname26)\.8.*" \ + -regex ".*(linux32|linux64|s390|s390x|i386|ppc|ppc64|ppc32|sparc|sparc64|sparc32|sparc32bash|mips|mips64|mips32|ia64|x86_64|parisc|parisc32|parisc64)\.8.*" \ -printf "%{_mandir}/man8/%f*\n" >> %{name}.files find %{buildroot}%{_bindir}/ -regextype posix-egrep -type l \ - -regex ".*(linux32|linux64|s390|s390x|i386|ppc|ppc64|ppc32|sparc|sparc64|sparc32|sparc32bash|mips|mips64|mips32|ia64|x86_64|parisc|parisc32|parisc64|uname26)$" \ + -regex ".*(linux32|linux64|s390|s390x|i386|ppc|ppc64|ppc32|sparc|sparc64|sparc32|sparc32bash|mips|mips64|mips32|ia64|x86_64|parisc|parisc32|parisc64)$" \ -printf "%{_bindir}/%f\n" >> %{name}.files mkdir -p %{buildroot}/run/uuidd @@ -706,8 +729,6 @@ mv %{buildroot}%{_bindir}/login %{buildroot}/bin/ %else # ulsubset != core, ulbuild == base echo -n "" >%{name}.lang -ln -sf /sbin/service %{buildroot}%{_sbindir}/rcuuidd -ln -sf /sbin/service %{buildroot}%{_sbindir}/rcfstrim %endif # ulsubset == core, ulbuild == base @@ -971,7 +992,6 @@ rmdir --ignore-fail-on-non-empty /run/run >/dev/null 2>&1 || : # defined no_config %config %dir %{_sysconfdir}/issue.d - %if %{ul_extra_bin_sbin} %core /bin/kill %core %verify(not mode) %attr(%ul_suid,root,root) /bin/su @@ -1015,7 +1035,9 @@ rmdir --ignore-fail-on-non-empty /run/run >/dev/null 2>&1 || : %core %{_bindir}/kill %core %verify(not mode) %attr(%ul_suid,root,root) %{_bindir}/su %core %{_bindir}/eject +%core %{_bindir}/bits %core %{_bindir}/cal +%core %{_bindir}/coresched %core %{_bindir}/chmem %core %{_bindir}/choom %core %{_bindir}/chrt @@ -1227,6 +1249,7 @@ rmdir --ignore-fail-on-non-empty /run/run >/dev/null 2>&1 || : %core %{_mandir}/man1/kill.1.gz %core %{_mandir}/man1/su.1.gz +%core %{_mandir}/man1/bits.1.gz %core %{_mandir}/man1/cal.1.gz %core %{_mandir}/man1/choom.1.gz %core %{_mandir}/man1/chrt.1.gz @@ -1234,6 +1257,7 @@ rmdir --ignore-fail-on-non-empty /run/run >/dev/null 2>&1 || : %core %{_mandir}/man1/colcrt.1.gz %core %{_mandir}/man1/colrm.1.gz %core %{_mandir}/man1/column.1.gz +%core %{_mandir}/man1/coresched.1.gz %core %{_mandir}/man1/dmesg.1.gz %core %{_mandir}/man1/enosys.1.gz %core %{_mandir}/man1/eject.1.gz @@ -1360,8 +1384,10 @@ rmdir --ignore-fail-on-non-empty /run/run >/dev/null 2>&1 || : %license README.licensing %license COPYING %license Documentation/licenses/* -%config(noreplace) %{_sysconfdir}/blkid.conf - +%ghost %config(missingok) %{_sysconfdir}/blkid.conf +%config %dir %{_sysconfdir}/blkid.conf.d +%{_distconfdir}/blkid.conf +%dir %{_distconfdir}/blkid.conf.d %{_datadir}/bash-completion/completions/* %exclude %{_datadir}/bash-completion/completions/findmnt %exclude %{_datadir}/bash-completion/completions/logger @@ -1386,10 +1412,11 @@ rmdir --ignore-fail-on-non-empty /run/run >/dev/null 2>&1 || : # Systemd files # ################# %if "%ulsubset" == "systemd" -%exclude %config(noreplace) %{_sysconfdir}/blkid.conf - +%exclude %{_distconfdir}/blkid.conf +%exclude %dir %{_distconfdir}/blkid.conf.d +%exclude %config(missingok) %{_sysconfdir}/blkid.conf +%exclude %config %{_sysconfdir}/blkid.conf.d %exclude %config %dir %{_sysconfdir}/issue.d - %if %{ul_extra_bin_sbin} /bin/findmnt /bin/logger @@ -1577,7 +1604,6 @@ rmdir --ignore-fail-on-non-empty /run/run >/dev/null 2>&1 || : %exclude %{_mandir}/man8/parisc64.8.gz %exclude %{_mandir}/man8/uname26.8.gz -%{_sbindir}/rcfstrim %{_unitdir}/fstrim.service %{_unitdir}/fstrim.timer %endif @@ -1724,7 +1750,6 @@ rmdir --ignore-fail-on-non-empty /run/run >/dev/null 2>&1 || : %attr(-,uuidd,uuidd) %ghost %dir /run/uuidd %{_datadir}/bash-completion/completions/uuidd %{_mandir}/man8/uuidd.8.gz -%{_sbindir}/rcuuidd %{_unitdir}/uuidd.service %{_unitdir}/uuidd.socket