284 lines
6.0 KiB
C
284 lines
6.0 KiB
C
/* SPDX-License-Identifier: MIT */
|
|
/*
|
|
* Description: test sharing a ring across a fork
|
|
*/
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include "liburing.h"
|
|
|
|
|
|
struct forktestmem
|
|
{
|
|
struct io_uring ring;
|
|
pthread_barrier_t barrier;
|
|
pthread_barrierattr_t barrierattr;
|
|
};
|
|
|
|
static int open_tempfile(const char *dir, const char *fname)
|
|
{
|
|
int fd;
|
|
char buf[32];
|
|
|
|
snprintf(buf, sizeof(buf), "%s/%s",
|
|
dir, fname);
|
|
fd = open(buf, O_RDWR | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
|
|
if (fd < 0) {
|
|
perror("open");
|
|
exit(1);
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
static int submit_write(struct io_uring *ring, int fd, const char *str,
|
|
int wait)
|
|
{
|
|
struct io_uring_sqe *sqe;
|
|
struct iovec iovec;
|
|
int ret;
|
|
|
|
sqe = io_uring_get_sqe(ring);
|
|
if (!sqe) {
|
|
fprintf(stderr, "could not get sqe\n");
|
|
return 1;
|
|
}
|
|
|
|
iovec.iov_base = (char *) str;
|
|
iovec.iov_len = strlen(str);
|
|
io_uring_prep_writev(sqe, fd, &iovec, 1, 0);
|
|
ret = io_uring_submit_and_wait(ring, wait);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "submit failed: %s\n", strerror(-ret));
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int wait_cqe(struct io_uring *ring, const char *stage)
|
|
{
|
|
struct io_uring_cqe *cqe;
|
|
int ret;
|
|
|
|
ret = io_uring_wait_cqe(ring, &cqe);
|
|
if (ret) {
|
|
fprintf(stderr, "%s wait_cqe failed %d\n", stage, ret);
|
|
return 1;
|
|
}
|
|
if (cqe->res < 0) {
|
|
fprintf(stderr, "%s cqe failed %d\n", stage, cqe->res);
|
|
return 1;
|
|
}
|
|
|
|
io_uring_cqe_seen(ring, cqe);
|
|
return 0;
|
|
}
|
|
|
|
static int verify_file(const char *tmpdir, const char *fname, const char* expect)
|
|
{
|
|
int fd;
|
|
char buf[512];
|
|
int err = 0;
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
fd = open_tempfile(tmpdir, fname);
|
|
if (fd < 0)
|
|
return 1;
|
|
|
|
if (read(fd, buf, sizeof(buf) - 1) < 0)
|
|
return 1;
|
|
|
|
if (strcmp(buf, expect) != 0) {
|
|
fprintf(stderr, "content mismatch for %s\n"
|
|
"got:\n%s\n"
|
|
"expected:\n%s\n",
|
|
fname, buf, expect);
|
|
err = 1;
|
|
}
|
|
|
|
close(fd);
|
|
return err;
|
|
}
|
|
|
|
static void cleanup(const char *tmpdir)
|
|
{
|
|
char buf[32];
|
|
|
|
/* don't check errors, called during partial runs */
|
|
|
|
snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "shared");
|
|
unlink(buf);
|
|
|
|
snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "parent1");
|
|
unlink(buf);
|
|
|
|
snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "parent2");
|
|
unlink(buf);
|
|
|
|
snprintf(buf, sizeof(buf), "%s/%s", tmpdir, "child");
|
|
unlink(buf);
|
|
|
|
rmdir(tmpdir);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
struct forktestmem *shmem;
|
|
char tmpdir[] = "forktmpXXXXXX";
|
|
int shared_fd;
|
|
int ret;
|
|
pid_t p;
|
|
|
|
if (argc > 1)
|
|
return 0;
|
|
|
|
shmem = mmap(0, sizeof(struct forktestmem), PROT_READ|PROT_WRITE,
|
|
MAP_SHARED | MAP_ANONYMOUS, 0, 0);
|
|
if (!shmem) {
|
|
fprintf(stderr, "mmap failed\n");
|
|
exit(1);
|
|
}
|
|
|
|
pthread_barrierattr_init(&shmem->barrierattr);
|
|
pthread_barrierattr_setpshared(&shmem->barrierattr, 1);
|
|
pthread_barrier_init(&shmem->barrier, &shmem->barrierattr, 2);
|
|
|
|
ret = io_uring_queue_init(10, &shmem->ring, 0);
|
|
if (ret < 0) {
|
|
fprintf(stderr, "queue init failed\n");
|
|
exit(1);
|
|
}
|
|
|
|
if (mkdtemp(tmpdir) == NULL) {
|
|
fprintf(stderr, "temp directory creation failed\n");
|
|
exit(1);
|
|
}
|
|
|
|
shared_fd = open_tempfile(tmpdir, "shared");
|
|
|
|
/*
|
|
* First do a write before the fork, to test whether child can
|
|
* reap that
|
|
*/
|
|
if (submit_write(&shmem->ring, shared_fd, "before fork: write shared fd\n", 0))
|
|
goto errcleanup;
|
|
|
|
p = fork();
|
|
switch (p) {
|
|
case -1:
|
|
fprintf(stderr, "fork failed\n");
|
|
goto errcleanup;
|
|
|
|
default: {
|
|
/* parent */
|
|
int parent_fd1;
|
|
int parent_fd2;
|
|
int wstatus;
|
|
|
|
/* wait till fork is started up */
|
|
pthread_barrier_wait(&shmem->barrier);
|
|
|
|
parent_fd1 = open_tempfile(tmpdir, "parent1");
|
|
parent_fd2 = open_tempfile(tmpdir, "parent2");
|
|
|
|
/* do a parent write to the shared fd */
|
|
if (submit_write(&shmem->ring, shared_fd, "parent: write shared fd\n", 0))
|
|
goto errcleanup;
|
|
|
|
/* do a parent write to an fd where same numbered fd exists in child */
|
|
if (submit_write(&shmem->ring, parent_fd1, "parent: write parent fd 1\n", 0))
|
|
goto errcleanup;
|
|
|
|
/* do a parent write to an fd where no same numbered fd exists in child */
|
|
if (submit_write(&shmem->ring, parent_fd2, "parent: write parent fd 2\n", 0))
|
|
goto errcleanup;
|
|
|
|
/* wait to switch read/writ roles with child */
|
|
pthread_barrier_wait(&shmem->barrier);
|
|
|
|
/* now wait for child to exit, to ensure we still can read completion */
|
|
waitpid(p, &wstatus, 0);
|
|
if (WEXITSTATUS(wstatus) != 0) {
|
|
fprintf(stderr, "child failed\n");
|
|
goto errcleanup;
|
|
}
|
|
|
|
if (wait_cqe(&shmem->ring, "p cqe 1"))
|
|
goto errcleanup;
|
|
|
|
if (wait_cqe(&shmem->ring, "p cqe 2"))
|
|
goto errcleanup;
|
|
|
|
/* check that IO can still be submitted after child exited */
|
|
if (submit_write(&shmem->ring, shared_fd, "parent: write shared fd after child exit\n", 0))
|
|
goto errcleanup;
|
|
|
|
if (wait_cqe(&shmem->ring, "p cqe 3"))
|
|
goto errcleanup;
|
|
|
|
break;
|
|
}
|
|
case 0: {
|
|
/* child */
|
|
int child_fd;
|
|
|
|
/* wait till fork is started up */
|
|
pthread_barrier_wait(&shmem->barrier);
|
|
|
|
child_fd = open_tempfile(tmpdir, "child");
|
|
|
|
if (wait_cqe(&shmem->ring, "c cqe shared"))
|
|
exit(1);
|
|
|
|
if (wait_cqe(&shmem->ring, "c cqe parent 1"))
|
|
exit(1);
|
|
|
|
if (wait_cqe(&shmem->ring, "c cqe parent 2"))
|
|
exit(1);
|
|
|
|
if (wait_cqe(&shmem->ring, "c cqe parent 3"))
|
|
exit(1);
|
|
|
|
/* wait to switch read/writ roles with parent */
|
|
pthread_barrier_wait(&shmem->barrier);
|
|
|
|
if (submit_write(&shmem->ring, child_fd, "child: write child fd\n", 0))
|
|
exit(1);
|
|
|
|
/* ensure both writes have finished before child exits */
|
|
if (submit_write(&shmem->ring, shared_fd, "child: write shared fd\n", 2))
|
|
exit(1);
|
|
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
if (verify_file(tmpdir, "shared",
|
|
"before fork: write shared fd\n"
|
|
"parent: write shared fd\n"
|
|
"child: write shared fd\n"
|
|
"parent: write shared fd after child exit\n") ||
|
|
verify_file(tmpdir, "parent1", "parent: write parent fd 1\n") ||
|
|
verify_file(tmpdir, "parent2", "parent: write parent fd 2\n") ||
|
|
verify_file(tmpdir, "child", "child: write child fd\n"))
|
|
goto errcleanup;
|
|
|
|
cleanup(tmpdir);
|
|
exit(0);
|
|
|
|
errcleanup:
|
|
cleanup(tmpdir);
|
|
exit(1);
|
|
}
|