1 /* SPDX-License-Identifier: MIT */
2
3 #include <stdio.h>
4 #include <assert.h>
5 #include <string.h>
6 #include <unistd.h>
7 #include <stdlib.h>
8 #include <sys/ioctl.h>
9 #include <linux/fs.h>
10
11 #include "liburing.h"
12 #include "helpers.h"
13
14 #define MAX_TEST_LBAS 1024
15
16 static const char *filename;
17 struct opcode {
18 int op;
19 bool test;
20 bool not_supported;
21 };
22
23 #define TEST_BLOCK_URING_CMD_MAX 3
24
25 static struct opcode opcodes[TEST_BLOCK_URING_CMD_MAX] = {
26 { .op = BLOCK_URING_CMD_DISCARD, .test = true, },
27 { .test = false, },
28 { .test = false, },
29 };
30
31 static int lba_size;
32 static uint64_t bdev_size;
33 static uint64_t bdev_size_lbas;
34 static char *buffer;
35
prep_blk_cmd(struct io_uring_sqe * sqe,int fd,uint64_t from,uint64_t len,int cmd_op)36 static void prep_blk_cmd(struct io_uring_sqe *sqe, int fd,
37 uint64_t from, uint64_t len,
38 int cmd_op)
39 {
40 assert(cmd_op == BLOCK_URING_CMD_DISCARD);
41
42 io_uring_prep_cmd_discard(sqe, fd, from, len);
43 }
44
queue_cmd_range(struct io_uring * ring,int bdev_fd,uint64_t from,uint64_t len,int cmd_op)45 static int queue_cmd_range(struct io_uring *ring, int bdev_fd,
46 uint64_t from, uint64_t len, int cmd_op)
47 {
48 struct io_uring_sqe *sqe;
49 struct io_uring_cqe *cqe;
50 int err;
51
52 sqe = io_uring_get_sqe(ring);
53 assert(sqe != NULL);
54 prep_blk_cmd(sqe, bdev_fd, from, len, cmd_op);
55
56 err = io_uring_submit_and_wait(ring, 1);
57 if (err != 1) {
58 fprintf(stderr, "io_uring_submit_and_wait failed %d\n", err);
59 exit(1);
60 }
61
62 err = io_uring_wait_cqe(ring, &cqe);
63 if (err) {
64 fprintf(stderr, "io_uring_wait_cqe failed %d (op %i)\n",
65 err, cmd_op);
66 exit(1);
67 }
68
69 err = cqe->res;
70 io_uring_cqe_seen(ring, cqe);
71 return err;
72 }
73
queue_cmd_lba(struct io_uring * ring,int bdev_fd,uint64_t from,uint64_t nr_lba,int cmd_op)74 static int queue_cmd_lba(struct io_uring *ring, int bdev_fd,
75 uint64_t from, uint64_t nr_lba, int cmd_op)
76 {
77 return queue_cmd_range(ring, bdev_fd, from * lba_size,
78 nr_lba * lba_size, cmd_op);
79 }
80
queue_discard_lba(struct io_uring * ring,int bdev_fd,uint64_t from,uint64_t nr_lba)81 static int queue_discard_lba(struct io_uring *ring, int bdev_fd,
82 uint64_t from, uint64_t nr_lba)
83 {
84 return queue_cmd_lba(ring, bdev_fd, from, nr_lba,
85 BLOCK_URING_CMD_DISCARD);
86 }
87
test_parallel(struct io_uring * ring,int fd,int cmd_op)88 static int test_parallel(struct io_uring *ring, int fd, int cmd_op)
89 {
90 struct io_uring_sqe *sqe;
91 struct io_uring_cqe *cqe;
92 int inflight = 0;
93 int max_inflight = 16;
94 int left = 1000;
95 int ret;
96
97 while (left || inflight) {
98 int queued = 0;
99 unsigned head, nr_cqes = 0;
100 int lba_len = 8;
101
102 while (inflight < max_inflight && left) {
103 int off = rand() % (MAX_TEST_LBAS - lba_len);
104 sqe = io_uring_get_sqe(ring);
105 assert(sqe != NULL);
106
107 prep_blk_cmd(sqe, fd, off * lba_size,
108 lba_len * lba_size, cmd_op);
109 if (rand() & 1)
110 sqe->flags |= IOSQE_ASYNC;
111
112 queued++;
113 left--;
114 inflight++;
115 }
116 if (queued) {
117 ret = io_uring_submit(ring);
118 if (ret != queued) {
119 fprintf(stderr, "io_uring_submit failed %d\n", ret);
120 return T_EXIT_FAIL;
121 }
122 }
123
124 ret = io_uring_wait_cqe(ring, &cqe);
125 if (ret) {
126 fprintf(stderr, "io_uring_wait_cqe failed %d\n", ret);
127 exit(1);
128 }
129
130 io_uring_for_each_cqe(ring, head, cqe) {
131 nr_cqes++;
132 inflight--;
133 if (cqe->res != 0) {
134 fprintf(stderr, "cmd %i failed %i\n", cmd_op,
135 cqe->res);
136 return T_EXIT_FAIL;
137 }
138 }
139 io_uring_cq_advance(ring, nr_cqes);
140 }
141
142 return 0;
143 }
144
cmd_issue_verify(struct io_uring * ring,int fd,int lba,int len,int cmd_op)145 static int cmd_issue_verify(struct io_uring *ring, int fd, int lba, int len,
146 int cmd_op)
147 {
148 int verify = (cmd_op != BLOCK_URING_CMD_DISCARD);
149 int ret, i;
150 ssize_t res;
151
152 if (verify) {
153 for (i = 0; i < len; i++) {
154 size_t off = (i + lba) * lba_size;
155
156 res = pwrite(fd, buffer, lba_size, off);
157 if (res == -1) {
158 fprintf(stderr, "pwrite failed\n");
159 return T_EXIT_FAIL;
160 }
161 }
162 }
163
164 ret = queue_cmd_lba(ring, fd, lba, len, cmd_op);
165 if (ret) {
166 if (ret == -EINVAL || ret == -EOPNOTSUPP)
167 return T_EXIT_SKIP;
168
169 fprintf(stderr, "cmd_issue_verify %i fail lba %i len %i ret %i\n",
170 cmd_op, lba, len, ret);
171 return T_EXIT_FAIL;
172 }
173
174 if (verify) {
175 for (i = 0; i < len; i++) {
176 size_t off = (i + lba) * lba_size;
177
178 res = pread(fd, buffer, lba_size, off);
179 if (res == -1) {
180 fprintf(stderr, "pread failed\n");
181 return T_EXIT_FAIL;
182 }
183 if (!memchr(buffer, 0, lba_size)) {
184 fprintf(stderr, "mem cmp failed, lba %i\n", lba + i);
185 return T_EXIT_FAIL;
186 }
187 }
188 }
189 return 0;
190 }
191
basic_cmd_test(struct io_uring * ring,int op)192 static int basic_cmd_test(struct io_uring *ring, int op)
193 {
194 int cmd_op = opcodes[op].op;
195 int ret, fd;
196
197 if (!opcodes[op].test)
198 return T_EXIT_SKIP;
199
200 fd = open(filename, O_DIRECT | O_RDWR | O_EXCL);
201 if (fd < 0) {
202 fprintf(stderr, "open failed %i\n", errno);
203 return T_EXIT_FAIL;
204 }
205
206 ret = cmd_issue_verify(ring, fd, 0, 1, cmd_op);
207 if (ret == T_EXIT_SKIP) {
208 printf("cmd %i not supported, skip\n", cmd_op);
209 opcodes[op].not_supported = 1;
210 close(fd);
211 return T_EXIT_SKIP;
212 } else if (ret) {
213 fprintf(stderr, "cmd %i fail 0 1\n", cmd_op);
214 return T_EXIT_FAIL;
215 }
216
217 ret = cmd_issue_verify(ring, fd, 7, 15, cmd_op);
218 if (ret) {
219 fprintf(stderr, "cmd %i fail 7 15 %i\n", cmd_op, ret);
220 return T_EXIT_FAIL;
221 }
222
223 ret = cmd_issue_verify(ring, fd, 1, MAX_TEST_LBAS - 1, cmd_op);
224 if (ret) {
225 fprintf(stderr, "large cmd %i failed %i\n", cmd_op, ret);
226 return T_EXIT_FAIL;
227 }
228
229 ret = test_parallel(ring, fd, cmd_op);
230 if (ret) {
231 fprintf(stderr, "test_parallel() %i failed %i\n", cmd_op, ret);
232 return T_EXIT_FAIL;
233 }
234
235 close(fd);
236 return 0;
237 }
238
test_fail_edge_cases(struct io_uring * ring,int op)239 static int test_fail_edge_cases(struct io_uring *ring, int op)
240 {
241 int cmd_op = opcodes[op].op;
242 int ret, fd;
243
244 if (!opcodes[op].test)
245 return T_EXIT_SKIP;
246
247 fd = open(filename, O_DIRECT | O_RDWR | O_EXCL);
248 if (fd < 0) {
249 fprintf(stderr, "open failed %i\n", errno);
250 return T_EXIT_FAIL;
251 }
252
253 ret = queue_cmd_lba(ring, fd, bdev_size_lbas, 1, cmd_op);
254 if (ret >= 0) {
255 fprintf(stderr, "cmd %i beyond capacity %i\n",
256 cmd_op, ret);
257 return 1;
258 }
259
260 ret = queue_cmd_lba(ring, fd, bdev_size_lbas - 1, 2, cmd_op);
261 if (ret >= 0) {
262 fprintf(stderr, "cmd %i beyond capacity with overlap %i\n",
263 cmd_op, ret);
264 return 1;
265 }
266
267 ret = queue_cmd_range(ring, fd, (uint64_t)-lba_size, lba_size + 2,
268 cmd_op);
269 if (ret >= 0) {
270 fprintf(stderr, "cmd %i range overflow %i\n",
271 cmd_op, ret);
272 return 1;
273 }
274
275 ret = queue_cmd_range(ring, fd, lba_size / 2, lba_size, cmd_op);
276 if (ret >= 0) {
277 fprintf(stderr, "cmd %i unaligned offset %i\n",
278 cmd_op, ret);
279 return 1;
280 }
281
282 ret = queue_cmd_range(ring, fd, 0, lba_size / 2, cmd_op);
283 if (ret >= 0) {
284 fprintf(stderr, "cmd %i unaligned size %i\n",
285 cmd_op, ret);
286 return 1;
287 }
288
289 close(fd);
290 return 0;
291 }
292
test_rdonly(struct io_uring * ring,int op)293 static int test_rdonly(struct io_uring *ring, int op)
294 {
295 int ret, fd;
296 int ro;
297
298 if (!opcodes[op].test)
299 return T_EXIT_SKIP;
300
301 fd = open(filename, O_DIRECT | O_RDONLY | O_EXCL);
302 if (fd < 0) {
303 fprintf(stderr, "open failed %i\n", errno);
304 return T_EXIT_FAIL;
305 }
306
307 ret = queue_discard_lba(ring, fd, 0, 1);
308 if (ret >= 0) {
309 fprintf(stderr, "discarded with O_RDONLY %i\n", ret);
310 return 1;
311 }
312 close(fd);
313
314 fd = open(filename, O_DIRECT | O_RDWR | O_EXCL);
315 if (fd < 0) {
316 fprintf(stderr, "open failed %i\n", errno);
317 return T_EXIT_FAIL;
318 }
319
320 ro = 1;
321 ret = ioctl(fd, BLKROSET, &ro);
322 if (ret) {
323 fprintf(stderr, "BLKROSET 1 failed %i\n", errno);
324 return T_EXIT_FAIL;
325 }
326
327 ret = queue_discard_lba(ring, fd, 0, 1);
328 if (ret >= 0) {
329 fprintf(stderr, "discarded with O_RDONLY %i\n", ret);
330 return 1;
331 }
332
333 ro = 0;
334 ret = ioctl(fd, BLKROSET, &ro);
335 if (ret) {
336 fprintf(stderr, "BLKROSET 0 failed %i\n", errno);
337 return T_EXIT_FAIL;
338 }
339 close(fd);
340 return 0;
341 }
342
main(int argc,char * argv[])343 int main(int argc, char *argv[])
344 {
345 struct io_uring ring;
346 int fd, ret, i, fret;
347 int cmd_op;
348
349 if (argc != 2)
350 return T_EXIT_SKIP;
351 filename = argv[1];
352
353 fd = open(filename, O_DIRECT | O_RDONLY | O_EXCL);
354 if (fd < 0) {
355 fprintf(stderr, "open failed %i\n", errno);
356 return T_EXIT_FAIL;
357 }
358
359 ret = ioctl(fd, BLKGETSIZE64, &bdev_size);
360 if (ret < 0) {
361 fprintf(stderr, "BLKGETSIZE64 failed %i\n", errno);
362 return T_EXIT_FAIL;
363 }
364 ret = ioctl(fd, BLKSSZGET, &lba_size);
365 if (ret < 0) {
366 fprintf(stderr, "BLKSSZGET failed %i\n", errno);
367 return T_EXIT_FAIL;
368 }
369 assert(bdev_size % lba_size == 0);
370 bdev_size_lbas = bdev_size / lba_size;
371 close(fd);
372
373 buffer = aligned_alloc(lba_size, lba_size);
374 if (!buffer) {
375 fprintf(stderr, "aligned_alloc failed\n");
376 return T_EXIT_FAIL;
377 }
378 for (i = 0; i < lba_size; i++)
379 buffer[i] = i ^ 0xA7;
380
381 if (bdev_size_lbas < MAX_TEST_LBAS) {
382 fprintf(stderr, "the device is too small, skip\n");
383 return T_EXIT_SKIP;
384 }
385
386 ret = io_uring_queue_init(16, &ring, 0);
387 if (ret) {
388 fprintf(stderr, "queue init failed: %d\n", ret);
389 return T_EXIT_FAIL;
390 }
391
392 fret = T_EXIT_SKIP;
393 for (cmd_op = 0; cmd_op < TEST_BLOCK_URING_CMD_MAX; cmd_op++) {
394 if (!opcodes[cmd_op].test)
395 continue;
396 ret = basic_cmd_test(&ring, cmd_op);
397 if (ret) {
398 if (ret == T_EXIT_SKIP)
399 continue;
400
401 fprintf(stderr, "basic_cmd_test() failed, cmd %i\n",
402 cmd_op);
403 return T_EXIT_FAIL;
404 }
405
406 ret = test_rdonly(&ring, cmd_op);
407 if (ret) {
408 fprintf(stderr, "test_rdonly() failed, cmd %i\n",
409 cmd_op);
410 return T_EXIT_FAIL;
411 }
412
413 ret = test_fail_edge_cases(&ring, cmd_op);
414 if (ret) {
415 fprintf(stderr, "test_fail_edge_cases() failed, cmd %i\n",
416 cmd_op);
417 return T_EXIT_FAIL;
418 }
419 fret = T_EXIT_PASS;
420 }
421
422 io_uring_queue_exit(&ring);
423 free(buffer);
424 return fret;
425 }
426