aboutsummaryrefslogtreecommitdiff
path: root/testing/sg_iovec_tst.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'testing/sg_iovec_tst.cpp')
-rw-r--r--testing/sg_iovec_tst.cpp599
1 files changed, 599 insertions, 0 deletions
diff --git a/testing/sg_iovec_tst.cpp b/testing/sg_iovec_tst.cpp
new file mode 100644
index 00000000..7cc18935
--- /dev/null
+++ b/testing/sg_iovec_tst.cpp
@@ -0,0 +1,599 @@
+/*
+ * Copyright (C) 2003-2021 D. Gilbert
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * Test code for D. Gilbert's extensions to the Linux OS SCSI generic ("sg")
+ * device driver.
+ * This C++ program will read a certain number of blocks of a given block
+ * size from a given sg device node using struct sg_iovec and write what is
+ * retrieved out to a normal file. The purpose is to test the sg_iovec
+ * mechanism within the sg_io_hdr and sg_io_v4 structures.
+ *
+ * struct sg_iovec and struct iovec [in include/uapi/uio.h] are basically
+ * the same thing: a pointer followed by a length (of type size_t). If
+ * applied to a disk then the pointer will hold a LBA and 'length' will
+ * be a number of logical blocks (which usually cannot exceed 2**32-1 .
+ *
+ */
+
+#include <unistd.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <errno.h>
+#include <poll.h>
+#include <limits.h>
+#include <time.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include <linux/bsg.h>
+
+#ifndef HAVE_LINUX_SG_V4_HDR
+
+/* Kernel uapi header contain __user decorations on user space pointers
+ * to indicate they are unsafe in the kernel space. However glibc takes
+ * all those __user decorations out from headers in /usr/include/linux .
+ * So to stop compile errors when directly importing include/uapi/scsi/sg.h
+ * undef __user before doing that include. */
+#define __user
+
+/* Want to block the original sg.h header from also being included. That
+ * causes lots of multiple definition errors. This will only work if this
+ * header is included _before_ the original sg.h header. */
+#define _SCSI_GENERIC_H /* original kernel header guard */
+#define _SCSI_SG_H /* glibc header guard */
+
+#include "uapi_sg.h" /* local copy of include/uapi/scsi/sg.h */
+
+#else
+#define __user
+#endif /* end of: ifndef HAVE_LINUX_SG_V4_HDR */
+
+#include "sg_lib.h"
+#include "sg_io_linux.h"
+#include "sg_unaligned.h"
+
+// C++ local header
+#include "sg_scat_gath.h"
+
+static const char * version_str = "1.08 20210214";
+
+#define ME "sg_iovec_tst: "
+
+#define IOVEC_ELEMS 1024 /* match current UIO_MAXIOV in <linux/uio.h> */
+
+#define DEF_BLK_SZ 512
+#define SENSE_BUFF_LEN 32
+#define DEF_TIMEOUT 40000 /* 40,000 milliseconds */
+
+static struct sg_iovec iovec[IOVEC_ELEMS];
+
+static int verbose;
+
+static struct option long_options[] = {
+ {"async", no_argument, 0, 'a'},
+ {"bs", required_argument, 0, 'b'},
+ {"elem_size", required_argument, 0, 'e'},
+ {"elem-size", required_argument, 0, 'e'},
+ {"elemsz", required_argument, 0, 'e'},
+ {"fill", required_argument, 0, 'f'},
+ {"from_skip", no_argument, 0, 'F'},
+ {"from-skip", no_argument, 0, 'F'},
+ {"help", no_argument, 0, 'h'},
+ {"num", required_argument, 0, 'n'},
+ {"num_blks", required_argument, 0, 'n'},
+ {"num-blks", required_argument, 0, 'n'},
+ {"sgl", required_argument, 0, 'S'},
+ {"sgv4", no_argument, 0, '4'},
+ {"skip", required_argument, 0, 's'},
+ {"verbose", no_argument, 0, 'v'},
+ {"version", no_argument, 0, 'V'},
+ {0, 0, 0, 0},
+};
+
+
+static void
+usage(void)
+{
+ printf("Usage: sg_iovec_tst [--async] [--bs=BS] [--elem_sz=ES] "
+ "[--fill=F_ELEMS]\n"
+ " [from_skip] [--help] --num=NUM [--sgl=SFN] "
+ "[--sgv4]\n"
+ " [--skip=SKIP] [--verbose] [--version] "
+ "SG_DEV OUT_F\n");
+ printf("where:\n"
+ " --async|-a async sg usage (def: use ioctl(SG_IO) )\n");
+ printf(" --bs=BS|-b BS logical block size of SG_DEV (def: 512 "
+ "bytes)\n");
+ printf(" --elem_sz=ES|-e ES iovec element size (def: BS bytes)\n");
+ printf(" --fill=F_ELEMS|-f F_ELEMS append F_ELEMS*ES zero bytes "
+ "onto OUT_F\n"
+ " after each iovec element (def: "
+ "0)\n");
+ printf(" --from_skip|-F sgl output starts from SKIP (def: 0)\n");
+ printf(" --help|-h this usage message\n");
+ printf(" --num=NUM|-n NUM number of blocks to read from SG_DEV\n");
+ printf(" --sgl=SFN|-S SFN Sgl FileName (SFN) that is written to, "
+ "with\n"
+ " addresses and lengths having ES as "
+ "their unit\n");
+ printf(" --sgv4|-4 use the sg v4 interface (def: v3 "
+ "interface)\n");
+ printf(" --skip=SKIP|-s SKIP SKIP blocks before reading S_DEV "
+ "(def: 0)\n");
+ printf(" --verbose|-v increase verbosity\n");
+ printf(" --version|-V print version and exit\n\n");
+ printf("Reads from SG_DEV and writes that data to OUT_F in binary. Uses "
+ "iovec\n(a scatter gather list) in linear mode (i.e. it cuts up "
+ "a contiguous\nbuffer). Example:\n"
+ " sg_iovec_tst -n 8k -e 4k /dev/sg3 out.bin\n");
+}
+
+/* Returns 0 if everything ok */
+static int
+sg_read(int sg_fd, uint8_t * buff, int num_blocks, int from_block, int bs,
+ int elem_size, int async)
+{
+ uint8_t rdCmd[10] = {READ_10, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t senseBuff[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_io_hdr io_hdr;
+ struct pollfd a_poll;
+ int dxfer_len = bs * num_blocks;
+ int k, pos, rem;
+
+ sg_put_unaligned_be32((uint32_t)from_block, rdCmd + 2);
+ sg_put_unaligned_be16((uint16_t)num_blocks, rdCmd + 7);
+
+ for (k = 0, pos = 0, rem = dxfer_len; k < IOVEC_ELEMS; ++k) {
+ iovec[k].iov_base = buff + pos;
+ iovec[k].iov_len = (rem > elem_size) ? elem_size : rem;
+ if (rem <= elem_size)
+ break;
+ pos += elem_size;
+ rem -= elem_size;
+ }
+ if (k >= IOVEC_ELEMS) {
+ fprintf(stderr, "Can't fit dxfer_len=%d bytes in %d iovec elements "
+ "(would need %d)\n", dxfer_len, IOVEC_ELEMS,
+ dxfer_len / elem_size);
+ fprintf(stderr, "Try expanding elem_size which is currently %d "
+ "bytes\n", elem_size);
+ return -1;
+ }
+ memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
+ io_hdr.interface_id = 'S';
+ io_hdr.cmd_len = sizeof(rdCmd);
+ io_hdr.cmdp = rdCmd;
+ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
+ io_hdr.dxfer_len = dxfer_len;
+ io_hdr.iovec_count = k + 1;
+ io_hdr.dxferp = iovec;
+ io_hdr.mx_sb_len = SENSE_BUFF_LEN;
+ io_hdr.sbp = senseBuff;
+ io_hdr.timeout = DEF_TIMEOUT;
+ io_hdr.pack_id = from_block;
+ if (verbose) {
+ char b[128];
+
+ fprintf(stderr, "cdb: %s\n", sg_get_command_str(rdCmd, 10, true,
+ sizeof(b), b));
+ }
+
+ if (async) {
+ int res = write(sg_fd, &io_hdr, sizeof(io_hdr));
+
+ if (res < 0) {
+ perror("write(<sg_device>), error");
+ return -1;
+ } else if (res < (int)sizeof(io_hdr)) {
+ fprintf(stderr, "write(<sg_device>) returned %d, expected %d\n",
+ res, (int)sizeof(io_hdr));
+ return -1;
+ }
+ a_poll.fd = sg_fd;
+ a_poll.events = POLLIN;
+ a_poll.revents = 0;
+ res = poll(&a_poll, 1, 2000 /* millisecs */ );
+ if (res < 0) {
+ perror("poll error on <sg_device>");
+ return -1;
+ }
+ if (0 == (POLLIN & a_poll.revents)) {
+ fprintf(stderr, "strange, poll() completed without data to "
+ "read\n");
+ return -1;
+ }
+ res = read(sg_fd, &io_hdr, sizeof(io_hdr));
+ if (res < 0) {
+ perror("read(<sg_device>), error");
+ return -1;
+ } else if (res < (int)sizeof(io_hdr)) {
+ fprintf(stderr, "read(<sg_device>) returned %d, expected %d\n",
+ res, (int)sizeof(io_hdr));
+ return -1;
+ }
+ } else if (ioctl(sg_fd, SG_IO, &io_hdr)) {
+ perror("reading (SG_IO) on sg device, error");
+ return -1;
+ }
+ switch (sg_err_category3(&io_hdr)) {
+ case SG_LIB_CAT_CLEAN:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ fprintf(stderr, "Recovered error while reading block=%d, num=%d\n",
+ from_block, num_blocks);
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ fprintf(stderr, "Unit attention\n");
+ return -1;
+ default:
+ sg_chk_n_print3("reading", &io_hdr, 1);
+ return -1;
+ }
+ return 0;
+}
+
+/* Returns 0 if everything ok */
+static int
+sg_read_v4(int sg_fd, uint8_t * buff, int num_blocks, int from_block, int bs,
+ int elem_size, int async)
+{
+ uint8_t rdCmd[10] = {READ_10, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ uint8_t senseBuff[SENSE_BUFF_LEN] SG_C_CPP_ZERO_INIT;
+ struct sg_io_v4 io_hdr;
+ struct pollfd a_poll;
+ int dxfer_len = bs * num_blocks;
+ int k, pos, rem, res;
+
+ sg_put_unaligned_be32((uint32_t)from_block, rdCmd + 2);
+ sg_put_unaligned_be16((uint16_t)num_blocks, rdCmd + 7);
+
+ for (k = 0, pos = 0, rem = dxfer_len; k < IOVEC_ELEMS; ++k) {
+ iovec[k].iov_base = buff + pos;
+ iovec[k].iov_len = (rem > elem_size) ? elem_size : rem;
+ if (rem <= elem_size)
+ break;
+ pos += elem_size;
+ rem -= elem_size;
+ }
+ if (k >= IOVEC_ELEMS) {
+ fprintf(stderr, "Can't fit dxfer_len=%d bytes in %d iovec elements "
+ "(would need %d)\n", dxfer_len, IOVEC_ELEMS,
+ dxfer_len / elem_size);
+ fprintf(stderr, "Try expanding elem_size which is currently %d "
+ "bytes\n", elem_size);
+ return -1;
+ }
+ memset(&io_hdr, 0, sizeof(struct sg_io_v4));
+ io_hdr.guard = 'Q';
+ io_hdr.request_len = sizeof(rdCmd);
+ io_hdr.request = (uint64_t)(uintptr_t)rdCmd;
+ io_hdr.din_xfer_len = dxfer_len;
+ io_hdr.din_xferp = (uint64_t)(uintptr_t)iovec;
+ io_hdr.din_iovec_count = k + 1;
+ io_hdr.max_response_len = SG_DXFER_FROM_DEV;
+ io_hdr.response = (uint64_t)(uintptr_t)senseBuff;
+ io_hdr.timeout = DEF_TIMEOUT;
+ io_hdr.request_extra = from_block; /* pack_id */
+ if (verbose) {
+ char b[128];
+
+ fprintf(stderr, "cdb: %s\n", sg_get_command_str(rdCmd, 10, true,
+ sizeof(b), b));
+ }
+ if (async) {
+ res = ioctl(sg_fd, SG_IOSUBMIT, &io_hdr);
+ if (res < 0) {
+ perror("ioctl(SG_IOSUBMIT <sg_device>), error");
+ return -1;
+ }
+ a_poll.fd = sg_fd;
+ a_poll.events = POLLIN;
+ a_poll.revents = 0;
+ res = poll(&a_poll, 1, 2000 /* millisecs */ );
+ if (res < 0) {
+ perror("poll error on <sg_device>");
+ return -1;
+ }
+ if (0 == (POLLIN & a_poll.revents)) {
+ fprintf(stderr, "strange, poll() completed without data to "
+ "read\n");
+ return -1;
+ }
+ res = ioctl(sg_fd, SG_IORECEIVE, &io_hdr);
+ if (res < 0) {
+ perror("ioctl(SG_IORECEIVE <sg_device>), error");
+ return -1;
+ }
+ } else if (ioctl(sg_fd, SG_IO, &io_hdr)) {
+ perror("ioctl(SG_IO) on sg device, error");
+ return -1;
+ }
+
+ res = sg_err_category_new(io_hdr.device_status, io_hdr.transport_status,
+ io_hdr.driver_status,
+ (const uint8_t *)(unsigned long)io_hdr.response,
+ io_hdr.response_len);
+ switch (res) {
+ case SG_LIB_CAT_CLEAN:
+ break;
+ case SG_LIB_CAT_RECOVERED:
+ fprintf(stderr, "Recovered error while reading block=%d, num=%d\n",
+ from_block, num_blocks);
+ break;
+ case SG_LIB_CAT_UNIT_ATTENTION:
+ fprintf(stderr, "Unit attention\n");
+ return -1;
+ default:
+ sg_linux_sense_print("reading", io_hdr.device_status,
+ io_hdr.transport_status, io_hdr.driver_status,
+ senseBuff, io_hdr.response_len, true);
+ return -1;
+ }
+ return 0;
+}
+
+
+int
+main(int argc, char * argv[])
+{
+ bool do_sgv4 = false;
+ bool do_async = false;
+ bool do_help = false;
+ bool from_skip = false;
+ bool blk_size_given = false;
+ bool elem_size_given = false;
+ int sg_fd, fd, c, res, res2, err, dxfer_len;
+ unsigned int k;
+ int blk_size = DEF_BLK_SZ;
+ int elem_size = blk_size;
+ int num_blks = 0;
+ int f_elems = 0;
+ int64_t start_blk = 0;
+ char * sg_dev_name = 0;
+ char * out_file_name = 0;
+ char * sgl_fn = 0;
+ uint8_t * buffp;
+ uint8_t * fillp = NULL;
+ FILE * fp = NULL;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "4ab:e:f:Fhn:s:S:vV",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case '4':
+ do_sgv4 = true;
+ break;
+ case 'a':
+ do_async = true;
+ break;
+ case 'b':
+ blk_size = sg_get_num(optarg);
+ if (blk_size < 1) {
+ printf("Couldn't decode positive number after '--bs=' "
+ "option\n");
+ sg_dev_name = 0;
+ } else
+ blk_size_given = true;
+ break;
+ case 'e':
+ elem_size = sg_get_num(optarg);
+ if (elem_size < 1) {
+ printf("Couldn't decode positive number after '--elem_size=' "
+ "option\n");
+ sg_dev_name = 0;
+ } else
+ elem_size_given = true;
+ break;
+ case 'f':
+ f_elems = sg_get_num(optarg);
+ if (f_elems < 0) {
+ printf("Couldn't decode number after '--fill=' option\n");
+ sg_dev_name = 0;
+ }
+ break;
+ case 'F':
+ from_skip = true;
+ break;
+ case 'h':
+ do_help = true;
+ break;
+ case 'n':
+ num_blks = sg_get_num(optarg);
+ if (num_blks < 1) {
+ printf("Couldn't decode positive number after '--num=' "
+ "option\n");
+ sg_dev_name = 0;
+ }
+ break;
+ case 's':
+ start_blk = sg_get_llnum(optarg);
+ if ((start_blk < 0) || (start_blk > INT_MAX)) {
+ printf("Couldn't decode number after '--skip=' option\n");
+ sg_dev_name = 0;
+ }
+ break;
+ case 'S':
+ if (sgl_fn) {
+ printf("Looks like --sgl=SFN has been given twice\n");
+ sg_dev_name = 0;
+ } else
+ sgl_fn = optarg;
+ break;
+ case 'v':
+ ++verbose;
+ break;
+ case 'V':
+ printf("Version: %s\n", version_str);
+ return 0;
+ default:
+ fprintf(stderr, "unrecognised option code 0x%x ??\n", c);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ if (optind < argc) {
+ if (NULL == sg_dev_name) {
+ sg_dev_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ if (sg_dev_name) {
+ out_file_name = argv[optind];
+ ++optind;
+ }
+ if (optind < argc) {
+ for (; optind < argc; ++optind)
+ fprintf(stderr, "Unexpected extra argument: %s\n",
+ argv[optind]);
+ usage();
+ return SG_LIB_SYNTAX_ERROR;
+ }
+ }
+ }
+ if (do_help) {
+ usage();
+ return 0;
+ }
+ if (NULL == sg_dev_name) {
+ printf(">>> need sg node name (e.g. /dev/sg3)\n\n");
+ usage();
+ return 1;
+ }
+ if (NULL == out_file_name) {
+ printf(">>> need out filename (to place what is fetched by READ\n\n");
+ usage();
+ return 1;
+ }
+ if (0 == num_blks) {
+ printf(">>> need number of blocks to READ\n\n");
+ usage();
+ return 1;
+ }
+
+ if ((! elem_size_given) && blk_size_given)
+ elem_size = blk_size;
+
+ if (do_async)
+ sg_fd = open(sg_dev_name, O_RDWR);
+ else
+ sg_fd = open(sg_dev_name, O_RDONLY);
+ if (sg_fd < 0) {
+ perror(ME "sg device node open error");
+ return 1;
+ }
+ /* Don't worry, being very careful not to write to a none-sg file ... */
+ res = ioctl(sg_fd, SG_GET_VERSION_NUM, &k);
+ if ((res < 0) || (k < 30000)) {
+ printf(ME "not a sg device, or driver prior to 3.x\n");
+ return 1;
+ }
+ fd = open(out_file_name, O_WRONLY | O_CREAT, 0666);
+ if (fd < 0) {
+ perror(ME "output file open error");
+ return 1;
+ }
+ if (f_elems > 0) {
+ fillp = (uint8_t *)calloc(f_elems, elem_size);
+ if (NULL == fillp) {
+ fprintf(stderr, "fill calloc for %d bytes failed\n",
+ f_elems * elem_size);
+ goto fini;
+ }
+ }
+ if (sgl_fn) {
+ time_t t = time(NULL);
+ struct tm *tm = localtime(&t);
+ char s[128];
+
+ fp = fopen(sgl_fn, "w");
+ if (NULL == fp) {
+ err = errno;
+ fprintf(stderr, "Unable to open %s, error: %s\n", sgl_fn,
+ strerror(err));
+ res = sg_convert_errno(err);
+ goto fini;
+ }
+ strftime(s, sizeof(s), "%c", tm);
+ fprintf(fp, "# Scatter gather list generated by sg_iovec_tst "
+ "%s\n#\n", s);
+ }
+
+ dxfer_len = num_blks * blk_size;
+ buffp = (uint8_t *)calloc(num_blks, blk_size);
+ if (buffp) {
+ int dx_len;
+ int64_t curr_blk = from_skip ? start_blk : 0;
+
+ if (do_sgv4) {
+ if (sg_read(sg_fd, buffp, num_blks, (int)start_blk, blk_size,
+ elem_size, do_async))
+ goto free_buff;
+ } else {
+ if (sg_read_v4(sg_fd, buffp, num_blks, (int)start_blk, blk_size,
+ elem_size, do_async))
+ goto free_buff;
+ }
+ if (f_elems > 0) {
+ int fill_len = f_elems * elem_size;
+
+ for (dx_len = 0; dx_len < dxfer_len; dx_len += elem_size) {
+ if (write(fd, buffp + dx_len, elem_size) < 0) {
+ perror(ME "partial dxfer output write failed");
+ break;
+ }
+ if (sgl_fn) {
+ fprintf(fp, "%" PRId64 ",1\n", curr_blk);
+ curr_blk += f_elems + 1;
+ }
+ if (write(fd, fillp, fill_len) < 0) {
+ perror(ME "partial fill output write failed");
+ break;
+ }
+ }
+ } else if (write(fd, buffp, dxfer_len) < 0)
+ perror(ME "full output write failed");
+ else if (sgl_fn) {
+ for (dx_len = 0; dx_len < dxfer_len; dx_len += elem_size)
+ fprintf(fp, "%" PRId64 ",1\n", curr_blk++);
+ }
+free_buff:
+ free(buffp);
+ } else
+ fprintf(stderr, "user space calloc for %d bytes failed\n",
+ dxfer_len);
+ res = close(fd);
+ if (res < 0) {
+ perror(ME "output file close error");
+ close(sg_fd);
+ return 1;
+ }
+fini:
+ res2 = close(sg_fd);
+ if (res2 < 0) {
+ err = errno;
+ perror(ME "sg device close error");
+ if (0 == res)
+ res = sg_convert_errno(err);
+ }
+ if (fp)
+ fclose(fp);
+ return res;
+}