From e01e38b24346a21f1d01498c265486a12c009e61 Mon Sep 17 00:00:00 2001
From: Karel Zak <kzak@redhat.com>
Date: Wed, 27 May 2026 10:35:39 +0200
Subject: [PATCH 05/12] lib/fileutils: add ul_open_no_symlinks()

Add a helper that opens a path rejecting symlinks at any component,
not just the last one.  Uses openat2(RESOLVE_NO_SYMLINKS) when
available (Linux >= 5.6), falls back to open(O_NOFOLLOW).

Signed-off-by: Karel Zak <kzak@redhat.com>
---
 configure.ac        |  1 +
 include/fileutils.h |  2 ++
 lib/fileutils.c     | 24 ++++++++++++++++++++++++
 meson.build         |  1 +
 4 files changed, 28 insertions(+)

Index: util-linux-2.39.3/configure.ac
===================================================================
--- util-linux-2.39.3.orig/configure.ac
+++ util-linux-2.39.3/configure.ac
@@ -313,6 +313,7 @@ AC_CHECK_HEADERS([ \
 	linux/kcmp.h \
 	linux/net_namespace.h \
 	linux/nsfs.h \
+	linux/openat2.h \
 	linux/pr.h \
 	linux/raw.h \
 	linux/securebits.h \
Index: util-linux-2.39.3/include/fileutils.h
===================================================================
--- util-linux-2.39.3.orig/include/fileutils.h
+++ util-linux-2.39.3/include/fileutils.h
@@ -61,6 +61,8 @@ static inline int is_same_inode(const in
 	return 1;
 }
 
+extern int ul_open_no_symlinks(const char *path, int flags, mode_t mode);
+
 extern int dup_fd_cloexec(int oldfd, int lowfd);
 extern unsigned int get_fd_tabsize(void);
 
Index: util-linux-2.39.3/lib/fileutils.c
===================================================================
--- util-linux-2.39.3.orig/lib/fileutils.c
+++ util-linux-2.39.3/lib/fileutils.c
@@ -11,7 +11,14 @@
 #include <unistd.h>
 #include <sys/time.h>
 #include <sys/resource.h>
+#include <sys/syscall.h>
 #include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef HAVE_LINUX_OPENAT2_H
+# include <linux/openat2.h>
+#endif
 
 #include "c.h"
 #include "all-io.h"
@@ -311,3 +318,20 @@ int ul_reopen(int fd, int flags)
 
 	return open(buf, flags);
 }
+
+int ul_open_no_symlinks(const char *path, int flags, mode_t mode)
+{
+#if defined(SYS_openat2) && defined(RESOLVE_NO_SYMLINKS)
+	struct open_how how = {
+		.flags = (__u64) flags,
+		.mode = (__u64) mode,
+		.resolve = RESOLVE_NO_SYMLINKS,
+	};
+	int fd = syscall(SYS_openat2, AT_FDCWD, path, &how, sizeof(how));
+
+	/* only fall back to O_NOFOLLOW if the syscall is unavailable */
+	if (fd >= 0 || errno != ENOSYS)
+		return fd;
+#endif
+	return open(path, flags | O_NOFOLLOW, mode);
+}
Index: util-linux-2.39.3/meson.build
===================================================================
--- util-linux-2.39.3.orig/meson.build
+++ util-linux-2.39.3/meson.build
@@ -177,6 +177,7 @@ headers = '''
         linux/kcmp.h
         linux/net_namespace.h
         linux/nsfs.h
+        linux/openat2.h
         linux/mount.h
         linux/pr.h
         linux/securebits.h
