/*
 * Test code for the extensions to the Linux OS SCSI generic ("sg")
 * device driver.
 * Copyright (C) 1999-2022 D. Gilbert and P. Allworth
 *
 * 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
 *
 * This program is a specialization of the Unix "dd" command in which
 * one or both of the given files is a scsi generic device. A block size
 * ('bs') is assumed to be 512 if not given. This program complains if
 * 'ibs' or 'obs' are given with some other value than 'bs'.
 * If 'if' is not given or 'if=-' then stdin is assumed. If 'of' is
 * not given of 'of=-' then stdout assumed. The multipliers "c, b, k, m"
 * are recognized on numeric arguments.
 *
 * A non-standard argument "bpt" (blocks per transfer) is added to control
 * the maximum number of blocks in each transfer. The default bpt value is
 * (64 * 1024 * 1024 / bs) or 1 if the first expression is 0. That is an
 * integer division (rounds toward 0). For example if "bs=512" and "bpt=32"
 * are given then a maximum of 32 blocks (16KB in this case) are transferred
 * to or from the sg device in a single SCSI command.
 *
 * BEWARE: If the 'of' file is a 'sg' device (eg a disk) then it _will_
 * be written to, potentially destroying its previous contents.
 *
 * This version should compile with Linux sg drivers with version numbers
 * >= 30000 . Also this version also allows SIGPOLL or a RT signal to be
 * chosen. SIGIO is a synonym for SIGPOLL; SIGIO seems to be deprecated.
 */

/* We need F_SETSIG, (signal redirect), so following define */
#define _GNU_SOURCE 1

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>           /* for mmap() system call */
#include <sys/eventfd.h>
#include <sys/epoll.h>
#define __STDC_FORMAT_MACROS 1
#include <inttypes.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_linux_inc.h"
#include "sg_io_linux.h"
#include "sg_pr2serr.h"
#include "sg_unaligned.h"


static const char * version_str = "4.24 20221020";
static const char * my_name = "sgs_dd";

#ifndef SGV4_FLAG_POLLED
#define SGV4_FLAG_POLLED 0x800
#endif

#define DEF_BLOCK_SIZE 512
#define DEF_BPT_TIMES_BS_SZ (64 * 1024) /* 64 KB */

#define SENSE_BUFF_LEN 32       /* Arbitrary, could be larger */
#define DEF_TIMEOUT 40000       /* 40,000 millisecs == 40 seconds */
#define S_RW_LEN 10             /* Use SCSI READ(10) and WRITE(10) */
#define SGQ_MAX_RD_AHEAD 32
#define SGQ_MAX_WR_AHEAD 32
#define SGQ_NUM_ELEMS (SGQ_MAX_RD_AHEAD + SGQ_MAX_WR_AHEAD + 1)
#define MAX_BPT_VALUE (1 << 24)         /* used for maximum bs as well */
#define MAX_COUNT_SKIP_SEEK (1LL << 48) /* coverity wants upper bound */

#define SGQ_FREE 0
#define SGQ_IO_STARTED 1
#define SGQ_IO_FINISHED 2
#define SGQ_IO_ERR 3
#define SGQ_IO_WAIT 4

#define SGQ_CAN_DO_NOTHING 0    /* only temporarily in use */
#define SGQ_CAN_READ 1
#define SGQ_CAN_WRITE 2
#define SGQ_TIMEOUT 4

#define DEF_SIGTIMEDWAIT_USEC 100


#define STR_SZ 1024
#define INOUTF_SZ 900
#define EBUFF_SZ 1024

struct flags_t {
    bool dio;
    bool evfd;
    bool excl;
    bool immed;
    bool mmap;
    bool noxfer;
    bool pack;
    bool polled;
    bool tag;
    bool v3;
    bool v4;
    bool given_v3v4;
};

typedef struct request_element
{
    struct request_element * nextp;
    bool stop_after_wr;
    bool wr;
    int state;
    int blk;
    int num_blks;
    uint8_t * buffp;
    uint8_t * free_buffp;
    sg_io_hdr_t io_hdr;
    struct sg_io_v4 io_v4;
    struct flags_t * iflagp;
    struct flags_t * oflagp;
    uint8_t cmd[S_RW_LEN];
    uint8_t sb[SENSE_BUFF_LEN];
    int result;
} Rq_elem;

typedef struct request_collection
{
    bool in_is_sg;
    bool out_is_sg;
    bool no_sig;
    bool use_rt_sig;
    bool both_mmap;
    int infd;
    int in_evfd;
    int in_blk;                 /* most recent read */
    int in_count;               /* most recent read */
    int in_done_count;          /* count of completed in blocks */
    int in_partial;
    int outfd;
    int out_evfd;
    int lowest_seek;
    int out_blk;                /* most recent write */
    int out_count;              /* most recent write */
    int out_done_count;         /* count of completed out blocks */
    int out_partial;
    int bs;
    int bpt;
    int dio_incomplete;
    int sum_of_resids;
    int poll_ms;
    int pollerr_count;
    int debug;                  /* also set with -v up to -vvvvv */
    sigset_t blocked_sigs;
    int sigs_waiting;
    int sigs_rt_received;
    int sigs_io_received;
    int blk_poll_count;
    Rq_elem * rd_posp;
    Rq_elem * wr_posp;
    uint8_t * in_mmapp;
    uint8_t * out_mmapp;
    struct flags_t iflag;
    struct flags_t oflag;
    Rq_elem elem[SGQ_NUM_ELEMS];
} Rq_coll;

static bool sgs_old_sg_driver = false;  /* true if VERSION_NUM < 4.00.00 */
static bool sgs_full_v4_sg_driver = false; /* set if VERSION_NUM >= 4.00.30 */
static bool sgs_nanosec_unit = false;

static int sgq_rd_ahead_lim = SGQ_MAX_RD_AHEAD;
static int sgq_wr_ahead_lim = SGQ_MAX_WR_AHEAD;
static int sgq_num_elems = (SGQ_MAX_RD_AHEAD + SGQ_MAX_WR_AHEAD + 1);


static void
usage(int pg_num)
{
    if (pg_num > 1)
        goto second_page;
    printf("Usage: "
           "sgs_dd  [bpt=BPT] [bs=BS] [count=NUM] [deb=DEB] [if=IFILE]\n"
           "               [iflag=FLAGS] [no_sig=0|1] [of=OFILE] "
           "[oflag=FLAGS]\n"
           "               [poll_ms=MS] [rt_sig=0|1] [seek=SEEK] "
           "[skip=SKIP]\n"
           "               [--help] [--version]\n"
           "where:\n"
           "  bpt      blocks_per_transfer (default: 65536/bs (or 128 for "
           "bs=512))\n"
           "  bs       must be the logical block size of device (def: 512)\n"
           "  deb      debug: 0->no debug (def); > 0 -> more debug\n"
           "           -v (up to -vvvvv) sets deb value to number of 'v's\n"
           "  iflag    comma separated list from: dio,evfd,excl,immed,mmap,"
           "noxfer,\n"
           "           null,pack,polled,tag,v3,v4 bound to IFILE\n"
           "  no_sig   0-> use signals; 1-> no signals, hard polling "
           "instead;\n"
           "           default 0, unless polled flag(s) given then it's 1\n"
           "  oflag    same flags as iflag but bound to OFILE\n"
           "  poll_ms    number of milliseconds to wait on poll (def: 0)\n"
           "  rt_sig   0->use SIGIO (def); 1->use RT sig (SIGRTMIN + 1)\n"
           "  <other operands>     as per dd command\n\n");
    printf("dd clone for testing Linux sg driver SIGPOLL and/or polling. "
           "Either\nIFILE or OFILE must be a scsi generic device. If OFILE "
           "not given then\n/dev/null assumed (rather than stdout like "
           "dd). Use '-hh' for flag\ninformation.\n");
    return;
second_page:
    printf("flag description:\n"
           "  dio      this driver's version of O_DIRECT\n"
           "  evfd     when poll() gives POLLIN, use eventfd to find "
           "out how many\n"
           "  excl     open IFILE or OFILE with O_EXCL\n"
           "  hipri    same as 'polled'; name 'hipri' is deprecated\n"
           "  immed    use SGV4_FLAG_IMMED flag on each request\n"
           "  mmap     use mmap()-ed IO on IFILE or OFILE\n"
           "  noxfer    no transfer between user space and kernel IO "
           "buffers\n"
           "  null      does nothing, placeholder\n"
           "  pack      submit with rising pack_id, complete matching "
           "each pack_id\n"
           "  polled    set POLLED flag and use blk_poll() for completion\n"
           "  tag       use tag (from block layer) rather than "
           "pack_id\n"
           "  v3        use sg v3 interface (default)\n"
           "  v4        use sg vr interface (i.e. struct sg_io_v4)\n");
}

static int
get_mmap_addr(int fd, int num, uint8_t ** mmpp)
{
    uint8_t * mmp;

    if (! mmpp)
        return -EINVAL;
    mmp = (uint8_t *)mmap(NULL, num, PROT_READ | PROT_WRITE,
                          MAP_SHARED, fd, 0);
    if (MAP_FAILED == mmp) {
        int err = errno;

        pr2serr("%s%s: sz=%d, fd=%d, mmap() failed: %s\n",
                my_name, __func__, num, fd, strerror(err));
        return -err;
    }
    *mmpp = mmp;
    return 0;
}

/* Return of 0 -> success, -1 -> failure, 2 -> try again */
static int
read_capacity(int sg_fd, int * num_sect, int * sect_sz)
{
    int res;
    uint8_t rcCmdBlk [10] = {0x25, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    uint8_t rcBuff[64];
    uint8_t sense_b[64];
    sg_io_hdr_t io_hdr;

    memset(&io_hdr, 0, sizeof(sg_io_hdr_t));
    io_hdr.interface_id = 'S';
    io_hdr.cmd_len = sizeof(rcCmdBlk);
    io_hdr.mx_sb_len = sizeof(sense_b);
    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
    io_hdr.dxfer_len = sizeof(rcBuff);
    io_hdr.dxferp = rcBuff;
    io_hdr.cmdp = rcCmdBlk;
    io_hdr.sbp = sense_b;
    io_hdr.timeout = DEF_TIMEOUT;

    if (ioctl(sg_fd, SG_IO, &io_hdr) < 0) {
        res = -errno;
        perror("read_capacity (SG_IO) error");
        return res;
    }
    res = sg_err_category3(&io_hdr);
    if (SG_LIB_CAT_UNIT_ATTENTION == res)
        return 2; /* probably have another go ... */
    else if (SG_LIB_CAT_CLEAN != res) {
        sg_chk_n_print3("read capacity", &io_hdr, true);
        return -1;
    }
    *num_sect = sg_get_unaligned_be32(rcBuff + 0) + 1;
    *sect_sz = sg_get_unaligned_be32(rcBuff + 4);
    return 0;
}

/* -ve -> unrecoverable error, 0 -> successful, 1 -> recoverable (ENOMEM) */
static int
sg_start_io(Rq_coll * clp, Rq_elem * rep)
{
    bool is_wr = rep->wr;
    int res;
    int fd = is_wr ? clp->outfd : clp->infd;
    int num_bytes = clp->bs * rep->num_blks;
    struct flags_t * flagp = is_wr ? rep->oflagp : rep->iflagp;
    sg_io_hdr_t * hp = &rep->io_hdr;
    struct sg_io_v4 * h4p = &rep->io_v4;

    if (clp->both_mmap && is_wr)
        memcpy(clp->out_mmapp, clp->in_mmapp, num_bytes);
    memset(rep->cmd, 0, sizeof(rep->cmd));
    rep->cmd[0] = is_wr ? 0x2a : 0x28;
    sg_put_unaligned_be32((uint32_t)rep->blk, rep->cmd + 2);
    sg_put_unaligned_be16((uint16_t)rep->num_blks, rep->cmd + 7);
    if (flagp->v4)
        goto do_v4;

    memset(hp, 0, sizeof(sg_io_hdr_t));
    hp->interface_id = 'S';
    hp->cmd_len = sizeof(rep->cmd);
    hp->cmdp = rep->cmd;
    hp->dxfer_direction = is_wr ? SG_DXFER_TO_DEV : SG_DXFER_FROM_DEV;
    hp->dxfer_len = num_bytes;
    hp->mx_sb_len = sizeof(rep->sb);
    hp->sbp = rep->sb;
    hp->timeout = DEF_TIMEOUT;
    hp->usr_ptr = rep;
    hp->pack_id = rep->blk;
    if (flagp->dio)
        hp->flags |= SG_FLAG_DIRECT_IO;
    if (flagp->noxfer)
        hp->flags |= SG_FLAG_NO_DXFER;
    if (flagp->immed)
        hp->flags |= SGV4_FLAG_IMMED;
    if (flagp->polled)
        hp->flags |= SGV4_FLAG_POLLED;
    if (flagp->mmap) {
        hp->flags |= SG_FLAG_MMAP_IO;
        hp->dxferp = is_wr ? clp->out_mmapp : clp->in_mmapp;
    } else
        hp->dxferp = rep->buffp;
    if (flagp->evfd)
        hp->flags |= SGV4_FLAG_EVENTFD;
    if (clp->debug > 5) {
        pr2serr("%s: SCSI %s, blk=%d num_blks=%d\n", __func__,
                is_wr ? "WRITE" : "READ", rep->blk, rep->num_blks);
        sg_print_command(hp->cmdp);
        pr2serr("dir=%d, len=%d, dxfrp=%p, cmd_len=%d\n", hp->dxfer_direction,
                hp->dxfer_len, hp->dxferp, hp->cmd_len);
    }

    while (((res = write(fd, hp, sizeof(sg_io_hdr_t))) < 0) &&
           (EINTR == errno))
        ;
    if (res < 0) {
        if (ENOMEM == errno)
            return 1;
        if ((EDOM == errno) || (EAGAIN == errno) || (EBUSY == errno)) {
            rep->state = SGQ_IO_WAIT;   /* busy so wait */
            return 0;
        }
        pr2serr("%s: write(): %s [%d]\n", __func__, strerror(errno), errno);
        rep->state = SGQ_IO_ERR;
        return res;
    }
    rep->state = SGQ_IO_STARTED;
    if (! clp->no_sig)
        clp->sigs_waiting++;
    return 0;
do_v4:
    memset(h4p, 0, sizeof(struct sg_io_v4));
    h4p->guard = 'Q';
    h4p->request_len = sizeof(rep->cmd);
    h4p->request = (uint64_t)(uintptr_t)rep->cmd;
    if (is_wr)
        h4p->dout_xfer_len = num_bytes;
    else if (rep->num_blks > 0)
        h4p->din_xfer_len = num_bytes;
    h4p->max_response_len = sizeof(rep->sb);
    h4p->response = (uint64_t)(uintptr_t)rep->sb;
    h4p->timeout = DEF_TIMEOUT;
    h4p->usr_ptr = (uint64_t)(uintptr_t)rep;
    h4p->request_extra = rep->blk;/* N.B. blk --> pack_id --> request_extra */
    if (flagp->dio)
        h4p->flags |= SG_FLAG_DIRECT_IO;
    if (flagp->noxfer)
        h4p->flags |= SG_FLAG_NO_DXFER;
    if (flagp->immed)
        h4p->flags |= SGV4_FLAG_IMMED;
    if (flagp->polled)
        h4p->flags |= SGV4_FLAG_POLLED;
    if (flagp->mmap) {
        h4p->flags |= SG_FLAG_MMAP_IO;
        hp->dxferp = is_wr ? clp->out_mmapp : clp->in_mmapp;
    } else {
        if (is_wr)
            h4p->dout_xferp = (uint64_t)(uintptr_t)rep->buffp;
        else if (rep->num_blks > 0)
            h4p->din_xferp = (uint64_t)(uintptr_t)rep->buffp;
    }
    if (flagp->tag)
        h4p->flags |= SGV4_FLAG_YIELD_TAG;
    if (flagp->evfd)
        h4p->flags |= SGV4_FLAG_EVENTFD;
    if (! clp->no_sig)
        h4p->flags |= SGV4_FLAG_SIGNAL;

    while (((res = ioctl(fd, SG_IOSUBMIT, h4p)) < 0) && (EINTR == errno))
        ;
    if (res < 0) {
        if (ENOMEM == errno)
            return 1;
        if ((EDOM == errno) || (EAGAIN == errno) || (EBUSY == errno)) {
            rep->state = SGQ_IO_WAIT;   /* busy so wait */
            return 0;
        }
        pr2serr("%s: ioctl(SG_IOSUBMIT): %s [%d]\n", __func__,
                strerror(errno), errno);
        rep->state = SGQ_IO_ERR;
        return res;
    }
    rep->state = SGQ_IO_STARTED;
    if (! clp->no_sig)
        clp->sigs_waiting++;
    if (clp->debug > 5) {
        if (is_wr ? clp->oflag.tag : clp->iflag.tag)
            pr2serr("%s:  generated_tag=0x%" PRIx64 "\n", __func__,
                    (uint64_t)h4p->generated_tag);
    }
    return 0;
}

/* -1 -> unrecoverable error, 0 -> successful, 1 -> try again */
static int
sg_finish_io(Rq_coll * clp, bool wr, Rq_elem ** repp)
{
    struct flags_t *flagsp = wr ? &clp->oflag : &clp->iflag;
    bool dio = false;
    bool is_v4 = flagsp->v4;
    bool use_pack = flagsp->pack;
    bool use_tag = flagsp->tag;
    int fd = wr ? clp->outfd : clp->infd;
    int res, id, n;
    sg_io_hdr_t io_hdr;
    sg_io_hdr_t * hp;
    struct sg_io_v4 io_v4;
    struct sg_io_v4 * h4p;
    Rq_elem * rep;

    if (is_v4)
        goto do_v4;
    if (use_pack) {
        while (true) {
            if ( ((res = ioctl(fd, SG_GET_NUM_WAITING, &n))) < 0) {
                res = -errno;
                pr2serr("%s: ioctl(SG_GET_NUM_WAITING): %s [%d]\n",
                        __func__, strerror(errno), errno);
                return res;
            }
            if (n > 0) {
                if ( (ioctl(fd, SG_GET_PACK_ID, &id)) < 0) {
                    res = errno;
                    pr2serr("%s: ioctl(SG_GET_PACK_ID): %s [%d]\n",
                            __func__, strerror(res), res);
                    return -res;
                }
                /* got pack_id or tag of first waiting */
                break;
            }
        }
    }
    memset(&io_hdr, 0 , sizeof(sg_io_hdr_t));
    if (use_pack)
        io_hdr.pack_id = id;
    while (((res = read(fd, &io_hdr, sizeof(sg_io_hdr_t))) < 0) &&
           ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno)))
        ;
    rep = (Rq_elem *)io_hdr.usr_ptr;
    if (rep) {
        dio = flagsp->dio;
        if (rep->io_hdr.flags & SGV4_FLAG_POLLED)
            ++clp->blk_poll_count;
    }
    if (res < 0) {
        res = -errno;
        pr2serr("%s: read(): %s [%d]\n", __func__, strerror(errno), errno);
        if (rep)
            rep->state = SGQ_IO_ERR;
        return res;
    }
    if (! (rep && (SGQ_IO_STARTED == rep->state))) {
        pr2serr("%s: bad usr_ptr\n", __func__);
        if (rep)
            rep->state = SGQ_IO_ERR;
        return -1;
    }
    memcpy(&rep->io_hdr, &io_hdr, sizeof(sg_io_hdr_t));
    hp = &rep->io_hdr;
    if (repp)
        *repp = rep;

    switch (sg_err_category3(hp)) {
        case SG_LIB_CAT_CLEAN:
            break;
        case SG_LIB_CAT_RECOVERED:
            pr2serr("Recovered error on block=%d, num=%d\n", rep->blk,
                    rep->num_blks);
            break;
        case SG_LIB_CAT_UNIT_ATTENTION:
            return 1;
        default:
            sg_chk_n_print3(wr ? "writing": "reading", hp, true);
            rep->state = SGQ_IO_ERR;
            return -1;
    }
    if (dio && ((hp->info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
        ++clp->dio_incomplete; /* count dios done as indirect IO */
    clp->sum_of_resids += hp->resid;
    rep->state = SGQ_IO_FINISHED;
    if (clp->debug > 5) {
        pr2serr("%s: %s  ", __func__, wr ? "writing" : "reading");
        pr2serr("    SGQ_IO_FINISHED elem idx=%zd\n", rep - clp->elem);
    }
    return 0;
do_v4:
    id = -1;
    if (use_pack || use_tag) {
        while (true) {
            if ( ((res = ioctl(fd, SG_GET_NUM_WAITING, &n))) < 0) {
                res = -errno;
                pr2serr("%s: ioctl(SG_GET_NUM_WAITING): %s [%d]\n",
                        __func__, strerror(errno), errno);
                return res;
            }
            if (n > 0) {
                if ( (ioctl(fd, SG_GET_PACK_ID, &id)) < 0) {
                    res = errno;
                    pr2serr("%s: ioctl(SG_GET_PACK_ID): %s [%d]\n",
                            __func__, strerror(res), res);
                    return -res;
                }
                /* got pack_id or tag of first waiting */
                break;
            }
        }
    }
    memset(&io_v4, 0 , sizeof(io_v4));
    io_v4.guard = 'Q';
    if (use_tag)
        io_v4.request_tag = id;
    else if (use_pack)
        io_v4.request_extra = id;
    io_v4.flags |= SGV4_FLAG_IMMED;
    if (flagsp->evfd)
        io_v4.flags |= SGV4_FLAG_EVENTFD;
    while (((res = ioctl(fd, SG_IORECEIVE, &io_v4)) < 0) &&
           ((EINTR == errno) || (EAGAIN == errno) || (EBUSY == errno)))
        ;
    rep = (Rq_elem *)(unsigned long)io_v4.usr_ptr;
    if (res < 0) {
        res = -errno;
        pr2serr("%s: ioctl(SG_IORECEIVE): %s [%d]\n", __func__,
                strerror(errno), errno);
        if (rep)
            rep->state = SGQ_IO_ERR;
        return res;
    }
    if (rep) {
        if (rep->io_v4.flags & SGV4_FLAG_POLLED)
            ++clp->blk_poll_count;
    }
    if (! (rep && (SGQ_IO_STARTED == rep->state))) {
        pr2serr("%s: bad usr_ptr=0x%p\n", __func__, (void *)rep);
        if (rep)
            rep->state = SGQ_IO_ERR;
        return -1;
    }
    memcpy(&rep->io_v4, &io_v4, sizeof(struct sg_io_v4));
    h4p = &rep->io_v4;
    if (repp)
        *repp = rep;

    res = sg_err_category_new(h4p->device_status, h4p->transport_status,
                              h4p->driver_status,
                      (const uint8_t *)(unsigned long)h4p->response,
                              h4p->response_len);
    switch (res) {
        case SG_LIB_CAT_CLEAN:
            break;
        case SG_LIB_CAT_RECOVERED:
            pr2serr("Recovered error on block=%d, num=%d\n", rep->blk,
                    rep->num_blks);
            break;
        case SG_LIB_CAT_UNIT_ATTENTION:
            return 1;
        default:
            sg_linux_sense_print(wr ? "writing": "reading",
                                 h4p->device_status, h4p->transport_status,
                                 h4p->driver_status,
                         (const uint8_t *)(unsigned long)h4p->response,
                                 h4p->response_len, true);
            rep->state = SGQ_IO_ERR;
            return -1;
    }
    if (dio && ((h4p->info & SG_INFO_DIRECT_IO_MASK) != SG_INFO_DIRECT_IO))
        ++clp->dio_incomplete; /* count dios done as indirect IO */
    clp->sum_of_resids += h4p->din_resid;
    rep->state = SGQ_IO_FINISHED;
    if (clp->debug > 5) {
        pr2serr("%s: %s  ", __func__, wr ? "writing" : "reading");
        pr2serr("    SGQ_IO_FINISHED elem idx=%zd\n", rep - clp->elem);
        if (use_pack)
            pr2serr("%s:  pack_id=%d\n", __func__, h4p->request_extra);
        else if (use_tag)
            pr2serr("%s:  request_tag=0x%" PRIx64 "\n", __func__,
                    (uint64_t)h4p->request_tag);
    }
    return 0;
}

static int
sz_reserve(Rq_coll * clp, bool is_in)
{
    const struct flags_t *flagsp = is_in ? &clp->iflag : &clp->oflag;
    bool pack = flagsp->pack;
    bool vb = clp->debug;
    int res, t, flags, err;
    int fd = is_in ? clp->infd : clp->outfd;
    int tag = flagsp->tag;
    struct sg_extended_info sei;
    struct sg_extended_info * seip;

    seip = &sei;
    res = ioctl(fd, SG_GET_VERSION_NUM, &t);
    if ((res < 0) || (t < 30000)) {
        pr2serr("%s: sg driver prior to 3.0.00\n", my_name);
        return 1;
    } else if (t < 40000) {
        if (vb)
            pr2serr("%s: warning: sg driver prior to 4.0.00\n", my_name);
        sgs_old_sg_driver = true;
    } else if (t < 40045) {
        sgs_old_sg_driver = false;
        sgs_full_v4_sg_driver = false;
    } else
        sgs_full_v4_sg_driver = true;
    t = clp->bs * clp->bpt;
    res = ioctl(fd, SG_SET_RESERVED_SIZE, &t);
    if (res < 0)
        perror("sgs_dd: SG_SET_RESERVED_SIZE error");

    if (sgs_full_v4_sg_driver) {
        if (sgs_nanosec_unit) {
            memset(seip, 0, sizeof(*seip));
            seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
            seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_TIME_IN_NS;
            seip->ctl_flags |= SG_CTL_FLAGM_TIME_IN_NS;
            if (ioctl(fd, SG_SET_GET_EXTENDED, seip) < 0) {
                pr2serr("ioctl(EXTENDED(TIME_IN_NS)) failed, errno=%d %s\n",
                        errno, strerror(errno));
                return 1;
            }
        }
        if (tag || pack) {
            t = 1;
            if (ioctl(fd, SG_SET_FORCE_PACK_ID, &t) < 0) {
                pr2serr("ioctl(SG_SET_FORCE_PACK_ID(on)) failed, errno=%d "
                        "%s\n", errno, strerror(errno));
                return 1;
            }
            if (tag) {
                memset(seip, 0, sizeof(*seip));
                seip->sei_wr_mask |= SG_SEIM_CTL_FLAGS;
                seip->ctl_flags_wr_mask |= SG_CTL_FLAGM_TAG_FOR_PACK_ID;
                seip->ctl_flags |= SG_CTL_FLAGM_TAG_FOR_PACK_ID;
                if (ioctl(fd, SG_SET_GET_EXTENDED, seip) < 0) {
                    pr2serr("ioctl(EXTENDED(TAG_FOR_PACK_ID)) failed, "
                            "errno=%d %s\n", errno, strerror(errno));
                    return 1;
                }
            }
        }
        if (flagsp->evfd) {
            int evfd = eventfd(0,0);

            if (evfd < 0) {
                err = errno;
                pr2serr("eventfd() failed: %s\n", strerror(err));
                return 1;
            }
            if (is_in)
                clp->in_evfd = evfd;
            else
                clp->out_evfd = evfd;

            memset(seip, 0, sizeof(*seip));
            seip->sei_wr_mask |= SG_SEIM_EVENTFD;
            seip->sei_rd_mask |= SG_SEIM_EVENTFD;
            seip->share_fd = evfd;
            if (ioctl(fd, SG_SET_GET_EXTENDED, seip) < 0) {
                err = errno;
                pr2serr("ioctl(EXTENDED(SG_SEIM_EVENTFD)) failed, "
                        "errno=%d %s\n", err, strerror(err));
                return 1;
            }
        }
    }
    if (!clp->no_sig) {
        if (-1 == fcntl(fd, F_SETOWN, getpid())) {
            perror("fcntl(F_SETOWN)");
            return 1;
        }
        flags = fcntl(fd, F_GETFL, 0);
        if (-1 == fcntl(fd, F_SETFL, flags | O_ASYNC)) {
            perror("fcntl(F_SETFL)");
            return 1;
        }
        if (clp->use_rt_sig) {/* displaces SIGIO/SIGPOLL with SIGRTMIN + 1 */
            if (-1 == fcntl(fd, F_SETSIG, SIGRTMIN + 1))
                perror("fcntl(F_SETSIG)");
        }
    }
    return 0;
}

static int
init_elems(Rq_coll * clp)
{
    bool either_mmap = false;
    int res = 0;
    int num_bytes = clp->bpt * clp->bs;
    int k;
    Rq_elem * rep;

    clp->wr_posp = &clp->elem[0]; /* making ring buffer */
    clp->rd_posp = clp->wr_posp;
    if (clp->iflag.mmap || clp->oflag.mmap) {
        int res;

        either_mmap = true;
        sgq_num_elems = 2;
        sgq_rd_ahead_lim = 1;
        sgq_wr_ahead_lim = 1;
        if (clp->iflag.mmap) {
            res = get_mmap_addr(clp->infd, num_bytes, &clp->in_mmapp);
            if (res < 0)
                return res;
        }
        if (clp->oflag.mmap) {
            res = get_mmap_addr(clp->outfd, num_bytes, &clp->out_mmapp);
            if (res < 0)
                return res;
        }
    }
    for (k = 0; k < sgq_num_elems - 1; ++k)
        clp->elem[k].nextp = &clp->elem[k + 1];
    clp->elem[sgq_num_elems - 1].nextp = &clp->elem[0];
    for (k = 0; k < sgq_num_elems; ++k) {
        rep = &clp->elem[k];
        rep->state = SGQ_FREE;
        rep->iflagp = &clp->iflag;
        rep->oflagp = &clp->oflag;
        if (either_mmap) {
            if (clp->both_mmap)
                continue;
            if (clp->iflag.mmap)
                rep->buffp = clp->in_mmapp;
            else
                rep->buffp = clp->out_mmapp;
            continue;
        }
        rep->buffp = sg_memalign(num_bytes, 0, &rep->free_buffp, false);
        if (NULL == rep->buffp) {
            pr2serr("out of memory creating user buffers\n");
            res = -ENOMEM;
        }
    }
    return res;
}

static void
remove_elems(Rq_coll * clp)
{
    Rq_elem * rep;
    int k;

    for (k = 0; k < sgq_num_elems; ++k) {
        rep = &clp->elem[k];
        if (rep->free_buffp)
            free(rep->free_buffp);
    }
}

static int
start_read(Rq_coll * clp)
{
    int blocks = (clp->in_count > clp->bpt) ? clp->bpt : clp->in_count;
    Rq_elem * rep = clp->rd_posp;
    int buf_sz, res;
    char ebuff[EBUFF_SZ];

    if (clp->debug > 5)
        pr2serr("%s: elem idx=%zd\n", __func__, rep - clp->elem);
    rep->wr = false;
    rep->blk = clp->in_blk;
    rep->num_blks = blocks;
    clp->in_blk += blocks;
    clp->in_count -= blocks;
    if (clp->in_is_sg) {
        res = sg_start_io(clp, rep);
        if (1 == res) {     /* ENOMEM, find what's available+try that */
            if (ioctl(clp->infd, SG_GET_RESERVED_SIZE, &buf_sz) < 0) {
                res = -errno;
                perror("RESERVED_SIZE ioctls failed");
                return res;
            }
            clp->bpt = (buf_sz + clp->bs - 1) / clp->bs;
            pr2serr("Reducing blocks per transfer to %d\n", clp->bpt);
            if (clp->bpt < 1)
                return -ENOMEM;
            res = sg_start_io(clp, rep);
            if (1 == res)
                res = -ENOMEM;
        }
        if (res < 0) {
            pr2serr("%s: inputting from sg failed, blk=%d\n", my_name,
                    rep->blk);
            rep->state = SGQ_IO_ERR;
            return res;
        }
    }
    else {
        rep->state = SGQ_IO_STARTED;
        while (((res = read(clp->infd, rep->buffp, blocks * clp->bs)) < 0) &&
               (EINTR == errno))
            ;
        if (res < 0) {
            res = -errno;
            snprintf(ebuff, EBUFF_SZ, "%s: reading, in_blk=%d ", my_name,
                     rep->blk);
            perror(ebuff);
            rep->state = SGQ_IO_ERR;
            return res;
        }
        if (res < blocks * clp->bs) {
            int o_blocks = blocks;
            rep->stop_after_wr = true;
            blocks = res / clp->bs;
            if ((res % clp->bs) > 0) {
                blocks++;
                clp->in_partial++;
            }
            /* Reverse out + re-apply blocks on clp */
            clp->in_blk -= o_blocks;
            clp->in_count += o_blocks;
            rep->num_blks = blocks;
            clp->in_blk += blocks;
            clp->in_count -= blocks;
        }
        clp->in_done_count -= blocks;
        rep->state = SGQ_IO_FINISHED;
    }
    clp->rd_posp = rep->nextp;
    return blocks;
}

static int
start_write(Rq_coll * clp)
{
    Rq_elem * rep = clp->wr_posp;
    int res, blocks;
    char ebuff[EBUFF_SZ];

    while ((0 != rep->wr) || (SGQ_IO_FINISHED != rep->state)) {
        rep = rep->nextp;
        if (rep == clp->rd_posp)
            return -1;
    }
    if (clp->debug > 5)
        pr2serr("%s: elem idx=%zd\n", __func__, rep - clp->elem);
    rep->wr = true;
    blocks = rep->num_blks;
    rep->blk = clp->out_blk;
    clp->out_blk += blocks;
    clp->out_count -= blocks;
    if (clp->out_is_sg) {
        res = sg_start_io(clp, rep);
        if (1 == res)      /* ENOMEM, give up */
            return -ENOMEM;
        else if (res < 0) {
            pr2serr("%s: output to sg failed, blk=%d\n", my_name, rep->blk);
            rep->state = SGQ_IO_ERR;
            return res;
        }
    }
    else {
        rep->state = SGQ_IO_STARTED;
        while (((res = write(clp->outfd, rep->buffp,
                     rep->num_blks * clp->bs)) < 0) && (EINTR == errno))
            ;
        if (res < 0) {
            res = -errno;
            snprintf(ebuff, EBUFF_SZ, "%s: output, out_blk=%d ", my_name,
                     rep->blk);
            perror(ebuff);
            rep->state = SGQ_IO_ERR;
            return res;
        }
        if (res < blocks * clp->bs) {
            blocks = res / clp->bs;
            if ((res % clp->bs) > 0) {
                blocks++;
                clp->out_partial++;
            }
            rep->num_blks = blocks;
        }
        rep->state = SGQ_IO_FINISHED;
    }
    return blocks;
}

/* Returns 0 if SIGIO/SIGPOLL or (SIGRTMIN + 1) received, else returns negated
 * errno value; -EAGAIN for timeout. */
static int
do_sigwait(Rq_coll * clp, bool inc1_clear0)
{
    siginfo_t info;
    struct timespec ts;

    if (clp->debug > 9)
        pr2serr("%s: inc1_clear0=%d\n", __func__, (int)inc1_clear0);
    ts.tv_sec = 0;
    ts.tv_nsec = DEF_SIGTIMEDWAIT_USEC * 1000;
    while (sigtimedwait(&clp->blocked_sigs, &info, &ts) < 0) {
        int err = errno;

        if (EINTR != err) {

            if (EAGAIN != err)
                pr2serr("%s: sigtimedwait(): %s [%d]\n", __func__,
                        strerror(err), err);
            return -err;        /* EAGAIN is timeout error */
        }
    }
    if ((SIGRTMIN + 1) == info.si_signo) {
        if (inc1_clear0) {
            clp->sigs_waiting--;
            clp->sigs_rt_received++;
        } else
            clp->sigs_waiting = 0;
    } else if (SIGPOLL == info.si_signo) {
        if (inc1_clear0) {
            clp->sigs_waiting--;
            clp->sigs_io_received++;
        } else
            clp->sigs_waiting = 0;
    } else {
        pr2serr("%s: sigwaitinfo() returned si_signo=%d\n",
                __func__, info.si_signo);
        return -EINVAL;
    }
    return 0;
}

/* Returns 1 (or more) on success (found), 0 on not found, -1 on error. */
static int
do_num_poll_in(Rq_coll * clp, int fd, bool is_evfd)
{
    int err, res;
    struct pollfd a_pollfd = {0, POLLIN | POLLOUT, 0};

    if (! clp->no_sig) {
        if (clp->sigs_waiting) {
            int res = do_sigwait(clp, true);

            if ((res < 0) && (-EAGAIN != res))
                return res;
        }
    }
    a_pollfd.fd = fd;
    if (poll(&a_pollfd, 1, clp->poll_ms) < 0) {
        err = errno;
        pr2serr("%s: poll(): %s [%d]\n", __func__, strerror(err), err);
        return -err;
    }
    /* pr2serr("%s: revents=0x%x\n", __func__, a_pollfd.revents); */
    if (a_pollfd.revents & POLLIN) {
        if (is_evfd) {
            uint64_t count;

            if ((res = read(fd, &count, sizeof(count))) < 0) {
                err = errno;
                pr2serr("%s: read(): %s [%d]\n", __func__,
                        strerror(err), err);
                return -err;
            }
            return (res < (int)sizeof(uint64_t)) ? 0 : (int)count;
        } else
            return 1;   /* could be more but don't know without evfd */
    } else if (a_pollfd.revents & POLLERR)
        ++clp->pollerr_count;

    return 0;
}

static int
can_read_write(Rq_coll * clp)
{
    Rq_elem * rep = NULL;
    bool writeable = false;
    bool in_is_evfd = (clp->in_evfd >= 0);
    bool out_is_evfd = (clp->out_evfd >= 0);
    int res = 0;
    int reading = 0;
    int writing = 0;
    int rd_waiting = 0;
    int wr_waiting = 0;
    int sg_finished = 0;
    int num;
    int ofd = out_is_evfd ? clp->out_evfd : clp->outfd;
    int ifd= in_is_evfd ? clp->in_evfd : clp->infd;

    /* if write completion pending, then complete it + start read */
    if (clp->out_is_sg) {
        while ((res = do_num_poll_in(clp, ofd, out_is_evfd))) {
            if (res < 0)
                return res;
            num = res;
            while (--num >= 0) {
                res = sg_finish_io(clp, true /* write */, &rep);
                if (res < 0)
                    return res;
                else if (1 == res) {
                    res = sg_start_io(clp, rep);
                    if (0 != res)
                        return -1;  /* give up if any problems with retry */
                } else
                    sg_finished++;
            }
        }
        while ((rep = clp->wr_posp) && (SGQ_IO_FINISHED == rep->state) &&
               rep->wr && (rep != clp->rd_posp)) {
            rep->state = SGQ_FREE;
            clp->out_done_count -= rep->num_blks;
            clp->wr_posp = rep->nextp;
            if (rep->stop_after_wr)
                return -1;
        }
    }
    else if ((rep = clp->wr_posp) && rep->wr &&
             (SGQ_IO_FINISHED == rep->state)) {
        rep->state = SGQ_FREE;
        clp->out_done_count -= rep->num_blks;
        clp->wr_posp = rep->nextp;
        if (rep->stop_after_wr)
            return -1;
    }

    /* if read completion pending, then complete it + start maybe write */
    if (clp->in_is_sg) {
        while ((res = do_num_poll_in(clp, ifd, in_is_evfd))) {
            if (res < 0)
                return res;
            num = res;
            while (--num >= 0) {
                res = sg_finish_io(clp, false /* read */, &rep);
                if (res < 0)
                    return res;
                if (1 == res) {
                    res = sg_start_io(clp, rep);
                    if (0 != res)
                        return -1;  /* give up if any problems with retry */
                } else {
                    sg_finished++;
                    clp->in_done_count -= rep->num_blks;
                }
            }
        }
    }

    for (rep = clp->wr_posp, res = 1;
         rep && (rep != clp->rd_posp); rep = rep->nextp) {
        if (SGQ_IO_STARTED == rep->state) {
            if (rep->wr)
                ++writing;
            else {
                res = 0;
                ++reading;
            }
        }
        else if ((! rep->wr) && (SGQ_IO_FINISHED == rep->state)) {
            if (res)
                writeable = true;
        }
        else if (SGQ_IO_WAIT == rep->state) {
            res = 0;
            if (rep->wr)
                ++wr_waiting;
            else
                ++rd_waiting;
        }
        else
            res = 0;
    }
    if (clp->debug > 6) {
        if ((clp->debug > 7) || wr_waiting || rd_waiting) {
            pr2serr("%d/%d (nwb/nrb): read=%d/%d (do/wt) "
                    "write=%d/%d (do/wt) writeable=%d sg_fin=%d\n",
                    clp->out_blk, clp->in_blk, reading, rd_waiting,
                    writing, wr_waiting, (int)writeable, sg_finished);
        }
        // fflush(stdout);
    }
    if (writeable && (writing < sgq_wr_ahead_lim) && (clp->out_count > 0))
        return SGQ_CAN_WRITE;
    if ((reading < sgq_rd_ahead_lim) && (clp->in_count > 0) &&
        (0 == rd_waiting) && (clp->rd_posp->nextp != clp->wr_posp))
        return SGQ_CAN_READ;

    if (clp->out_done_count <= 0)
        return SGQ_CAN_DO_NOTHING;

    /* usleep(10000); */      /* hang about for 10 milliseconds */
    if ((! clp->no_sig) && clp->sigs_waiting) {
        res = do_sigwait(clp, false);
        if ((res < 0) && (-EAGAIN != res))
            return res;     /* wasn't timeout */
    }
    /* Now check the _whole_ buffer for pending requests */
    for (rep = clp->rd_posp->nextp; rep && (rep != clp->rd_posp);
         rep = rep->nextp) {
        if (SGQ_IO_WAIT == rep->state) {
            res = sg_start_io(clp, rep);
            if (res < 0)
                return res;
            if (res > 0)
                return -1;
            break;
        }
    }
    return SGQ_CAN_DO_NOTHING;
}

static bool
process_flags(const char * arg, struct flags_t * fp)
{
    char buff[256];
    char * cp;
    char * np;

    strncpy(buff, arg, sizeof(buff));
    buff[sizeof(buff) - 1] = '\0';
    if ('\0' == buff[0]) {
        pr2serr("no flag found, 'null' can be used as a placeholder\n");
        return false;
    }
    cp = buff;
    do {
        np = strchr(cp, ',');
        if (np)
            *np++ = '\0';
        if (0 == strcmp(cp, "dio"))
            fp->dio = true;
        else if (0 == strcmp(cp, "evfd"))
            fp->evfd = true;
        else if (0 == strcmp(cp, "excl"))
            fp->excl = true;
        else if (0 == strcmp(cp, "hipri"))
            fp->polled = true;
        else if (0 == strcmp(cp, "immed"))
            fp->immed = true;
        else if (0 == strcmp(cp, "mmap"))
            fp->mmap = true;
        else if (0 == strcmp(cp, "noxfer"))
            fp->noxfer = true;
        else if (0 == strcmp(cp, "null"))
            ;
        else if (0 == strcmp(cp, "pack"))
            fp->pack = true;
        else if (0 == strcmp(cp, "polled"))
            fp->polled = true;
        else if (0 == strcmp(cp, "tag"))
            fp->tag = true;
        else if (0 == strcmp(cp, "v3")) {
            fp->v3 = true;
            fp->v4 = false;
            fp->given_v3v4 = true;
        } else if (0 == strcmp(cp, "v4")) {
            fp->v3 = false;
            fp->v4 = true;
            fp->given_v3v4 = true;
        } else {
            pr2serr("unrecognised flag: %s\n", cp);
            return false;
        }
        cp = np;
    } while (cp);
    if (fp->dio && fp->mmap) {
        pr2serr(" Can't set both mmap and dio\n");
        return false;
    }
    if ((fp->dio || fp->mmap) && fp->noxfer) {
        pr2serr(" Can't have mmap or dio with noxfer\n");
        return false;
    }
    return true;
}


int
main(int argc, char * argv[])
{
    bool bs_given = false;
    bool no_sig_given = false;
    bool polled_present;
    int skip = 0;
    int seek = 0;
    int ibs = 0;
    int obs = 0;
    int count = -1;
    int in_num_sect = 0;
    int out_num_sect = 0;
    int help_pg = 0;
    int res, k, in_sect_sz, out_sect_sz, crw, open_fl;
    char str[STR_SZ];
    char * key;
    char * buf;
    char inf[INOUTF_SZ];
    char outf[INOUTF_SZ];
    char ebuff[EBUFF_SZ];
    Rq_coll rcoll;
    Rq_coll * clp = &rcoll;

    memset(clp, 0, sizeof(*clp));
    clp->bpt = 0;
    clp->in_evfd = -1;
    clp->out_evfd = -1;
    clp->iflag.v3 = true;
    clp->oflag.v3 = true;
    inf[0] = '\0';
    outf[0] = '\0';
    if (argc < 2) {
        usage(1);
        return 1;
    }
    sgs_nanosec_unit = !!getenv("SG3_UTILS_LINUX_NANO");

    for(k = 1; k < argc; k++) {
        if (argv[k]) {
            strncpy(str, argv[k], STR_SZ);
            str[STR_SZ - 1] = '\0';
        }
        else
            continue;
        for(key = str, buf = key; *buf && *buf != '=';)
            buf++;
        if (*buf)
            *buf++ = '\0';
        if (0 == strcmp(key,"bpt")) {
            clp->bpt = sg_get_num(buf);
            if ((clp->bpt < 0) || (clp->bpt > MAX_BPT_VALUE)) {
                pr2serr("%s: bad argument to 'bpt='\n", my_name);
                return SG_LIB_SYNTAX_ERROR;
            }
        } else if (0 == strcmp(key,"bs")) {
            clp->bs = sg_get_num(buf);
            if ((clp->bs < 0) || (clp->bs > MAX_BPT_VALUE)) {
                pr2serr("%s: bad argument to 'bs='\n", my_name);
                return SG_LIB_SYNTAX_ERROR;
            }
        } else if (0 == strcmp(key,"count")) {
            count = sg_get_num(buf);
            if (count < 0) {
                pr2serr("%s: bad argument to 'count='\n", my_name);
                return SG_LIB_SYNTAX_ERROR;
            }
        } else if (0 == strcmp(key,"deb"))
            clp->debug += sg_get_num(buf);
        else if (0 == strcmp(key,"ibs")) {
            ibs = sg_get_num(buf);
            if ((ibs < 0) || (ibs > MAX_BPT_VALUE)) {
                pr2serr("%s: bad argument to 'ibs='\n", my_name);
                return SG_LIB_SYNTAX_ERROR;
            }
        } else if (strcmp(key,"if") == 0) {
            memcpy(inf, buf, INOUTF_SZ);
            inf[INOUTF_SZ - 1] = '\0';
        } else if (0 == strcmp(key, "iflag")) {
            if (! process_flags(buf, &clp->iflag)) {
                pr2serr("%s: bad argument to 'iflag='\n", my_name);
                return SG_LIB_SYNTAX_ERROR;
            }
        } else if (strcmp(key,"mrq") == 0)
            ;           /* do nothing */
        else if (0 == strcmp(key,"no_sig")) { /* default changes */
            clp->no_sig = !!sg_get_num(buf);
            no_sig_given = true;
        } else if (0 == strcmp(key,"obs")) {
            obs = sg_get_num(buf);
            if ((obs < 0) || (obs > MAX_BPT_VALUE)) {
                pr2serr("%s: bad argument to 'obs='\n", my_name);
                return SG_LIB_SYNTAX_ERROR;
            }
        } else if (strcmp(key,"of") == 0) {
            memcpy(outf, buf, INOUTF_SZ);
            outf[INOUTF_SZ - 1] = '\0';
        } else if (0 == strcmp(key, "oflag")) {
            if (! process_flags(buf, &clp->oflag)) {
                pr2serr("%s: bad argument to 'oflag='\n", my_name);
                return SG_LIB_SYNTAX_ERROR;
            }
        } else if (0 == strcmp(key,"poll_ms"))
            clp->poll_ms = sg_get_num(buf);
        else if (0 == strcmp(key,"rt_sig"))
            clp->use_rt_sig = !!sg_get_num(buf);
        else if (0 == strcmp(key,"seek")) {
            seek = sg_get_num(buf);
            if (seek < 0) {
                pr2serr("%s: bad argument to 'seek='\n", my_name);
                return SG_LIB_SYNTAX_ERROR;
            }
        } else if (0 == strcmp(key,"skip")) {
            skip = sg_get_num(buf);
            if (skip < 0) {
                pr2serr("%s: bad argument to 'skip='\n", my_name);
                return SG_LIB_SYNTAX_ERROR;
            }
        } else if (0 == strcmp(key,"time"))
            ;           /* do nothing */
        else if ((0 == strcmp(key,"-V")) || (0 == strcmp(key,"--version"))) {
            pr2serr("%s: version: %s\n", my_name, version_str);
            return 0;
        } else if (0 == strncmp(key,"-vvvvvvv", 8))
            clp->debug += 7;
        else if (0 == strncmp(key,"-vvvvvv", 7))
            clp->debug += 6;
        else if (0 == strncmp(key,"-vvvvv", 6))
            clp->debug += 5;
        else if (0 == strncmp(key,"-vvvv", 5))
            clp->debug += 4;
        else if (0 == strncmp(key,"-vvv", 4))
            clp->debug += 3;
        else if (0 == strncmp(key,"-vv", 3))
            clp->debug += 2;
        else if ((0 == strcmp(key,"--verbose")) || (0 == strncmp(key,"-v", 2)))
            ++clp->debug;
        else if (0 == strcmp(key,"-hhhh"))
            help_pg += 4;
        else if (0 == strcmp(key,"-hhh"))
            help_pg += 3;
        else if (0 == strcmp(key,"-hh"))
            help_pg += 2;
        else if ((0 == strcmp(key,"-h")) || (0 == strcmp(key,"--help")))
            ++help_pg;
        else {
            pr2serr("Unrecognized argument '%s'\n", key);
            usage(help_pg);
            return 1;
        }
    }
    if (clp->bs <= 0) {
        clp->bs = DEF_BLOCK_SIZE;
    } else
        bs_given = true;

    if (help_pg > 0) {
        usage(help_pg);
        return 0;
    }

    polled_present = (clp->iflag.polled || clp->oflag.polled);
    if (no_sig_given) {
        if ((0 == clp->no_sig) && polled_present)
            pr2serr("Warning: signalling doesn't work with polled flag\n");
    } else      /* no_sig default varies: 0 normally and 1 if polled present */
        clp->no_sig = polled_present ? 1 : 0;

    if ((ibs && (ibs != clp->bs)) || (obs && (obs != clp->bs))) {
        pr2serr("If 'ibs' or 'obs' given must be same as 'bs'\n");
        usage(1);
        return 1;
    }
    if (clp->bpt <= 0) {
        clp->bpt = (DEF_BPT_TIMES_BS_SZ / clp->bs);
        if (0 == clp->bpt)
            clp->bpt = 1;
        if (! bs_given)
            pr2serr("Assume blocks size bs=%d [bytes] and blocks "
                    "per transfer bpt=%d\n", clp->bs, clp->bpt);
    } else if (! bs_given)
        pr2serr("Assume 'bs' (block size) of %d bytes\n", clp->bs);

    if ((skip < 0) || (seek < 0)) {
        pr2serr("%s: skip and seek cannot be negative\n", my_name);
        return 1;
    }
    if (clp->iflag.mmap && clp->oflag.mmap)
        clp->both_mmap = true;

    if (clp->debug > 3)
        pr2serr("%s: if=%s skip=%d of=%s seek=%d count=%d\n", my_name,
                inf, skip, outf, seek, count);
    if (! clp->no_sig) {
        /* Need to block signals before SIGPOLL is enabled in sz_reserve() */
        sigemptyset(&clp->blocked_sigs);
        if (clp->use_rt_sig)
            sigaddset(&clp->blocked_sigs, SIGRTMIN + 1);
        sigaddset(&clp->blocked_sigs, SIGINT);
        sigaddset(&clp->blocked_sigs, SIGPOLL);
        sigprocmask(SIG_BLOCK, &clp->blocked_sigs, 0);
    }

    clp->infd = STDIN_FILENO;
    clp->outfd = STDOUT_FILENO;
    if (inf[0] && ('-' != inf[0])) {
        open_fl = clp->iflag.excl ? O_EXCL : 0;
        if ((clp->infd = open(inf, open_fl | O_RDONLY)) < 0) {
            snprintf(ebuff, EBUFF_SZ, "%s: could not open %s for reading",
                     my_name, inf);
            perror(ebuff);
            return 1;
        }
        if (ioctl(clp->infd, SG_GET_TIMEOUT, 0) < 0) {
            clp->in_is_sg = false;
            if (skip > 0) {
                off_t offset = skip;

                offset *= clp->bs;       /* could overflow here! */
                if (lseek(clp->infd, offset, SEEK_SET) < 0) {
                    snprintf(ebuff, EBUFF_SZ, "%s: couldn't skip to required "
                                              "position on %s", my_name, inf);
                    perror(ebuff);
                    return 1;
                }
            }
        } else { /* looks like sg device so close then re-open it RW */
            close(clp->infd);
            open_fl = clp->iflag.excl ? O_EXCL : 0;
            open_fl |= (O_RDWR | O_NONBLOCK);
            if ((clp->infd = open(inf, open_fl)) < 0) {
                pr2serr("If %s is a sg device, need read+write "
                        "permissions, even to read it!\n", inf);
                return 1;
            }
            clp->in_is_sg = true;
            if (sz_reserve(clp, true /* is_in */))
                return 1;
            if (sgs_old_sg_driver && (clp->iflag.v4 || clp->oflag.v4)) {
                pr2serr("Unable to implement v4 flag because sg driver too "
                        "old\n");
                return 1;
            }
        }
    }
    if (outf[0] && ('-' != outf[0])) {
        open_fl = clp->oflag.excl ? O_EXCL : 0;
        open_fl |= (O_RDWR | O_NONBLOCK);
        if ((clp->outfd = open(outf, open_fl)) >= 0) {
            if (ioctl(clp->outfd, SG_GET_TIMEOUT, 0) < 0) {
                /* not a scsi generic device so now try and open RDONLY */
                close(clp->outfd);
                clp->outfd = -1;
            }
            else {
                clp->out_is_sg = true;
                if (sz_reserve(clp, false /* hence ! is_in */))
                    return 1;
                if (sgs_old_sg_driver && (clp->iflag.v4 || clp->oflag.v4)) {
                    pr2serr("Unable to implement v4 flag because sg driver "
                            "too old\n");
                    return 1;
                }
            }
        }
        if (! clp->out_is_sg) {
            if (clp->outfd >= 0) {
                close(clp->outfd);
                clp->outfd = -1;
            }
            open_fl = clp->oflag.excl ? O_EXCL : 0;
            open_fl |= (O_WRONLY | O_CREAT);
            if ((clp->outfd = open(outf, open_fl, 0666)) < 0) {
                snprintf(ebuff, EBUFF_SZ,
                         "%s: could not open %s for writing", my_name, outf);
                perror(ebuff);
                return 1;
            }
            else if (seek > 0) {
                off_t offset = seek;

                offset *= clp->bs;       /* could overflow here! */
                if (lseek(clp->outfd, offset, SEEK_SET) < 0) {
                    snprintf(ebuff, EBUFF_SZ, "%s: couldn't seek to required "
                             "position on %s", my_name, outf);
                    perror(ebuff);
                    return 1;
                }
            }
        }
    } else if ('\0' == outf[0]) {
        if (STDIN_FILENO == clp->infd) {
            pr2serr("Can't have both 'if' as stdin _and_ 'of' as "
                    "/dev/null\n");
            return 1;
        }
        clp->outfd = open("/dev/null", O_RDWR);
        if (clp->outfd < 0) {
            perror("sgs_dd: could not open /dev/null");
            return 1;
        }
        clp->out_is_sg = false;
        /* ignore any seek */
    } else {    /* must be '-' for stdout */
        if (STDIN_FILENO == clp->infd) {
            pr2serr("Can't have both 'if' as stdin _and_ 'of' as stdout\n");
            return 1;
        }
    }
    if ((clp->in_is_sg || clp->out_is_sg) && !clp->iflag.given_v3v4 &&
        !clp->oflag.given_v3v4 && (clp->debug > 0)) {
        clp->iflag.v3 = true;
        pr2serr("using sg driver version 3 interface on %s\n",
                clp->in_is_sg ? inf : outf);
    }

    if (0 == count)
        return 0;
    else if (count < 0) {
        if (clp->in_is_sg) {
            res = read_capacity(clp->infd, &in_num_sect, &in_sect_sz);
            if (2 == res) {
                pr2serr("Unit attention, media changed(in), try again\n");
                res = read_capacity(clp->infd, &in_num_sect, &in_sect_sz);
            }
            if (0 != res) {
                pr2serr("Unable to read capacity on %s\n", inf);
                in_num_sect = -1;
            } else {
                if (clp->debug > 4)
                    pr2serr("ifile: number of sectors=%d, sector size=%d\n",
                            in_num_sect, in_sect_sz);
                if (in_num_sect > skip)
                    in_num_sect -= skip;
            }
        }
        if (clp->out_is_sg) {
            res = read_capacity(clp->outfd, &out_num_sect, &out_sect_sz);
            if (2 == res) {
                pr2serr("Unit attention, media changed(out), try again\n");
                res = read_capacity(clp->outfd, &out_num_sect, &out_sect_sz);
            }
            if (0 != res) {
                pr2serr("Unable to read capacity on %s\n", outf);
                out_num_sect = -1;
            } else {
                if (clp->debug > 4)
                    pr2serr("ofile: number of sectors=%d, sector size=%d\n",
                            out_num_sect, out_sect_sz);
                if (out_num_sect > seek)
                    out_num_sect -= seek;
            }
        }
        if (clp->debug > 3)
            pr2serr("Start of loop, count=%d, in_num_sect=%d, "
                    "out_num_sect=%d\n", count, in_num_sect, out_num_sect);
        if (in_num_sect > 0) {
            if (out_num_sect > 0)
                count = (in_num_sect > out_num_sect) ? out_num_sect :
                                                       in_num_sect;
            else
                count = in_num_sect;
        }
        else
            count = out_num_sect;
    }
    if (clp->debug > 4)
        pr2serr("Start of loop, count=%d, bpt=%d\n", count, clp->bpt);

    clp->in_count = count;
    clp->in_done_count = count;
    clp->in_blk = skip;
    clp->out_count = count;
    clp->out_done_count = count;
    clp->out_blk = seek;
    res = init_elems(clp);
    if (res < 0)
        pr2serr("init_elems() failed, res=%d\n", res);
    res = 0;

/* vvvvvvvvvvvvvvvvv  Main Loop  vvvvvvvvvvvvvvvvvvvvvvvv */
    while (clp->out_done_count > 0) {
        crw = can_read_write(clp);
        if (crw < 0)
            break;
        if (SGQ_CAN_READ & crw) {
            res = start_read(clp);
            if (res <= 0) {
                pr2serr("start_read: res=%d\n", res);
                break;
            }
            res = 0;
        }
        if (SGQ_CAN_WRITE & crw) {
            res = start_write(clp);
            if (res <= 0) {
                pr2serr("start_write: res=%d\n", res);
                break;
            }
            res = 0;
        }
    }

    if ((STDIN_FILENO != clp->infd) && (clp->infd >= 0))
        close(clp->infd);
    if ((STDOUT_FILENO != clp->outfd) && (clp->outfd >= 0))
        close(clp->outfd);
    if (0 != clp->out_count) {
        pr2serr("Some error occurred, remaining blocks=%d\n", clp->out_count);
        res = 1;
    }
    pr2serr("%d+%d records in\n", count - clp->in_done_count,
            clp->in_partial);
    pr2serr("%d+%d records out\n", count - clp->out_done_count,
            clp->out_partial);
    if (clp->dio_incomplete)
        pr2serr(">> Direct IO requested but incomplete %d times\n",
                clp->dio_incomplete);
    if (clp->sum_of_resids)
        pr2serr(">> Non-zero sum of residual counts=%d\n",
                clp->sum_of_resids);
    if (clp->debug > 0) {
        if (! clp->no_sig)
            pr2serr("SIGIO/SIGPOLL signals received: %d, RT sigs: %d\n",
                    clp->sigs_io_received, clp->sigs_rt_received);
        if (polled_present)
            pr2serr("POLLED (blk_poll) used to complete %d commands\n",
                    clp->blk_poll_count);
    }
    if (clp->pollerr_count > 0)
        pr2serr(">> poll() system call gave POLLERR %d times\n",
                clp->pollerr_count);
    remove_elems(clp);
    return res < 0 ? 99 : res;
}
