| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (c) International Business Machines Corp., 2003 |
| * Copyright (c) Linux Test Project, 2004-2019 |
| * |
| * AUTHORS |
| * Kai Zhao (ltcd3@cn.ibm.com) |
| */ |
| |
| #include "config.h" |
| #include "tst_test.h" |
| |
| #ifdef HAVE_LIBAIO |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <libaio.h> |
| |
| #define AIO_MAXIO 32 |
| #define AIO_BLKSIZE (64*1024) |
| |
| static int wait_count = 0; |
| |
| #define DESC_FLAGS_OPR(x, y) .desc = (x == IO_CMD_PWRITE ? "WRITE: " #y: "READ : " #y), \ |
| .flags = y, .operation = x |
| |
| struct testcase { |
| const char *desc; |
| int flags; |
| int operation; |
| } testcases[] = { |
| { |
| DESC_FLAGS_OPR(IO_CMD_PWRITE, O_WRONLY | O_TRUNC | O_DIRECT | O_LARGEFILE | O_CREAT), |
| }, |
| { |
| DESC_FLAGS_OPR(IO_CMD_PREAD, O_RDONLY | O_DIRECT | O_LARGEFILE), |
| }, |
| { |
| DESC_FLAGS_OPR(IO_CMD_PWRITE, O_RDWR | O_TRUNC), |
| }, |
| { |
| DESC_FLAGS_OPR(IO_CMD_PREAD, O_RDWR), |
| }, |
| { |
| DESC_FLAGS_OPR(IO_CMD_PWRITE, O_WRONLY | O_TRUNC), |
| }, |
| { |
| DESC_FLAGS_OPR(IO_CMD_PREAD, O_RDONLY), |
| }, |
| }; |
| |
| /* |
| * Fatal error handler |
| */ |
| static void io_error(const char *func, int rc) |
| { |
| if (rc == -ENOSYS) |
| tst_brk(TCONF, "AIO not in this kernel\n"); |
| else if (rc < 0) |
| tst_brk(TFAIL, "%s: %s\n", func, strerror(-rc)); |
| else |
| tst_brk(TFAIL, "%s: error %d\n", func, rc); |
| } |
| |
| /* |
| * write work done |
| */ |
| static void work_done(io_context_t ctx, struct iocb *iocb, long res, long res2) |
| { |
| (void) ctx; // silence compiler warning (-Wunused) |
| |
| if (res2 != 0) |
| io_error("aio write", res2); |
| |
| if (res != (long)iocb->u.c.nbytes) |
| tst_brk(TFAIL, "write missed bytes expect %lu got %ld\n", |
| iocb->u.c.nbytes, res); |
| |
| wait_count--; |
| } |
| |
| /* |
| * io_wait_run() - wait for an io_event and then call the callback. |
| */ |
| static int io_wait_run(io_context_t ctx, struct timespec *to) |
| { |
| struct io_event events[AIO_MAXIO]; |
| struct io_event *ep; |
| int ret, n; |
| |
| /* |
| * get up to aio_maxio events at a time. |
| */ |
| ret = n = io_getevents(ctx, 1, AIO_MAXIO, events, to); |
| |
| /* |
| * Call the callback functions for each event. |
| */ |
| for (ep = events; n-- > 0; ep++) { |
| io_callback_t cb = (io_callback_t) ep->data; |
| struct iocb *iocb = ep->obj; |
| cb(ctx, iocb, ep->res, ep->res2); |
| } |
| return ret; |
| } |
| |
| static int io_tio(char *pathname, int flag, int operation) |
| { |
| int res, fd = 0, i = 0; |
| void *bufptr = NULL; |
| off_t offset = 0; |
| struct timespec timeout; |
| struct stat fi_stat; |
| size_t alignment; |
| |
| io_context_t myctx; |
| struct iocb iocb_array[AIO_MAXIO]; |
| struct iocb *iocbps[AIO_MAXIO]; |
| |
| fd = SAFE_OPEN(pathname, flag, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); |
| |
| /* determine the alignment from the blksize of the underlying device */ |
| SAFE_FSTAT(fd, &fi_stat); |
| alignment = fi_stat.st_blksize; |
| |
| res = io_queue_init(AIO_MAXIO, &myctx); |
| |
| for (i = 0; i < AIO_MAXIO; i++) { |
| |
| switch (operation) { |
| case IO_CMD_PWRITE: |
| if (posix_memalign(&bufptr, alignment, AIO_BLKSIZE)) { |
| tst_brk(TBROK | TERRNO, "posix_memalign failed"); |
| return -1; |
| } |
| memset(bufptr, 0, AIO_BLKSIZE); |
| |
| io_prep_pwrite(&iocb_array[i], fd, bufptr, |
| AIO_BLKSIZE, offset); |
| io_set_callback(&iocb_array[i], work_done); |
| iocbps[i] = &iocb_array[i]; |
| offset += AIO_BLKSIZE; |
| |
| break; |
| case IO_CMD_PREAD: |
| if (posix_memalign(&bufptr, alignment, AIO_BLKSIZE)) { |
| tst_brk(TBROK | TERRNO, "posix_memalign failed"); |
| return -1; |
| } |
| memset(bufptr, 0, AIO_BLKSIZE); |
| |
| io_prep_pread(&iocb_array[i], fd, bufptr, |
| AIO_BLKSIZE, offset); |
| io_set_callback(&iocb_array[i], work_done); |
| iocbps[i] = &iocb_array[i]; |
| offset += AIO_BLKSIZE; |
| break; |
| default: |
| tst_res(TFAIL, "Command failed; opcode returned: %d\n", operation); |
| return -1; |
| break; |
| } |
| } |
| |
| do { |
| res = io_submit(myctx, AIO_MAXIO, iocbps); |
| } while (res == -EAGAIN); |
| |
| if (res < 0) |
| io_error("io_submit tio", res); |
| |
| /* |
| * We have submitted all the i/o requests. Wait for them to complete and |
| * call the callbacks. |
| */ |
| wait_count = AIO_MAXIO; |
| |
| timeout.tv_sec = 30; |
| timeout.tv_nsec = 0; |
| |
| switch (operation) { |
| case IO_CMD_PREAD: |
| case IO_CMD_PWRITE: |
| { |
| while (wait_count) { |
| res = io_wait_run(myctx, &timeout); |
| if (res < 0) |
| io_error("io_wait_run", res); |
| } |
| } |
| break; |
| } |
| |
| SAFE_CLOSE(fd); |
| |
| for (i = 0; i < AIO_MAXIO; i++) |
| if (iocb_array[i].u.c.buf != NULL) |
| free(iocb_array[i].u.c.buf); |
| |
| io_queue_release(myctx); |
| |
| return 0; |
| } |
| |
| static void test_io(unsigned int n) |
| { |
| int status; |
| struct testcase *tc = testcases + n; |
| |
| status = io_tio("file", tc->flags, tc->operation); |
| if (status) |
| tst_res(TFAIL, "%s, status = %d", tc->desc, status); |
| else |
| tst_res(TPASS, "%s", tc->desc); |
| } |
| |
| static struct tst_test test = { |
| .needs_tmpdir = 1, |
| .test = test_io, |
| .tcnt = ARRAY_SIZE(testcases), |
| }; |
| |
| #else |
| TST_TEST_TCONF("test requires libaio and its development packages"); |
| #endif |