From dda85e02bf46fa7445b21a3dde7c21b22c8e4223 Mon Sep 17 00:00:00 2001
From: Max Kellermann <max.kellermann@ionos.com>
Date: Mon, 3 Feb 2025 21:35:40 +0100
Subject: [PATCH] io/Open: add TryOpen(struct open_how), Open(struct open_how)

---
 src/io/FileAt.hxx          | 16 ++++++++++++++++
 src/io/Open.cxx            | 24 +++++++++++++++++++++++-
 src/io/Open.hxx            | 21 +++++++++++++++++++++
 src/system/linux/openat2.h | 17 +++++++++++++++++
 4 files changed, 77 insertions(+), 1 deletion(-)
 create mode 100644 src/io/FileAt.hxx
 create mode 100644 src/system/linux/openat2.h

diff --git a/src/io/FileAt.hxx b/src/io/FileAt.hxx
new file mode 100644
index 000000000..53cd9686c
--- /dev/null
+++ b/src/io/FileAt.hxx
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: BSD-2-Clause
+// Copyright CM4all GmbH
+// author: Max Kellermann <mk@cm4all.com>
+
+#pragma once
+
+#include "FileDescriptor.hxx"
+
+/**
+ * Reference to a file by an anchor directory (which can be an
+ * `O_PATH` descriptor) and a path name relative to it.
+ */
+struct FileAt {
+	FileDescriptor directory;
+	const char *name;
+};
diff --git a/src/io/Open.cxx b/src/io/Open.cxx
index 8514e95cb..f962c273d 100644
--- a/src/io/Open.cxx
+++ b/src/io/Open.cxx
@@ -5,6 +5,11 @@
 #include "UniqueFileDescriptor.hxx"
 #include "lib/fmt/SystemError.hxx"
 
+#ifdef __linux__
+#include "FileAt.hxx"
+#include "system/linux/openat2.h"
+#endif
+
 #include <fcntl.h>
 
 UniqueFileDescriptor
@@ -93,4 +98,21 @@ OpenDirectory(FileDescriptor directory, const char *name, int flags)
 	return fd;
 }
 
-#endif
+UniqueFileDescriptor
+TryOpen(FileAt file, const struct open_how &how) noexcept
+{
+	int fd = openat2(file.directory.Get(), file.name, &how, sizeof(how));
+	return UniqueFileDescriptor{AdoptTag{}, fd};
+}
+
+UniqueFileDescriptor
+Open(FileAt file, const struct open_how &how)
+{
+	auto fd = TryOpen(file, how);
+	if (!fd.IsDefined())
+		throw FmtErrno("Failed to open {:?}", file.name);
+
+	return fd;
+}
+
+#endif // __linux__
diff --git a/src/io/Open.hxx b/src/io/Open.hxx
index 2ff0501f8..08c9bbdc5 100644
--- a/src/io/Open.hxx
+++ b/src/io/Open.hxx
@@ -36,4 +36,25 @@ OpenWriteOnly(FileDescriptor directory, const char *name, int flags=0);
 UniqueFileDescriptor
 OpenDirectory(FileDescriptor directory, const char *name, int flags=0);
 
+struct opwn_how;
+struct FileAt;
+
+/**
+ * Wrapper for openat2() which converts the returned file descriptor
+ * to a #UniqueFileDescriptor.
+ *
+ * Returns an "undefined" instance on error and sets errno.
+ */
+UniqueFileDescriptor
+TryOpen(FileAt file, const struct open_how &how) noexcept;
+
+/**
+ * Wrapper for openat2() which converts the returned file descriptor
+ * to a #UniqueFileDescriptor.
+ *
+ * Throws on error.
+ */
+UniqueFileDescriptor
+Open(FileAt file, const struct open_how &how);
+
 #endif
diff --git a/src/system/linux/openat2.h b/src/system/linux/openat2.h
new file mode 100644
index 000000000..362ffad2e
--- /dev/null
+++ b/src/system/linux/openat2.h
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: BSD-2-Clause
+// Copyright CM4all GmbH
+// author: Max Kellermann <mk@cm4all.com>
+
+#pragma once
+
+#include <fcntl.h> // for O_*
+#include <linux/openat2.h> // for RESOLVE_*
+#include <sys/syscall.h>
+#include <unistd.h>
+
+static inline int
+openat2(int dirfd, const char *pathname,
+	const struct open_how *how, size_t size)
+{
+	return syscall(__NR_openat2, dirfd, pathname, how, size);
+}