From e38375a8ef1486670f5de1b6d1066c4ea5357659 Mon Sep 17 00:00:00 2001
From: Florian Fischer <florian.fl.fischer@fau.de>
Date: Thu, 28 Jan 2021 21:50:50 +0100
Subject: [PATCH] [IO] add support for openat and open

Modified-by: Florian Schmaus <flow@cs.fau.de>
---
 emper/c_emper.cpp                  | 30 ++++++++++++
 emper/include/emper.h              | 12 ++++-
 emper/io.hpp                       | 76 ++++++++++++++++++++++++++++++
 emper/io/Future.hpp                | 10 ++++
 emper/io/Operation.cpp             |  3 ++
 emper/io/Operation.hpp             |  1 +
 tests/SimpleDiskAndNetworkTest.cpp |  4 +-
 7 files changed, 132 insertions(+), 4 deletions(-)

diff --git a/emper/c_emper.cpp b/emper/c_emper.cpp
index 85a9bf96..4a648a33 100644
--- a/emper/c_emper.cpp
+++ b/emper/c_emper.cpp
@@ -9,9 +9,11 @@
 #include "emper.h"											 // for fiber, cps, bps, runtime
 
 #ifdef EMPER_IO
+#include <fcntl.h>
 #include <sys/socket.h>	 // for socklen_t
 #include <sys/types.h>	 // for off_t
 
+#include <cstdarg>
 #include <cstddef>	// for size_t
 
 #include "io.hpp"	 // for emper::io::emper_*
@@ -139,5 +141,33 @@ auto emper_writev(int fildes, const struct iovec* iov, int iovcnt) -> ssize_t {
 	return emper::io::writevAndWait(fildes, iov, iovcnt);
 }
 
+// C function overloading inspired by
+// http://locklessinc.com/articles/overloading
+auto emper_open(const char* pathname, int flags, ...) -> int {
+	if (flags | O_CREAT || flags | O_TMPFILE) {
+		va_list v;
+		va_start(v, flags);
+
+		mode_t mode = va_arg(v, mode_t);
+		va_end(v);
+		return emper::io::openAndWait(pathname, flags, mode);
+	}
+
+	return emper::io::openAndWait(pathname, flags);
+}
+
+auto emper_openat(int dirfd, const char* pathname, int flags, ...) -> int {
+	if (flags | O_CREAT || flags | O_TMPFILE) {
+		va_list v;
+		va_start(v, flags);
+
+		mode_t mode = va_arg(v, mode_t);
+		va_end(v);
+		return emper::io::openatAndWait(dirfd, pathname, flags, mode);
+	}
+
+	return emper::io::openatAndWait(dirfd, pathname, flags);
+}
+
 auto emper_close(int fd) -> int { return emper::io::closeAndWait(fd); }
 #endif
diff --git a/emper/include/emper.h b/emper/include/emper.h
index 4a1402e7..e21ad5a9 100644
--- a/emper/include/emper.h
+++ b/emper/include/emper.h
@@ -2,12 +2,14 @@
 // Copyright © 2020 Florian Schmaus, Florian Fischer
 #pragma once
 
+#include <stdarg.h>	 // NOLINT(modernize-deprecated-headers)
+#include <stddef.h>	 // NOLINT(modernize-deprecated-headers)
+
 #include "emper-common.h"
 #include "emper-config.h"
-#include "stddef.h"	 // NOLINT(modernize-deprecated-headers)
 
 #ifdef EMPER_IO
-#include "sys/socket.h"
+#include <sys/socket.h>
 #endif
 
 typedef struct runtime runtime;	 // NOLINT(modernize-use-using)
@@ -88,6 +90,12 @@ ssize_t emper_write_file(int fildes, const void* buf, size_t nbyte, off_t offset
 // NOLINTNEXTLINE(modernize-use-trailing-return-type)
 ssize_t emper_writev(int fildes, const struct iovec* iov, int iovcnt);
 
+// NOLINTNEXTLINE(modernize-use-trailing-return-type)
+int emper_open(const char* pathname, int flags, ...);
+
+// NOLINTNEXTLINE(modernize-use-trailing-return-type)
+int emper_openat(int dirfd, const char* pathname, int flags, ...);
+
 // NOLINTNEXTLINE(modernize-use-trailing-return-type)
 int emper_close(int fd);
 #endif
diff --git a/emper/io.hpp b/emper/io.hpp
index fc154f36..4addc610 100644
--- a/emper/io.hpp
+++ b/emper/io.hpp
@@ -2,6 +2,7 @@
 // Copyright © 2020-2021 Florian Fischer
 #pragma once
 
+#include <fcntl.h>
 #include <sys/socket.h>	 // for socklen_t
 #include <sys/types.h>	 // for ssize_t, off_t
 
@@ -334,6 +335,81 @@ inline auto writevAndWait(int fildes, const struct iovec *iov, int iovcnt) -> ss
 	return future.waitAndSetErrno();
 }
 
+/**
+ * @brief asynchronous openat mimicking POSIX openat(3)
+ *
+ * This method must be called from inside the emper runtime because it uses
+ * the worker-local IoContext
+ *
+ * @param dirfd directory the pathname is interpreted relative to
+ * @param pathname path to the file to open
+ * @param flags specify access mode
+ * @param mode in which the file should be created
+ *
+ * @return Future object which signals the completion of the openat request
+ */
+inline auto openat(int dirfd, const char *pathname, int flags, mode_t mode = 0)
+		-> std::unique_ptr<Future> {
+	auto future = std::make_unique<OpenatFuture>(dirfd, pathname, flags, mode);
+	future->submit();
+	return future;
+}
+
+/**
+ * @brief synchronous openat mimicking POSIX openat(3)
+ *
+ * This method must be called from inside the emper runtime because it uses
+ * the worker-local IoContext
+ *
+ * @param dirfd directory the pathname is interpreted relative to
+ * @param pathname path to the file to open
+ * @param flags specify access mode
+ * @param mode in which the file should be created
+ *
+ * @return fd on success, -1 on error
+ */
+inline auto openatAndWait(int dirfd, const char *pathname, int flags, mode_t mode = 0) -> size_t {
+	OpenatFuture future(dirfd, pathname, flags, mode);
+	future.submit();
+	return future.waitAndSetErrno();
+}
+
+/**
+ * @brief asynchronous open mimicking POSIX open(3)
+ *
+ * This method must be called from inside the emper runtime because it uses
+ * the worker-local IoContext
+ *
+ * @param pathname path to the file to open
+ * @param flags specify access mode
+ * @param mode in which the file should be created
+ *
+ * @return Future object which signals the completion of the open request
+ */
+inline auto open(const char *pathname, int flags, mode_t mode = 0) -> std::unique_ptr<Future> {
+	auto future = std::make_unique<OpenatFuture>(AT_FDCWD, pathname, flags, mode);
+	future->submit();
+	return future;
+}
+
+/**
+ * @brief synchronous open mimicking POSIX open(3)
+ *
+ * This method must be called from inside the emper runtime because it uses
+ * the worker-local IoContext
+ *
+ * @param pathname path to the file to open
+ * @param flags specify access mode
+ * @param mode in which the file should be created
+ *
+ * @return fd on success, -1 on error
+ */
+inline auto openAndWait(const char *pathname, int flags, mode_t mode = 0) -> size_t {
+	OpenatFuture future(AT_FDCWD, pathname, flags, mode);
+	future.submit();
+	return future.waitAndSetErrno();
+}
+
 /**
  * @brief Non-blocking close mimicking POSIX close(3)
  *
diff --git a/emper/io/Future.hpp b/emper/io/Future.hpp
index 772cd74e..bbf29d5d 100644
--- a/emper/io/Future.hpp
+++ b/emper/io/Future.hpp
@@ -421,6 +421,16 @@ class ReadFuture : public PartialCompletableFuture {
 						read_all ? ENABLE_PARTIAL_COMPLETION : DISABLE_PARTIAL_COMPLETION){};
 };
 
+class OpenatFuture : public Future {
+	void prepareSqe(io_uring_sqe* sqe) override {
+		io_uring_prep_openat(sqe, fd, reinterpret_cast<const char*>(buf), len, offsetOrFlags);
+	}
+
+ public:
+	OpenatFuture(int dirfd, const void* pathname, int flags, mode_t mode = 0)
+			: Future(Operation::OPENAT, dirfd, const_cast<void*>(pathname), flags, mode){};
+};
+
 class WriteFuture : public PartialCompletableFuture {
 	void prepareSqe(io_uring_sqe* sqe) override {
 		if (partialCompletion == DISABLE_PARTIAL_COMPLETION) {
diff --git a/emper/io/Operation.cpp b/emper/io/Operation.cpp
index 83a67abe..e881f1b1 100644
--- a/emper/io/Operation.cpp
+++ b/emper/io/Operation.cpp
@@ -20,6 +20,9 @@ auto operator<<(std::ostream& os, const Operation& op) -> std::ostream& {
 		case Operation::ACCEPT:
 			os << "accept";
 			break;
+		case Operation::OPENAT:
+			os << "openat";
+			break;
 		case Operation::READ:
 			os << "read";
 			break;
diff --git a/emper/io/Operation.hpp b/emper/io/Operation.hpp
index 49891929..d193b1cf 100644
--- a/emper/io/Operation.hpp
+++ b/emper/io/Operation.hpp
@@ -11,6 +11,7 @@ enum class Operation {
 	RECV,
 	CONNECT,
 	ACCEPT,
+	OPENAT,
 	READ,
 	WRITE,
 	WRITEV,
diff --git a/tests/SimpleDiskAndNetworkTest.cpp b/tests/SimpleDiskAndNetworkTest.cpp
index cf5ec503..60a2e569 100644
--- a/tests/SimpleDiskAndNetworkTest.cpp
+++ b/tests/SimpleDiskAndNetworkTest.cpp
@@ -63,7 +63,7 @@ static void server_func(int sockfd) {
 		}
 		close(file_fd);
 
-		file_fd = open(file_name, O_RDONLY);
+		file_fd = emper::io::openAndWait(file_name, O_RDONLY);
 		if (file_fd == -1) {
 			DIE_MSG_ERRNO("open failed");
 		}
@@ -113,7 +113,7 @@ static void server_func(int sockfd) {
 		}
 		close(file_fd);
 
-		file_fd = open(file2_name, O_RDONLY);
+		file_fd = emper::io::openAndWait(file2_name, O_RDONLY);
 		if (file_fd == -1) {
 			DIE_MSG_ERRNO("open failed");
 		}
-- 
GitLab