sg_turs: estimated time-to-ready, add --delay=MS option; sg_requests: cleanup; sg_pt: add partial_clear_scsi_pt_obj, get_scsi_pt_cdb_len and get_scsi_pt_cdb_buf

git-svn-id: https://svn.bingwo.ca/repos/sg3_utils/trunk@857 6180dd3e-e324-4e3e-922d-17de1ae2f315
diff --git a/ChangeLog b/ChangeLog
index f78cd94..5a87f87 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,8 +2,11 @@
 some description at the top of its ".c" file. All utilities in the main
 directory have their own "man" pages. There is also a sg3_utils man page.
 
-Changelog for sg3_utils-1.46 [20200716] [svn: r856]
+Changelog for sg3_utils-1.46 [20200724] [svn: r857]
   - sg_rep_pip: report new provisioning initialization pattern cmd
+  - sg_turs: estimated time-to-ready [20-061r2]
+    - add --delay=MS option
+  - sg_requests: substantial cleanup
   - sg_dd: separate category for miscompare errors
   - sg_get_elem_status: add ralwd bit sbc4r20a
   - sg_write_x: add dld bits to write(32) [sbc4r19a]
@@ -13,6 +16,8 @@
   - sg_pt_solaris+sg_pt_osf1: fix problem with clear_scsi_pt_obj()
     which needs to remember is_nvme and dev_fd values
   - sg_lib: restore elements and rebuild command added
+  - sg_lib,sg_pt: add partial_clear_scsi_pt_obj(),
+    get_scsi_pt_cdb_len() and get_scsi_pt_cdb_buf()
   - sg_lib: Linux NVMe SNTL: add read, write and verify;
     synchronize cache and write same translations
     - add dummy start stop unit and test unit ready commands
diff --git a/debian/changelog b/debian/changelog
index eae95a6..26aeef1 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -2,7 +2,7 @@
 
   * New upstream version
 
- -- Douglas Gilbert <dgilbert@interlog.com>  Mon, 01 Jun 2020 12:00:00 -0400
+ -- Douglas Gilbert <dgilbert@interlog.com>  Fri, 24 Jul 2020 12:00:00 -0400
 
 sg3-utils (1.45-0.1) unstable; urgency=low
 
diff --git a/doc/sg_turs.8 b/doc/sg_turs.8
index 6bf8d9c..bc6b4fd 100644
--- a/doc/sg_turs.8
+++ b/doc/sg_turs.8
@@ -1,14 +1,14 @@
-.TH SG_TURS "8" "September 2019" "sg3_utils\-1.45" SG3_UTILS
+.TH SG_TURS "8" "July 2020" "sg3_utils\-1.46" SG3_UTILS
 .SH NAME
 sg_turs \- send one or more SCSI TEST UNIT READY commands
 .SH SYNOPSIS
 .B sg_turs
-[\fI\-\-help\fR] [\fI\-\-low\fR] [\fI\-\-num=NUM\fR] [\fI\-\-number=NUM\fR]
-[\fI\-\-progress\fR] [\fI\-\-time\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR]
-\fIDEVICE\fR
+[\fI\-\-delay=MS\fR] [\fI\-\-help\fR] [\fI\-\-low\fR] [\fI\-\-num=NUM\fR]
+[\fI\-\-number=NUM\fR] [\fI\-\-progress\fR] [\fI\-\-time\fR]
+[\fI\-\-verbose\fR] [\fI\-\-version\fR] \fIDEVICE\fR
 .PP
 .B sg_turs
-[\fI\-n=NUM\fR] [\fI\-p\fR]  [\fI\-t\fR] [\fI\-v\fR] [\fI\-V\fR]
+[\fI\-d=MS\fR] [\fI\-n=NUM\fR] [\fI\-p\fR]  [\fI\-t\fR] [\fI\-v\fR] [\fI\-V\fR]
 \fIDEVICE\fR
 .SH DESCRIPTION
 .\" Add any additional description here
@@ -24,6 +24,10 @@
 .SH OPTIONS
 Arguments to long options are mandatory for short options as well.
 .TP
+\fB\-d\fR, \fB\-\-delay\fR=\fIMS\fR
+this option causes a delay of \fIMS\fR milliseconds to occur before each
+TEST UNIT READY command is issued.
+.TP
 \fB\-h\fR, \fB\-\-help\fR
 print out the usage message then exit.
 .TP
@@ -55,6 +59,8 @@
 show progress indication (a percentage) if available. If \fI\-\-num=NUM\fR
 is given, \fINUM\fR is greater than 1 and an initial progress indication
 was detected then this utility waits 30 seconds before subsequent checks.
+If the \fI\-\-delay=MS\fR option is given then it will wait for that number
+of milliseconds instead of 30 seconds.
 Exits when \fINUM\fR is reached or there are no more progress indications.
 Ignores \fI\-\-time\fR option. See NOTES section below.
 .TP
@@ -101,6 +107,10 @@
 first option. See the ENVIRONMENT VARIABLES section for another way to
 force the use of these older command line options.
 .TP
+\fB\-d\fR, \fB\-\-delay\fR=\fIMS\fR
+this option causes a delay of \fIMS\fR milliseconds to occur before each
+TEST UNIT READY command is issued.
+.TP
 \fB\-n\fR=\fINUM\fR
 performs TEST UNIT READY \fINUM\fR times. If not given defaults to 1.
 Equivalent to \fI\-\-num=NUM\fR in the main description.
@@ -130,7 +140,7 @@
 .SH AUTHORS
 Written by D. Gilbert
 .SH COPYRIGHT
-Copyright \(co 2000\-2019 Douglas Gilbert
+Copyright \(co 2000\-2020 Douglas Gilbert
 .br
 This software is distributed under the GPL version 2. There is NO
 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/include/sg_cmds_basic.h b/include/sg_cmds_basic.h
index e40d101..48aabc9 100644
--- a/include/sg_cmds_basic.h
+++ b/include/sg_cmds_basic.h
@@ -2,7 +2,7 @@
 #define SG_CMDS_BASIC_H
 
 /*
- * Copyright (c) 2004-2019 Douglas Gilbert.
+ * Copyright (c) 2004-2020 Douglas Gilbert.
  * All rights reserved.
  * Use of this source code is governed by a BSD-style
  * license that can be found in the BSD_LICENSE file.
@@ -33,7 +33,13 @@
  * sg_pt_base rather than an open file descriptor as their first argument.
  * That object is assumed to be constructed and have a device file descriptor
  * associated with it. clear_scsi_pt_obj() is called at the start of each
- * "_pt" function. Caller is responsible for lifetime of ptp. */
+ * "_pt" function. Caller is responsible for lifetime of ptp.
+ * If the sense buffer is accessed outside the "_pt" function then the caller
+ * must invoke set_scsi_pt_sense() _prior_ to the "_pt" function. Otherwise
+ * a sense buffer local to "_pt" function is used.
+ * Usually the cdb pointer will be NULL going into the "_pt" functions but
+ * could be given by the caller in which case it will used rather than a
+ * locally generated one. */
 
 struct sg_pt_base;
 
diff --git a/include/sg_pt.h b/include/sg_pt.h
index 2be9cbf..8f55fc1 100644
--- a/include/sg_pt.h
+++ b/include/sg_pt.h
@@ -2,7 +2,7 @@
 #define SG_PT_H
 
 /*
- * Copyright (c) 2005-2019 Douglas Gilbert.
+ * Copyright (c) 2005-2020 Douglas Gilbert.
  * All rights reserved.
  * Use of this source code is governed by a BSD-style
  * license that can be found in the BSD_LICENSE file.
@@ -96,18 +96,23 @@
  * Use set_pt_file_handle() to change dev_fd. */
 void clear_scsi_pt_obj(struct sg_pt_base * objp);
 
+/* Partially clear state information held in *objp . Any error settings and
+ * the data-in and data-out settings are cleared. So dev_fd, cdb and sense
+ * settings are kept. */
+void partial_clear_scsi_pt_obj(struct sg_pt_base * objp);
+
 /* Set the CDB (command descriptor block). May also be a NVMe Admin command
  * which will be 64 bytes long.
  *
  * Note that the sg_cmds_is_nvme() function found in sg_cmds_basic.h can be
  * called after this function to "guess" which command set the given command
- * belongs to. */
+ * belongs to. It is valid to supply a cdb value of NULL. */
 void set_scsi_pt_cdb(struct sg_pt_base * objp, const uint8_t * cdb,
                      int cdb_len);
 
 /* Set the sense buffer and the maximum length of that buffer. For NVMe
  * commands this "sense" buffer will receive the 4 DWORDs of from the
- * completion queue. */
+ * completion queue. It is valid to supply a sense value of NULL. */
 void set_scsi_pt_sense(struct sg_pt_base * objp, uint8_t * sense,
                        int max_sense_len);
 
@@ -190,6 +195,12 @@
  * 'objp' is NULL then returns 0xffffffff. */
 uint32_t get_pt_result(const struct sg_pt_base * objp);
 
+/* These two get functions should just echo what has been given to
+ * set_scsi_pt_cdb(). If it has not been called or clear_scsi_pt_obj()
+ * has been called then return 0 and NULL respectively. */
+int get_scsi_pt_cdb_len(const struct sg_pt_base * objp);
+uint8_t * get_scsi_pt_cdb_buf(const struct sg_pt_base * objp);
+
 /* Actual sense length returned. If sense data is present but
    actual sense length is not known, return 'max_sense_len' */
 int get_scsi_pt_sense_len(const struct sg_pt_base * objp);
diff --git a/lib/sg_cmds_basic.c b/lib/sg_cmds_basic.c
index 323df25..4521b80 100644
--- a/lib/sg_cmds_basic.c
+++ b/lib/sg_cmds_basic.c
@@ -42,7 +42,7 @@
 #endif
 
 
-static const char * const version_str = "1.96 20200503";
+static const char * const version_str = "1.97 20200722";
 
 
 #define SENSE_BUFF_LEN 64       /* Arbitrary, could be larger */
@@ -367,10 +367,13 @@
 /* Returns 0 on success, while positive values are SG_LIB_CAT_* errors
  * (e.g. SG_LIB_CAT_MALFORMED). If OS error, returns negated errno or -1. */
 static int
-sg_ll_inquiry_com(struct sg_pt_base * ptvp, bool cmddt, bool evpd, int pg_op,
-                  void * resp, int mx_resp_len, int timeout_secs,
+sg_ll_inquiry_com(struct sg_pt_base * ptvp, int sg_fd, bool cmddt, bool evpd,
+                  int pg_op, void * resp, int mx_resp_len, int timeout_secs,
                   int * residp, bool noisy, int verbose)
 {
+    bool ptvp_given = false;
+    bool local_sense = true;
+    bool local_cdb = true;
     int res, ret, sense_cat, resid;
     uint8_t inq_cdb[INQUIRY_CMDLEN] = {INQUIRY_CMD, 0, 0, 0, 0, 0};
     uint8_t sense_b[SENSE_BUFF_LEN];
@@ -403,8 +406,24 @@
     }
     if (timeout_secs <= 0)
         timeout_secs = DEF_PT_TIMEOUT;
-    set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb));
-    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    if (ptvp) {
+        ptvp_given = true;
+        partial_clear_scsi_pt_obj(ptvp);
+        if (get_scsi_pt_cdb_buf(ptvp))
+            local_cdb = false; /* N.B. Ignores locally built cdb */
+        else
+            set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb));
+        if (get_scsi_pt_sense_buf(ptvp))
+            local_sense = false;
+        else
+            set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    } else {
+        ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
+        if (NULL == ptvp)
+            return sg_convert_errno(ENOMEM);
+        set_scsi_pt_cdb(ptvp, inq_cdb, sizeof(inq_cdb));
+        set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    }
     set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
     res = do_scsi_pt(ptvp, -1, timeout_secs, verbose);
     ret = sg_cmds_process_resp(ptvp, inquiry_s, res, noisy, verbose,
@@ -435,11 +454,23 @@
         if (resid > mx_resp_len) {
             pr2ws("%s resid (%d) should never exceed requested "
                     "len=%d\n", inquiry_s, resid, mx_resp_len);
-            return ret ? ret : SG_LIB_CAT_MALFORMED;
+            if (0 == ret)
+                ret = SG_LIB_CAT_MALFORMED;
+            goto fini;
         }
         /* zero unfilled section of response buffer, based on resid */
         memset((uint8_t *)resp + (mx_resp_len - resid), 0, resid);
     }
+fini:
+    if (ptvp_given) {
+        if (local_sense)    /* stop caller trying to access local sense */
+            set_scsi_pt_sense(ptvp, NULL, 0);
+        if (local_cdb)
+            set_scsi_pt_cdb(ptvp, NULL, 0);
+    } else {
+        if (ptvp)
+            destruct_scsi_pt_obj(ptvp);
+    }
     return ret;
 }
 
@@ -450,16 +481,9 @@
 sg_ll_inquiry(int sg_fd, bool cmddt, bool evpd, int pg_op, void * resp,
               int mx_resp_len, bool noisy, int verbose)
 {
-    int ret;
-    struct sg_pt_base * ptvp;
-
-    ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
-    if (NULL == ptvp)
-        return sg_convert_errno(ENOMEM);
-    ret = sg_ll_inquiry_com(ptvp, cmddt, evpd, pg_op, resp, mx_resp_len,
-                            0 /* timeout_sec */, NULL, noisy, verbose);
-    destruct_scsi_pt_obj(ptvp);
-    return ret;
+    return sg_ll_inquiry_com(NULL, sg_fd, cmddt, evpd, pg_op, resp,
+                             mx_resp_len, 0 /* timeout_sec */, NULL, noisy,
+                             verbose);
 }
 
 /* Invokes a SCSI INQUIRY command and yields the response. Returns 0 when
@@ -478,16 +502,9 @@
                  int mx_resp_len, int timeout_secs, int * residp,
                  bool noisy, int verbose)
 {
-    int ret;
-    struct sg_pt_base * ptvp;
-
-    ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
-    if (NULL == ptvp)
-        return sg_convert_errno(ENOMEM);
-    ret = sg_ll_inquiry_com(ptvp, false, evpd, pg_op, resp, mx_resp_len,
-                            timeout_secs, residp, noisy, verbose);
-    destruct_scsi_pt_obj(ptvp);
-    return ret;
+    return sg_ll_inquiry_com(NULL, sg_fd, false, evpd, pg_op, resp,
+                             mx_resp_len, timeout_secs, residp, noisy,
+                             verbose);
 }
 
 /* Similar to _v2 but takes a pointer to an object (derived from) sg_pt_base.
@@ -498,10 +515,8 @@
                  int mx_resp_len, int timeout_secs, int * residp, bool noisy,
                  int verbose)
 {
-    clear_scsi_pt_obj(ptvp);
-    return sg_ll_inquiry_com(ptvp, false, evpd, pg_op, resp, mx_resp_len,
+    return sg_ll_inquiry_com(ptvp, -1, false, evpd, pg_op, resp, mx_resp_len,
                              timeout_secs, residp, noisy, verbose);
-
 }
 
 /* Yields most of first 36 bytes of a standard INQUIRY (evpd==0) response.
@@ -525,8 +540,8 @@
         pr2ws("%s: out of memory\n", __func__);
         return sg_convert_errno(ENOMEM);
     }
-    ret = sg_ll_inquiry_v2(sg_fd, false, 0, inq_resp, SAFE_STD_INQ_RESP_LEN,
-                           0, NULL, noisy, verbose);
+    ret = sg_ll_inquiry_com(NULL, sg_fd, false, false, 0, inq_resp,
+                            SAFE_STD_INQ_RESP_LEN, 0, NULL, noisy, verbose);
 
     if (inq_data && (0 == ret)) {
         inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7;
@@ -567,8 +582,8 @@
         pr2ws("%s: out of memory\n", __func__);
         return sg_convert_errno(ENOMEM);
     }
-    ret = sg_ll_inquiry_pt(ptvp, false, 0, inq_resp, SAFE_STD_INQ_RESP_LEN,
-                           0, NULL, noisy, verbose);
+    ret = sg_ll_inquiry_com(ptvp, -1, false, false, 0, inq_resp,
+                            SAFE_STD_INQ_RESP_LEN, 0, NULL, noisy, verbose);
 
     if (inq_data && (0 == ret)) {
         inq_data->peripheral_qualifier = (inq_resp[0] >> 5) & 0x7;
@@ -589,16 +604,21 @@
 }
 
 /* Invokes a SCSI TEST UNIT READY command.
+ * N.B. To access the sense buffer outside this routine then one be
+ * provided by the caller.
  * 'pack_id' is just for diagnostics, safe to set to 0.
  * Looks for progress indicator if 'progress' non-NULL;
  * if found writes value [0..65535] else write -1.
  * Returns 0 when successful, various SG_LIB_CAT_* positive values or
  * -1 -> other errors */
-int
-sg_ll_test_unit_ready_progress_pt(struct sg_pt_base * ptvp, int pack_id,
-                                  int * progress, bool noisy, int verbose)
+static int
+sg_ll_test_unit_ready_com(struct sg_pt_base * ptvp, int sg_fd, int pack_id,
+                          int * progress, bool noisy, int verbose)
 {
     static const char * const tur_s = "test unit ready";
+    bool ptvp_given = false;
+    bool local_sense = true;
+    bool local_cdb = true;
     int res, ret, sense_cat;
     uint8_t tur_cdb[TUR_CMDLEN] = {TUR_CMD, 0, 0, 0, 0, 0};
     uint8_t sense_b[SENSE_BUFF_LEN];
@@ -609,10 +629,24 @@
         pr2ws("    %s cdb: %s\n", tur_s,
               sg_get_command_str(tur_cdb, TUR_CMDLEN, false, sizeof(b), b));
     }
-
-    clear_scsi_pt_obj(ptvp);
-    set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb));
-    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    if (ptvp) {
+        ptvp_given = true;
+        partial_clear_scsi_pt_obj(ptvp);
+        if (get_scsi_pt_cdb_buf(ptvp))
+            local_cdb = false; /* N.B. Ignores locally built cdb */
+        else
+            set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb));
+        if (get_scsi_pt_sense_buf(ptvp))
+            local_sense = false;
+        else
+            set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    } else {
+        ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
+        if (NULL == ptvp)
+            return sg_convert_errno(ENOMEM);
+        set_scsi_pt_cdb(ptvp, tur_cdb, sizeof(tur_cdb));
+        set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    }
     set_scsi_pt_packet_id(ptvp, pack_id);
     res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose);
     ret = sg_cmds_process_resp(ptvp, tur_s, res, noisy, verbose, &sense_cat);
@@ -636,23 +670,32 @@
         }
     } else
         ret = 0;
+    if (ptvp_given) {
+        if (local_sense)    /* stop caller trying to access local sense */
+            set_scsi_pt_sense(ptvp, NULL, 0);
+        if (local_cdb)
+            set_scsi_pt_cdb(ptvp, NULL, 0);
+    } else {
+        if (ptvp)
+            destruct_scsi_pt_obj(ptvp);
+    }
     return ret;
 }
 
 int
+sg_ll_test_unit_ready_progress_pt(struct sg_pt_base * ptvp, int pack_id,
+                                  int * progress, bool noisy, int verbose)
+{
+    return sg_ll_test_unit_ready_com(ptvp, -1, pack_id, progress, noisy,
+                                     verbose);
+}
+
+int
 sg_ll_test_unit_ready_progress(int sg_fd, int pack_id, int * progress,
                                bool noisy, int verbose)
 {
-    int ret;
-    struct sg_pt_base * ptvp;
-
-    ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
-    if (NULL == ptvp)
-        return sg_convert_errno(ENOMEM);
-    ret = sg_ll_test_unit_ready_progress_pt(ptvp, pack_id, progress, noisy,
-                                            verbose);
-    destruct_scsi_pt_obj(ptvp);
-    return ret;
+    return sg_ll_test_unit_ready_com(NULL, sg_fd, pack_id, progress, noisy,
+                                     verbose);
 }
 
 /* Invokes a SCSI TEST UNIT READY command.
@@ -662,24 +705,15 @@
 int
 sg_ll_test_unit_ready(int sg_fd, int pack_id, bool noisy, int verbose)
 {
-    int ret;
-    struct sg_pt_base * ptvp;
-
-    ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
-    if (NULL == ptvp)
-        return sg_convert_errno(ENOMEM);
-    ret = sg_ll_test_unit_ready_progress_pt(ptvp, pack_id, NULL, noisy,
-                                            verbose);
-    destruct_scsi_pt_obj(ptvp);
-    return ret;
+    return sg_ll_test_unit_ready_com(NULL, sg_fd, pack_id, NULL, noisy,
+                                     verbose);
 }
 
 int
 sg_ll_test_unit_ready_pt(struct sg_pt_base * ptvp, int pack_id, bool noisy,
                          int verbose)
 {
-    return sg_ll_test_unit_ready_progress_pt(ptvp, pack_id, NULL, noisy,
-                                             verbose);
+    return sg_ll_test_unit_ready_com(ptvp, -1, pack_id, NULL, noisy, verbose);
 }
 
 /* Invokes a SCSI REQUEST SENSE command. Returns 0 when successful, various
@@ -689,6 +723,8 @@
                         void * resp, int mx_resp_len, bool noisy, int verbose)
 {
     bool ptvp_given = false;
+    bool local_cdb = true;
+    bool local_sense = true;
     int ret, res, sense_cat;
     static const char * const rq_s = "request sense";
     uint8_t rs_cdb[REQUEST_SENSE_CMDLEN] =
@@ -709,15 +745,23 @@
               sg_get_command_str(rs_cdb, REQUEST_SENSE_CMDLEN, false,
                                  sizeof(b), b));
     }
-    if (ptvp)
+    if (ptvp) {
         ptvp_given = true;
-    else {
+        if (get_scsi_pt_sense_buf(ptvp))
+            local_cdb = false;
+        else
+            set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb));
+        if (get_scsi_pt_sense_buf(ptvp))
+            local_sense = false;
+        else
+            set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    } else {
         ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
         if (NULL == ptvp)
             return sg_convert_errno(ENOMEM);
+        set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb));
+        set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
     }
-    set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb));
-    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
     set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
     res = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose);
     ret = sg_cmds_process_resp(ptvp, rq_s, res, noisy, verbose, &sense_cat);
@@ -742,7 +786,12 @@
         } else
             ret = 0;
     }
-    if ((! ptvp_given) && ptvp)
+    if (ptvp_given) {
+        if (local_sense)        /* stop caller accessing local sense */
+        set_scsi_pt_sense(ptvp, NULL, 0);
+        if (local_cdb)  /* stop caller accessing local sense */
+        set_scsi_pt_cdb(ptvp, NULL, 0);
+    } else if (ptvp)
         destruct_scsi_pt_obj(ptvp);
     return ret;
 }
@@ -759,7 +808,6 @@
 sg_ll_request_sense_pt(struct sg_pt_base * ptvp, bool desc, void * resp,
                        int mx_resp_len, bool noisy, int verbose)
 {
-    clear_scsi_pt_obj(ptvp);
     return sg_ll_request_sense_com(ptvp, -1, desc, resp, mx_resp_len,
                                    noisy, verbose);
 }
@@ -772,6 +820,8 @@
 {
     static const char * const report_luns_s = "report luns";
     bool ptvp_given = false;
+    bool local_cdb = true;
+    bool local_sense = true;
     int ret, res, sense_cat;
     uint8_t rl_cdb[REPORT_LUNS_CMDLEN] =
                          {REPORT_LUNS_CMD, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
@@ -786,12 +836,23 @@
               sg_get_command_str(rl_cdb, REPORT_LUNS_CMDLEN, false,
                                  sizeof(b), b));
     }
-    if (ptvp)
+    if (ptvp) {
         ptvp_given = true;
-    else if (NULL == ((ptvp = create_pt_obj(report_luns_s))))
-        return sg_convert_errno(ENOMEM);
-    set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb));
-    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+        partial_clear_scsi_pt_obj(ptvp);
+        if (get_scsi_pt_cdb_buf(ptvp))
+            local_cdb = false;
+        else
+            set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb));
+        if (get_scsi_pt_sense_buf(ptvp))
+            local_sense = false;
+        else
+            set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    } else {
+        if (NULL == ((ptvp = create_pt_obj(report_luns_s))))
+            return sg_convert_errno(ENOMEM);
+        set_scsi_pt_cdb(ptvp, rl_cdb, sizeof(rl_cdb));
+        set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    }
     set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
     res = do_scsi_pt(ptvp, sg_fd, DEF_PT_TIMEOUT, verbose);
     ret = sg_cmds_process_resp(ptvp, report_luns_s, res, noisy, verbose,
@@ -810,8 +871,15 @@
         }
     } else
         ret = 0;
-    if ((! ptvp_given) && ptvp)
-        destruct_scsi_pt_obj(ptvp);
+    if (ptvp_given) {
+        if (local_sense)        /* stop caller accessing local sense */
+            set_scsi_pt_sense(ptvp, NULL, 0);
+        if (local_cdb)
+            set_scsi_pt_cdb(ptvp, NULL, 0);
+    } else {
+        if (ptvp)
+            destruct_scsi_pt_obj(ptvp);
+    }
     return ret;
 }
 
@@ -834,7 +902,6 @@
 sg_ll_report_luns_pt(struct sg_pt_base * ptvp, int select_report,
                      void * resp, int mx_resp_len, bool noisy, int verbose)
 {
-    clear_scsi_pt_obj(ptvp);
     return sg_ll_report_luns_com(ptvp, -1, select_report, resp,
                                  mx_resp_len, noisy, verbose);
 }
diff --git a/lib/sg_cmds_basic2.c b/lib/sg_cmds_basic2.c
index c6c8c0b..ba06920 100644
--- a/lib/sg_cmds_basic2.c
+++ b/lib/sg_cmds_basic2.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999-2019 Douglas Gilbert.
+ * Copyright (c) 1999-2020 Douglas Gilbert.
  * All rights reserved.
  * Use of this source code is governed by a BSD-style
  * license that can be found in the BSD_LICENSE file.
@@ -927,23 +927,6 @@
     return ret;
 }
 
-int
-sg_ll_start_stop_unit(int sg_fd, bool immed, int pc_mod__fl_num,
-                      int power_cond, bool noflush__fl, bool loej, bool start,
-                      bool noisy, int verbose)
-{
-    int ret;
-    struct sg_pt_base * ptvp;
-
-    ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
-    if (NULL == ptvp)
-        return sg_convert_errno(ENOMEM);
-    ret = sg_ll_start_stop_unit_pt(ptvp, immed, pc_mod__fl_num, power_cond,
-                                   noflush__fl, loej, start, noisy, verbose);
-    destruct_scsi_pt_obj(ptvp);
-    return ret;
-}
-
 /* Invokes a SCSI START STOP UNIT command (SBC + MMC).
  * Return of 0 -> success,
  * various SG_LIB_CAT_* positive values or -1 -> other errors.
@@ -952,12 +935,15 @@
  * and fl(mmc) one bit field. This is the cause of the awkardly named
  * pc_mod__fl_num and noflush__fl arguments to this function.
  *  */
-int
-sg_ll_start_stop_unit_pt(struct sg_pt_base * ptvp, bool immed,
-                         int pc_mod__fl_num, int power_cond, bool noflush__fl,
-                         bool loej, bool start, bool noisy, int verbose)
+static int
+sg_ll_start_stop_unit_com(struct sg_pt_base * ptvp, int sg_fd, bool immed,
+                          int pc_mod__fl_num, int power_cond, bool noflush__fl,
+                          bool loej, bool start, bool noisy, int verbose)
 {
     static const char * const cdb_s = "start stop unit";
+    bool ptvp_given = false;
+    bool local_sense = true;
+    bool local_cdb = true;
     int res, ret, sense_cat;
     uint8_t ssuBlk[START_STOP_CMDLEN] = {START_STOP_CMD, 0, 0, 0, 0, 0};
     uint8_t sense_b[SENSE_BUFF_LEN];
@@ -979,10 +965,24 @@
               sg_get_command_str(ssuBlk, sizeof(ssuBlk), false,
                                  sizeof(b), b));
     }
-
-    clear_scsi_pt_obj(ptvp);
-    set_scsi_pt_cdb(ptvp, ssuBlk, sizeof(ssuBlk));
-    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    if (ptvp) {
+        ptvp_given = true;
+        partial_clear_scsi_pt_obj(ptvp);
+        if (get_scsi_pt_cdb_buf(ptvp))
+            local_cdb = false; /* N.B. Ignores locally built cdb */
+        else
+            set_scsi_pt_cdb(ptvp, ssuBlk, sizeof(ssuBlk));
+        if (get_scsi_pt_sense_buf(ptvp))
+            local_sense = false;
+        else
+            set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    } else {
+        ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
+        if (NULL == ptvp)
+            return sg_convert_errno(ENOMEM);
+        set_scsi_pt_cdb(ptvp, ssuBlk, sizeof(ssuBlk));
+        set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    }
     res = do_scsi_pt(ptvp, -1, START_PT_TIMEOUT, verbose);
     ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, verbose, &sense_cat);
     if (-1 == ret)
@@ -999,9 +999,38 @@
         }
     } else
             ret = 0;
+    if (ptvp_given) {
+        if (local_sense)    /* stop caller trying to access local sense */
+            set_scsi_pt_sense(ptvp, NULL, 0);
+        if (local_cdb)
+            set_scsi_pt_cdb(ptvp, NULL, 0);
+    } else {
+        if (ptvp)
+            destruct_scsi_pt_obj(ptvp);
+    }
     return ret;
 }
 
+int
+sg_ll_start_stop_unit(int sg_fd, bool immed, int pc_mod__fl_num,
+                      int power_cond, bool noflush__fl, bool loej, bool start,
+                      bool noisy, int verbose)
+{
+    return sg_ll_start_stop_unit_com(NULL, sg_fd, immed, pc_mod__fl_num,
+                                     power_cond, noflush__fl, loej, start,
+                                     noisy, verbose);
+}
+
+int
+sg_ll_start_stop_unit_pt(struct sg_pt_base * ptvp, bool immed,
+                         int pc_mod__fl_num, int power_cond, bool noflush__fl,
+                         bool loej, bool start, bool noisy, int verbose)
+{
+    return sg_ll_start_stop_unit_com(ptvp, -1, immed, pc_mod__fl_num,
+                                     power_cond, noflush__fl, loej, start,
+                                     noisy, verbose);
+}
+
 /* Invokes a SCSI PREVENT ALLOW MEDIUM REMOVAL command
  * [was in SPC-3 but displaced from SPC-4 into SBC-3, MMC-5, SSC-3]
  * prevent==0 allows removal, prevent==1 prevents removal ...
diff --git a/lib/sg_cmds_extra.c b/lib/sg_cmds_extra.c
index 71af035..7874dd1 100644
--- a/lib/sg_cmds_extra.c
+++ b/lib/sg_cmds_extra.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1999-2019 Douglas Gilbert.
+ * Copyright (c) 1999-2020 Douglas Gilbert.
  * All rights reserved.
  * Use of this source code is governed by a BSD-style
  * license that can be found in the BSD_LICENSE file.
@@ -448,12 +448,15 @@
  * value is taken as the timeout value in seconds. Return of 0 -> success,
  * various SG_LIB_CAT_* positive values or -1 -> other errors */
 int
-sg_ll_send_diag_pt(struct sg_pt_base * ptvp, int st_code, bool pf_bit,
-                   bool st_bit, bool devofl_bit, bool unitofl_bit,
-                   int long_duration, void * paramp, int param_len,
-                   bool noisy, int vb)
+sg_ll_send_diag_com(struct sg_pt_base * ptvp, int sg_fd, int st_code,
+                    bool pf_bit, bool st_bit, bool devofl_bit,
+                    bool unitofl_bit, int long_duration, void * paramp,
+                    int param_len, bool noisy, int vb)
 {
     static const char * const cdb_s = "Send diagnostic";
+    bool ptvp_given = false;
+    bool local_sense = true;
+    bool local_cdb = true;
     int res, ret, s_cat, tmout;
     uint8_t senddiag_cdb[SEND_DIAGNOSTIC_CMDLEN] =
         {SEND_DIAGNOSTIC_CMD, 0, 0, 0, 0, 0};
@@ -488,9 +491,24 @@
             pr2ws("    %s timeout: %d seconds\n", cdb_s, tmout);
         }
     }
-
-    set_scsi_pt_cdb(ptvp, senddiag_cdb, sizeof(senddiag_cdb));
-    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    if (ptvp) {
+        ptvp_given = true;
+        partial_clear_scsi_pt_obj(ptvp);
+        if (get_scsi_pt_cdb_buf(ptvp))
+            local_cdb = false; /* N.B. Ignores locally built cdb */
+        else
+            set_scsi_pt_cdb(ptvp, senddiag_cdb, sizeof(senddiag_cdb));
+        if (get_scsi_pt_sense_buf(ptvp))
+            local_sense = false;
+        else
+            set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    } else {
+        ptvp = construct_scsi_pt_obj_with_fd(sg_fd, vb);
+        if (NULL == ptvp)
+            return sg_convert_errno(ENOMEM);
+        set_scsi_pt_cdb(ptvp, senddiag_cdb, sizeof(senddiag_cdb));
+        set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    }
     set_scsi_pt_data_out(ptvp, (uint8_t *)paramp, param_len);
     res = do_scsi_pt(ptvp, -1, tmout, vb);
     ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
@@ -509,34 +527,49 @@
     } else
         ret = 0;
 
+    if (ptvp_given) {
+        if (local_sense)    /* stop caller trying to access local sense */
+            set_scsi_pt_sense(ptvp, NULL, 0);
+        if (local_cdb)
+            set_scsi_pt_cdb(ptvp, NULL, 0);
+    } else {
+        if (ptvp)
+            destruct_scsi_pt_obj(ptvp);
+    }
     return ret;
 }
 
 int
+sg_ll_send_diag_pt(struct sg_pt_base * ptvp, int st_code, bool pf_bit,
+                   bool st_bit, bool devofl_bit, bool unitofl_bit,
+                   int long_duration, void * paramp, int param_len,
+                   bool noisy, int vb)
+{
+    return sg_ll_send_diag_com(ptvp, -1, st_code, pf_bit, st_bit, devofl_bit,
+                               unitofl_bit, long_duration, paramp,
+                               param_len, noisy, vb);
+}
+
+int
 sg_ll_send_diag(int sg_fd, int st_code, bool pf_bit, bool st_bit,
                 bool devofl_bit, bool unitofl_bit, int long_duration,
                 void * paramp, int param_len, bool noisy, int vb)
 {
-    int ret;
-    struct sg_pt_base * ptvp;
-
-    ptvp = construct_scsi_pt_obj_with_fd(sg_fd, vb);
-    if (NULL == ptvp)
-        return sg_convert_errno(ENOMEM);
-    ret = sg_ll_send_diag_pt(ptvp, st_code, pf_bit, st_bit, devofl_bit,
-                             unitofl_bit, long_duration, paramp, param_len,
-                             noisy, vb);
-    destruct_scsi_pt_obj(ptvp);
-    return ret;
+    return sg_ll_send_diag_com(NULL, sg_fd, st_code, pf_bit, st_bit,
+                               devofl_bit, unitofl_bit, long_duration, paramp,
+                               param_len, noisy, vb);
 }
 
 /* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success,
  * various SG_LIB_CAT_* positive values or -1 -> other errors */
-int
-sg_ll_receive_diag_pt(struct sg_pt_base * ptvp, bool pcv, int pg_code,
-                      void * resp, int mx_resp_len, int timeout_secs,
-                      int * residp, bool noisy, int vb)
+static int
+sg_ll_receive_diag_com(struct sg_pt_base * ptvp, int sg_fd, bool pcv,
+                       int pg_code, void * resp, int mx_resp_len,
+                       int timeout_secs, int * residp, bool noisy, int vb)
 {
+    bool ptvp_given = false;
+    bool local_sense = true;
+    bool local_cdb = true;
     int resid = 0;
     int res, ret, s_cat;
     static const char * const cdb_s = "Receive diagnostic results";
@@ -559,8 +592,24 @@
     if (timeout_secs <= 0)
         timeout_secs = DEF_PT_TIMEOUT;
 
-    set_scsi_pt_cdb(ptvp, rcvdiag_cdb, sizeof(rcvdiag_cdb));
-    set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    if (ptvp) {
+        ptvp_given = true;
+        partial_clear_scsi_pt_obj(ptvp);
+        if (get_scsi_pt_cdb_buf(ptvp))
+            local_cdb = false; /* N.B. Ignores locally built cdb */
+        else
+            set_scsi_pt_cdb(ptvp, rcvdiag_cdb, sizeof(rcvdiag_cdb));
+        if (get_scsi_pt_sense_buf(ptvp))
+            local_sense = false;
+        else
+            set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    } else {
+        ptvp = construct_scsi_pt_obj_with_fd(sg_fd, vb);
+        if (NULL == ptvp)
+            return sg_convert_errno(ENOMEM);
+        set_scsi_pt_cdb(ptvp, rcvdiag_cdb, sizeof(rcvdiag_cdb));
+        set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
+    }
     set_scsi_pt_data_in(ptvp, (uint8_t *)resp, mx_resp_len);
     res = do_scsi_pt(ptvp, -1, timeout_secs, vb);
     ret = sg_cmds_process_resp(ptvp, cdb_s, res, noisy, vb, &s_cat);
@@ -593,9 +642,27 @@
         }
         ret = 0;
     }
+
+    if (ptvp_given) {
+        if (local_sense)    /* stop caller trying to access local sense */
+            set_scsi_pt_sense(ptvp, NULL, 0);
+        if (local_cdb)
+            set_scsi_pt_cdb(ptvp, NULL, 0);
+    } else {
+        if (ptvp)
+            destruct_scsi_pt_obj(ptvp);
+    }
     return ret;
 }
 
+int
+sg_ll_receive_diag_pt(struct sg_pt_base * ptvp, bool pcv, int pg_code,
+                      void * resp, int mx_resp_len, int timeout_secs,
+                      int * residp, bool noisy, int vb)
+{
+    return sg_ll_receive_diag_com(ptvp, -1, pcv, pg_code, resp, mx_resp_len,
+                                  timeout_secs, residp, noisy, vb);
+}
 
 /* Invokes a SCSI RECEIVE DIAGNOSTIC RESULTS command. Return of 0 -> success,
  * various SG_LIB_CAT_* positive values or -1 -> other errors */
@@ -603,16 +670,8 @@
 sg_ll_receive_diag(int sg_fd, bool pcv, int pg_code, void * resp,
                    int mx_resp_len, bool noisy, int vb)
 {
-    int ret;
-    struct sg_pt_base * ptvp;
-
-    ptvp = construct_scsi_pt_obj_with_fd(sg_fd, vb);
-    if (NULL == ptvp)
-        return sg_convert_errno(ENOMEM);
-    ret = sg_ll_receive_diag_pt(ptvp, pcv, pg_code, resp, mx_resp_len, 0,
-                                NULL, noisy, vb);
-    destruct_scsi_pt_obj(ptvp);
-    return ret;
+    return sg_ll_receive_diag_com(NULL, sg_fd, pcv, pg_code, resp,
+                                  mx_resp_len, 0, NULL, noisy, vb);
 }
 
 int
@@ -620,16 +679,9 @@
                       int mx_resp_len, int timeout_secs, int * residp,
                       bool noisy, int vb)
 {
-    int ret;
-    struct sg_pt_base * ptvp;
-
-    ptvp = construct_scsi_pt_obj_with_fd(sg_fd, vb);
-    if (NULL == ptvp)
-        return sg_convert_errno(ENOMEM);
-    ret = sg_ll_receive_diag_pt(ptvp, pcv, pg_code, resp, mx_resp_len,
-                                timeout_secs, residp, noisy, vb);
-    destruct_scsi_pt_obj(ptvp);
-    return ret;
+    return sg_ll_receive_diag_com(NULL, sg_fd, pcv, pg_code, resp,
+                                  mx_resp_len, timeout_secs, residp, noisy,
+                                  vb);
 }
 
 /* Invokes a SCSI READ DEFECT DATA (10) command (SBC). Return of 0 -> success
diff --git a/lib/sg_pt_common.c b/lib/sg_pt_common.c
index 4d80320..d469ef6 100644
--- a/lib/sg_pt_common.c
+++ b/lib/sg_pt_common.c
@@ -31,7 +31,7 @@
 #include "sg_pt_nvme.h"
 #endif
 
-static const char * scsi_pt_version_str = "3.15 20200412";
+static const char * scsi_pt_version_str = "3.16 20200722";
 
 
 const char *
diff --git a/lib/sg_pt_freebsd.c b/lib/sg_pt_freebsd.c
index a36b45b..4d2e588 100644
--- a/lib/sg_pt_freebsd.c
+++ b/lib/sg_pt_freebsd.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005-2019 Douglas Gilbert.
+ * Copyright (c) 2005-2020 Douglas Gilbert.
  * All rights reserved.
  * Use of this source code is governed by a BSD-style
  * license that can be found in the BSD_LICENSE file.
@@ -7,7 +7,7 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
-/* sg_pt_freebsd version 1.35 20190210 */
+/* sg_pt_freebsd version 1.36 20200724 */
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -452,6 +452,32 @@
     }
 }
 
+void
+partial_clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+    struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+    if (NULL == ptp)
+        return;
+    ptp->in_err = 0;
+    ptp->os_err = 0;
+    ptp->transport_err = 0;
+    if (ptp->nvme_direct) {
+        struct freebsd_dev_channel *fdc_p;
+
+        fdc_p = get_fdc_p(ptp);
+        if (fdc_p)
+            fdc_p->nvme_result = 0;
+    } else {
+        ptp->scsi_status = 0;
+        ptp->dxfer_dir = CAM_DIR_NONE;
+        ptp->dxferip = NULL;
+        ptp->dxfer_ilen = 0;
+        ptp->dxferop = NULL;
+        ptp->dxfer_olen = 0;
+    }
+}
+
 /* Forget any previous dev_han and install the one given. May attempt to
  * find file type (e.g. if pass-though) from OS so there could be an error.
  * Returns 0 for success or the same value as get_scsi_pt_os_err()
@@ -511,21 +537,36 @@
 {
     struct sg_pt_freebsd_scsi * ptp = &vp->impl;
 
-    if (ptp->cdb)
-        ++ptp->in_err;
     ptp->cdb = (uint8_t *)cdb;
     ptp->cdb_len = cdb_len;
 }
 
+int
+get_scsi_pt_cdb_len(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+    return ptp->cdb_len;
+}
+
+uint8_t *
+get_scsi_pt_cdb_buf(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_freebsd_scsi * ptp = &vp->impl;
+
+    return ptp->cdb;
+}
+
 void
 set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense,
                   int max_sense_len)
 {
     struct sg_pt_freebsd_scsi * ptp = &vp->impl;
 
-    if (ptp->sense)
-        ++ptp->in_err;
-    memset(sense, 0, max_sense_len);
+    if (sense) {
+        if (max_sense_len > 0)
+            memset(sense, 0, max_sense_len);
+    }
     ptp->sense = sense;
     ptp->sense_len = max_sense_len;
 }
diff --git a/lib/sg_pt_linux.c b/lib/sg_pt_linux.c
index c96c1d1..9e8a571 100644
--- a/lib/sg_pt_linux.c
+++ b/lib/sg_pt_linux.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2005-2019 Douglas Gilbert.
+ * Copyright (c) 2005-2020 Douglas Gilbert.
  * All rights reserved.
  * Use of this source code is governed by a BSD-style
  * license that can be found in the BSD_LICENSE file.
@@ -7,7 +7,7 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
-/* sg_pt_linux version 1.47 20190612 */
+/* sg_pt_linux version 1.48 20200722 */
 
 
 #include <stdio.h>
@@ -477,6 +477,28 @@
     }
 }
 
+void
+partial_clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+    struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    if (NULL == ptp)
+        return;
+    ptp->in_err = 0;
+    ptp->os_err = 0;
+    if (ptp->nvme_direct)
+        ptp->nvme_result = 0;
+    else {
+        ptp->io_hdr.device_status = 0;
+        ptp->io_hdr.transport_status = 0;
+        ptp->io_hdr.driver_status = 0;
+        ptp->io_hdr.din_xferp = 0;
+        ptp->io_hdr.din_xfer_len = 0;
+        ptp->io_hdr.dout_xferp = 0;
+        ptp->io_hdr.dout_xfer_len = 0;
+    }
+}
+
 #ifndef SG_SET_GET_EXTENDED
 
 /* If both sei_wr_mask and sei_rd_mask are 0, this ioctl does nothing */
@@ -619,21 +641,36 @@
 {
     struct sg_pt_linux_scsi * ptp = &vp->impl;
 
-    if (ptp->io_hdr.request)
-        ++ptp->in_err;
     ptp->io_hdr.request = (__u64)(sg_uintptr_t)cdb;
     ptp->io_hdr.request_len = cdb_len;
 }
 
+int
+get_scsi_pt_cdb_len(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    return ptp->io_hdr.request_len;
+}
+
+uint8_t *
+get_scsi_pt_cdb_buf(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_linux_scsi * ptp = &vp->impl;
+
+    return (uint8_t *)ptp->io_hdr.request;
+}
+
 void
 set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense,
                   int max_sense_len)
 {
     struct sg_pt_linux_scsi * ptp = &vp->impl;
 
-    if (ptp->io_hdr.response)
-        ++ptp->in_err;
-    memset(sense, 0, max_sense_len);
+    if (sense) {
+        if (max_sense_len > 0)
+            memset(sense, 0, max_sense_len);
+    }
     ptp->io_hdr.response = (__u64)(sg_uintptr_t)sense;
     ptp->io_hdr.max_response_len = max_sense_len;
 }
@@ -686,7 +723,7 @@
 {
     struct sg_pt_linux_scsi * ptp = &vp->impl;
 
-    ptp->io_hdr.request_extra = pack_id;        /* was place in spare_in */
+    ptp->io_hdr.request_extra = pack_id;        /* was placed in spare_in */
 }
 
 void
diff --git a/lib/sg_pt_osf1.c b/lib/sg_pt_osf1.c
index edf79d1..5901b16 100644
--- a/lib/sg_pt_osf1.c
+++ b/lib/sg_pt_osf1.c
@@ -30,7 +30,7 @@
 #include "sg_lib.h"
 #include "sg_pr2serr.h"
 
-/* Version 2.02 20200713 */
+/* Version 2.03 20200722 */
 
 #define OSF1_MAXDEV 64
 
@@ -219,26 +219,57 @@
 }
 
 void
+partial_clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+    struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+    if (NULL == ptp)
+        return;
+    ptp->in_err = 0;
+    ptp->os_err = 0;
+    ptp->transport_err = 0;
+    ptp->scsi_status = 0;
+    ptp->dxfer_dir = CAM_DIR_NONE;
+    ptp->dxferp = NULL;
+    ptp->dxfer_len = 0;
+}
+
+void
 set_scsi_pt_cdb(struct sg_pt_base * vp, const uint8_t * cdb,
                 int cdb_len)
 {
     struct sg_pt_osf1_scsi * ptp = &vp->impl;
 
-    if (ptp->cdb)
-        ++ptp->in_err;
     ptp->cdb = (uint8_t *)cdb;
     ptp->cdb_len = cdb_len;
 }
 
+int
+get_scsi_pt_cdb_len(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+    return ptp->cdb_len;
+}
+
+uint8_t *
+get_scsi_pt_cdb_buf(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_osf1_scsi * ptp = &vp->impl;
+
+    return ptp->cdb;
+}
+
 void
 set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense,
                   int max_sense_len)
 {
     struct sg_pt_osf1_scsi * ptp = &vp->impl;
 
-    if (ptp->sense)
-        ++ptp->in_err;
-    bzero(sense, max_sense_len);
+    if (sense) {
+        if (max_sense_len > 0)
+            bzero(sense, max_sense_len);
+    }
     ptp->sense = sense;
     ptp->sense_len = max_sense_len;
 }
diff --git a/lib/sg_pt_solaris.c b/lib/sg_pt_solaris.c
index f195624..c244499 100644
--- a/lib/sg_pt_solaris.c
+++ b/lib/sg_pt_solaris.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2007-2019 Douglas Gilbert.
+ * Copyright (c) 2007-2020 Douglas Gilbert.
  * All rights reserved.
  * Use of this source code is governed by a BSD-style
  * license that can be found in the BSD_LICENSE file.
@@ -7,7 +7,7 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
-/* sg_pt_solaris version 1.13 20200713 */
+/* sg_pt_solaris version 1.14 20200724 */
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -148,26 +148,55 @@
 }
 
 void
+partial_clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+    struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+    if (ptp) {
+        ptp->in_err = 0;
+        ptp->os_err = 0;
+        ptp->uscsi.uscsi_status = 0;
+        ptp->uscsi.uscsi_bufaddr = NULL;
+        ptp->uscsi.uscsi_buflen = 0;
+        ptp->uscsi.uscsi_flags = USCSI_ISOLATE | USCSI_DIAGNOSE |
+                                 USCSI_RQENABLE;
+    }
+}
+
+void
 set_scsi_pt_cdb(struct sg_pt_base * vp, const uint8_t * cdb,
                 int cdb_len)
 {
     struct sg_pt_solaris_scsi * ptp = &vp->impl;
 
-    if (ptp->uscsi.uscsi_cdb)
-        ++ptp->in_err;
     ptp->uscsi.uscsi_cdb = (char *)cdb;
     ptp->uscsi.uscsi_cdblen = cdb_len;
 }
 
+int
+get_scsi_pt_cdb_len(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+    return ptp->uscsi.uscsi_cdblen;
+}
+
+uint8_t *
+get_scsi_pt_cdb_buf(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_solaris_scsi * ptp = &vp->impl;
+
+    return (uint8_t *)ptp->uscsi.uscsi_cdb;
+}
+
 void
 set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense,
                   int max_sense_len)
 {
     struct sg_pt_solaris_scsi * ptp = &vp->impl;
 
-    if (ptp->uscsi.uscsi_rqbuf)
-        ++ptp->in_err;
-    memset(sense, 0, max_sense_len);
+    if (sense && (max_sense_len > 0))
+        memset(sense, 0, max_sense_len);
     ptp->uscsi.uscsi_rqbuf = (char *)sense;
     ptp->uscsi.uscsi_rqlen = max_sense_len;
     ptp->max_sense_len = max_sense_len;
@@ -185,7 +214,8 @@
     if (dxfer_len > 0) {
         ptp->uscsi.uscsi_bufaddr = (char *)dxferp;
         ptp->uscsi.uscsi_buflen = dxfer_len;
-        ptp->uscsi.uscsi_flags = USCSI_READ | USCSI_ISOLATE | USCSI_RQENABLE;
+        ptp->uscsi.uscsi_flags = USCSI_READ | USCSI_ISOLATE | USCSI_DIAGNOSE |
+                                 USCSI_RQENABLE;
     }
 }
 
@@ -201,7 +231,8 @@
     if (dxfer_len > 0) {
         ptp->uscsi.uscsi_bufaddr = (char *)dxferp;
         ptp->uscsi.uscsi_buflen = dxfer_len;
-        ptp->uscsi.uscsi_flags = USCSI_WRITE | USCSI_ISOLATE | USCSI_RQENABLE;
+        ptp->uscsi.uscsi_flags = USCSI_WRITE | USCSI_ISOLATE | USCSI_DIAGNOSE |
+                                 USCSI_RQENABLE;
     }
 }
 
diff --git a/lib/sg_pt_win32.c b/lib/sg_pt_win32.c
index 41295fc..d01f7a8 100644
--- a/lib/sg_pt_win32.c
+++ b/lib/sg_pt_win32.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2006-2019 Douglas Gilbert.
+ * Copyright (c) 2006-2020 Douglas Gilbert.
  * All rights reserved.
  * Use of this source code is governed by a BSD-style
  * license that can be found in the BSD_LICENSE file.
@@ -7,7 +7,7 @@
  * SPDX-License-Identifier: BSD-2-Clause
  */
 
-/* sg_pt_win32 version 1.30 20190210 */
+/* sg_pt_win32 version 1.31 20200723 */
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -864,6 +864,32 @@
 }
 
 void
+partial_clear_scsi_pt_obj(struct sg_pt_base * vp)
+{
+    struct sg_pt_win32_scsi * psp = &vp->impl;
+
+    if (NULL == psp)
+        return;
+    psp->in_err = 0;
+    psp->os_err = 0;
+    psp->transport_err = 0;
+    psp->scsi_status = 0;
+    if (spt_direct) {
+        psp->swb_d.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
+        psp->swb_d.spt.SenseInfoLength = SCSI_MAX_SENSE_LEN;
+        psp->swb_d.spt.SenseInfoOffset =
+            offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf);
+        psp->swb_d.spt.TimeOutValue = DEF_TIMEOUT;
+    } else {
+        psp->swb_i.spt.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
+        psp->swb_i.spt.SenseInfoLength = SCSI_MAX_SENSE_LEN;
+        psp->swb_i.spt.SenseInfoOffset =
+            offsetof(SCSI_PASS_THROUGH_WITH_BUFFERS, ucSenseBuf);
+        psp->swb_i.spt.TimeOutValue = DEF_TIMEOUT;
+    }
+}
+
+void
 set_scsi_pt_cdb(struct sg_pt_base * vp, const uint8_t * cdb,
                 int cdb_len)
 {
@@ -871,14 +897,9 @@
     struct sg_pt_win32_scsi * psp = vp->implp;
 
     if (! scsi_cdb) {
-        if (psp->have_nvme_cmd)
-            ++psp->in_err;
-        else
-            psp->have_nvme_cmd = true;
+        psp->have_nvme_cmd = true;
         memcpy(psp->nvme_cmd, cdb, cdb_len);
     } else if (spt_direct) {
-        if (psp->swb_d.spt.CdbLength > 0)
-            ++psp->in_err;
         if (cdb_len > (int)sizeof(psp->swb_d.spt.Cdb)) {
             ++psp->in_err;
             return;
@@ -886,8 +907,6 @@
         memcpy(psp->swb_d.spt.Cdb, cdb, cdb_len);
         psp->swb_d.spt.CdbLength = cdb_len;
     } else {
-        if (psp->swb_i.spt.CdbLength > 0)
-            ++psp->in_err;
         if (cdb_len > (int)sizeof(psp->swb_i.spt.Cdb)) {
             ++psp->in_err;
             return;
@@ -897,15 +916,29 @@
     }
 }
 
+int
+get_scsi_pt_cdb_len(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_win32_scsi * psp = &vp->impl;
+
+    return spt_direct ? psp->swb_d.spt.CdbLength : psp->swb_i.spt.CdbLength;
+}
+
+uint8_t *
+get_scsi_pt_cdb_buf(const struct sg_pt_base * vp)
+{
+    const struct sg_pt_win32_scsi * psp = &vp->impl;
+
+    return (uint8_t *)(spt_direct ? psp->swb_d.spt.Cdb : psp->swb_i.spt.Cdb);
+}
+
 void
-set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense,
-                  int sense_len)
+set_scsi_pt_sense(struct sg_pt_base * vp, uint8_t * sense, int sense_len)
 {
     struct sg_pt_win32_scsi * psp = vp->implp;
 
-    if (psp->sensep)
-        ++psp->in_err;
-    memset(sense, 0, sense_len);
+    if (sense && (sense_len > 0))
+        memset(sense, 0, sense_len);
     psp->sensep = sense;
     psp->sense_len = sense_len;
 }
diff --git a/sg3_utils.spec b/sg3_utils.spec
index 09ccd98..0dccb8e 100644
--- a/sg3_utils.spec
+++ b/sg3_utils.spec
@@ -84,7 +84,7 @@
 %{_libdir}/*.la
 
 %changelog
-* Mon Jun 01 2020 - dgilbert at interlog dot com
+* Fri Jul 24 2020 - dgilbert at interlog dot com
 - track t10 changes
   * sg3_utils-1.46
 
diff --git a/src/sg_requests.c b/src/sg_requests.c
index 331c84d..37ce166 100644
--- a/src/sg_requests.c
+++ b/src/sg_requests.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004-2019 Douglas Gilbert.
+ * Copyright (c) 2004-2020 Douglas Gilbert.
  * All rights reserved.
  * Use of this source code is governed by a BSD-style
  * license that can be found in the BSD_LICENSE file.
@@ -34,7 +34,7 @@
  * This program issues the SCSI command REQUEST SENSE to the given SCSI device.
  */
 
-static const char * version_str = "1.34 20190706";
+static const char * version_str = "1.35 20200723";
 
 #define MAX_REQS_RESP_LEN 255
 #define DEF_REQS_RESP_LEN 252
@@ -126,19 +126,22 @@
 int
 main(int argc, char * argv[])
 {
-    int c, n, resp_len, k, progress, rs, sense_cat;
+    int c, n, k, progress, rs, sense_cat, act_din_len;
     int do_error = 0;
     int err = 0;
     int num_errs = 0;
+    int num_din_errs = 0;
+    int most_recent_skey = 0;
     int sg_fd = -1;
     int res = 0;
-    uint8_t requestSenseBuff[MAX_REQS_RESP_LEN + 1];
+    uint8_t rsBuff[MAX_REQS_RESP_LEN + 1];
     bool desc = false;
     bool do_progress = false;
     bool do_raw = false;
     bool do_status = false;
     bool verbose_given = false;
     bool version_given = false;
+    bool not_raw_hex;
     int num_rs = 1;
     int do_hex = 0;
     int maxlen = 0;
@@ -146,7 +149,7 @@
     const char * device_name = NULL;
     int ret = 0;
     struct sg_pt_base * ptvp = NULL;
-    char b[80];
+    char b[256];
     uint8_t rs_cdb[REQUEST_SENSE_CMDLEN] =
         {REQUEST_SENSE_CMD, 0, 0, 0, 0, 0};
     uint8_t sense_b[SENSE_BUFF_LEN];
@@ -265,10 +268,20 @@
             return SG_LIB_FILE_ERROR;
         }
     }
+    if (do_raw || do_hex) {
+        not_raw_hex = false;
+        if (do_progress || do_time) {
+            pr2serr("With either --raw or --hex, --progress and --time "
+                    "contradict\n");
+            ret = SG_LIB_CONTRADICT;
+            goto finish;
+        }
+    } else
+        not_raw_hex = true;
 
     sg_fd = sg_cmds_open_device(device_name, true /* ro */, verbose);
     if (sg_fd < 0) {
-        if (verbose)
+        if (not_raw_hex && verbose)
             pr2serr(ME "open error: %s: %s\n", device_name,
                     safe_strerror(-sg_fd));
         ret = sg_convert_errno(-sg_fd);
@@ -276,7 +289,8 @@
     }
     ptvp = construct_scsi_pt_obj_with_fd(sg_fd, verbose);
     if ((NULL == ptvp) || ((err = get_scsi_pt_os_err(ptvp)))) {
-        pr2serr("%s: unable to construct pt object\n", __func__);
+        if (not_raw_hex)
+            pr2serr("%s: unable to construct pt object\n", __func__);
         ret = sg_convert_errno(err ? err : ENOMEM);
         goto finish;
     }
@@ -287,18 +301,25 @@
     rs_cdb[4] = maxlen;
     if (do_progress) {
         for (k = 0; k < num_rs; ++k) {
+            act_din_len = 0;
             if (k > 0)
                 sleep_for(30);
             set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb));
             set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
-            memset(requestSenseBuff, 0x0, sizeof(requestSenseBuff));
-            set_scsi_pt_data_in(ptvp, requestSenseBuff,
-                                sizeof(requestSenseBuff));
+            memset(rsBuff, 0x0, sizeof(rsBuff));
+            set_scsi_pt_data_in(ptvp, rsBuff, sizeof(rsBuff));
             set_scsi_pt_packet_id(ptvp, k + 1);
             if (do_error > 1) {
                 ++num_errs;
                 n = 0;
             } else {
+                if (verbose && (0 == k)) {
+                    char b[128];
+
+                    pr2serr("    cdb: %s\n",
+                            sg_get_command_str(rs_cdb, REQUEST_SENSE_CMDLEN,
+                                               true, sizeof(b), b));
+                }
                 rs = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose);
                 n = sg_cmds_process_resp(ptvp, "Request sense", rs, (0 == k),
                                          verbose, &sense_cat);
@@ -336,36 +357,17 @@
                     break;
                 }
             }
-            clear_scsi_pt_obj(ptvp);
+            if (n >= 0)
+                act_din_len = n;
             if (ret)
                 goto finish;
 
-#if 0
-            res = sg_ll_request_sense(sg_fd, desc, requestSenseBuff, maxlen,
-                                      true, verbose);
-            if (res) {
-                ret = res;
-                if (SG_LIB_CAT_INVALID_OP == res)
-                    pr2serr("Request Sense command not supported\n");
-                else if (SG_LIB_CAT_ILLEGAL_REQ == res)
-                    pr2serr("bad field in Request Sense cdb\n");
-                else if (SG_LIB_CAT_ABORTED_COMMAND == res)
-                    pr2serr("Request Sense, aborted command\n");
-                else {
-                    sg_get_category_sense_str(res, sizeof(b), b, verbose);
-                    pr2serr("Request Sense command: %s\n", b);
-                }
-                break;
-            }
-#endif
-            /* "Additional sense length" same in descriptor and fixed */
-            resp_len = requestSenseBuff[7] + 8;
             if (verbose > 1) {
                 pr2serr("Parameter data in hex\n");
-                hex2stderr(requestSenseBuff, resp_len, 1);
+                hex2stderr(rsBuff, act_din_len, 1);
             }
             progress = -1;
-            sg_get_sense_progress_fld(requestSenseBuff, resp_len, &progress);
+            sg_get_sense_progress_fld(rsBuff, act_din_len, &progress);
             if (progress < 0) {
                 ret = res;
                 if (verbose > 1)
@@ -377,32 +379,40 @@
                 printf("Progress indication: %d.%02d%% done\n",
                        (progress * 100) / 65536,
                        ((progress * 100) % 65536) / 656);
-        }
+            partial_clear_scsi_pt_obj(ptvp);
+        }                               /* >>>>> end of for(num_rs) loop */
         goto finish;
     }
 
 #ifndef SG_LIB_MINGW
-    if (do_time) {
+    if (not_raw_hex && do_time) {
         start_tm.tv_sec = 0;
         start_tm.tv_usec = 0;
         gettimeofday(&start_tm, NULL);
     }
 #endif
 
-    requestSenseBuff[0] = '\0';
-    requestSenseBuff[7] = '\0';
+    rsBuff[0] = '\0';
+    rsBuff[7] = '\0';
     for (k = 0; k < num_rs; ++k) {
-        memset(requestSenseBuff, 0x0, sizeof(requestSenseBuff));
+        act_din_len = 0;
+        ret = 0;
         set_scsi_pt_cdb(ptvp, rs_cdb, sizeof(rs_cdb));
         set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
-        memset(requestSenseBuff, 0x0, sizeof(requestSenseBuff));
-        set_scsi_pt_data_in(ptvp, requestSenseBuff,
-                            sizeof(requestSenseBuff));
+        memset(rsBuff, 0x0, sizeof(rsBuff));
+        set_scsi_pt_data_in(ptvp, rsBuff, sizeof(rsBuff));
         set_scsi_pt_packet_id(ptvp, k + 1);
         if (do_error > 1) {
             ++num_errs;
             n = 0;
         } else {
+            if (verbose && (0 == k)) {
+                char b[128];
+
+                pr2serr("    cdb: %s\n",
+                        sg_get_command_str(rs_cdb, REQUEST_SENSE_CMDLEN,
+                                           true, sizeof(b), b));
+            }
             rs = do_scsi_pt(ptvp, -1, DEF_PT_TIMEOUT, verbose);
             n = sg_cmds_process_resp(ptvp, "Request sense", rs, (0 == k),
                                      verbose, &sense_cat);
@@ -440,55 +450,50 @@
                 break;
             }
         }
-        clear_scsi_pt_obj(ptvp);
+        if (n >= 0)
+            act_din_len = n;
+
+        if (act_din_len > 7) {
+            struct sg_scsi_sense_hdr ssh;
+
+            if (sg_scsi_normalize_sense(rsBuff, act_din_len, &ssh)) {
+                if (ssh.sense_key > 0) {
+                    ++num_din_errs;
+                    most_recent_skey = ssh.sense_key;
+                }
+                if (not_raw_hex && ((1 == num_rs) || verbose)) {
+                    char b[144];
+
+                    sg_get_sense_str(NULL, rsBuff, act_din_len,
+                                     false, sizeof(b), b);
+                    pr2serr("data-in decoded as sense:\n%s\n", b);
+                }
+            }
+        }
+        partial_clear_scsi_pt_obj(ptvp);
         if (ret)
             goto finish;
 
-        // res = sg_ll_request_sense(sg_fd, desc, requestSenseBuff, maxlen,
-                                  // true, verbose);
-        // ret = res;
-        resp_len = requestSenseBuff[7] + 8;
-        if (do_raw)
-            dStrRaw(requestSenseBuff, resp_len);
-        else if (do_hex)
-            hex2stdout(requestSenseBuff, resp_len, 1);
-        else if (1 == num_rs) {
-            pr2serr("Decode parameter data as sense data:\n");
-            sg_print_sense(NULL, requestSenseBuff, resp_len, 0);
-            if (verbose > 1) {
-                pr2serr("\nParameter data in hex\n");
-                hex2stderr(requestSenseBuff, resp_len, 1);
-            }
+        if (act_din_len > 0) {
+            if (do_raw)
+                dStrRaw(rsBuff, act_din_len);
+            else if (do_hex)
+                hex2stdout(rsBuff, act_din_len, 1);
         }
-#if 0
-            continue;
-        else if (SG_LIB_CAT_INVALID_OP == res)
-            pr2serr("Request Sense command not supported\n");
-        else if (SG_LIB_CAT_ILLEGAL_REQ == res)
-            pr2serr("bad field in Request Sense cdb\n");
-        else if (SG_LIB_CAT_ABORTED_COMMAND == res)
-            pr2serr("Request Sense, aborted command\n");
-        else {
-            sg_get_category_sense_str(res, sizeof(b), b, verbose);
-            pr2serr("Request Sense command: %s\n", b);
-        }
-        break;
-#endif
-    }
+    }                                   /* <<<<< end of for(num_rs) loop */
     if ((0 == ret) && do_status) {
-        resp_len = requestSenseBuff[7] + 8;
-        ret = sg_err_category_sense(requestSenseBuff, resp_len);
+        ret = sg_err_category_sense(rsBuff, act_din_len);
         if (SG_LIB_CAT_NO_SENSE == ret) {
             struct sg_scsi_sense_hdr ssh;
 
-            if (sg_scsi_normalize_sense(requestSenseBuff, resp_len, &ssh)) {
+            if (sg_scsi_normalize_sense(rsBuff, act_din_len, &ssh)) {
                 if ((0 == ssh.asc) && (0 == ssh.ascq))
                     ret = 0;
             }
         }
     }
 #ifndef SG_LIB_MINGW
-    if (do_time && (start_tm.tv_sec || start_tm.tv_usec)) {
+    if (not_raw_hex && do_time && (start_tm.tv_sec || start_tm.tv_usec)) {
         struct timeval res_tm;
         double den, num;
 
@@ -512,17 +517,23 @@
 #endif
 
 finish:
-    if (num_errs > 0)
-        printf("Number of errors detected: %d\n", num_errs);
+    if (not_raw_hex) {
+        if (num_errs > 0)
+            printf("Number of command errors detected: %d\n", num_errs);
+        if (num_din_errs > 0)
+            printf("Number of data-in errors detected: %d, most recent "
+                   "sense_key=%d\n", num_din_errs, most_recent_skey);
+    }
     if (sg_fd >= 0) {
         res = sg_cmds_close_device(sg_fd);
         if (res < 0) {
-            pr2serr("close error: %s\n", safe_strerror(-res));
+            if (not_raw_hex)
+                pr2serr("close error: %s\n", safe_strerror(-res));
             if (0 == ret)
                 ret = sg_convert_errno(-res);
         }
     }
-    if (0 == verbose) {
+    if (not_raw_hex && (0 == verbose)) {
         if (! sg_if_can2stderr("sg_requests failed: ", ret))
             pr2serr("Some error occurred, try again with '-v' "
                     "or '-vv' for more information\n");
diff --git a/src/sg_stream_ctl.c b/src/sg_stream_ctl.c
index 4179aa7..61bb40f 100644
--- a/src/sg_stream_ctl.c
+++ b/src/sg_stream_ctl.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018-2019 Douglas Gilbert.
+ * Copyright (c) 2018-2020 Douglas Gilbert.
  * All rights reserved.
  * Use of this source code is governed by a BSD-style
  * license that can be found in the BSD_LICENSE file.
@@ -35,7 +35,7 @@
  * to the given SCSI device. Based on sbc4r15.pdf .
  */
 
-static const char * version_str = "1.08 20191220";
+static const char * version_str = "1.09 20200724";
 
 #define STREAM_CONTROL_SA 0x14
 #define GET_STREAM_STATUS_SA 0x16
@@ -294,7 +294,7 @@
             return 0;
         case 'i':
             k = sg_get_num(optarg);
-            if ((k < 0) || (k > UINT16_MAX)) {
+            if ((k < 0) || (k > (int)UINT16_MAX)) {
                 pr2serr("--id= expects a number from 0 to 65535\n");
                 return SG_LIB_SYNTAX_ERROR;
             }
diff --git a/src/sg_turs.c b/src/sg_turs.c
index d31d9da..88075a4 100644
--- a/src/sg_turs.c
+++ b/src/sg_turs.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2000-2019 D. Gilbert
+ * Copyright (C) 2000-2020 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)
@@ -22,8 +22,11 @@
 #include <stdarg.h>
 #include <stdbool.h>
 #include <string.h>
+#include <time.h>
 #include <errno.h>
 #include <getopt.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
 
 #ifdef HAVE_CONFIG_H
 #include "config.h"
@@ -42,23 +45,13 @@
 #include "sg_pr2serr.h"
 
 
-static const char * version_str = "3.46 20190618";
-
-#if defined(MSC_VER) || defined(__MINGW32__)
-#define HAVE_MS_SLEEP
-#endif
-
-#ifdef HAVE_MS_SLEEP
-#include <windows.h>
-#define sleep_for(seconds)    Sleep( (seconds) * 1000)
-#else
-#define sleep_for(seconds)    sleep(seconds)
-#endif
+static const char * version_str = "3.47 20200722";
 
 #define DEF_PT_TIMEOUT  60       /* 60 seconds */
 
 
 static struct option long_options[] = {
+        {"delay", required_argument, 0, 'd'},
         {"help", no_argument, 0, 'h'},
         {"low", no_argument, 0, 'l'},
         {"new", no_argument, 0, 'N'},
@@ -74,12 +67,14 @@
 };
 
 struct opts_t {
+    bool delay_given;
     bool do_low;
     bool do_progress;
     bool do_time;
     bool opts_new;
     bool verbose_given;
     bool version_given;
+    int delay;
     int do_help;
     int do_number;
     int verbose;
@@ -96,10 +91,13 @@
 static void
 usage()
 {
-    printf("Usage: sg_turs [--help] [--low] [--number=NUM] [--num=NUM] "
-           "[--progress]\n"
-           "               [--time] [--verbose] [--version] DEVICE\n"
+    printf("Usage: sg_turs [--delay=MS] [--help] [--low] [--number=NUM] "
+           "[--num=NUM]\n"
+           "               [--progress] [--time] [--verbose] [--version] "
+           "DEVICE\n"
            "  where:\n"
+           "    --delay=MS|-d MS    delay MS miiliseconds before sending "
+           "each tur\n"
            "    --help|-h        print usage message then exit\n"
            "    --low|-l         use low level (sg_pt) interface for "
            "speed\n"
@@ -109,19 +107,23 @@
            "    --old|-O         use old interface (use as first option)\n"
            "    --progress|-p    outputs progress indication (percentage) "
            "if available\n"
+           "                     waits 30 seconds before TUR unless "
+           "--delay=MS given\n"
            "    --time|-t        outputs total duration and commands per "
            "second\n"
            "    --verbose|-v     increase verbosity\n"
            "    --version|-V     print version string then exit\n\n"
-           "Performs a SCSI TEST UNIT READY command (or many of them).\n");
+           "Performs a SCSI TEST UNIT READY command (or many of them).\n"
+           "This SCSI command is often known by its abbreviation: TUR .\n");
 }
 
 static void
 usage_old()
 {
-    printf("Usage: sg_turs [-l] [-n=NUM] [-p] [-t] [-v] [-V] "
+    printf("Usage: sg_turs [-d=MS] [-l] [-n=NUM] [-p] [-t] [-v] [-V] "
            "DEVICE\n"
            "  where:\n"
+           "    -d=MS     same as --delay=MS in new interface\n"
            "    -l        use low level interface (sg_pt) for speed\n"
            "    -n=NUM    number of test_unit_ready commands "
            "(def: 1)\n"
@@ -152,12 +154,22 @@
     while (1) {
         int option_index = 0;
 
-        c = getopt_long(argc, argv, "hln:NOptvV", long_options,
+        c = getopt_long(argc, argv, "d:hln:NOptvV", long_options,
                         &option_index);
         if (c == -1)
             break;
 
         switch (c) {
+        case 'd':
+            n = sg_get_num(optarg);
+            if (n < 0) {
+                pr2serr("bad argument to '--delay='\n");
+                usage();
+                return SG_LIB_SYNTAX_ERROR;
+            }
+            op->delay = n;
+            op->delay_given = true;
+            break;
         case 'h':
         case '?':
             ++op->do_help;
@@ -263,7 +275,15 @@
             }
             if (plen <= 0)
                 continue;
-            if (0 == strncmp("n=", cp, 2)) {
+            if (0 == strncmp("d=", cp, 2)) {
+                op->delay = sg_get_num(cp + 2);
+                if (op->delay < 0) {
+                    printf("Couldn't decode number after 'd=' option\n");
+                    usage_old();
+                    return SG_LIB_SYNTAX_ERROR;
+                }
+                op->delay_given = true;
+            } else if (0 == strncmp("n=", cp, 2)) {
                 op->do_number = sg_get_num(cp + 2);
                 if (op->do_number <= 0) {
                     printf("Couldn't decode number after 'n=' option\n");
@@ -310,6 +330,38 @@
     return res;
 }
 
+static void
+wait_millisecs(int millisecs)
+{
+    struct timespec wait_period, rem;
+
+    wait_period.tv_sec = millisecs / 1000;
+    wait_period.tv_nsec = (millisecs % 1000) * 1000000;
+    while ((nanosleep(&wait_period, &rem) < 0) && (EINTR == errno))
+                wait_period = rem;
+}
+
+/* Returns true if prints estimate of duration to ready */
+bool
+check_for_lu_becoming(struct sg_pt_base * ptvp)
+{
+    int s_len = get_scsi_pt_sense_len(ptvp);
+    uint64_t info;
+    uint8_t * sense_b = get_scsi_pt_sense_buf(ptvp);
+    struct sg_scsi_sense_hdr ssh;
+
+    /* Check for "LU is in process of becoming ready" with a non-zero INFO
+     * field that isn't too big. As per 20-061r2 it means the following: */
+    if (sg_scsi_normalize_sense(sense_b, s_len, &ssh) && (ssh.asc == 0x4) &&
+        (ssh.ascq == 0x1) && sg_get_sense_info_fld(sense_b, s_len, &info) &&
+        (info > 0x0) && (info < 0x1000000)) {
+        printf("device not ready, estimated to be ready in %" PRIu64
+               " milliseconds\n", info);
+        return true;
+    }
+    return false;
+}
+
 /* Returns number of TURs performed */
 static int
 loop_turs(struct sg_pt_base * ptvp, struct loop_res_t * resp,
@@ -319,13 +371,15 @@
     int packet_id = 0;
     int vb = op->verbose;
     char b[80];
+    uint8_t sense_b[32];
 
     if (op->do_low) {
         int rs, n, sense_cat;
         uint8_t cdb[6];
-        uint8_t sense_b[32];
 
         for (k = 0; k < op->do_number; ++k) {
+            if (op->delay > 0)
+                wait_millisecs(op->delay);
             /* Might get Unit Attention on first invocation */
             memset(cdb, 0, sizeof(cdb));    /* TUR's cdb is 6 zeros */
             set_scsi_pt_cdb(ptvp, cdb, sizeof(cdb));
@@ -344,9 +398,10 @@
                     break;
                 case SG_LIB_CAT_NOT_READY:
                     ++resp->num_errs;
-                    if (1 ==  op->do_number) {
+                    if ((1 == op->do_number) || (op->delay > 0)) {
+                        if (! check_for_lu_becoming(ptvp))
+                            printf("device not ready\n");
                         resp->ret = sense_cat;
-                        printf("device not ready\n");
                         resp->reported = true;
                     }
                     break;
@@ -369,20 +424,25 @@
                     break;
                 }
             }
-            clear_scsi_pt_obj(ptvp);
+            partial_clear_scsi_pt_obj(ptvp);
         }
         return k;
     } else {
         for (k = 0; k < op->do_number; ++k) {
+            if (op->delay > 0)
+                wait_millisecs(op->delay);
+            set_scsi_pt_sense(ptvp, sense_b, sizeof(sense_b));
             /* Might get Unit Attention on first invocation */
             res = sg_ll_test_unit_ready_pt(ptvp, k, (0 == k), vb);
             if (res) {
                 ++resp->num_errs;
                 resp->ret = res;
-                if (1 == op->do_number) {
-                    if (SG_LIB_CAT_NOT_READY == res)
-                        printf("device not ready\n");
-                    else {
+                if ((1 == op->do_number) || (op->delay > 0)) {
+                    if (SG_LIB_CAT_NOT_READY == res) {
+                        if (! check_for_lu_becoming(ptvp))
+                            printf("device not ready\n");
+			continue;
+                    } else {
                         sg_get_category_sense_str(res, sizeof(b), b, vb);
                         printf("%s\n", b);
                     }
@@ -447,6 +507,8 @@
         pr2serr("Version string: %s\n", version_str);
         return 0;
     }
+    if (op->do_progress && (! op->delay_given))
+        op->delay = 30 * 1000;  /* progress has 30 second default delay */
 
     if (NULL == op->device_name) {
         pr2serr("No DEVICE argument given\n");
@@ -469,8 +531,12 @@
     }
     if (op->do_progress) {
         for (k = 0; k < op->do_number; ++k) {
-            if (k > 0)
-                sleep_for(30);
+            if (op->delay > 0) {
+                if (op->delay_given)
+                    wait_millisecs(op->delay);
+                else if (k > 0)
+                    wait_millisecs(op->delay);
+            }
             progress = -1;
             res = sg_ll_test_unit_ready_progress_pt(ptvp, k, &progress,
                              (1 == op->do_number), op->verbose);
diff --git a/testing/Makefile b/testing/Makefile
index dedf23a..16de2c3 100644
--- a/testing/Makefile
+++ b/testing/Makefile
@@ -109,7 +109,7 @@
 sgh_dd: sgh_dd.o $(LIBFILESNEW)
 	$(CXXLD) -o $@ $(LDFLAGS) -pthread -latomic $^
 
-sg_mrq_dd: sg_mrq_dd.o $(LIBFILESNEW)
+sg_mrq_dd: sg_mrq_dd.o sg_scat_gath.o $(LIBFILESNEW)
 	$(CXXLD) -o $@ $(LDFLAGS) -pthread -latomic $^
 
 
diff --git a/testing/sg_mrq_dd.cpp b/testing/sg_mrq_dd.cpp
index 900f45d..83d921a 100644
--- a/testing/sg_mrq_dd.cpp
+++ b/testing/sg_mrq_dd.cpp
@@ -25,18 +25,12 @@
  *
  * This version is designed for the linux kernel 4 and 5 series.
  *
- * sgp_dd is a Posix threads specialization of the sg_dd utility. Both
- * sgp_dd and sg_dd only perform special tasks when one or both of the given
- * devices belong to the Linux sg driver.
+ * sg_mrq_dd uses C++ threads and MRQ (multiple requests (in one invocation))
+ * facilities in the sg version 4 driver to do "dd" type copies and verifies.
  *
- * sgh_dd further extends sgp_dd to use the experimental kernel buffer
- * sharing feature added in 3.9.02 .
- * N.B. This utility was previously called sgs_dd but there was already an
- * archived version of a dd variant called sgs_dd so this utility name was
- * renamed [20181221]
  */
 
-static const char * version_str = "1.03 20200716";
+static const char * version_str = "1.04 20200720";
 
 #define _XOPEN_SOURCE 600
 #ifndef _GNU_SOURCE
@@ -103,6 +97,9 @@
 #define __user
 #endif  /* end of: ifndef HAVE_LINUX_SG_V4_HDR */
 
+// C++ local header
+#include "sg_scat_gath.h"
+
 #include "sg_lib.h"
 #include "sg_cmds_basic.h"
 #include "sg_io_linux.h"
@@ -112,11 +109,11 @@
 
 using namespace std;
 
-#ifdef __GNUC__
-#ifndef  __clang__
-#pragma GCC diagnostic ignored "-Wclobbered"
-#endif
-#endif
+// #ifdef __GNUC__
+// #ifndef  __clang__
+// #pragma GCC diagnostic ignored "-Wclobbered"
+// #endif
+// #endif
 
 
 #define MAX_SGL_NUM_VAL (INT32_MAX - 1)  /* should reduce for testing */
@@ -169,127 +166,6 @@
 #define PROC_SCSI_SG_VERSION "/proc/scsi/sg/version"
 #define SYS_SCSI_SG_VERSION "/sys/module/sg/version"
 
-#define SG_SGL_MAX_ELEMENTS 16384
-
-#define SG_COUNT_INDEFINITE (-1)
-#define SG_LBA_INVALID SG_COUNT_INDEFINITE
-
-/* Sizing matches largest SCSI READ and WRITE commands plus those of Unix
- * read(2)s and write(2)s. User can give larger than 31 bit 'num's but they
- * are split into several consecutive elements. */
-struct scat_gath_elem {
-    uint64_t lba;       /* of start block */
-    uint32_t num;       /* number of blocks from and including start block */
-
-    void make_bad() { lba = UINT64_MAX; num = UINT32_MAX; }
-    bool is_bad() const { return (lba == UINT64_MAX && num == UINT32_MAX); }
-};
-
-/* Consider "linearity" as a scatter gather list property. Elements of this
- * of from the strongest form to the weakest. */
-enum sgl_linearity_e {
-    SGL_LINEAR = 0,     /* empty list and 0,0 considered linear */
-    SGL_MONOTONIC,      /* since not linear, implies holes */
-    SGL_MONO_OVERLAP,   /* monotonic but same LBA in two or more elements */
-    SGL_NON_MONOTONIC   /* weakest */
-};
-
-
-/* Holds one scatter gather list and its associated metadata */
-class scat_gath_list {
-public:
-    scat_gath_list() : linearity(SGL_LINEAR), sum_hard(false), m_errno(0),
-        high_lba_p1(0), lowest_lba(0), sum(0) { }
-
-    scat_gath_list(const scat_gath_list &) = default;
-    scat_gath_list & operator=(const scat_gath_list &) = default;
-    ~scat_gath_list() = default;
-
-    bool empty() const;
-    bool empty_or_00() const;
-    int num_elems() const;
-    int64_t get_lowest_lba(bool ignore_degen, bool always_last) const;
-    int64_t get_low_lba_from_linear() const;
-    bool is_pipe_suitable() const;
-
-    friend bool sgls_eq_off(const scat_gath_list &left, int l_e_ind,
-                            int l_blk_off,
-                            const scat_gath_list &right, int r_e_ind,
-                            int r_blk_off, bool allow_partial);
-
-    bool load_from_cli(const char * cl_p, bool b_vb);
-    bool load_from_file(const char * file_name, bool def_hex, bool flexible,
-                        bool b_vb);
-    int append_1or(int64_t extra_blks, int64_t start_lba);
-    int append_1or(int64_t extra_blks);
-
-    void dbg_print(bool skip_meta, const char * id_str, bool to_stdout,
-                   bool show_sgl, bool lock = true) const;
-
-    /* calculates and sets following bool-s and int64_t-s */
-    void sum_scan(const char * id_str, bool show_sgl, bool b_verbose);
-
-    void set_weaker_linearity(enum sgl_linearity_e lin);
-    enum sgl_linearity_e linearity;
-    const char * linearity_as_str() const;
-
-    bool sum_hard;      /* 'num' in last element of 'sgl' is > 0 */
-    int m_errno;        /* OS failure errno */
-    int64_t high_lba_p1;  /* highest LBA plus 1, next write from and above */
-    int64_t lowest_lba; /* initialized to 0 */
-    int64_t sum;        /* of all 'num' elements in 'sgl' */
-
-    friend int diff_between_iters(const struct scat_gath_iter & left,
-                                  const struct scat_gath_iter & right);
-
-private:
-    friend class scat_gath_iter;
-
-    bool file2sgl_helper(FILE * fp, const char * fnp, bool def_hex,
-                         bool flexible, bool b_vb);
-
-    vector<scat_gath_elem> sgl;  /* an array on heap [0..num_elems()) */
-};
-
-
-class scat_gath_iter {
-public:
-    scat_gath_iter(const scat_gath_list & my_scat_gath_list);
-    scat_gath_iter(const scat_gath_iter & src) = default;
-    scat_gath_iter&  operator=(const scat_gath_iter&) = delete;
-    ~scat_gath_iter() = default;
-
-    int64_t current_lba() const;
-    int64_t current_lba_rem_num(int & rem_num) const;
-    struct scat_gath_elem current_elem() const;
-    bool at_end() const;
-    bool is_sgl_linear() const; /* the whole list */
-    int linear_for_n_blks(int max_n) const;
-
-    bool set_by_blk_idx(int64_t _blk_idx);
-    /* add/sub blocks return true if they reach EOL, else false */
-    bool add_blks(uint64_t blk_count);
-    bool sub_blks(uint64_t blk_count);
-
-    void dbg_print(const char * id_str, bool to_stdout, int verbose) const;
-
-    friend int diff_between_iters(const struct scat_gath_iter & left,
-                                  const struct scat_gath_iter & right);
-
-    friend bool sgls_eq_from_iters(const struct scat_gath_iter & left,
-                                   const struct scat_gath_iter & right,
-                                   bool allow_partial);
-
-private:
-    const scat_gath_list &sglist;
-
-    /* dual representation: either it_el_ind,it_blk_off or blk_idx */
-    int it_el_ind;      /* refers to sge==sglist[it_el_ind] */
-    int it_blk_off;     /* refers to LBA==(sge.lba + it_blk_off) */
-    int64_t blk_idx;    /* in range: [0 .. sglist.sum) */
-    bool extend_last;
-};
-
 
 struct flags_t {
     bool append;
@@ -846,10 +722,14 @@
     pr2serr("%s%" PRId64 "+%d records in\n", str,
             infull, gcoll.in_partial.load());
 
-    outfull = gcoll.dd_count - gcoll.out_rem_count.load();
-    pr2serr("%s%" PRId64 "+%d records %s\n", str,
-            outfull, gcoll.out_partial.load(),
-            (gcoll.verify ? "verified" : "out"));
+    if (gcoll.out_type == FT_DEV_NULL)
+        pr2serr("%s0+0 records out\n", str);
+    else {
+        outfull = gcoll.dd_count - gcoll.out_rem_count.load();
+        pr2serr("%s%" PRId64 "+%d records %s\n", str,
+                outfull, gcoll.out_partial.load(),
+                (gcoll.verify ? "verified" : "out"));
+    }
 }
 
 static void
@@ -1134,1009 +1014,6 @@
 }
 
 
-bool
-scat_gath_list::empty() const
-{
-    return sgl.empty();
-}
-
-bool
-scat_gath_list::empty_or_00() const
-{
-    if (sgl.empty())
-        return true;
-    return ((sgl.size() == 1) && (sgl[0].lba == 0) && (sgl[0].num == 0));
-}
-
-int
-scat_gath_list::num_elems() const
-{
-    return sgl.size();
-}
-
-
-/* Read numbers (up to 64 bits in size) from command line (comma (or
- * (single) space **) separated list). Assumed decimal unless prefixed
- * by '0x', '0X' or contains trailing 'h' or 'H' (which indicate hex).
- * Returns 0 if ok, or 1 if error. Assumed to be LBA (64 bit) and
- * number_of_block (32 bit) pairs. ** Space on command line needs to
- * be escaped, otherwise it is an operand/option separator. */
-bool
-scat_gath_list::load_from_cli(const char * cl_p, bool b_vb)
-{
-    bool split, full_pair;
-    int in_len, k, j;
-    const int max_nbs = MAX_SGL_NUM_VAL;
-    int64_t ll, large_num;
-    uint64_t prev_lba;
-    char * cp;
-    char * c2p;
-    const char * lcp;
-    struct scat_gath_elem sge;
-
-    if (NULL == cl_p) {
-        pr2serr("%s: bad arguments\n", __func__);
-        goto err_out;
-    }
-    lcp = cl_p;
-    in_len = strlen(cl_p);
-    if ('-' == cl_p[0]) {        /* read from stdin */
-        pr2serr("%s: logic error: no stdin here\n", __func__);
-        goto err_out;
-    } else {        /* list of numbers (default decimal) on command line */
-        k = strspn(cl_p, "0123456789aAbBcCdDeEfFhHxXiIkKmMgGtTpP, ");
-        if (in_len != k) {
-            if (b_vb)
-                pr2serr("%s: error at pos %d\n", __func__, k + 1);
-            goto err_out;
-        }
-        j = 0;
-        full_pair = true;
-        for (k = 0, split = false; ; ++k) {
-            if (split) {
-                /* splitting given elem with large number_of_blocks into
-                 * multiple elems within array being built */
-                ++j;
-                sge.lba = prev_lba + (uint64_t)max_nbs;
-                if (large_num > max_nbs) {
-                    sge.num = (uint32_t)max_nbs;
-                    prev_lba = sge.lba;
-                    large_num -= max_nbs;
-                    sgl.push_back(sge);
-                } else {
-                    sge.num = (uint32_t)large_num;
-                    split = false;
-                    if (b_vb)
-                        pr2serr("%s: split large sg elem into %d element%s\n",
-                                __func__, j, (j == 1 ? "" : "s"));
-                    sgl.push_back(sge);
-                    goto check_for_next;
-                }
-                continue;
-            }
-            full_pair = false;
-            ll = sg_get_llnum(lcp);
-            if (-1 != ll) {
-                sge.lba = (uint64_t)ll;
-                cp = (char *)strchr(lcp, ',');
-                c2p = (char *)strchr(lcp, ' ');
-                if (NULL == cp) {
-                    cp = c2p;
-                    if (NULL == cp)
-                        break;
-                }
-                if (c2p && (c2p < cp))
-                    cp = c2p;
-                lcp = cp + 1;
-            } else {
-                if (b_vb)
-                    pr2serr("%s: error at pos %d\n", __func__,
-                            (int)(lcp - cl_p + 1));
-                goto err_out;
-            }
-            ll = sg_get_llnum(lcp);
-            if (ll >= 0) {
-                full_pair = true;
-                if (ll > max_nbs) {
-                    sge.num = (uint32_t)max_nbs;
-                    prev_lba = sge.lba;
-                    large_num = ll - max_nbs;
-                    split = true;
-                    j = 1;
-                    continue;
-                }
-                sge.num = (uint32_t)ll;
-            } else {    /* bad or negative number as number_of_blocks */
-                if (b_vb)
-                    pr2serr("%s: bad number at pos %d\n", __func__,
-                            (int)(lcp - cl_p + 1));
-                goto err_out;
-            }
-            sgl.push_back(sge);
-check_for_next:
-            cp = (char *)strchr(lcp, ',');
-            c2p = (char *)strchr(lcp, ' ');
-            if (NULL == cp) {
-                cp = c2p;
-                if (NULL == cp)
-                    break;
-            }
-            if (c2p && (c2p < cp))
-                cp = c2p;
-            lcp = cp + 1;
-        }       /* end of for loop over items in operand */
-        /* other than first pair, expect even number of items */
-        if ((k > 0) && (! full_pair)) {
-            if (b_vb)
-                pr2serr("%s:  expected even number of items: "
-                        "LBA0,NUM0,LBA1,NUM1...\n", __func__);
-            goto err_out;
-        }
-    }
-    return true;
-err_out:
-    if (0 == m_errno)
-        m_errno = SG_LIB_SYNTAX_ERROR;
-    return false;
-}
-
-bool
-scat_gath_list::file2sgl_helper(FILE * fp, const char * fnp, bool def_hex,
-                                bool flexible, bool b_vb)
-{
-    bool bit0;
-    bool pre_addr1 = true;
-    bool pre_hex_seen = false;
-    int in_len, k, j, m, ind;
-    const int max_nbs = MAX_SGL_NUM_VAL;
-    int off = 0;
-    int64_t ll;
-    uint64_t ull, prev_lba;
-    char * lcp;
-    struct scat_gath_elem sge;
-    char line[1024];
-
-    for (j = 0 ; ; ++j) {
-        if (NULL == fgets(line, sizeof(line), fp))
-            break;
-        // could improve with carry_over logic if sizeof(line) too small
-        in_len = strlen(line);
-        if (in_len > 0) {
-            if ('\n' == line[in_len - 1]) {
-                --in_len;
-                line[in_len] = '\0';
-            } else {
-                m_errno = SG_LIB_SYNTAX_ERROR;
-                if (b_vb)
-                    pr2serr("%s: %s: line too long, max %d bytes\n",
-                            __func__, fnp, (int)(sizeof(line) - 1));
-                goto err_out;
-            }
-        }
-        if (in_len < 1)
-            continue;
-        lcp = line;
-        m = strspn(lcp, " \t");
-        if (m == in_len)
-            continue;
-        lcp += m;
-        in_len -= m;
-        if ('#' == *lcp)
-            continue;
-        if (pre_addr1 || pre_hex_seen) {
-            /* Accept lines with leading 'HEX' and ignore as long as there
-             * is one _before_ any LBA,NUM lines in the file. This allows
-             * HEX marked sgls to be concaternated together. */
-            if (('H' == toupper(lcp[0])) && ('E' == toupper(lcp[1])) &&
-                ('X' == toupper(lcp[2]))) {
-                pre_hex_seen = true;
-                if (def_hex)
-                    continue; /* bypass 'HEX' marker line if expecting hex */
-                else {
-                    if (flexible) {
-                        def_hex = true; /* okay, switch to hex parse */
-                        continue;
-                    } else {
-                        pr2serr("%s: %s: 'hex' string detected on line %d, "
-                                "expecting decimal\n", __func__, fnp, j + 1);
-                        m_errno = EINVAL;
-                        goto err_out;
-                    }
-                }
-            }
-        }
-        k = strspn(lcp, "0123456789aAbBcCdDeEfFhHxXbBdDiIkKmMgGtTpP, \t");
-        if ((k < in_len) && ('#' != lcp[k])) {
-            m_errno = EINVAL;
-            if (b_vb)
-                pr2serr("%s: %s: syntax error at line %d, pos %d\n",
-                        __func__, fnp, j + 1, m + k + 1);
-            goto err_out;
-        }
-        for (k = 0; k < 256; ++k) {
-            /* limit parseable items on one line to 256 */
-            if (def_hex) {      /* don't accept negatives or multipliers */
-                if (1 == sscanf(lcp, "%" SCNx64, &ull))
-                    ll = (int64_t)ull;
-                else
-                    ll = -1;    /* use (2**64 - 1) as error flag */
-            } else
-                ll = sg_get_llnum(lcp);
-            if (-1 != ll) {
-                ind = ((off + k) >> 1);
-                bit0 = !! (0x1 & (off + k));
-                if (ind >= SG_SGL_MAX_ELEMENTS) {
-                    m_errno = EINVAL;
-                    if (b_vb)
-                        pr2serr("%s: %s: array length exceeded\n", __func__,
-                                fnp);
-                    goto err_out;
-                }
-                if (bit0) {     /* bit0 set when decoding a NUM */
-                    if (ll < 0) {
-                        m_errno = EINVAL;
-                        if (b_vb)
-                            pr2serr("%s: %s: bad number in line %d, at pos "
-                                    "%d\n", __func__, fnp, j + 1,
-                                    (int)(lcp - line + 1));
-                        goto err_out;
-                    }
-                    if (ll > max_nbs) {
-                        int h = 1;
-
-                        /* split up this elem into multiple, smaller elems */
-                        do {
-                            sge.num = (uint32_t)max_nbs;
-                            prev_lba = sge.lba;
-                            sgl.push_back(sge);
-                            sge.lba = prev_lba + (uint64_t)max_nbs;
-                            ++h;
-                            off += 2;
-                            ll -= max_nbs;
-                        } while (ll > max_nbs);
-                        if (b_vb)
-                            pr2serr("%s: split large sg elem into %d "
-                                    "elements\n", __func__, h);
-                    }
-                    sge.num = (uint32_t)ll;
-                    sgl.push_back(sge);
-                } else {        /* bit0 clear when decoding a LBA */
-                    if (pre_addr1)
-                        pre_addr1 = false;
-                    sge.lba = (uint64_t)ll;
-                }
-            } else {    /* failed to decode number on line */
-                if ('#' == *lcp) { /* numbers before #, rest of line comment */
-                    --k;
-                    break;      /* goes to next line */
-                }
-                m_errno = EINVAL;
-                if (b_vb)
-                    pr2serr("%s: %s: error in line %d, at pos %d\n",
-                            __func__, fnp, j + 1, (int)(lcp - line + 1));
-                goto err_out;
-            }
-            lcp = strpbrk(lcp, " ,\t#");
-            if ((NULL == lcp) || ('#' == *lcp))
-                break;
-            lcp += strspn(lcp, " ,\t");
-            if ('\0' == *lcp)
-                break;
-        }       /* <<< end of for(k < 256) loop */
-        off += (k + 1);
-    }   /* <<< end of for loop, one iteration per line */
-    /* allow one items, but not higher odd number of items */
-    if ((off > 1) && (0x1 & off)) {
-        m_errno = EINVAL;
-        if (b_vb)
-            pr2serr("%s: %s: expect even number of items: "
-                    "LBA0,NUM0,LBA1,NUM1...\n", __func__, fnp);
-        goto err_out;
-    }
-    clearerr(fp);    /* even EOF on first pass needs this before rescan */
-    return true;
-err_out:
-    clearerr(fp);
-    return false;
-}
-
-/* Read numbers from filename (or stdin), line by line (comma (or (single)
- * space) separated list); places starting_LBA,number_of_block pairs in an
- * array of scat_gath_elem elements pointed to by the returned value. If
- * this fails NULL is returned and an error number is written to errp (if it
- * is non-NULL). Assumed decimal (and may have suffix multipliers) when
- * def_hex==false; if a number is prefixed by '0x', '0X' or contains trailing
- * 'h' or 'H' that denotes a hex number. When def_hex==true all numbers are
- * assumed to be hex (ignored '0x' prefixes and 'h' suffixes) and multiplers
- * are not permitted. Heap allocates an array just big enough to hold all
- * elements if the file is countable. Pipes and stdin are not considered
- * countable. In the non-countable case an array of MAX_FIXED_SGL_ELEMS
- * elements is pre-allocated; if it is exceeded sg_convert_errno(EDOM) is
- * placed in *errp (if it is non-NULL). One of the first actions is to write
- * 0 to *errp (if it is non-NULL) so the caller does not need to zero it
- * before calling. */
-bool
-scat_gath_list::load_from_file(const char * file_name, bool def_hex,
-                               bool flexible, bool b_vb)
-{
-    bool have_stdin;
-    bool have_err = false;
-    FILE * fp;
-    const char * fnp;
-
-    have_stdin = ((1 == strlen(file_name)) && ('-' == file_name[0]));
-    if (have_stdin) {
-        fp = stdin;
-        fnp = "<stdin>";
-    } else {
-        fnp = file_name;
-        fp = fopen(fnp, "r");
-        if (NULL == fp) {
-            m_errno = errno;
-            if (b_vb)
-                pr2serr("%s: opening %s: %s\n", __func__, fnp,
-                        safe_strerror(m_errno));
-            return false;
-        }
-    }
-    if (! file2sgl_helper(fp, fnp, def_hex, flexible, b_vb))
-        have_err = true;
-    if (! have_stdin)
-        fclose(fp);
-    return have_err ? false : true;
-}
-
-const char *
-scat_gath_list::linearity_as_str() const
-{
-    switch (linearity) {
-    case SGL_LINEAR:
-        return "linear";
-    case SGL_MONOTONIC:
-        return "monotonic";
-    case SGL_MONO_OVERLAP:
-        return "monotonic, overlapping";
-    case SGL_NON_MONOTONIC:
-        return "non-monotonic";
-    default:
-        return "unknown";
-    }
-}
-
-void
-scat_gath_list::set_weaker_linearity(enum sgl_linearity_e lin)
-{
-    int i_lin = (int)lin;
-
-    if (i_lin > (int)linearity)
-        linearity = lin;
-}
-
-/* id_str may be NULL (if so replace by "unknown"), present to enhance verbose
- * output. */
-void
-scat_gath_list::dbg_print(bool skip_meta, const char * id_str, bool to_stdout,
-                          bool show_sgl, bool lock) const
-{
-    int k;
-    if (lock)
-        strerr_mut.lock();
-    int num = sgl.size();
-    const char * caller = id_str ? id_str : "unknown";
-    FILE * fp = to_stdout ? stdout : stderr;
-
-    if (! skip_meta) {
-        fprintf(fp, "%s: elems=%d, sgl %spresent, linearity=%s\n",
-                caller, num, (sgl.empty() ? "not " : ""),
-                linearity_as_str());
-        fprintf(fp, "  sum=%" PRId64 ", sum_hard=%s lowest=0x%" PRIx64
-                ", high_lba_p1=", sum, (sum_hard ? "true" : "false"),
-                lowest_lba);
-        fprintf(fp, "0x%" PRIx64 "\n", high_lba_p1);
-    }
-    fprintf(fp, "  >> %s scatter gather list (%d element%s):\n", caller, num,
-            (num == 1 ? "" : "s"));
-    if (show_sgl) {
-        for (k = 0; k < num; ++k) {
-            const struct scat_gath_elem & sge = sgl[k];
-
-            fprintf(fp, "    lba: 0x%" PRIx64 ", number: 0x%" PRIx32,
-                    sge.lba, sge.num);
-            if (sge.lba > 0)
-                fprintf(fp, " [next lba: 0x%" PRIx64 "]", sge.lba + sge.num);
-            fprintf(fp, "\n");
-        }
-    }
-    if (lock)
-        strerr_mut.unlock();
-}
-
-/* Assumes sgl array (vector) is setup. The other fields in this object are
- * set by analyzing sgl in a single pass. The fields that are set are:
- * fragmented, lowest_lba, high_lba_p1, monotonic, overlapping, sum and
- * sum_hard. Degenerate elements (i.e. those with 0 blocks) are ignored apart
- * from when one is last which makes sum_hard false and its LBA becomes
- * high_lba_p1 if it is the highest in the list. An empty sgl is equivalent
- * to a 1 element list with [0, 0], so sum_hard==false, monit==true,
- * fragmented==false and overlapping==false . id_str may be NULL, present
- * to enhance verbose output. */
-void
-scat_gath_list::sum_scan(const char * id_str, bool show_sgl, bool b_vb)
-{
-    bool degen = false;
-    bool first = true;
-    bool regular = true;        /* no overlapping segments detected */
-    int k;
-    int elems = sgl.size();
-    uint32_t prev_num, t_num;
-    uint64_t prev_lba, t_lba, low, high, end;
-
-    sum = 0;
-    for (k = 0, low = 0, high = 0; k < elems; ++k) {
-        const struct scat_gath_elem & sge = sgl[k];
-
-        degen = false;
-        t_num = sge.num;
-        if (0 == t_num) {
-            degen = true;
-            if (! first)
-                continue;       /* ignore degen element that not first */
-        }
-        if (first) {
-            low = sge.lba;
-            sum = t_num;
-            high = sge.lba + sge.num;
-            first = false;
-        } else {
-            t_lba = sge.lba;
-            if ((prev_lba + prev_num) != t_lba)
-                set_weaker_linearity(SGL_MONOTONIC);
-            sum += t_num;
-            end = t_lba + t_num;
-            if (end > high)
-                high = end;     /* high is one plus highest LBA */
-            if (prev_lba < t_lba)
-                ;
-            else if (prev_lba == t_lba) {
-                if (prev_num > 0) {
-                    set_weaker_linearity(SGL_MONO_OVERLAP);
-                    break;
-                }
-            } else {
-                low = t_lba;
-                set_weaker_linearity(SGL_NON_MONOTONIC);
-                break;
-            }
-            if (regular) {
-                if ((prev_lba + prev_num) > t_lba)
-                    regular = false;
-            }
-        }
-        prev_lba = sge.lba;
-        prev_num = sge.num;
-    }           /* end of for loop while still elements and monot true */
-
-    if (k < elems) {    /* only here if above breaks are taken */
-        prev_lba = t_lba;
-        ++k;
-        for ( ; k < elems; ++k) {
-            const struct scat_gath_elem & sge = sgl[k];
-
-            degen = false;
-            t_lba = sge.lba;
-            t_num = sge.num;
-            if (0 == t_num) {
-                degen = true;
-                continue;
-            }
-            sum += t_num;
-            end = t_lba + t_num;
-            if (end > high)
-                high = end;
-            if (prev_lba > t_lba) {
-                if (t_lba < low)
-                    low = t_lba;
-            }
-            prev_lba = t_lba;
-        }
-    } else
-        if (! regular)
-            set_weaker_linearity(SGL_MONO_OVERLAP);
-
-    lowest_lba = low;
-    if (degen && (elems > 0)) { /* last element always impacts high_lba_p1 */
-        t_lba = sgl[elems - 1].lba;
-        high_lba_p1 = (t_lba > high) ? t_lba : high;
-    } else
-        high_lba_p1 = high;
-    sum_hard = (elems > 0) ? ! degen : false;
-    if (b_vb)
-        dbg_print(false, id_str, false, show_sgl);
-}
-
-/* Usually will append (or add to start if empty) sge unless 'extra_blks'
- * exceeds MAX_SGL_NUM_VAL. In that case multiple sge_s are added with
- * sge.num = MAX_SGL_NUM_VAL or less (for final sge) until extra_blks is
- * exhausted. Returns new size of scatter gather list. */
-int
-scat_gath_list::append_1or(int64_t extra_blks, int64_t start_lba)
-{
-    int o_num = sgl.size();
-    const int max_nbs = MAX_SGL_NUM_VAL;
-    int64_t cnt = 0;
-    struct scat_gath_elem sge;
-
-    if ((extra_blks <= 0) || (start_lba < 0))
-        return o_num;       /* nothing to do */
-    if ((o_num > 0) && (! sum_hard)) {
-        sge = sgl[o_num - 1];   /* assume sge.num==0 */
-        if (sge.lba == (uint64_t)start_lba) {
-            if (extra_blks <= max_nbs)
-                sge.num = extra_blks;
-            else
-                sge.num = max_nbs;
-            sgl[o_num - 1] = sge;
-            cnt = sge.num;
-            sum += cnt;
-            sum_hard = true;
-            if (cnt <= extra_blks) {
-                high_lba_p1 = sge.lba + cnt;
-                return o_num;
-            }
-        }
-    } else if (0 == o_num)
-        lowest_lba = start_lba;
-
-    for ( ; cnt < extra_blks; cnt += max_nbs) {
-        sge.lba = start_lba + cnt;
-        if ((extra_blks - cnt) <= max_nbs)
-            sge.num = extra_blks - cnt;
-        else
-            sge.num = max_nbs;
-        sgl.push_back(sge);
-        sum += sge.num;
-    }           /* always loops at least once */
-    sum_hard = true;
-    high_lba_p1 = sge.lba + sge.num;
-    return sgl.size();
-}
-
-int
-scat_gath_list::append_1or(int64_t extra_blks)
-{
-    int o_num = sgl.size();
-    if (o_num < 1)
-        return append_1or(extra_blks, 0);
-
-    struct scat_gath_elem sge = sgl[o_num - 1];
-    return append_1or(extra_blks, sge.lba + sge.num);
-}
-
-bool
-sgls_eq_off(const scat_gath_list & left, int l_e_ind, int l_blk_off,
-            const scat_gath_list & right, int r_e_ind, int r_blk_off,
-            bool allow_partial)
-{
-    int lrem, rrem;
-    int lelems = left.sgl.size();
-    int relems = right.sgl.size();
-
-    while ((l_e_ind < lelems) && (r_e_ind < relems)) {
-        if ((left.sgl[l_e_ind].lba + l_blk_off) !=
-            (right.sgl[r_e_ind].lba + r_blk_off))
-            return false;
-        lrem = left.sgl[l_e_ind].num - l_blk_off;
-        rrem = right.sgl[r_e_ind].num - r_blk_off;
-        if (lrem == rrem) {
-            ++l_e_ind;
-            l_blk_off = 0;
-            ++r_e_ind;
-            r_blk_off = 0;
-        } else if (lrem < rrem) {
-            ++l_e_ind;
-            l_blk_off = 0;
-            r_blk_off += lrem;
-        } else {
-            ++r_e_ind;
-            r_blk_off = 0;
-            l_blk_off += rrem;
-        }
-    }
-    if ((l_e_ind >= lelems) && (r_e_ind >= relems))
-        return true;
-    return allow_partial;
-}
-
-/* If bad arguments returns -1, otherwise returns the lowest LBA in *sglp .
- * If no elements considered returns 0. If ignore_degen is true than
- * ignores all elements with sge.num zero unless always_last is also
- * true in which case the last element is always considered. */
-int64_t
-scat_gath_list::get_lowest_lba(bool ignore_degen, bool always_last) const
-{
-    int k;
-    const int num_elems = sgl.size();
-    bool some = (num_elems > 0);
-    int64_t res = INT64_MAX;
-
-    for (k = 0; k < num_elems; ++k) {
-        if ((0 == sgl[k].num) && ignore_degen)
-            continue;
-        if ((int64_t)sgl[k].lba < res)
-            res = sgl[k].lba;
-    }
-    if (always_last && some) {
-        if ((int64_t)sgl[k - 1].lba < res)
-            res = sgl[k - 1].lba;
-    }
-    return (INT64_MAX == res) ? 0 : res;
-}
-
-/* Returns >= 0 if sgl can be simplified to a single LBA. So an empty sgl
- * will return 0; a one element sgl will return its LBA. A multiple element
- * sgl only returns the first element's LBA (that is not degenerate) if the
- * sgl is monotonic and not fragmented. In the extreme case takes last
- * element's LBA if all prior elements are degenerate. Else returns -1 .
- * Assumes sgl_sum_scan() has been called. */
-int64_t
-scat_gath_list::get_low_lba_from_linear() const
-{
-    const int num_elems = sgl.size();
-    int k;
-
-    if (num_elems <= 1)
-        return (1 == num_elems) ? sgl[0].lba : 0;
-    else {
-        if (linearity == SGL_LINEAR) {
-            for (k = 0; k < (num_elems - 1); ++k) {
-                if (sgl[k].num > 0)
-                    return sgl[k].lba;
-            }
-            /* take last element's LBA if all earlier are degenerate */
-            return sgl[k].lba;
-        } else
-            return -1;
-    }
-}
-
-bool
-scat_gath_list::is_pipe_suitable() const
-{
-    return (lowest_lba == 0) && (linearity == SGL_LINEAR);
-}
-
-scat_gath_iter::scat_gath_iter(const scat_gath_list & parent)
-    : sglist(parent), it_el_ind(0), it_blk_off(0), blk_idx(0)
-{
-    int elems = sglist.num_elems();
-
-    if (elems > 0)
-        extend_last = (0 == sglist.sgl[elems - 1].num);
-}
-
-bool
-scat_gath_iter::set_by_blk_idx(int64_t _blk_idx)
-{
-    bool first;
-    int k;
-    const int elems = sglist.sgl.size();
-    const int last_ind = elems - 1;
-    uint32_t num;
-    int64_t bc = _blk_idx;
-
-    if (bc < 0)
-        return false;
-
-    if (bc == blk_idx)
-        return true;
-    else if (bc > blk_idx) {
-        k = it_el_ind;
-        bc -= blk_idx;
-    } else
-        k = 0;
-    for (first = true; k < elems; ++k, first = false) {
-        num = ((k == last_ind) && extend_last) ? MAX_SGL_NUM_VAL :
-                                                 sglist.sgl[k].num;
-        if (first) {
-            if ((int64_t)(num - it_blk_off) < bc)
-                bc -= (num - it_blk_off);
-            else {
-                it_blk_off = bc + it_blk_off;
-                break;
-            }
-        } else {
-            if ((int64_t)num < bc)
-                bc -= num;
-            else {
-                it_blk_off = (uint32_t)bc;
-                break;
-            }
-        }
-    }
-    it_el_ind = k;
-    blk_idx = _blk_idx;
-
-    if (k < elems)
-        return true;
-    else if ((k == elems) && (0 == it_blk_off))
-        return true;    /* EOL */
-    else
-        return false;
-}
-
-/* Given a blk_count, the iterator (*iter_p) is moved toward the EOL.
- * Returns true unless blk_count takes iterator two or more past the last
- * element. So if blk_count takes the iterator to the EOL, this function
- * returns true. Takes into account iterator's extend_last flag. */
-bool
-scat_gath_iter::add_blks(uint64_t blk_count)
-{
-    bool first;
-    int k;
-    const int elems = sglist.sgl.size();
-    const int last_ind = elems - 1;
-    uint32_t num;
-    uint64_t bc = blk_count;
-
-    if (0 == bc)
-        return true;
-    for (first = true, k = it_el_ind; k < elems; ++k, first = false) {
-        num = ((k == last_ind) && extend_last) ? MAX_SGL_NUM_VAL :
-                                                 sglist.sgl[k].num;
-        if (first) {
-            if ((uint64_t)(num - it_blk_off) < bc)
-                bc -= (num - it_blk_off);
-            else {
-                it_blk_off = bc + it_blk_off;
-                break;
-            }
-        } else {
-            if ((uint64_t)num < bc)
-                bc -= num;
-            else {
-                it_blk_off = (uint32_t)bc;
-                break;
-            }
-        }
-    }
-    it_el_ind = k;
-    blk_idx += blk_count;
-
-    if (k < elems)
-        return true;
-    else if ((k == elems) && (0 == it_blk_off))
-        return true;    /* EOL */
-    else
-        return false;
-}
-
-/* Move the iterator from its current position (which may be to EOL) towards
- * the start of the sgl (i.e. backwards) for blk_count blocks. Returns true
- * if iterator is valid after the move, else returns false. N.B. if false is
- * returned, then the iterator is invalid and may need to set it to a valid
- * value. */
-bool
-scat_gath_iter::sub_blks(uint64_t blk_count)
-{
-    bool first;
-    int k = it_el_ind;
-    uint64_t bc = 0;
-    const uint64_t orig_blk_count = blk_count;
-
-    if (0 == blk_count)
-        return true;
-    for (first = true; k >= 0; --k) {
-        if (first) {
-            if (blk_count > (uint64_t)it_blk_off)
-                blk_count -= it_blk_off;
-            else {
-                it_blk_off -= blk_count;
-                break;
-            }
-            first = false;
-        } else {
-            uint32_t off = sglist.sgl[k].num;
-
-            bc = blk_count;
-            if (bc > (uint64_t)off)
-                blk_count -= off;
-            else {
-                bc = off - bc;
-                break;
-            }
-        }
-    }
-    if (k < 0) {
-        blk_idx = 0;
-        return false;           /* bad situation */
-    }
-    if ((int64_t)orig_blk_count <= blk_idx)
-        blk_idx -= orig_blk_count;
-    else
-        blk_idx = 0;
-    it_el_ind = k;
-    if (! first)
-        it_blk_off = (uint32_t)bc;
-    return true;
-}
-
-/* Returns LBA referred to by iterator if valid or returns SG_LBA_INVALID
- * (-1) if at end or invalid. */
-int64_t
-scat_gath_iter::current_lba() const
-{
-    const int elems = sglist.sgl.size();
-    int64_t res = SG_LBA_INVALID; /* for at end or invalid (-1) */
-
-    if (it_el_ind < elems) {
-        struct scat_gath_elem sge = sglist.sgl[it_el_ind];
-
-        if ((uint32_t)it_blk_off < sge.num)
-            return sge.lba + it_blk_off;
-        else if (((uint32_t)it_blk_off == sge.num) &&
-                 ((it_el_ind + 1) < elems)) {
-            class scat_gath_iter iter(*this);
-
-            ++iter.it_el_ind;
-            iter.it_blk_off = 0;
-            /* worst case recursion will stop at end of sgl */
-            return iter.current_lba();
-        }
-    }
-    return res;
-}
-
-int64_t
-scat_gath_iter::current_lba_rem_num(int & rem_num) const
-{
-    const int elems = sglist.sgl.size();
-    int64_t res = SG_LBA_INVALID; /* for at end or invalid (-1) */
-
-    if (it_el_ind < elems) {
-        struct scat_gath_elem sge = sglist.sgl[it_el_ind];
-
-        if ((uint32_t)it_blk_off < sge.num) {
-            rem_num = sge.num - it_blk_off;
-            return sge.lba + it_blk_off;
-        } else if (((uint32_t)it_blk_off == sge.num) &&
-                 ((it_el_ind + 1) < elems)) {
-            class scat_gath_iter iter(*this);
-
-            ++iter.it_el_ind;
-            iter.it_blk_off = 0;
-            /* worst case recursion will stop at end of sgl */
-            return iter.current_lba_rem_num(rem_num);
-        }
-    }
-    rem_num = -1;
-    return res;
-}
-
-struct scat_gath_elem
-scat_gath_iter::current_elem() const
-{
-    const int elems = sglist.sgl.size();
-    struct scat_gath_elem sge;
-
-    sge.make_bad();
-    if (it_el_ind < elems)
-        return sglist.sgl[it_el_ind];
-    return sge;
-}
-
-/* Returns true of no sgl or sgl is at the end [elems, 0], otherwise it
- * returns false. */
-bool
-scat_gath_iter::at_end() const
-{
-    const int elems = sglist.sgl.size();
-
-    return ((0 == elems) || ((it_el_ind == elems) && (0 == it_blk_off)));
-}
-
-/* Returns true if associated iterator is monotonic (increasing) and not
- * fragmented. Empty sgl and single element degenerate considered linear.
- * Assumes sgl_sum_scan() has been called on sgl. */
-bool
-scat_gath_iter::is_sgl_linear() const
-{
-    return sglist.linearity == SGL_LINEAR;
-}
-
-int
-scat_gath_iter::linear_for_n_blks(int max_n) const
-{
-    int k, rem;
-    const int elems = sglist.sgl.size();
-    uint64_t prev_lba;
-    struct scat_gath_elem sge;
-
-    if (at_end() || (max_n <= 0))
-        return 0;
-    sge = sglist.sgl[it_el_ind];
-    rem = (int)sge.num - it_blk_off;
-    if (max_n <= rem)
-        return max_n;
-    prev_lba = sge.lba + sge.num;
-    for (k = it_el_ind + 1; k < elems; ++k) {
-        sge = sglist.sgl[k];
-        if (sge.lba != prev_lba)
-            return rem;
-        rem += sge.num;
-        if (max_n <= rem)
-            return max_n;
-        prev_lba = sge.lba + sge.num;
-    }
-    return rem;
-}
-
-/* id_str may be NULL (if so replace by "unknown"), present to enhance verbose
- * output. */
-void
-scat_gath_iter::dbg_print(const char * id_str, bool to_stdout,
-                          int verbose) const
-{
-    const char * caller = id_str ? id_str : "unknown";
-    FILE * fp = to_stdout ? stdout : stderr;
-    lock_guard<mutex> lk(strerr_mut);
-
-    fprintf(fp, "%s: it_el_ind=%d, it_blk_off=%d, blk_idx=%" PRId64 "\n",
-            caller, it_el_ind, it_blk_off, blk_idx);
-    fprintf(fp, "  extend_last=%d\n", extend_last);
-    if (verbose)
-        sglist.dbg_print(false, " iterator's", to_stdout, verbose > 1, false);
-}
-
-/* Calculates difference between iterators, logically: res <-- lhs - rhs
- * Checks that lhsp and rhsp have same underlying sgl, if not returns
- * INT_MIN. Assumes iterators close enough for result to lie in range
- * from (-INT_MAX) to INT_MAX (inclusive). */
-int
-diff_between_iters(const struct scat_gath_iter & left,
-                   const struct scat_gath_iter & right)
-{
-    int res, k, r_e_ind, l_e_ind;
-
-    if (&left.sglist != &right.sglist) {
-        pr2serr("%s: bad args\n", __func__);
-        return INT_MIN;
-    }
-    r_e_ind = right.it_el_ind;
-    l_e_ind = left.it_el_ind;
-    if (l_e_ind < r_e_ind) { /* so difference will be negative */
-        res = diff_between_iters(right, left);        /* cheat */
-        if (INT_MIN == res)
-            return res;
-        return -res;
-    } else if (l_e_ind == r_e_ind)
-        return (int)left.it_blk_off - (int)right.it_blk_off;
-    /* (l_e_ind > r_e_ind) so (lhs > rhs) */
-    res = (int)right.sglist.sgl[r_e_ind].num - right.it_blk_off;
-    for (k = 1; (r_e_ind + k) < l_e_ind; ++k) {
-        // pr2serr("%s: k=%d, res=%d, num=%d\n", __func__, k, res,
-        //         (int)right.sglist.sgl[r_e_ind + k].num);
-        res += (int)right.sglist.sgl[r_e_ind + k].num;
-    }
-    res += left.it_blk_off;
-    // pr2serr("%s: at exit res=%d\n", __func__, res);
-    return res;
-}
-
-/* Compares from the current iterator positions of left and left until
- * the shorter list is exhausted. Returns false on the first inequality.
- * If no inequality and both remaining lists are same length then returns
- * true. If no inequality but remaining lists differ in length then returns
- * allow_partial. */
-bool
-sgls_eq_from_iters(const struct scat_gath_iter & left,
-                   const struct scat_gath_iter & right,
-                   bool allow_partial)
-{
-    return sgls_eq_off(left.sglist, left.it_el_ind, left.it_blk_off,
-                       right.sglist, right.it_el_ind, right.it_blk_off,
-                       allow_partial);
-}
-
 get_next_res
 global_collection::get_next(int desired_num_blks)
 {
@@ -2421,6 +1298,8 @@
             break;
         }
         if (! i_sg_it.set_by_blk_idx(gnr.first)) {
+            lock_guard<mutex> lk(strerr_mut);
+
             pr2serr_lk("[%d]: input set_by_blk_idx() failed\n", id);
             i_sg_it.dbg_print("input after set_by_blk_idx", false, vb > 5);
             res = 2;
@@ -2811,7 +1690,7 @@
     int hole_count = 0;
     int vb = clp->verbose;
     int k, j, f1, slen, sstatus, blen;
-    char b[80];
+    char b[160];
 
     blen = sizeof(b);
     good_inblks = 0;
@@ -3276,8 +2155,10 @@
         seg_blks = res;
         in_fin_blks = seg_blks;
 
-        if (FT_DEV_NULL == clp->out_type)
+        if (FT_DEV_NULL == clp->out_type) {
+            out_fin_blks = seg_blks;/* so finish logic doesn't suspect ... */
             goto bypass;
+        }
         d_off = 0;
         for (k = 0; seg_blks > 0; ++k, seg_blks -= num, d_off += num) {
             kk = min<int>(seg_blks, clp->bpt);
@@ -3732,6 +2613,8 @@
     either_list.sum_scan(key, vb > 3 /* bool show_sgl */, vb > 1);
 #if 0
     if (vb > 3) {
+        lock_guard<mutex> lk(strerr_mut);
+
         pr2serr("%s: scatter gathet list:\n", is_skip ? ("skip" : "seek"));
         either_list.dbg_print(false, is_skip ? ("skip" : "seek"), false,
                    bool show_sgl)
@@ -4590,6 +3473,10 @@
         }
         clp->outfp = outf;
     }
+    if (clp->verify && (clp->out_type == FT_DEV_NULL)) {
+        pr2serr("Can't do verify when OFILE not given\n");
+        return SG_LIB_SYNTAX_ERROR;
+    }
 
     if ((FT_SG == clp->in_type ) && (FT_SG == clp->out_type)) {
         ;
diff --git a/testing/sg_scat_gath.cpp b/testing/sg_scat_gath.cpp
new file mode 100644
index 0000000..dc51f37
--- /dev/null
+++ b/testing/sg_scat_gath.cpp
@@ -0,0 +1,1029 @@
+/*
+ * Copyright (c) 2014-2020 Douglas Gilbert.
+ * All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the BSD_LICENSE file.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// C headers
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <limits.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+// C++ headers
+#include <array>
+
+#include "sg_scat_gath.h"
+#include "sg_lib.h"
+#include "sg_pr2serr.h"
+
+using namespace std;
+
+#define MAX_SGL_NUM_VAL (INT32_MAX - 1)  /* should reduce for testing */
+// #define MAX_SGL_NUM_VAL 7  /* should reduce for testing */
+#if MAX_SGL_NUM_VAL > INT32_MAX
+#error "MAX_SGL_NUM_VAL cannot exceed 2^31 - 1"
+#endif
+
+bool
+scat_gath_list::empty() const
+{
+    return sgl.empty();
+}
+
+bool
+scat_gath_list::empty_or_00() const
+{
+    if (sgl.empty())
+        return true;
+    return ((sgl.size() == 1) && (sgl[0].lba == 0) && (sgl[0].num == 0));
+}
+
+int
+scat_gath_list::num_elems() const
+{
+    return sgl.size();
+}
+
+
+/* Read numbers (up to 64 bits in size) from command line (comma (or
+ * (single) space **) separated list). Assumed decimal unless prefixed
+ * by '0x', '0X' or contains trailing 'h' or 'H' (which indicate hex).
+ * Returns 0 if ok, or 1 if error. Assumed to be LBA (64 bit) and
+ * number_of_block (32 bit) pairs. ** Space on command line needs to
+ * be escaped, otherwise it is an operand/option separator. */
+bool
+scat_gath_list::load_from_cli(const char * cl_p, bool b_vb)
+{
+    bool split, full_pair;
+    int in_len, k, j;
+    const int max_nbs = MAX_SGL_NUM_VAL;
+    int64_t ll, large_num;
+    uint64_t prev_lba;
+    char * cp;
+    char * c2p;
+    const char * lcp;
+    struct scat_gath_elem sge;
+
+    if (NULL == cl_p) {
+        pr2serr("%s: bad arguments\n", __func__);
+        goto err_out;
+    }
+    lcp = cl_p;
+    in_len = strlen(cl_p);
+    if ('-' == cl_p[0]) {        /* read from stdin */
+        pr2serr("%s: logic error: no stdin here\n", __func__);
+        goto err_out;
+    } else {        /* list of numbers (default decimal) on command line */
+        k = strspn(cl_p, "0123456789aAbBcCdDeEfFhHxXiIkKmMgGtTpP, ");
+        if (in_len != k) {
+            if (b_vb)
+                pr2serr("%s: error at pos %d\n", __func__, k + 1);
+            goto err_out;
+        }
+        j = 0;
+        full_pair = true;
+        for (k = 0, split = false; ; ++k) {
+            if (split) {
+                /* splitting given elem with large number_of_blocks into
+                 * multiple elems within array being built */
+                ++j;
+                sge.lba = prev_lba + (uint64_t)max_nbs;
+                if (large_num > max_nbs) {
+                    sge.num = (uint32_t)max_nbs;
+                    prev_lba = sge.lba;
+                    large_num -= max_nbs;
+                    sgl.push_back(sge);
+                } else {
+                    sge.num = (uint32_t)large_num;
+                    split = false;
+                    if (b_vb)
+                        pr2serr("%s: split large sg elem into %d element%s\n",
+                                __func__, j, (j == 1 ? "" : "s"));
+                    sgl.push_back(sge);
+                    goto check_for_next;
+                }
+                continue;
+            }
+            full_pair = false;
+            ll = sg_get_llnum(lcp);
+            if (-1 != ll) {
+                sge.lba = (uint64_t)ll;
+                cp = (char *)strchr(lcp, ',');
+                c2p = (char *)strchr(lcp, ' ');
+                if (NULL == cp) {
+                    cp = c2p;
+                    if (NULL == cp)
+                        break;
+                }
+                if (c2p && (c2p < cp))
+                    cp = c2p;
+                lcp = cp + 1;
+            } else {
+                if (b_vb)
+                    pr2serr("%s: error at pos %d\n", __func__,
+                            (int)(lcp - cl_p + 1));
+                goto err_out;
+            }
+            ll = sg_get_llnum(lcp);
+            if (ll >= 0) {
+                full_pair = true;
+                if (ll > max_nbs) {
+                    sge.num = (uint32_t)max_nbs;
+                    prev_lba = sge.lba;
+                    large_num = ll - max_nbs;
+                    split = true;
+                    j = 1;
+                    continue;
+                }
+                sge.num = (uint32_t)ll;
+            } else {    /* bad or negative number as number_of_blocks */
+                if (b_vb)
+                    pr2serr("%s: bad number at pos %d\n", __func__,
+                            (int)(lcp - cl_p + 1));
+                goto err_out;
+            }
+            sgl.push_back(sge);
+check_for_next:
+            cp = (char *)strchr(lcp, ',');
+            c2p = (char *)strchr(lcp, ' ');
+            if (NULL == cp) {
+                cp = c2p;
+                if (NULL == cp)
+                    break;
+            }
+            if (c2p && (c2p < cp))
+                cp = c2p;
+            lcp = cp + 1;
+        }       /* end of for loop over items in operand */
+        /* other than first pair, expect even number of items */
+        if ((k > 0) && (! full_pair)) {
+            if (b_vb)
+                pr2serr("%s:  expected even number of items: "
+                        "LBA0,NUM0,LBA1,NUM1...\n", __func__);
+            goto err_out;
+        }
+    }
+    return true;
+err_out:
+    if (0 == m_errno)
+        m_errno = SG_LIB_SYNTAX_ERROR;
+    return false;
+}
+
+bool
+scat_gath_list::file2sgl_helper(FILE * fp, const char * fnp, bool def_hex,
+                                bool flexible, bool b_vb)
+{
+    bool bit0;
+    bool pre_addr1 = true;
+    bool pre_hex_seen = false;
+    int in_len, k, j, m, ind;
+    const int max_nbs = MAX_SGL_NUM_VAL;
+    int off = 0;
+    int64_t ll;
+    uint64_t ull, prev_lba;
+    char * lcp;
+    struct scat_gath_elem sge;
+    char line[1024];
+
+    for (j = 0 ; ; ++j) {
+        if (NULL == fgets(line, sizeof(line), fp))
+            break;
+        // could improve with carry_over logic if sizeof(line) too small
+        in_len = strlen(line);
+        if (in_len > 0) {
+            if ('\n' == line[in_len - 1]) {
+                --in_len;
+                line[in_len] = '\0';
+            } else {
+                m_errno = SG_LIB_SYNTAX_ERROR;
+                if (b_vb)
+                    pr2serr("%s: %s: line too long, max %d bytes\n",
+                            __func__, fnp, (int)(sizeof(line) - 1));
+                goto err_out;
+            }
+        }
+        if (in_len < 1)
+            continue;
+        lcp = line;
+        m = strspn(lcp, " \t");
+        if (m == in_len)
+            continue;
+        lcp += m;
+        in_len -= m;
+        if ('#' == *lcp)
+            continue;
+        if (pre_addr1 || pre_hex_seen) {
+            /* Accept lines with leading 'HEX' and ignore as long as there
+             * is one _before_ any LBA,NUM lines in the file. This allows
+             * HEX marked sgls to be concaternated together. */
+            if (('H' == toupper(lcp[0])) && ('E' == toupper(lcp[1])) &&
+                ('X' == toupper(lcp[2]))) {
+                pre_hex_seen = true;
+                if (def_hex)
+                    continue; /* bypass 'HEX' marker line if expecting hex */
+                else {
+                    if (flexible) {
+                        def_hex = true; /* okay, switch to hex parse */
+                        continue;
+                    } else {
+                        pr2serr("%s: %s: 'hex' string detected on line %d, "
+                                "expecting decimal\n", __func__, fnp, j + 1);
+                        m_errno = EINVAL;
+                        goto err_out;
+                    }
+                }
+            }
+        }
+        k = strspn(lcp, "0123456789aAbBcCdDeEfFhHxXbBdDiIkKmMgGtTpP, \t");
+        if ((k < in_len) && ('#' != lcp[k])) {
+            m_errno = EINVAL;
+            if (b_vb)
+                pr2serr("%s: %s: syntax error at line %d, pos %d\n",
+                        __func__, fnp, j + 1, m + k + 1);
+            goto err_out;
+        }
+        for (k = 0; k < 256; ++k) {
+            /* limit parseable items on one line to 256 */
+            if (def_hex) {      /* don't accept negatives or multipliers */
+                if (1 == sscanf(lcp, "%" SCNx64, &ull))
+                    ll = (int64_t)ull;
+                else
+                    ll = -1;    /* use (2**64 - 1) as error flag */
+            } else
+                ll = sg_get_llnum(lcp);
+            if (-1 != ll) {
+                ind = ((off + k) >> 1);
+                bit0 = !! (0x1 & (off + k));
+                if (ind >= SG_SGL_MAX_ELEMENTS) {
+                    m_errno = EINVAL;
+                    if (b_vb)
+                        pr2serr("%s: %s: array length exceeded\n", __func__,
+                                fnp);
+                    goto err_out;
+                }
+                if (bit0) {     /* bit0 set when decoding a NUM */
+                    if (ll < 0) {
+                        m_errno = EINVAL;
+                        if (b_vb)
+                            pr2serr("%s: %s: bad number in line %d, at pos "
+                                    "%d\n", __func__, fnp, j + 1,
+                                    (int)(lcp - line + 1));
+                        goto err_out;
+                    }
+                    if (ll > max_nbs) {
+                        int h = 1;
+
+                        /* split up this elem into multiple, smaller elems */
+                        do {
+                            sge.num = (uint32_t)max_nbs;
+                            prev_lba = sge.lba;
+                            sgl.push_back(sge);
+                            sge.lba = prev_lba + (uint64_t)max_nbs;
+                            ++h;
+                            off += 2;
+                            ll -= max_nbs;
+                        } while (ll > max_nbs);
+                        if (b_vb)
+                            pr2serr("%s: split large sg elem into %d "
+                                    "elements\n", __func__, h);
+                    }
+                    sge.num = (uint32_t)ll;
+                    sgl.push_back(sge);
+                } else {        /* bit0 clear when decoding a LBA */
+                    if (pre_addr1)
+                        pre_addr1 = false;
+                    sge.lba = (uint64_t)ll;
+                }
+            } else {    /* failed to decode number on line */
+                if ('#' == *lcp) { /* numbers before #, rest of line comment */
+                    --k;
+                    break;      /* goes to next line */
+                }
+                m_errno = EINVAL;
+                if (b_vb)
+                    pr2serr("%s: %s: error in line %d, at pos %d\n",
+                            __func__, fnp, j + 1, (int)(lcp - line + 1));
+                goto err_out;
+            }
+            lcp = strpbrk(lcp, " ,\t#");
+            if ((NULL == lcp) || ('#' == *lcp))
+                break;
+            lcp += strspn(lcp, " ,\t");
+            if ('\0' == *lcp)
+                break;
+        }       /* <<< end of for(k < 256) loop */
+        off += (k + 1);
+    }   /* <<< end of for loop, one iteration per line */
+    /* allow one items, but not higher odd number of items */
+    if ((off > 1) && (0x1 & off)) {
+        m_errno = EINVAL;
+        if (b_vb)
+            pr2serr("%s: %s: expect even number of items: "
+                    "LBA0,NUM0,LBA1,NUM1...\n", __func__, fnp);
+        goto err_out;
+    }
+    clearerr(fp);    /* even EOF on first pass needs this before rescan */
+    return true;
+err_out:
+    clearerr(fp);
+    return false;
+}
+
+/* Read numbers from filename (or stdin), line by line (comma (or (single)
+ * space) separated list); places starting_LBA,number_of_block pairs in an
+ * array of scat_gath_elem elements pointed to by the returned value. If
+ * this fails NULL is returned and an error number is written to errp (if it
+ * is non-NULL). Assumed decimal (and may have suffix multipliers) when
+ * def_hex==false; if a number is prefixed by '0x', '0X' or contains trailing
+ * 'h' or 'H' that denotes a hex number. When def_hex==true all numbers are
+ * assumed to be hex (ignored '0x' prefixes and 'h' suffixes) and multiplers
+ * are not permitted. Heap allocates an array just big enough to hold all
+ * elements if the file is countable. Pipes and stdin are not considered
+ * countable. In the non-countable case an array of MAX_FIXED_SGL_ELEMS
+ * elements is pre-allocated; if it is exceeded sg_convert_errno(EDOM) is
+ * placed in *errp (if it is non-NULL). One of the first actions is to write
+ * 0 to *errp (if it is non-NULL) so the caller does not need to zero it
+ * before calling. */
+bool
+scat_gath_list::load_from_file(const char * file_name, bool def_hex,
+                               bool flexible, bool b_vb)
+{
+    bool have_stdin;
+    bool have_err = false;
+    FILE * fp;
+    const char * fnp;
+
+    have_stdin = ((1 == strlen(file_name)) && ('-' == file_name[0]));
+    if (have_stdin) {
+        fp = stdin;
+        fnp = "<stdin>";
+    } else {
+        fnp = file_name;
+        fp = fopen(fnp, "r");
+        if (NULL == fp) {
+            m_errno = errno;
+            if (b_vb)
+                pr2serr("%s: opening %s: %s\n", __func__, fnp,
+                        safe_strerror(m_errno));
+            return false;
+        }
+    }
+    if (! file2sgl_helper(fp, fnp, def_hex, flexible, b_vb))
+        have_err = true;
+    if (! have_stdin)
+        fclose(fp);
+    return have_err ? false : true;
+}
+
+const char *
+scat_gath_list::linearity_as_str() const
+{
+    switch (linearity) {
+    case SGL_LINEAR:
+        return "linear";
+    case SGL_MONOTONIC:
+        return "monotonic";
+    case SGL_MONO_OVERLAP:
+        return "monotonic, overlapping";
+    case SGL_NON_MONOTONIC:
+        return "non-monotonic";
+    default:
+        return "unknown";
+    }
+}
+
+void
+scat_gath_list::set_weaker_linearity(enum sgl_linearity_e lin)
+{
+    int i_lin = (int)lin;
+
+    if (i_lin > (int)linearity)
+        linearity = lin;
+}
+
+/* id_str may be NULL (if so replace by "unknown"), present to enhance verbose
+ * output. */
+void
+scat_gath_list::dbg_print(bool skip_meta, const char * id_str, bool to_stdout,
+                          bool show_sgl) const
+{
+    int k;
+    int num = sgl.size();
+    const char * caller = id_str ? id_str : "unknown";
+    FILE * fp = to_stdout ? stdout : stderr;
+
+    if (! skip_meta) {
+        fprintf(fp, "%s: elems=%d, sgl %spresent, linearity=%s\n",
+                caller, num, (sgl.empty() ? "not " : ""),
+                linearity_as_str());
+        fprintf(fp, "  sum=%" PRId64 ", sum_hard=%s lowest=0x%" PRIx64
+                ", high_lba_p1=", sum, (sum_hard ? "true" : "false"),
+                lowest_lba);
+        fprintf(fp, "0x%" PRIx64 "\n", high_lba_p1);
+    }
+    fprintf(fp, "  >> %s scatter gather list (%d element%s):\n", caller, num,
+            (num == 1 ? "" : "s"));
+    if (show_sgl) {
+        for (k = 0; k < num; ++k) {
+            const struct scat_gath_elem & sge = sgl[k];
+
+            fprintf(fp, "    lba: 0x%" PRIx64 ", number: 0x%" PRIx32,
+                    sge.lba, sge.num);
+            if (sge.lba > 0)
+                fprintf(fp, " [next lba: 0x%" PRIx64 "]", sge.lba + sge.num);
+            fprintf(fp, "\n");
+        }
+    }
+}
+
+/* Assumes sgl array (vector) is setup. The other fields in this object are
+ * set by analyzing sgl in a single pass. The fields that are set are:
+ * fragmented, lowest_lba, high_lba_p1, monotonic, overlapping, sum and
+ * sum_hard. Degenerate elements (i.e. those with 0 blocks) are ignored apart
+ * from when one is last which makes sum_hard false and its LBA becomes
+ * high_lba_p1 if it is the highest in the list. An empty sgl is equivalent
+ * to a 1 element list with [0, 0], so sum_hard==false, monit==true,
+ * fragmented==false and overlapping==false . id_str may be NULL, present
+ * to enhance verbose output. */
+void
+scat_gath_list::sum_scan(const char * id_str, bool show_sgl, bool b_vb)
+{
+    bool degen = false;
+    bool first = true;
+    bool regular = true;        /* no overlapping segments detected */
+    int k;
+    int elems = sgl.size();
+    uint32_t prev_num, t_num;
+    uint64_t prev_lba, t_lba, low, high, end;
+
+    sum = 0;
+    for (k = 0, low = 0, high = 0; k < elems; ++k) {
+        const struct scat_gath_elem & sge = sgl[k];
+
+        degen = false;
+        t_num = sge.num;
+        if (0 == t_num) {
+            degen = true;
+            if (! first)
+                continue;       /* ignore degen element that not first */
+        }
+        if (first) {
+            low = sge.lba;
+            sum = t_num;
+            high = sge.lba + sge.num;
+            first = false;
+        } else {
+            t_lba = sge.lba;
+            if ((prev_lba + prev_num) != t_lba)
+                set_weaker_linearity(SGL_MONOTONIC);
+            sum += t_num;
+            end = t_lba + t_num;
+            if (end > high)
+                high = end;     /* high is one plus highest LBA */
+            if (prev_lba < t_lba)
+                ;
+            else if (prev_lba == t_lba) {
+                if (prev_num > 0) {
+                    set_weaker_linearity(SGL_MONO_OVERLAP);
+                    break;
+                }
+            } else {
+                low = t_lba;
+                set_weaker_linearity(SGL_NON_MONOTONIC);
+                break;
+            }
+            if (regular) {
+                if ((prev_lba + prev_num) > t_lba)
+                    regular = false;
+            }
+        }
+        prev_lba = sge.lba;
+        prev_num = sge.num;
+    }           /* end of for loop while still elements and monot true */
+
+    if (k < elems) {    /* only here if above breaks are taken */
+        prev_lba = t_lba;
+        ++k;
+        for ( ; k < elems; ++k) {
+            const struct scat_gath_elem & sge = sgl[k];
+
+            degen = false;
+            t_lba = sge.lba;
+            t_num = sge.num;
+            if (0 == t_num) {
+                degen = true;
+                continue;
+            }
+            sum += t_num;
+            end = t_lba + t_num;
+            if (end > high)
+                high = end;
+            if (prev_lba > t_lba) {
+                if (t_lba < low)
+                    low = t_lba;
+            }
+            prev_lba = t_lba;
+        }
+    } else
+        if (! regular)
+            set_weaker_linearity(SGL_MONO_OVERLAP);
+
+    lowest_lba = low;
+    if (degen && (elems > 0)) { /* last element always impacts high_lba_p1 */
+        t_lba = sgl[elems - 1].lba;
+        high_lba_p1 = (t_lba > high) ? t_lba : high;
+    } else
+        high_lba_p1 = high;
+    sum_hard = (elems > 0) ? ! degen : false;
+    if (b_vb)
+        dbg_print(false, id_str, false, show_sgl);
+}
+
+/* Usually will append (or add to start if empty) sge unless 'extra_blks'
+ * exceeds MAX_SGL_NUM_VAL. In that case multiple sge_s are added with
+ * sge.num = MAX_SGL_NUM_VAL or less (for final sge) until extra_blks is
+ * exhausted. Returns new size of scatter gather list. */
+int
+scat_gath_list::append_1or(int64_t extra_blks, int64_t start_lba)
+{
+    int o_num = sgl.size();
+    const int max_nbs = MAX_SGL_NUM_VAL;
+    int64_t cnt = 0;
+    struct scat_gath_elem sge;
+
+    if ((extra_blks <= 0) || (start_lba < 0))
+        return o_num;       /* nothing to do */
+    if ((o_num > 0) && (! sum_hard)) {
+        sge = sgl[o_num - 1];   /* assume sge.num==0 */
+        if (sge.lba == (uint64_t)start_lba) {
+            if (extra_blks <= max_nbs)
+                sge.num = extra_blks;
+            else
+                sge.num = max_nbs;
+            sgl[o_num - 1] = sge;
+            cnt = sge.num;
+            sum += cnt;
+            sum_hard = true;
+            if (cnt <= extra_blks) {
+                high_lba_p1 = sge.lba + cnt;
+                return o_num;
+            }
+        }
+    } else if (0 == o_num)
+        lowest_lba = start_lba;
+
+    for ( ; cnt < extra_blks; cnt += max_nbs) {
+        sge.lba = start_lba + cnt;
+        if ((extra_blks - cnt) <= max_nbs)
+            sge.num = extra_blks - cnt;
+        else
+            sge.num = max_nbs;
+        sgl.push_back(sge);
+        sum += sge.num;
+    }           /* always loops at least once */
+    sum_hard = true;
+    high_lba_p1 = sge.lba + sge.num;
+    return sgl.size();
+}
+
+int
+scat_gath_list::append_1or(int64_t extra_blks)
+{
+    int o_num = sgl.size();
+    if (o_num < 1)
+        return append_1or(extra_blks, 0);
+
+    struct scat_gath_elem sge = sgl[o_num - 1];
+    return append_1or(extra_blks, sge.lba + sge.num);
+}
+
+bool
+sgls_eq_off(const scat_gath_list & left, int l_e_ind, int l_blk_off,
+            const scat_gath_list & right, int r_e_ind, int r_blk_off,
+            bool allow_partial)
+{
+    int lrem, rrem;
+    int lelems = left.sgl.size();
+    int relems = right.sgl.size();
+
+    while ((l_e_ind < lelems) && (r_e_ind < relems)) {
+        if ((left.sgl[l_e_ind].lba + l_blk_off) !=
+            (right.sgl[r_e_ind].lba + r_blk_off))
+            return false;
+        lrem = left.sgl[l_e_ind].num - l_blk_off;
+        rrem = right.sgl[r_e_ind].num - r_blk_off;
+        if (lrem == rrem) {
+            ++l_e_ind;
+            l_blk_off = 0;
+            ++r_e_ind;
+            r_blk_off = 0;
+        } else if (lrem < rrem) {
+            ++l_e_ind;
+            l_blk_off = 0;
+            r_blk_off += lrem;
+        } else {
+            ++r_e_ind;
+            r_blk_off = 0;
+            l_blk_off += rrem;
+        }
+    }
+    if ((l_e_ind >= lelems) && (r_e_ind >= relems))
+        return true;
+    return allow_partial;
+}
+
+/* If bad arguments returns -1, otherwise returns the lowest LBA in *sglp .
+ * If no elements considered returns 0. If ignore_degen is true than
+ * ignores all elements with sge.num zero unless always_last is also
+ * true in which case the last element is always considered. */
+int64_t
+scat_gath_list::get_lowest_lba(bool ignore_degen, bool always_last) const
+{
+    int k;
+    const int num_elems = sgl.size();
+    bool some = (num_elems > 0);
+    int64_t res = INT64_MAX;
+
+    for (k = 0; k < num_elems; ++k) {
+        if ((0 == sgl[k].num) && ignore_degen)
+            continue;
+        if ((int64_t)sgl[k].lba < res)
+            res = sgl[k].lba;
+    }
+    if (always_last && some) {
+        if ((int64_t)sgl[k - 1].lba < res)
+            res = sgl[k - 1].lba;
+    }
+    return (INT64_MAX == res) ? 0 : res;
+}
+
+/* Returns >= 0 if sgl can be simplified to a single LBA. So an empty sgl
+ * will return 0; a one element sgl will return its LBA. A multiple element
+ * sgl only returns the first element's LBA (that is not degenerate) if the
+ * sgl is monotonic and not fragmented. In the extreme case takes last
+ * element's LBA if all prior elements are degenerate. Else returns -1 .
+ * Assumes sgl_sum_scan() has been called. */
+int64_t
+scat_gath_list::get_low_lba_from_linear() const
+{
+    const int num_elems = sgl.size();
+    int k;
+
+    if (num_elems <= 1)
+        return (1 == num_elems) ? sgl[0].lba : 0;
+    else {
+        if (linearity == SGL_LINEAR) {
+            for (k = 0; k < (num_elems - 1); ++k) {
+                if (sgl[k].num > 0)
+                    return sgl[k].lba;
+            }
+            /* take last element's LBA if all earlier are degenerate */
+            return sgl[k].lba;
+        } else
+            return -1;
+    }
+}
+
+bool
+scat_gath_list::is_pipe_suitable() const
+{
+    return (lowest_lba == 0) && (linearity == SGL_LINEAR);
+}
+
+scat_gath_iter::scat_gath_iter(const scat_gath_list & parent)
+    : sglist(parent), it_el_ind(0), it_blk_off(0), blk_idx(0)
+{
+    int elems = sglist.num_elems();
+
+    if (elems > 0)
+        extend_last = (0 == sglist.sgl[elems - 1].num);
+}
+
+bool
+scat_gath_iter::set_by_blk_idx(int64_t _blk_idx)
+{
+    bool first;
+    int k;
+    const int elems = sglist.sgl.size();
+    const int last_ind = elems - 1;
+    uint32_t num;
+    int64_t bc = _blk_idx;
+
+    if (bc < 0)
+        return false;
+
+    if (bc == blk_idx)
+        return true;
+    else if (bc > blk_idx) {
+        k = it_el_ind;
+        bc -= blk_idx;
+    } else
+        k = 0;
+    for (first = true; k < elems; ++k, first = false) {
+        num = ((k == last_ind) && extend_last) ? MAX_SGL_NUM_VAL :
+                                                 sglist.sgl[k].num;
+        if (first) {
+            if ((int64_t)(num - it_blk_off) < bc)
+                bc -= (num - it_blk_off);
+            else {
+                it_blk_off = bc + it_blk_off;
+                break;
+            }
+        } else {
+            if ((int64_t)num < bc)
+                bc -= num;
+            else {
+                it_blk_off = (uint32_t)bc;
+                break;
+            }
+        }
+    }
+    it_el_ind = k;
+    blk_idx = _blk_idx;
+
+    if (k < elems)
+        return true;
+    else if ((k == elems) && (0 == it_blk_off))
+        return true;    /* EOL */
+    else
+        return false;
+}
+
+/* Given a blk_count, the iterator (*iter_p) is moved toward the EOL.
+ * Returns true unless blk_count takes iterator two or more past the last
+ * element. So if blk_count takes the iterator to the EOL, this function
+ * returns true. Takes into account iterator's extend_last flag. */
+bool
+scat_gath_iter::add_blks(uint64_t blk_count)
+{
+    bool first;
+    int k;
+    const int elems = sglist.sgl.size();
+    const int last_ind = elems - 1;
+    uint32_t num;
+    uint64_t bc = blk_count;
+
+    if (0 == bc)
+        return true;
+    for (first = true, k = it_el_ind; k < elems; ++k, first = false) {
+        num = ((k == last_ind) && extend_last) ? MAX_SGL_NUM_VAL :
+                                                 sglist.sgl[k].num;
+        if (first) {
+            if ((uint64_t)(num - it_blk_off) < bc)
+                bc -= (num - it_blk_off);
+            else {
+                it_blk_off = bc + it_blk_off;
+                break;
+            }
+        } else {
+            if ((uint64_t)num < bc)
+                bc -= num;
+            else {
+                it_blk_off = (uint32_t)bc;
+                break;
+            }
+        }
+    }
+    it_el_ind = k;
+    blk_idx += blk_count;
+
+    if (k < elems)
+        return true;
+    else if ((k == elems) && (0 == it_blk_off))
+        return true;    /* EOL */
+    else
+        return false;
+}
+
+/* Move the iterator from its current position (which may be to EOL) towards
+ * the start of the sgl (i.e. backwards) for blk_count blocks. Returns true
+ * if iterator is valid after the move, else returns false. N.B. if false is
+ * returned, then the iterator is invalid and may need to set it to a valid
+ * value. */
+bool
+scat_gath_iter::sub_blks(uint64_t blk_count)
+{
+    bool first;
+    int k = it_el_ind;
+    uint64_t bc = 0;
+    const uint64_t orig_blk_count = blk_count;
+
+    if (0 == blk_count)
+        return true;
+    for (first = true; k >= 0; --k) {
+        if (first) {
+            if (blk_count > (uint64_t)it_blk_off)
+                blk_count -= it_blk_off;
+            else {
+                it_blk_off -= blk_count;
+                break;
+            }
+            first = false;
+        } else {
+            uint32_t off = sglist.sgl[k].num;
+
+            bc = blk_count;
+            if (bc > (uint64_t)off)
+                blk_count -= off;
+            else {
+                bc = off - bc;
+                break;
+            }
+        }
+    }
+    if (k < 0) {
+        blk_idx = 0;
+        return false;           /* bad situation */
+    }
+    if ((int64_t)orig_blk_count <= blk_idx)
+        blk_idx -= orig_blk_count;
+    else
+        blk_idx = 0;
+    it_el_ind = k;
+    if (! first)
+        it_blk_off = (uint32_t)bc;
+    return true;
+}
+
+/* Returns LBA referred to by iterator if valid or returns SG_LBA_INVALID
+ * (-1) if at end or invalid. */
+int64_t
+scat_gath_iter::current_lba() const
+{
+    const int elems = sglist.sgl.size();
+    int64_t res = SG_LBA_INVALID; /* for at end or invalid (-1) */
+
+    if (it_el_ind < elems) {
+        struct scat_gath_elem sge = sglist.sgl[it_el_ind];
+
+        if ((uint32_t)it_blk_off < sge.num)
+            return sge.lba + it_blk_off;
+        else if (((uint32_t)it_blk_off == sge.num) &&
+                 ((it_el_ind + 1) < elems)) {
+            class scat_gath_iter iter(*this);
+
+            ++iter.it_el_ind;
+            iter.it_blk_off = 0;
+            /* worst case recursion will stop at end of sgl */
+            return iter.current_lba();
+        }
+    }
+    return res;
+}
+
+int64_t
+scat_gath_iter::current_lba_rem_num(int & rem_num) const
+{
+    const int elems = sglist.sgl.size();
+    int64_t res = SG_LBA_INVALID; /* for at end or invalid (-1) */
+
+    if (it_el_ind < elems) {
+        struct scat_gath_elem sge = sglist.sgl[it_el_ind];
+
+        if ((uint32_t)it_blk_off < sge.num) {
+            rem_num = sge.num - it_blk_off;
+            return sge.lba + it_blk_off;
+        } else if (((uint32_t)it_blk_off == sge.num) &&
+                 ((it_el_ind + 1) < elems)) {
+            class scat_gath_iter iter(*this);
+
+            ++iter.it_el_ind;
+            iter.it_blk_off = 0;
+            /* worst case recursion will stop at end of sgl */
+            return iter.current_lba_rem_num(rem_num);
+        }
+    }
+    rem_num = -1;
+    return res;
+}
+
+struct scat_gath_elem
+scat_gath_iter::current_elem() const
+{
+    const int elems = sglist.sgl.size();
+    struct scat_gath_elem sge;
+
+    sge.make_bad();
+    if (it_el_ind < elems)
+        return sglist.sgl[it_el_ind];
+    return sge;
+}
+
+/* Returns true of no sgl or sgl is at the end [elems, 0], otherwise it
+ * returns false. */
+bool
+scat_gath_iter::at_end() const
+{
+    const int elems = sglist.sgl.size();
+
+    return ((0 == elems) || ((it_el_ind == elems) && (0 == it_blk_off)));
+}
+
+/* Returns true if associated iterator is monotonic (increasing) and not
+ * fragmented. Empty sgl and single element degenerate considered linear.
+ * Assumes sgl_sum_scan() has been called on sgl. */
+bool
+scat_gath_iter::is_sgl_linear() const
+{
+    return sglist.linearity == SGL_LINEAR;
+}
+
+int
+scat_gath_iter::linear_for_n_blks(int max_n) const
+{
+    int k, rem;
+    const int elems = sglist.sgl.size();
+    uint64_t prev_lba;
+    struct scat_gath_elem sge;
+
+    if (at_end() || (max_n <= 0))
+        return 0;
+    sge = sglist.sgl[it_el_ind];
+    rem = (int)sge.num - it_blk_off;
+    if (max_n <= rem)
+        return max_n;
+    prev_lba = sge.lba + sge.num;
+    for (k = it_el_ind + 1; k < elems; ++k) {
+        sge = sglist.sgl[k];
+        if (sge.lba != prev_lba)
+            return rem;
+        rem += sge.num;
+        if (max_n <= rem)
+            return max_n;
+        prev_lba = sge.lba + sge.num;
+    }
+    return rem;
+}
+
+/* id_str may be NULL (if so replace by "unknown"), present to enhance verbose
+ * output. */
+void
+scat_gath_iter::dbg_print(const char * id_str, bool to_stdout,
+                          int verbose) const
+{
+    const char * caller = id_str ? id_str : "unknown";
+    FILE * fp = to_stdout ? stdout : stderr;
+
+    fprintf(fp, "%s: it_el_ind=%d, it_blk_off=%d, blk_idx=%" PRId64 "\n",
+            caller, it_el_ind, it_blk_off, blk_idx);
+    fprintf(fp, "  extend_last=%d\n", extend_last);
+    if (verbose)
+        sglist.dbg_print(false, " iterator's", to_stdout, verbose > 1);
+}
+
+/* Calculates difference between iterators, logically: res <-- lhs - rhs
+ * Checks that lhsp and rhsp have same underlying sgl, if not returns
+ * INT_MIN. Assumes iterators close enough for result to lie in range
+ * from (-INT_MAX) to INT_MAX (inclusive). */
+int
+diff_between_iters(const struct scat_gath_iter & left,
+                   const struct scat_gath_iter & right)
+{
+    int res, k, r_e_ind, l_e_ind;
+
+    if (&left.sglist != &right.sglist) {
+        pr2serr("%s: bad args\n", __func__);
+        return INT_MIN;
+    }
+    r_e_ind = right.it_el_ind;
+    l_e_ind = left.it_el_ind;
+    if (l_e_ind < r_e_ind) { /* so difference will be negative */
+        res = diff_between_iters(right, left);        /* cheat */
+        if (INT_MIN == res)
+            return res;
+        return -res;
+    } else if (l_e_ind == r_e_ind)
+        return (int)left.it_blk_off - (int)right.it_blk_off;
+    /* (l_e_ind > r_e_ind) so (lhs > rhs) */
+    res = (int)right.sglist.sgl[r_e_ind].num - right.it_blk_off;
+    for (k = 1; (r_e_ind + k) < l_e_ind; ++k) {
+        // pr2serr("%s: k=%d, res=%d, num=%d\n", __func__, k, res,
+        //         (int)right.sglist.sgl[r_e_ind + k].num);
+        res += (int)right.sglist.sgl[r_e_ind + k].num;
+    }
+    res += left.it_blk_off;
+    // pr2serr("%s: at exit res=%d\n", __func__, res);
+    return res;
+}
+
+/* Compares from the current iterator positions of left and left until
+ * the shorter list is exhausted. Returns false on the first inequality.
+ * If no inequality and both remaining lists are same length then returns
+ * true. If no inequality but remaining lists differ in length then returns
+ * allow_partial. */
+bool
+sgls_eq_from_iters(const struct scat_gath_iter & left,
+                   const struct scat_gath_iter & right,
+                   bool allow_partial)
+{
+    return sgls_eq_off(left.sglist, left.it_el_ind, left.it_blk_off,
+                       right.sglist, right.it_el_ind, right.it_blk_off,
+                       allow_partial);
+}
diff --git a/testing/sg_scat_gath.h b/testing/sg_scat_gath.h
new file mode 100644
index 0000000..2b17fb3
--- /dev/null
+++ b/testing/sg_scat_gath.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2014-2020 Douglas Gilbert.
+ * All rights reserved.
+ * Use of this source code is governed by a BSD-style
+ * license that can be found in the BSD_LICENSE file.
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+// C headers
+#include <stdio.h>
+#include <stdint.h>
+#define __STDC_FORMAT_MACROS 1
+#include <inttypes.h>
+
+// C++ headers
+#include <vector>
+
+// This file is a C++ header file
+
+
+#define SG_SGL_MAX_ELEMENTS 16384
+
+#define SG_COUNT_INDEFINITE (-1)
+#define SG_LBA_INVALID SG_COUNT_INDEFINITE
+
+/* Sizing matches largest SCSI READ and WRITE commands plus those of Unix
+ * read(2)s and write(2)s. User can give larger than 31 bit 'num's but they
+ * are split into several consecutive elements. */
+struct scat_gath_elem {
+    uint64_t lba;       /* of start block */
+    uint32_t num;       /* number of blocks from and including start block */
+
+    void make_bad() { lba = UINT64_MAX; num = UINT32_MAX; }
+    bool is_bad() const { return (lba == UINT64_MAX && num == UINT32_MAX); }
+};
+
+/* Consider "linearity" as a scatter gather list property. Elements of this
+ * of from the strongest form to the weakest. */
+enum sgl_linearity_e {
+    SGL_LINEAR = 0,     /* empty list and 0,0 considered linear */
+    SGL_MONOTONIC,      /* since not linear, implies holes */
+    SGL_MONO_OVERLAP,   /* monotonic but same LBA in two or more elements */
+    SGL_NON_MONOTONIC   /* weakest */
+};
+
+
+/* Holds one scatter gather list and its associated metadata */
+class scat_gath_list {
+public:
+    scat_gath_list() : linearity(SGL_LINEAR), sum_hard(false), m_errno(0),
+        high_lba_p1(0), lowest_lba(0), sum(0) { }
+
+    scat_gath_list(const scat_gath_list &) = default;
+    scat_gath_list & operator=(const scat_gath_list &) = default;
+    ~scat_gath_list() = default;
+
+    bool empty() const;
+    bool empty_or_00() const;
+    int num_elems() const;
+    int64_t get_lowest_lba(bool ignore_degen, bool always_last) const;
+    int64_t get_low_lba_from_linear() const;
+    bool is_pipe_suitable() const;
+
+    friend bool sgls_eq_off(const scat_gath_list &left, int l_e_ind,
+                            int l_blk_off,
+                            const scat_gath_list &right, int r_e_ind,
+                            int r_blk_off, bool allow_partial);
+
+    bool load_from_cli(const char * cl_p, bool b_vb);
+    bool load_from_file(const char * file_name, bool def_hex, bool flexible,
+                        bool b_vb);
+    int append_1or(int64_t extra_blks, int64_t start_lba);
+    int append_1or(int64_t extra_blks);
+
+    void dbg_print(bool skip_meta, const char * id_str, bool to_stdout,
+                   bool show_sgl) const;
+
+    /* calculates and sets following bool-s and int64_t-s */
+    void sum_scan(const char * id_str, bool show_sgl, bool b_verbose);
+
+    void set_weaker_linearity(enum sgl_linearity_e lin);
+    enum sgl_linearity_e linearity;
+    const char * linearity_as_str() const;
+
+    bool sum_hard;      /* 'num' in last element of 'sgl' is > 0 */
+    int m_errno;        /* OS failure errno */
+    int64_t high_lba_p1;  /* highest LBA plus 1, next write from and above */
+    int64_t lowest_lba; /* initialized to 0 */
+    int64_t sum;        /* of all 'num' elements in 'sgl' */
+
+    friend int diff_between_iters(const struct scat_gath_iter & left,
+                                  const struct scat_gath_iter & right);
+
+private:
+    friend class scat_gath_iter;
+
+    bool file2sgl_helper(FILE * fp, const char * fnp, bool def_hex,
+                         bool flexible, bool b_vb);
+
+    std::vector<scat_gath_elem> sgl;  /* an array on heap [0..num_elems()) */
+};
+
+
+class scat_gath_iter {
+public:
+    scat_gath_iter(const scat_gath_list & my_scat_gath_list);
+    scat_gath_iter(const scat_gath_iter & src) = default;
+    scat_gath_iter&  operator=(const scat_gath_iter&) = delete;
+    ~scat_gath_iter() = default;
+
+    int64_t current_lba() const;
+    int64_t current_lba_rem_num(int & rem_num) const;
+    struct scat_gath_elem current_elem() const;
+    bool at_end() const;
+    bool is_sgl_linear() const; /* the whole list */
+    int linear_for_n_blks(int max_n) const;
+
+    bool set_by_blk_idx(int64_t _blk_idx);
+    /* add/sub blocks return true if they reach EOL, else false */
+    bool add_blks(uint64_t blk_count);
+    bool sub_blks(uint64_t blk_count);
+
+    void dbg_print(const char * id_str, bool to_stdout, int verbose) const;
+
+    friend int diff_between_iters(const struct scat_gath_iter & left,
+                                  const struct scat_gath_iter & right);
+
+    friend bool sgls_eq_from_iters(const struct scat_gath_iter & left,
+                                   const struct scat_gath_iter & right,
+                                   bool allow_partial);
+
+private:
+    const scat_gath_list &sglist;
+
+    /* dual representation: either it_el_ind,it_blk_off or blk_idx */
+    int it_el_ind;      /* refers to sge==sglist[it_el_ind] */
+    int it_blk_off;     /* refers to LBA==(sge.lba + it_blk_off) */
+    int64_t blk_idx;    /* in range: [0 .. sglist.sum) */
+    bool extend_last;
+};
diff --git a/testing/sg_tst_ioctl.c b/testing/sg_tst_ioctl.c
index 54e0d23..1fd9133 100644
--- a/testing/sg_tst_ioctl.c
+++ b/testing/sg_tst_ioctl.c
@@ -60,14 +60,14 @@
  * later of the Linux sg driver.  */
 
 
-static const char * version_str = "Version: 1.18  20200716";
+static const char * version_str = "Version: 1.18  20200719";
 
 #define INQ_REPLY_LEN 128
 #define INQ_CMD_LEN 6
 #define SDIAG_CMD_LEN 6
 #define SENSE_BUFFER_LEN 96
 
-#define EBUFF_SZ 256
+#define EBUFF_SZ 512
 
 #ifndef SG_FLAG_Q_AT_TAIL
 #define SG_FLAG_Q_AT_TAIL 0x10
@@ -105,6 +105,9 @@
 static int reserve_buff_sz = DEF_RESERVE_BUFF_SZ;
 static int num_mrqs = 0;
 static int num_sgnw = 0;
+static int dname_current = 0;
+static int dname_last = 0;
+static int dname_pos = 0;
 static int verbose = 0;
 
 static const char * relative_cp = NULL;
@@ -119,16 +122,18 @@
            "                    [-m=MRQS[,I|S]] [-M] [-n] [-o] [-r=SZ] "
            "[-s=SEC]\n"
            "                    [-S] [-t] [-T=NUM] [-v] [-V] [-w]\n"
-           "                    <sg_device> [<sg_device2>]\n"
+           "                    <sg_device>[-<num>] [<sg_device2>]\n"
            " where:\n"
+           "      -3      use sg v3 interface (def: sg v4 if available)\n"
            "      -c      timestamp when sg driver created <sg_device>\n"
            "      -f      fork and test share between processes\n"
            "      -h      help: print usage message then exit\n"
            "      -I=0|1    iterator test of mid-level; 0: unlocked, 1: "
            "locked\n"
            "                does test -T=NUM times, outputs duration\n"
-           "      -J=0|1    object walk (to root); 0: without ptr; 1: with "
-           "ptr\n"
+           "      -J=0|1    object walk up then 2 lookups; 0: no logging; "
+           "1: log\n"
+           "                up-scan once per 1000 iterations\n"
            "      -l=Q_LEN    queue length, between 1 and 511 (def: 16)\n"
            "      -m=MRQS[,I|S]    test multi-req, MRQS number to do; if "
            "the letter\n"
@@ -151,7 +156,14 @@
            "                ioctl(SG_GET_NUM_WAITING); then exit\n"
            "      -v    increase verbosity of output\n"
            "      -V    print version string then exit\n"
-           "      -w    write (submit) only then exit\n");
+           "      -w    write (submit) only then exit\n\n");
+    printf("There are various groups of options for different tests. The "
+           "get_num_waiting\ngroup needs '-T=NUM' given. When '-I=0|1' is "
+           "also given then an object tree\niterator test is done NUM "
+           "times. If instead '-J=0|1' is given then an\nobject tree "
+           "traversal (up/down) is done 10,000 times (and NUM is\n"
+           "ignored).\n"
+          );
 }
 
 static void
@@ -756,10 +768,13 @@
 int
 main(int argc, char * argv[])
 {
-    bool done;
+    bool done, is_first;
     bool nw_given = false;
-    int sg_fd, k, ok, pack_id, num_waiting;
+    bool has_dname_range = false;
+    int k, ok, pack_id, num_waiting;
     int res = 0;
+    int sum_nw = 0;
+    int sg_fd = -1;
     int sg_fd2 = -1;
     int sock = -1;
     uint8_t inq_cdb[INQ_CMD_LEN] =
@@ -770,9 +785,11 @@
     sg_io_hdr_t io_hdr[MAX_Q_LEN];
     sg_io_hdr_t rio_hdr;
     char ebuff[EBUFF_SZ];
+    char dname[256];
     uint8_t sense_buffer[MAX_Q_LEN][SENSE_BUFFER_LEN];
     const char * second_fname = NULL;
     const char * cp;
+    char * chp;
     struct sg_scsi_id ssi;
 
 
@@ -900,7 +917,7 @@
             break;
         }
     }
-    if (iterator_test >= 0)
+    if ((iterator_test >= 0) || (object_walk_test >= 0))
         nw_given = false;
 
     if (show_size_value) {
@@ -953,58 +970,87 @@
         usage();
         return 1;
     }
+    memset(dname, 0, sizeof(dname));
+    if (strlen(file_name) > 255) {
+        fprintf(stderr, "file_name too long\n");
+        goto out;
+    }
+    strncpy(dname, file_name, sizeof(dname) - 1);
+    if ((chp = strchr(dname, '-'))) {
+        if (1 != sscanf(chp + 1, "%d", &dname_last)) {
+            fprintf(stderr, "can't code number after '-' in file_name\n");
+            goto out;
+        }
+        *chp = '\0';
+        --chp;
+        while (isdigit(*chp))
+            --chp;
+        ++chp;
+        if (1 != sscanf(chp, "%d", &dname_current)) {
+            fprintf(stderr, "can't code number before '-' in file_name\n");
+            goto out;
+        }
+        *chp = '\0';
+        has_dname_range = true;
+        dname_pos = strlen(dname);
+    }
+    is_first = true;
+
+dname_range_loop:
+    if (has_dname_range)
+        sprintf(dname + dname_pos, "%d", dname_current);
 
     /* An access mode of O_RDWR is required for write()/read() interface */
-    if ((sg_fd = open(file_name, O_RDWR)) < 0) {
-        snprintf(ebuff, EBUFF_SZ,
-                 "error opening file: %s", file_name);
+    if ((sg_fd = open(dname, O_RDWR)) < 0) {
+        snprintf(ebuff, EBUFF_SZ, "error opening file: %s", dname);
         perror(ebuff);
         return 1;
     }
     if (verbose)
         fprintf(stderr, "opened given file: %s successfully, fd=%d\n",
-                file_name, sg_fd);
+                dname, sg_fd);
 
     if (ioctl(sg_fd, SG_GET_VERSION_NUM, &sg_drv_ver_num) < 0) {
         pr2serr("ioctl(SG_GET_VERSION_NUM) failed, errno=%d %s\n", errno,
                 strerror(errno));
         goto out;
     }
-    printf("Linux sg driver version: %d\n", sg_drv_ver_num);
-
-    if (object_walk_test >= 0) {
-        k = (object_walk_test == 0) ? -999 : 999;
-        if (ioctl(sg_fd, SG_SET_DEBUG, &k) < 0) {
-            res = errno;
-            fprintf(stderr, "%s%d: ioctl(SG_SET_DEBUG) failed errno=%d\n",
-                    relative_cp, k, res);
-        }
-        goto out;
-    }
+    if (is_first)
+        printf("Linux sg driver version: %d\n", sg_drv_ver_num);
 
     if (create_time && (sg_drv_ver_num > 40030)) {
-        pr_create_dev_time(sg_fd, file_name);
+        pr_create_dev_time(sg_fd, dname);
         goto out;
     }
 
-    if (nw_given || (iterator_test >= 0)) {     /* -T=NUM and/or -I=0|1 */
+    if (nw_given || (iterator_test >= 0) || (object_walk_test >= 0)) {
+        /* -T=NUM and/or -I=0|1 or -j=0|1 */
         /* time ioctl(SG_GET_NUM_WAITING) or do iterator_test */
-        int nw, sum_nw;
+        int nw;
         struct timespec start_tm, fin_tm, res_tm;
 
-        if (nw_given)
-            printf("Timing %d calls to ioctl(SG_GET_NUM_WAITING)\n",
-                   num_sgnw);
-        else
-            printf("Timing calls to ioctl(SG_SET_DEBUG, %d)\n",
-                   num_sgnw);
-        if (0 != clock_gettime(CLOCK_MONOTONIC, &start_tm)) {
-                res = errno;
-                perror("start clock_gettime() failed:");
-                goto out;
+        if (is_first) {
+            int rang = has_dname_range ? (1 + dname_last - dname_current) : 1;
+
+            is_first = false;
+            if (nw_given)
+                printf("Timing %dx%d calls to ioctl(SG_GET_NUM_WAITING)\n",
+                       rang, num_sgnw);
+            else if (iterator_test >= 0) {
+                k = num_sgnw + 1000;
+                printf("Timing %d calls to ioctl(SG_SET_DEBUG, %d)\n",
+                       rang, ((0 == iterator_test) ? -k : k));
+            } else
+                printf("Timing %d calls to ioctl(SG_SET_DEBUG, %d)\n",
+                       rang, (object_walk_test == 0) ? 999 : -999);
+            if (0 != clock_gettime(CLOCK_MONOTONIC, &start_tm)) {
+                    res = errno;
+                    perror("start clock_gettime() failed:");
+                    goto out;
+            }
         }
         if (nw_given) {
-            for (k = 0, sum_nw = 0; k < num_sgnw; ++k, sum_nw += nw) {
+            for (k = 0; k < num_sgnw; ++k, sum_nw += nw) {
                 if (ioctl(sg_fd, SG_GET_NUM_WAITING, &nw) < 0) {
                     res = errno;
                     fprintf(stderr, "%d: ioctl(SG_GET_NUM_WAITING) failed "
@@ -1012,7 +1058,7 @@
                     goto out;
                 }
             }
-        } else {
+        } else if (iterator_test >= 0) {
             int fd, pid;
 
             k = num_sgnw + 1000;
@@ -1049,6 +1095,31 @@
                 fprintf(stderr, "%s%d: ioctl(SG_SET_DEBUG) failed errno=%d\n",
                         relative_cp, k, res);
                 goto out;
+            } else if (verbose)
+                fprintf(stderr, "%siterator_test good ioctl(SG_SET_DEBUG, "
+                        "%d)\n", relative_cp, k);
+            sum_nw += num_sgnw;
+        } else if (object_walk_test >= 0) {
+            const char * ccp = "object_walk_test";
+
+            relative_cp = "";
+            k = (object_walk_test == 0) ? 999 : -999;
+            if (ioctl(sg_fd, SG_SET_DEBUG, &k) < 0) {
+                res = errno;
+                fprintf(stderr, "%s: ioctl(SG_SET_DEBUG, %d) failed "
+                        "errno=%d\n", ccp, k, res);
+            } else if (verbose)
+                fprintf(stderr, "%s: good call to ioctl(SG_SET_DEBUG, %d)\n",
+                        ccp, k);
+            sum_nw += 10000;    /* (1_up-scan + 2_lookups) * 10,000 times */
+        }
+
+        if (has_dname_range) {
+            ++dname_current;
+            if (dname_current <= dname_last) {
+                if (sg_fd >= 0)
+                    close(sg_fd);
+                goto dname_range_loop;
             }
         }
         if (0 != clock_gettime(CLOCK_MONOTONIC, &fin_tm)) {
@@ -1076,7 +1147,7 @@
 
             if (m > 0.000001)
                 printf("%sCalls per second: %.2f\n", relative_cp,
-                       (double)num_sgnw / m);
+                       (double)sum_nw / m);
         }
         res = 0;
         goto out;
@@ -1133,7 +1204,7 @@
 
     cp = do_fork ? relative_cp : "";
     if (! do_v3_only) {
-        if (tst_extended_ioctl(file_name, sg_fd, second_fname, sg_fd2, sock,
+        if (tst_extended_ioctl(dname, sg_fd, second_fname, sg_fd2, sock,
                                cp))
             goto out;
     }
diff --git a/testing/sgh_dd.cpp b/testing/sgh_dd.cpp
index 75fc536..519f1f8 100644
--- a/testing/sgh_dd.cpp
+++ b/testing/sgh_dd.cpp
@@ -36,7 +36,7 @@
  * renamed [20181221]
  */
 
-static const char * version_str = "1.84 20200716";
+static const char * version_str = "1.85 20200720";
 
 #define _XOPEN_SOURCE 600
 #ifndef _GNU_SOURCE
@@ -2267,7 +2267,7 @@
     int hole_count = 0;
     int vb = clp->verbose;
     int k, j, f1, slen, sstatus, blen;
-    char b[80];
+    char b[160];
 
     blen = sizeof(b);
     good_inblks = 0;