* [PATCH RFC liburing 1/2] Add IORING_OP_CLONE/EXEC support
2024-12-09 23:44 [PATCH RFC liburing 0/2] IORING_OP_CLONE/EXEC support and tests Gabriel Krisman Bertazi
@ 2024-12-09 23:44 ` Gabriel Krisman Bertazi
2024-12-10 21:06 ` Josh Triplett
2024-12-09 23:44 ` [PATCH RFC liburing 2/2] tests: Add test for CLONE/EXEC operations Gabriel Krisman Bertazi
1 sibling, 1 reply; 4+ messages in thread
From: Gabriel Krisman Bertazi @ 2024-12-09 23:44 UTC (permalink / raw)
To: axboe, asml.silence; +Cc: io-uring, josh, Gabriel Krisman Bertazi
Signed-off-by: Gabriel Krisman Bertazi <[email protected]>
---
src/include/liburing.h | 25 +++++++++++++++++++++++++
src/include/liburing/io_uring.h | 3 +++
2 files changed, 28 insertions(+)
diff --git a/src/include/liburing.h b/src/include/liburing.h
index 627fc47..6d344b1 100644
--- a/src/include/liburing.h
+++ b/src/include/liburing.h
@@ -1229,6 +1229,31 @@ IOURINGINLINE void io_uring_prep_socket_direct_alloc(struct io_uring_sqe *sqe,
__io_uring_set_target_fixed_file(sqe, IORING_FILE_INDEX_ALLOC - 1);
}
+static inline void io_uring_prep_clone(struct io_uring_sqe *sqe)
+{
+ io_uring_prep_rw(IORING_OP_CLONE, sqe, 0, NULL, 0, 0);
+}
+
+static inline void io_uring_prep_execveat(struct io_uring_sqe *sqe, int dfd,
+ const char *filename, char *const *argv,
+ char *const *envp, int flags)
+{
+ io_uring_prep_rw(IORING_OP_EXECVEAT, sqe, dfd, filename, 0, 0);
+ sqe->addr2 = (unsigned long)(void *)argv;
+ sqe->addr3 = (unsigned long)(void *)envp;
+ sqe->execve_flags = flags;
+}
+
+static inline void io_uring_prep_exec(struct io_uring_sqe *sqe,
+ const char *filename, char *const *argv,
+ char *const *envp)
+{
+ io_uring_prep_rw(IORING_OP_EXECVEAT, sqe, 0, filename, 0, 0);
+ sqe->addr2 = (unsigned long)(void *)argv;
+ sqe->addr3 = (unsigned long)(void *)envp;
+}
+
+
/*
* Prepare commands for sockets
*/
diff --git a/src/include/liburing/io_uring.h b/src/include/liburing/io_uring.h
index 7659198..a198969 100644
--- a/src/include/liburing/io_uring.h
+++ b/src/include/liburing/io_uring.h
@@ -73,6 +73,7 @@ struct io_uring_sqe {
__u32 futex_flags;
__u32 install_fd_flags;
__u32 nop_flags;
+ __u32 execve_flags;
};
__u64 user_data; /* data to be passed back at completion time */
/* pack this to avoid bogus arm OABI complaints */
@@ -262,6 +263,8 @@ enum io_uring_op {
IORING_OP_FTRUNCATE,
IORING_OP_BIND,
IORING_OP_LISTEN,
+ IORING_OP_CLONE,
+ IORING_OP_EXECVEAT,
/* this goes last, obviously */
IORING_OP_LAST,
--
2.47.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH RFC liburing 2/2] tests: Add test for CLONE/EXEC operations
2024-12-09 23:44 [PATCH RFC liburing 0/2] IORING_OP_CLONE/EXEC support and tests Gabriel Krisman Bertazi
2024-12-09 23:44 ` [PATCH RFC liburing 1/2] Add IORING_OP_CLONE/EXEC support Gabriel Krisman Bertazi
@ 2024-12-09 23:44 ` Gabriel Krisman Bertazi
1 sibling, 0 replies; 4+ messages in thread
From: Gabriel Krisman Bertazi @ 2024-12-09 23:44 UTC (permalink / raw)
To: axboe, asml.silence; +Cc: io-uring, josh, Gabriel Krisman Bertazi
Signed-off-by: Gabriel Krisman Bertazi <[email protected]>
---
test/Makefile | 1 +
test/clone-exec.c | 436 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 437 insertions(+)
create mode 100644 test/clone-exec.c
diff --git a/test/Makefile b/test/Makefile
index 3fc4a42..70ebd5f 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -61,6 +61,7 @@ test_srcs := \
buf-ring-nommap.c \
buf-ring-put.c \
ce593a6c480a.c \
+ clone-exec.c \
close-opath.c \
connect.c \
connect-rep.c \
diff --git a/test/clone-exec.c b/test/clone-exec.c
new file mode 100644
index 0000000..7e00345
--- /dev/null
+++ b/test/clone-exec.c
@@ -0,0 +1,436 @@
+#include <assert.h>
+#include <err.h>
+#include <inttypes.h>
+#include <sched.h>
+#include <spawn.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+ #include <sys/stat.h>
+
+#include "liburing.h"
+#include "helpers.h"
+
+char **t_envp;
+
+#define MAX_PATH 1024
+
+int test_fail_sequence(void)
+{
+ struct io_uring_sqe *sqe;
+ struct io_uring_cqe *cqe;
+ struct io_uring ring;
+ int ret;
+
+ ret = t_create_ring(10, &ring, IORING_SETUP_SUBMIT_ALL);
+ if (ret < 0) {
+ fprintf(stderr, "queue_init: %s\n", strerror(-ret));
+ return T_SETUP_SKIP;
+ }
+
+ sqe = io_uring_get_sqe(&ring);
+ io_uring_prep_clone(sqe);
+ sqe->flags |= IOSQE_IO_LINK;
+
+ /* Add a command that will fail. */
+ sqe = io_uring_get_sqe(&ring);
+ io_uring_prep_nop(sqe);
+ sqe->nop_flags = IORING_NOP_INJECT_RESULT;
+
+ /*
+ * A random magic number to be retrieved in cqe->res. Not a
+ * valid errno returned by io_uring.
+ */
+ sqe->len = -255;
+ sqe->flags |= IOSQE_IO_LINK;
+
+ /* And a NOP that will succeed */
+ sqe = io_uring_get_sqe(&ring);
+ io_uring_prep_nop(sqe);
+
+ io_uring_submit(&ring);
+
+ if (io_uring_wait_cqes(&ring, &cqe, 3, NULL, NULL)) {
+ fprintf(stderr, "%s: Failed to wait for cqes\n", __func__);
+ return T_EXIT_FAIL;
+ }
+
+ /* Check the CLONE */
+ if (cqe->res) {
+ fprintf(stderr, "%s: failed to clone. Got %d\n",
+ __func__, cqe->res);
+ return T_EXIT_FAIL;
+ }
+ io_uring_cqe_seen(&ring, cqe);
+
+ io_uring_peek_cqe(&ring, &cqe);
+ if (cqe->res != -255) {
+ fprintf(stderr, "%s: This nop should have failed with 255. Got %d\n",
+ __func__, cqe->res);
+ return T_EXIT_FAIL;
+ }
+ io_uring_cqe_seen(&ring, cqe);
+
+ io_uring_peek_cqe(&ring, &cqe);
+ if (cqe->res != -ECANCELED) {
+ fprintf(stderr, "%s: This should have been -ECANCELED. Got %d\n",
+ __func__, cqe->res);
+ return T_EXIT_FAIL;
+ }
+ io_uring_cqe_seen(&ring, cqe);
+
+ io_uring_queue_exit(&ring);
+
+ return 0;
+}
+
+int test_bad_exec(void)
+{
+ struct io_uring_sqe *sqe;
+ struct io_uring_cqe *cqe;
+ struct io_uring ring;
+ char *command = "/usr/bin/echo";
+ char *const t_argv[] = { "/usr/bin/echo", "Hello World", NULL };
+ int ret;
+
+ ret = t_create_ring(10, &ring, IORING_SETUP_SUBMIT_ALL);
+ if (ret < 0) {
+ fprintf(stderr, "queue_init: %s\n", strerror(-ret));
+ return T_SETUP_SKIP;
+ }
+
+ sqe = io_uring_get_sqe(&ring);
+ io_uring_prep_execveat(sqe, AT_FDCWD, command, t_argv, t_envp, 0);
+ io_uring_sqe_set_flags(sqe, IOSQE_IO_LINK|IOSQE_IO_HARDLINK);
+
+ io_uring_submit(&ring);
+
+ if (io_uring_wait_cqe(&ring, &cqe)) {
+ fprintf(stderr, "%s: Failed to wait for cqe\n", __func__);
+ return T_EXIT_FAIL;
+ }
+
+ /* Check the EXEC */
+ if (cqe->res != -EINVAL) {
+ fprintf(stderr, "%s: EXEC should have failed. Got %d\n",
+ __func__, cqe->res);
+ return T_EXIT_FAIL;
+ }
+ io_uring_cqe_seen(&ring, cqe);
+
+ io_uring_queue_exit(&ring);
+
+ return 0;
+}
+
+int test_simple_sequence(void)
+{
+ struct io_uring_sqe *sqe;
+ struct io_uring_cqe *cqe;
+ struct io_uring ring;
+ int ret, head, i, reaped = 0;
+
+ ret = t_create_ring(10, &ring, IORING_SETUP_SUBMIT_ALL);
+ if (ret < 0) {
+ fprintf(stderr, "queue_init: %s\n", strerror(-ret));
+ return T_SETUP_SKIP;
+ }
+
+ sqe = io_uring_get_sqe(&ring);
+ io_uring_prep_clone(sqe);
+ sqe->flags |= IOSQE_IO_LINK;
+
+ /* And a few that will succeed */
+ for (i = 0; i < 5; i++) {
+ sqe = io_uring_get_sqe(&ring);
+ io_uring_prep_nop(sqe);
+ sqe->flags |= IOSQE_IO_LINK;
+ }
+
+ io_uring_submit(&ring);
+
+ if (io_uring_wait_cqes(&ring, &cqe, i+1, NULL, NULL)) {
+ fprintf(stderr, "%s: Failed to wait for cqes\n", __func__);
+ return T_EXIT_FAIL;
+ }
+
+ /* Check the CLONE */
+ if (cqe->res) {
+ fprintf(stderr, "%s: failed to clone. Got %d\n",
+ __func__, cqe->res);
+ return T_EXIT_FAIL;
+ }
+ io_uring_cqe_seen(&ring, cqe);
+
+ /* Check the NOPS */
+ io_uring_for_each_cqe(&ring, head, cqe) {
+ reaped++;
+ if (cqe->res) {
+ fprintf(stderr, "%s: This NOP should have succeeded. Got %d\n",
+ __func__, cqe->res);
+ return T_EXIT_FAIL;
+ }
+ } io_uring_cq_advance(&ring, reaped);
+
+ io_uring_queue_exit(&ring);
+
+ return 0;
+}
+
+
+int test_unlinked_clone_sequence(void)
+{
+ struct io_uring_sqe *sqe;
+ struct io_uring_cqe *cqe;
+ struct io_uring ring;
+ int ret;
+
+ ret = t_create_ring(10, &ring, IORING_SETUP_SUBMIT_ALL);
+ if (ret < 0) {
+ fprintf(stderr, "queue_init: %s\n", strerror(-ret));
+ return T_SETUP_SKIP;
+ }
+
+ /* Issue unlinked clone. */
+ sqe = io_uring_get_sqe(&ring);
+ io_uring_prep_clone(sqe);
+
+ io_uring_submit(&ring);
+
+ if (io_uring_wait_cqe(&ring, &cqe)) {
+ fprintf(stderr, "%s: Failed to wait for cqes\n", __func__);
+ return T_EXIT_FAIL;
+ }
+
+ if (cqe->res != -EINVAL) {
+ fprintf(stderr,
+ "%s: Unlinked clone should have failed. Got %d\n",
+ __func__, cqe->res);
+ return T_EXIT_FAIL;
+ }
+ io_uring_cqe_seen(&ring, cqe);
+
+ io_uring_queue_exit(&ring);
+ return 0;
+}
+
+int test_bad_link_sequence(void)
+{
+ struct io_uring_sqe *sqe;
+ struct io_uring_cqe *cqe;
+ struct io_uring ring;
+ int ret;
+
+ ret = t_create_ring(10, &ring, IORING_SETUP_SUBMIT_ALL);
+ if (ret < 0) {
+ fprintf(stderr, "queue_init: %s\n", strerror(-ret));
+ return T_SETUP_SKIP;
+ }
+
+ /* Issue link that doesn't start with clone. */
+ sqe = io_uring_get_sqe(&ring);
+ io_uring_prep_nop(sqe);
+ sqe->flags |= IOSQE_IO_LINK;
+
+ sqe = io_uring_get_sqe(&ring);
+ io_uring_prep_clone(sqe);
+ sqe->flags |= IOSQE_IO_LINK;
+
+ sqe = io_uring_get_sqe(&ring);
+ io_uring_prep_nop(sqe);
+
+ io_uring_submit(&ring);
+
+ if (io_uring_wait_cqes(&ring, &cqe, 3, NULL, NULL)) {
+ fprintf(stderr, "%s: Failed to wait for cqes\n", __func__);
+ return T_EXIT_FAIL;
+ }
+
+ if (cqe->res != -ECANCELED) {
+ fprintf(stderr,
+ "%s: first NOP should have been canceled. Got %d\n",
+ __func__, cqe->res);
+ return T_EXIT_FAIL;
+ }
+ io_uring_cqe_seen(&ring, cqe);
+
+ io_uring_peek_cqe(&ring, &cqe);
+ if (cqe->res != -EINVAL) {
+ fprintf(stderr,
+ "%s: CLONE not starting link should've failed. "
+ "Got %d\n", __func__, cqe->res);
+ return T_EXIT_FAIL;
+ }
+ io_uring_cqe_seen(&ring, cqe);
+
+ io_uring_queue_exit(&ring);
+ return 0;
+}
+
+#define TEST_FILE "./clone-exec-test-file"
+
+/* Execute 'touch TEST_FILE' by doing a lookup in $PATH and verify the
+ command was executed by checking the file exists. It would have been
+ better to just redirect the output and use an echo, but we have no
+ dup(2) in io_uring to redirect stdout inside the spawned task yet.
+ */
+int test_spawn_sequence(void)
+{
+ struct io_uring_sqe *sqe;
+ struct io_uring_cqe *cqe;
+ struct io_uring ring;
+ unsigned int head;
+ int ret, i, reaped = 0;
+ char *buf;
+
+ bool did_exec = false;
+ struct stat statbuf;
+
+ char *const t_argv[] = { "touch", TEST_FILE, NULL };
+
+ char *path[]= { "/usr/local/bin/", "/usr/local/sbin/", "/usr/bin/",
+ "/usr/sbin/", "/sbin", "/bin" };
+
+ ret = t_create_ring(10, &ring, IORING_SETUP_SUBMIT_ALL);
+ if (ret < 0) {
+
+ fprintf(stderr, "queue_init: %s\n", strerror(-ret));
+ return T_SETUP_SKIP;
+ }
+
+ ret = fstatat(AT_FDCWD, TEST_FILE, &statbuf, 0);
+ if (!ret) {
+ ret = unlinkat(AT_FDCWD, TEST_FILE, 0);
+ if (ret) {
+ printf("Failed to unlink tmp file\n");
+ return T_SETUP_SKIP;
+ }
+ } else if (errno == ENOENT) {
+ ret = 0;
+ } else {
+ printf("failed to fstatat %d\n", errno);
+ return T_EXIT_FAIL;
+ }
+
+ sqe = io_uring_get_sqe(&ring);
+ io_uring_prep_clone(sqe);
+ /* allocate from heap to simplify freeing. */
+ sqe->user_data = (__u64) strdup("clone");
+ sqe->flags |= IOSQE_IO_LINK;
+
+ for (i = 0; i < ARRAY_SIZE(path); i++ ) {
+ buf = malloc(MAX_PATH);
+ if (!buf)
+ return -ENOMEM;
+ snprintf(buf, MAX_PATH, "%s/%s", path[i], "touch");
+
+ sqe = io_uring_get_sqe(&ring);
+ io_uring_prep_execveat(sqe, AT_FDCWD, buf, t_argv, t_envp, 0);
+ sqe->user_data = (__u64) buf;
+ io_uring_sqe_set_flags(sqe, IOSQE_IO_LINK|IOSQE_IO_HARDLINK);
+ }
+
+ io_uring_submit_and_wait(&ring, i + 1);
+
+ /* Check the CLONE */
+ io_uring_peek_cqe(&ring, &cqe);
+ if (cqe->res) {
+ fprintf(stderr, "%s: failed to clone. Got %d\n",
+ __func__, cqe->res);
+ return T_EXIT_FAIL;
+ }
+ io_uring_cqe_seen(&ring, cqe);
+
+ /* Check the EXEC */
+ io_uring_for_each_cqe(&ring, head, cqe) {
+ reaped++;
+
+ if (!did_exec) {
+ if (cqe->res == 0) {
+ /* An execve succeeded */
+ did_exec = true;
+ } else if (!(cqe->res == -ENOENT)) {
+ printf("EXEC %s: expecting -ENOENT got %d\n",
+ (char*)cqe->user_data, cqe->res);
+ ret = T_EXIT_FAIL;
+ }
+ /* We are looking at $PATH to find a suitable
+ * binary. Any -ENOENT before succeeding the
+ * exec is expected.
+ */
+ } else {
+ /* After an exec, further requests must be canceled. */
+ if (!(cqe->res == -ECANCELED)) {
+ printf("EXEC %s: expecting -ECANCELED got %d\n",
+ (char*)cqe->user_data, cqe->res);
+ ret = T_EXIT_FAIL;
+ }
+ }
+ free((char*)cqe->user_data);
+ } io_uring_cq_advance(&ring, reaped);
+
+
+ if (!did_exec) {
+ printf("All OP_EXEC failed!\n");
+ ret = T_EXIT_FAIL;
+ }
+
+ ret = fstatat(AT_FDCWD, TEST_FILE, &statbuf, 0);
+ if (ret) {
+ /* We might need to give the spawned command a chance to run. */
+ sleep(1);
+ ret = fstatat(AT_FDCWD, TEST_FILE, &statbuf, 0);
+ if (ret) {
+ printf("Touch didn't run? File wasn't created! errno=%d\n", errno);
+ return T_EXIT_FAIL;
+ }
+ }
+
+ io_uring_queue_exit(&ring);
+ return ret;
+}
+
+int main(int argc, char *argv[], char *envp[])
+{
+ int ret = 0;
+
+ t_envp = envp;
+
+ if (test_fail_sequence()) {
+ fprintf(stderr, "test_failed_sequence failed\n");
+ ret = T_EXIT_FAIL;
+ }
+
+ if (test_unlinked_clone_sequence()) {
+ fprintf(stderr, "test_unlinked_clone_sequence\n");
+ ret = T_EXIT_FAIL;
+ }
+
+ if (test_bad_link_sequence()) {
+ fprintf(stderr, "test_bad_link_sequence failed\n");
+ ret = T_EXIT_FAIL;
+ }
+
+ if (test_simple_sequence()) {
+ fprintf(stderr, "test_simple_sequence failed\n");
+ ret = T_EXIT_FAIL;
+ }
+
+ if (test_bad_exec()) {
+ fprintf(stderr, "test_bad_exec failed\n");
+ ret = T_EXIT_FAIL;
+ }
+
+ if (test_spawn_sequence()) {
+ fprintf(stderr, "test_spawn_sequence failed\n");
+ ret = T_EXIT_FAIL;
+ }
+
+ return ret;
+}
--
2.47.0
^ permalink raw reply related [flat|nested] 4+ messages in thread