@@ -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 \
new file mode 100644
@@ -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;
+}
Signed-off-by: Gabriel Krisman Bertazi <krisman@suse.de> --- test/Makefile | 1 + test/clone-exec.c | 436 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 437 insertions(+) create mode 100644 test/clone-exec.c