blob: 54e187fc384f98712f68a64afd2ed1eda5535d15 [file] [log] [blame]
/* SPDX-License-Identifier: MIT */
/*
* Description: test waitid functionality
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "liburing.h"
#include "helpers.h"
static bool no_waitid;
static void child(long usleep_time)
{
if (usleep_time)
usleep(usleep_time);
exit(0);
}
static int test_invalid_infop(struct io_uring *ring)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
siginfo_t *si = (siginfo_t *) (uintptr_t) 0x1234;
int ret, w;
pid_t pid;
pid = fork();
if (!pid) {
child(200000);
exit(0);
}
sqe = io_uring_get_sqe(ring);
io_uring_prep_waitid(sqe, P_PID, pid, si, WEXITED, 0);
sqe->user_data = 1;
io_uring_submit(ring);
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "cqe wait: %d\n", ret);
return T_EXIT_FAIL;
}
if (cqe->res != -EFAULT) {
fprintf(stderr, "Bad return on invalid infop: %d\n", cqe->res);
return T_EXIT_FAIL;
}
io_uring_cqe_seen(ring, cqe);
wait(&w);
return T_EXIT_PASS;
}
/*
* Test linked timeout with child not exiting in time
*/
static int test_noexit(struct io_uring *ring)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
struct __kernel_timespec ts;
int ret, i, w;
siginfo_t si;
pid_t pid;
pid = fork();
if (!pid) {
child(200000);
exit(0);
}
sqe = io_uring_get_sqe(ring);
io_uring_prep_waitid(sqe, P_PID, pid, &si, WEXITED, 0);
sqe->flags |= IOSQE_IO_LINK;
sqe->user_data = 1;
ts.tv_sec = 0;
ts.tv_nsec = 100 * 1000 * 1000ULL;
sqe = io_uring_get_sqe(ring);
io_uring_prep_link_timeout(sqe, &ts, 0);
sqe->user_data = 2;
io_uring_submit(ring);
for (i = 0; i < 2; i++) {
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "cqe wait: %d\n", ret);
return T_EXIT_FAIL;
}
if (cqe->user_data == 2 && cqe->res != 1) {
fprintf(stderr, "timeout res: %d\n", cqe->res);
return T_EXIT_FAIL;
}
if (cqe->user_data == 1 && cqe->res != -ECANCELED) {
fprintf(stderr, "waitid res: %d\n", cqe->res);
return T_EXIT_FAIL;
}
io_uring_cqe_seen(ring, cqe);
}
wait(&w);
return T_EXIT_PASS;
}
/*
* Test one child exiting, but not the one we were looking for
*/
static int test_double(struct io_uring *ring)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
siginfo_t si;
pid_t p1, p2;
int ret, w;
/* p1 will exit shortly */
p1 = fork();
if (!p1) {
child(100000);
exit(0);
}
/* p2 will linger */
p2 = fork();
if (!p2) {
child(200000);
exit(0);
}
sqe = io_uring_get_sqe(ring);
io_uring_prep_waitid(sqe, P_PID, p2, &si, WEXITED, 0);
io_uring_submit(ring);
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "cqe wait: %d\n", ret);
return T_EXIT_FAIL;
}
if (cqe->res < 0) {
fprintf(stderr, "cqe res: %d\n", cqe->res);
return T_EXIT_FAIL;
}
if (si.si_pid != p2) {
fprintf(stderr, "expected pid %d, got %d\n", p2, si.si_pid);
return T_EXIT_FAIL;
}
io_uring_cqe_seen(ring, cqe);
wait(&w);
return T_EXIT_PASS;
}
/*
* Test reaping of an already exited task
*/
static int test_ready(struct io_uring *ring)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
siginfo_t si;
pid_t pid;
int ret;
pid = fork();
if (!pid) {
child(0);
exit(0);
}
sqe = io_uring_get_sqe(ring);
io_uring_prep_waitid(sqe, P_PID, pid, &si, WEXITED, 0);
io_uring_submit(ring);
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "cqe wait: %d\n", ret);
return T_EXIT_FAIL;
}
if (cqe->res < 0) {
fprintf(stderr, "cqe res: %d\n", cqe->res);
return T_EXIT_FAIL;
}
if (si.si_pid != pid) {
fprintf(stderr, "expected pid %d, got %d\n", pid, si.si_pid);
return T_EXIT_FAIL;
}
io_uring_cqe_seen(ring, cqe);
return T_EXIT_PASS;
}
/*
* Test cancelation of pending waitid
*/
static int test_cancel(struct io_uring *ring)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
int ret, i, w;
pid_t pid;
pid = fork();
if (!pid) {
child(20000);
exit(0);
}
sqe = io_uring_get_sqe(ring);
io_uring_prep_waitid(sqe, P_PID, pid, NULL, WEXITED, 0);
sqe->user_data = 1;
io_uring_submit(ring);
sqe = io_uring_get_sqe(ring);
io_uring_prep_cancel64(sqe, 1, 0);
sqe->user_data = 2;
io_uring_submit(ring);
for (i = 0; i < 2; i++) {
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "cqe wait: %d\n", ret);
return T_EXIT_FAIL;
}
if (cqe->user_data == 1 && cqe->res != -ECANCELED) {
fprintf(stderr, "cqe res: %d\n", cqe->res);
return T_EXIT_FAIL;
}
if (cqe->user_data == 2 && cqe->res != 1) {
fprintf(stderr, "cqe res: %d\n", cqe->res);
return T_EXIT_FAIL;
}
io_uring_cqe_seen(ring, cqe);
}
wait(&w);
return T_EXIT_PASS;
}
/*
* Test cancelation of pending waitid, with expected races that either
* waitid trigger or cancelation will win.
*/
static int test_cancel_race(struct io_uring *ring, int async)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
int ret, i, to_wait, total_forks;
pid_t pid;
total_forks = 0;
for (i = 0; i < 10; i++) {
total_forks++;
pid = fork();
if (!pid) {
child(getpid() & 1);
exit(0);
}
}
sqe = io_uring_get_sqe(ring);
io_uring_prep_waitid(sqe, P_ALL, -1, NULL, WEXITED, 0);
if (async)
sqe->flags |= IOSQE_ASYNC;
sqe->user_data = 1;
io_uring_submit(ring);
sqe = io_uring_get_sqe(ring);
io_uring_prep_cancel64(sqe, 1, 0);
sqe->user_data = 2;
usleep(1);
io_uring_submit(ring);
to_wait = total_forks;
for (i = 0; i < 2; i++) {
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "cqe wait: %d\n", ret);
return T_EXIT_FAIL;
}
if (cqe->user_data == 1) {
if (!cqe->res)
to_wait--;
if (!(cqe->res == -ECANCELED || cqe->res == 0)) {
fprintf(stderr, "cqe1 res: %d\n", cqe->res);
return T_EXIT_FAIL;
}
}
if (cqe->user_data == 2 &&
!(cqe->res == 1 || cqe->res == 0 || cqe->res == -ENOENT ||
cqe->res == -EALREADY)) {
fprintf(stderr, "cqe2 res: %d\n", cqe->res);
return T_EXIT_FAIL;
}
io_uring_cqe_seen(ring, cqe);
}
for (i = 0; i < to_wait; i++) {
int w;
wait(&w);
}
return T_EXIT_PASS;
}
/*
* Test basic reap of child exit
*/
static int test(struct io_uring *ring)
{
struct io_uring_sqe *sqe;
struct io_uring_cqe *cqe;
siginfo_t si;
pid_t pid;
int ret;
pid = fork();
if (!pid) {
child(100);
exit(0);
}
sqe = io_uring_get_sqe(ring);
io_uring_prep_waitid(sqe, P_PID, pid, &si, WEXITED, 0);
io_uring_submit(ring);
ret = io_uring_wait_cqe(ring, &cqe);
if (ret) {
fprintf(stderr, "cqe wait: %d\n", ret);
return T_EXIT_FAIL;
}
/* no waitid support */
if (cqe->res == -EINVAL) {
no_waitid = true;
return T_EXIT_SKIP;
}
if (cqe->res < 0) {
fprintf(stderr, "cqe res: %d\n", cqe->res);
return T_EXIT_FAIL;
}
if (si.si_pid != pid) {
fprintf(stderr, "expected pid %d, got %d\n", pid, si.si_pid);
return T_EXIT_FAIL;
}
io_uring_cqe_seen(ring, cqe);
return T_EXIT_PASS;
}
int main(int argc, char *argv[])
{
struct io_uring ring;
int ret, i;
if (argc > 1)
return T_EXIT_SKIP;
io_uring_queue_init(8, &ring, 0);
ret = test(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test failed\n");
return T_EXIT_FAIL;
}
if (no_waitid)
return T_EXIT_SKIP;
ret = test_noexit(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_noexit failed\n");
return T_EXIT_FAIL;
}
ret = test_noexit(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_noexit failed\n");
return T_EXIT_FAIL;
}
ret = test_double(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_double failed\n");
return T_EXIT_FAIL;
}
ret = test_ready(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_ready failed\n");
return T_EXIT_FAIL;
}
ret = test_cancel(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_cancel failed\n");
return T_EXIT_FAIL;
}
ret = test_invalid_infop(&ring);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_invalid_infop failed\n");
return T_EXIT_FAIL;
}
for (i = 0; i < 1000; i++) {
ret = test_cancel_race(&ring, i & 1);
if (ret == T_EXIT_FAIL) {
fprintf(stderr, "test_cancel_race failed\n");
return T_EXIT_FAIL;
}
}
io_uring_queue_exit(&ring);
return T_EXIT_PASS;
}