summaryrefslogtreecommitdiff
path: root/test/submit-link-fail.c
blob: a12bdae502f9164ad340a549ce76a7320995fcc0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/* SPDX-License-Identifier: MIT */
/*
 * Description: tests linked requests failing during submission
 */
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>

#include "liburing.h"

#define DRAIN_USER_DATA 42

static int test_underprep_fail(bool hardlink, bool drain, bool link_last,
			       int link_size, int fail_idx)
{
	const int invalid_fd = 42;
	int link_flags = IOSQE_IO_LINK;
	int total_submit = link_size;
	struct io_uring ring;
	struct io_uring_sqe *sqe;
	struct io_uring_cqe *cqe;
	char buffer[1];
	int i, ret, fds[2];

	if (drain)
		link_flags |= IOSQE_IO_DRAIN;
	if (hardlink)
		link_flags |= IOSQE_IO_HARDLINK;

	assert(fail_idx < link_size);
	assert(link_size < 40);

	/* create a new ring as it leaves it dirty */
	ret = io_uring_queue_init(8, &ring, 0);
	if (ret) {
		printf("ring setup failed\n");
		return -1;
	}
	if (pipe(fds)) {
		perror("pipe");
		return -1;
	}

	if (drain) {
		/* clog drain, so following reqs sent to draining */
		sqe = io_uring_get_sqe(&ring);
		io_uring_prep_read(sqe, fds[0], buffer, sizeof(buffer), 0);
		sqe->user_data = DRAIN_USER_DATA;
		sqe->flags |= IOSQE_IO_DRAIN;
		total_submit++;
	}

	for (i = 0; i < link_size; i++) {
		sqe = io_uring_get_sqe(&ring);
		if (i == fail_idx)
			io_uring_prep_read(sqe, invalid_fd, buffer, 1, 0);
		else
			io_uring_prep_nop(sqe);

		if (i != link_size - 1 || !link_last)
			sqe->flags |= link_flags;
		sqe->user_data = i;
	}

	ret = io_uring_submit(&ring);
	if (ret != total_submit) {
		/* Old behaviour, failed early and under-submitted */
		if (ret == fail_idx + 1 + drain)
			goto out;
		fprintf(stderr, "submit failed: %d\n", ret);
		return -1;
	}

	if (drain) {
		/* unclog drain */
		ret = write(fds[1], buffer, sizeof(buffer));
		if (ret < 0) {
			perror("write");
			return 1;
		}
	}

	for (i = 0; i < total_submit; i++) {
		ret = io_uring_wait_cqe(&ring, &cqe);
		if (ret) {
			fprintf(stderr, "wait_cqe=%d\n", ret);
			return 1;
		}

		ret = cqe->res;
		if (cqe->user_data == DRAIN_USER_DATA) {
			if (ret != 1) {
				fprintf(stderr, "drain failed %d\n", ret);
				return 1;
			}
		} else if (cqe->user_data == fail_idx) {
			if (ret == 0 || ret == -ECANCELED) {
				fprintf(stderr, "half-prep req unexpected return %d\n", ret);
				return 1;
			}
		} else {
			if (ret != -ECANCELED) {
				fprintf(stderr, "cancel failed %d, ud %d\n", ret, (int)cqe->user_data);
				return 1;
			}
		}
		io_uring_cqe_seen(&ring, cqe);
	}
out:
	close(fds[0]);
	close(fds[1]);
	io_uring_queue_exit(&ring);
	return 0;
}

int main(int argc, char *argv[])
{
	int ret, link_size, fail_idx, i;

	if (argc > 1)
		return 0;

	/*
	 * hardlink, size=3, fail_idx=1, drain=false -- kernel fault
	 * link, size=3, fail_idx=0, drain=true -- kernel fault
	 * link, size=3, fail_idx=1, drain=true -- invalid cqe->res
	 */
	for (link_size = 0; link_size < 3; link_size++) {
		for (fail_idx = 0; fail_idx < link_size; fail_idx++) {
			for (i = 0; i < 8; i++) {
				bool hardlink = (i & 1) != 0;
				bool drain = (i & 2) != 0;
				bool link_last = (i & 4) != 0;

				ret = test_underprep_fail(hardlink, drain, link_last,
							  link_size, fail_idx);
				if (!ret)
					continue;

				fprintf(stderr, "failed %d, hard %d, drain %d,"
						"link_last %d, size %d, idx %d\n",
						ret, hardlink, drain, link_last,
						link_size, fail_idx);
				return 1;
			}
		}
	}

	return 0;
}