blob: 0d28526b8b1e533e7c828d5480f42b752e3b6167 [file] [log] [blame]
/*
* 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] = {0};
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] = {0};
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;
}