/* * 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 #include #include #include #include #include #include #include #include #include #include #define __STDC_FORMAT_MACROS 1 #include #include #include #include #include #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 */ #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(), error"); return -1; } else if (res < (int)sizeof(io_hdr)) { fprintf(stderr, "write() 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 "); 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(), error"); return -1; } else if (res < (int)sizeof(io_hdr)) { fprintf(stderr, "read() 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 ), 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 "); 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 ), 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; }