| /* |
| * 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; |
| } |