add include/sg_pt_linux.h lib/sg_pt_linux_nvme.c and .gitignore; sg_write_x: almost finished; more NVMe work (for sg_ses)

git-svn-id: https://svn.bingwo.ca/repos/sg3_utils/trunk@733 6180dd3e-e324-4e3e-922d-17de1ae2f315
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..d194ced
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,102 @@
+# Please keep the entries in this file sorted with the following vi command:
+# :3,$!LC_ALL=C sort -fu
+
+*.exe
+*.la
+*.lo
+*.o
+*~
+.deps
+.libs
+aclocal.m4
+ar-lib
+autom4te.cache/
+compile
+config.guess
+config.h
+config.log
+config.status
+config.sub
+configure
+depcomp
+doc/Makefile
+doc/sg_scan.8
+include/Makefile
+INSTALL
+install-sh
+lib/Makefile
+libtool
+ltmain.sh
+Makefile
+Makefile.in
+missing
+src/Makefile
+src/sginfo
+src/sgm_dd
+src/sgp_dd
+src/sg_bg_ctl
+src/sg_compare_and_write
+src/sg_copy_results
+src/sg_dd
+src/sg_decode_sense
+src/sg_emc_trespass
+src/sg_format
+src/sg_get_config
+src/sg_get_lba_status
+src/sg_ident
+src/sg_inq
+src/sg_logs
+src/sg_luns
+src/sg_map
+src/sg_map26
+src/sg_modes
+src/sg_opcodes
+src/sg_persist
+src/sg_prevent
+src/sg_raw
+src/sg_rbuf
+src/sg_rdac
+src/sg_read
+src/sg_readcap
+src/sg_read_attr
+src/sg_read_block_limits
+src/sg_read_buffer
+src/sg_read_long
+src/sg_reassign
+src/sg_referrals
+src/sg_rep_zones
+src/sg_requests
+src/sg_reset
+src/sg_reset_wp
+src/sg_rmsn
+src/sg_rtpg
+src/sg_safte
+src/sg_sanitize
+src/sg_sat_identify
+src/sg_sat_phy_event
+src/sg_sat_read_gplog
+src/sg_sat_set_features
+src/sg_scan
+src/sg_scan.c
+src/sg_senddiag
+src/sg_ses
+src/sg_ses_microcode
+src/sg_start
+src/sg_stpg
+src/sg_sync
+src/sg_test_rwbuf
+src/sg_timestamp
+src/sg_turs
+src/sg_unmap
+src/sg_verify
+src/sg_vpd
+src/sg_write_atomic
+src/sg_write_buffer
+src/sg_write_long
+src/sg_write_same
+src/sg_write_verify
+src/sg_write_x
+src/sg_wr_mode
+src/sg_xcopy
+src/sg_zone
+stamp-h1
diff --git a/COVERAGE b/COVERAGE
index 313a4c9..83c8105 100644
--- a/COVERAGE
+++ b/COVERAGE
@@ -37,6 +37,8 @@
 MODE SENSE(10)      sdparm, sg_modes, sg_wr_mode, sginfo, sg_format,
                     sg_senddiag('-e'), sg_rdac, ++
 OPEN ZONE           sg_zone
+ORWRITE(16)         sg_write_x
+ORWRITE(32)         sg_write_x
 PERSISTENT RESERVE IN       sg_persist, ++
 PERSISTENT RESERVE OUT      sg_persist, ++
 POPULATE TOKEN      ddpt, ddptctl, ++
@@ -99,15 +101,18 @@
 WRITE LONG(10)      sg_write_long, ++
 WRITE LONG(16)      sg_write_long, ++
 WRITE SAME(10)      sg_write_same
-WRITE SAME(16)      sg_write_same
-WRITE SAME(32)      sg_write_same
+WRITE SAME(16)      sg_write_same, sg_write_x
+WRITE SAME(32)      sg_write_same, sg_write_x
+WRITE SCATTERED(16)    sg_write_x
+WRITE SCATTERED(32)    sg_write_x
+WRITE STREAM(16)    sg_write_x
+WRITE STREAM(32)    sg_write_x
 WRITE USING TOKEN   ddpt, ddptctl, ++
 <most commands>     sg_raw
 
 
-
-ATA command         sg3_utils utilities that use this SCSI command
------------         ----------------------------------------------
+ATA command         sg3_utils utilities that use this (S)ATA command
+-----------         ------------------------------------------------
 CHECK POWER MODE    examples/sg_sat_chk_power
 IDENTIFY DEVICE     sg_inq, sg_scan, sg_sat_identify,
                     examples/sg__sat_identify
@@ -121,6 +126,13 @@
 SMART READ DATA     examples/sg_sat_smart_rd_data
 
 
+NVMe command        sg3_utild utilities that use this NVMe command
+------------        ------------------------------------------------
+IDENTIFY            sg_inq
+SES READ            sg_senddiag, sg_ses (NVME-MI command)
+SES WRITE           sg_senddiag, sg_ses (NVME-MI command)
+
+
 ++  command wrapper found in sg_cmds_basic.c, sg_cmds_mmc.c  or
     sg_cmds_extra.c for this command
 (2) this command was known as REPORT DEVICE IDENTIFIER prior to spc4r07
@@ -137,4 +149,4 @@
 
 
 Douglas Gilbert
-15th November 2017
+30th November 2017
diff --git a/ChangeLog b/ChangeLog
index 4209f57..2ea98dd 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -2,7 +2,7 @@
 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.43 [20171127] [svn: r732]
+Changelog for sg3_utils-1.43 [20171206] [svn: r733]
   - sg_bg_ctl: new Background control command (sbc4r08)
   - sg_write_x: where x can be normal, atomic, orwrite,
     same, scattered, or stream writes with 16 or 32 byte
@@ -92,6 +92,7 @@
     - add sg_decode_transportid_str()
     - add sg_msense_calc_length()
     - add sg_all_zeros(), sg_all_ffs()
+    - add sg_memalign() and sg_get_page_size()
     - implement 'format' argument in dStrHexStr()
     - add read buffer(16) command mode names
     - add Microcode activation sense descriptor spc5r10
@@ -112,14 +113,17 @@
     - add to install list in Makefile, hope it does
       not clash with other package providing it
   - 55-scsi-sg3_id.rules: fixes from Suse
-  - https://github.com/hreinecke/sg3_utils
-    branch sles15 synced 20170914
+  - https://github.com/hreinecke/sg3_utils branch
+    sles15 synced 20170914
   - move some testing utilities out of the
     'examples' and 'utils' directories into the new
     'testing' directory
   - gcc 7.2 cleanups (sysmacros.h etc)
   - clang --analyze static checker clean ups
   - shellcheck cleanup on scripts
+  - --disable-linuxbsg to ./configure still accepted
+    but now ignored, Linux sg v3 or v4 interface
+    decision made at runtime
   - automake: add AM_PROG_AR to configure.ac
     - upgrade to version 1.15
 
diff --git a/config.h.in b/config.h.in
index 5bab057..12379ef 100644
--- a/config.h.in
+++ b/config.h.in
@@ -60,7 +60,7 @@
 /* Define to 1 if you have the <unistd.h> header file. */
 #undef HAVE_UNISTD_H
 
-/* ignore linux bsg */
+/* option ignored */
 #undef IGNORE_LINUX_BSG
 
 /* Define to the sub-directory where libtool stores uninstalled libraries. */
diff --git a/configure b/configure
index f1970e9..4ab0fc6 100755
--- a/configure
+++ b/configure
@@ -1438,7 +1438,7 @@
   --enable-fast-install[=PKGS]
                           optimize for fast installation [default=yes]
   --disable-libtool-lock  avoid locking (might break parallel builds)
-  --disable-linuxbsg      ignore linux bsg (sgv4) if present
+  --disable-linuxbsg      option ignored, this is placeholder
   --enable-win32-spt-direct
                           enable Win32 SPT Direct
   --disable-scsistrings   Disable full SCSI sense strings
diff --git a/configure.ac b/configure.ac
index 135a579..c32e058 100644
--- a/configure.ac
+++ b/configure.ac
@@ -95,8 +95,8 @@
 AM_CONDITIONAL(OS_WIN32_CYGWIN, [echo $host_os | grep '^cygwin' > /dev/null])
 
 AC_ARG_ENABLE([linuxbsg],
-  AC_HELP_STRING([--disable-linuxbsg], [ignore linux bsg (sgv4) if present]),
-  [AC_DEFINE_UNQUOTED(IGNORE_LINUX_BSG, 1, [ignore linux bsg], )], [])
+  AC_HELP_STRING([--disable-linuxbsg], [option ignored, this is placeholder]),
+  [AC_DEFINE_UNQUOTED(IGNORE_LINUX_BSG, 1, [option ignored], )], [])
 
 AC_ARG_ENABLE([win32-spt-direct],
   AC_HELP_STRING([--enable-win32-spt-direct], [enable Win32 SPT Direct]),
diff --git a/debian/changelog b/debian/changelog
index 763093b..64d72ec 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -2,7 +2,7 @@
 
   * New upstream version
 
- -- Douglas Gilbert <[email protected]>  Fri, 03 Nov 2017 12:00:00 -0400
+ -- Douglas Gilbert <[email protected]>  Mon, 04 Dec 2017 15:00:00 -0500
 
 sg3-utils (1.42-0.1) unstable; urgency=low
 
diff --git a/doc/sg_write_same.8 b/doc/sg_write_same.8
index b8da690..55e739d 100644
--- a/doc/sg_write_same.8
+++ b/doc/sg_write_same.8
@@ -1,4 +1,4 @@
-.TH SG_WRITE_SAME "8" "September 2017" "sg3_utils\-1.43" SG3_UTILS
+.TH SG_WRITE_SAME "8" "November 2017" "sg3_utils\-1.43" SG3_UTILS
 .SH NAME
 sg_write_same \- send SCSI WRITE SAME command
 .SH SYNOPSIS
@@ -31,7 +31,7 @@
 The \fI\-\-10\fR, \fI\-\-16\fR and \fI\-\-32\fR options are mutually
 exclusive.
 .PP
-SBC\-3 revision 35d introduced a "no data\-out buffer" (NDOB) bit which, if
+SBC\-3 revision 35d introduced a "No Data\-Out Buffer" (NDOB) bit which, if
 set, bypasses the requirement to send a single block of data to the
 \fIDEVICE\fR together with the command. Only WRITE SAME (16 and 32 byte)
 support the NDOB bit. If given, a user block of zeros is assumed; if
@@ -94,13 +94,13 @@
 output the usage message then exit.
 .TP
 \fB\-i\fR, \fB\-\-in\fR=\fIIF\fR
-read data (binary) from file named \fIIF\fR and use it as the data out
-buffer for the SCSI WRITE SAME command. The length of the data out buffer
+read data (binary) from file named \fIIF\fR and use it as the data\-out
+buffer for the SCSI WRITE SAME command. The length of the data\-out buffer
 is \fI\-\-xferlen=LEN\fR or, if that is not given, the length of the \fIIF\fR
 file. If \fIIF\fR is "\-" then stdin is read. If this option is not given
-then 0x00 bytes are used as fill with the length of the data out buffer
+then 0x00 bytes are used as fill with the length of the data\-out buffer
 obtained from \fI\-\-xferlen=LEN\fR or by calling READ CAPACITY(16 or 10).
-If the response to READ CAPACITY(16) has the PROT_EN bit set then data
+If the response to READ CAPACITY(16) has the PROT_EN bit set then data\-
 out buffer size is modified accordingly with the last 8 bytes set to 0xff.
 .TP
 \fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR
@@ -121,10 +121,10 @@
 .TP
 \fB\-n\fR, \fB\-\-num\fR=\fINUM\fR
 where \fINUM\fR is the number of blocks, starting at \fILBA\fR, to write the
-data out buffer to. The default value for \fINUM\fR is 1. The value corresponds
-to the 'Number of logical blocks' field in the WRITE SAME cdb.
+data\-out buffer to. The default value for \fINUM\fR is 1. The value
+corresponds to the 'Number of logical blocks' field in the WRITE SAME cdb.
 .br
-Note that a value of 0 in \fINUM\fR may be interpreted as write the data out
+Note that a value of 0 in \fINUM\fR may be interpreted as write the data\-out
 buffer on every block starting at \fILBA\fR to the end of the \fIDEVICE\fR.
 If the WSNZ bit (introduced in sbc3r26, January 2011) in the Block Limits VPD
 page is set then the value of 0 is disallowed, yielding an Invalid request
@@ -154,17 +154,17 @@
 default value is zero. \fIWPR\fR should be a value between 0 and 7.
 When \fIWPR\fR is 1 or greater, and the disk's protection type is 1 or
 greater, then 8 extra bytes of protection information are expected or
-generated (to place in the command's data out buffer).
+generated (to place in the command's data\-out buffer).
 .TP
 \fB\-x\fR, \fB\-\-xferlen\fR=\fILEN\fR
-where \fILEN\fR is the data out buffer length. Defaults to the length of
+where \fILEN\fR is the data\-out buffer length. Defaults to the length of
 the \fIIF\fR file or, if that is not given, then the READ CAPACITY(16 or 10)
 command is used to find the 'Logical block length in bytes'. That figure
 may be increased by 8 bytes if the \fIDEVICE\fR's protection type is 1 or
 greater and the WRPROTECT field (see \fI\-\-wrprotect=WPR\fR) is 1 or
 greater. If both this option and the \fIIF\fR option are given and
 \fILEN\fR exceeds the length of the \fIIF\fR file then \fILEN\fR is the
-data out buffer length with zeros used as pad bytes.
+data\-out buffer length with zeros used as pad bytes.
 .SH UNMAP
 Logical block provisioning is a new term introduced in SBC\-3 revision 25
 for the ability to mark blocks as unused. For large storage arrays, it is a
@@ -194,13 +194,13 @@
 the device supports the UNMAP command (see the sg_unmap utility). When the
 ANC_SUP bit is set it indicates the device supports anchored LBAs.
 .PP
-When the UNMAP bit is set in the cdb then the data out buffer is also sent.
-Additionally the data section of that data out buffer should be full of 0x0
+When the UNMAP bit is set in the cdb then the data\-out buffer is also sent.
+Additionally the data section of that data\-out buffer should be full of 0x0
 bytes while the data protection block, 8 bytes at the end if present, should
 be set to 0xff bytes. If these conditions are not met and the LBPRZ bit is
-set then the UNMAP bit is ignored and the data out buffer is written to the
+set then the UNMAP bit is ignored and the data\-out buffer is written to the
 \fIDEVICE\fR as if the UNMAP bit was zero. In the absence of the
-\fI\-\-in=IF\fR option, this utility will attempt build a data out buffer
+\fI\-\-in=IF\fR option, this utility will attempt build a data\-out buffer
 that meets the requirements for the UNMAP bit in the cdb to be acted on by
 the \fIDEVICE\fR.
 .PP
@@ -239,7 +239,7 @@
 \fI\-\-in=IF\fR is not given a block of zeros is assumed. So 63 blocks
 of zeros (each block containing 512 bytes) will be written from (and
 including) LBA 0x1234 . Note that only one block of zeros is passed
-to the SCSI WRITE SAME command in the data out buffer (as required by
+to the SCSI WRITE SAME command in the data\-out buffer (as required by
 SBC\-3).
 .PP
 A similar example follows but in this case the blocks
diff --git a/doc/sg_write_x.8 b/doc/sg_write_x.8
index 44fe5dd..760c0d6 100644
--- a/doc/sg_write_x.8
+++ b/doc/sg_write_x.8
@@ -1,4 +1,4 @@
-.TH SG_WRITE_X "8" "November 2017" "sg3_utils\-1.43" SG3_UTILS
+.TH SG_WRITE_X "8" "December 2017" "sg3_utils\-1.43" SG3_UTILS
 .SH NAME
 sg_write_x \- SCSI WRITE normal/ATOMIC/SAME/SCATTERED/STREAM, ORWRITE commands
 .SH SYNOPSIS
@@ -9,8 +9,8 @@
 [\fI\-\-generation=EOG,NOG\fR] [\fI\-\-grpnum=GN\fR] [\fI\-\-help\fR]
 \fI\-\-in=IF\fR [\fI\-\-lba=LBA[,LBA...]\fR] [\fI\-\-normal\fR]
 [\fI\-\-num=NUM[,NUM...]\fR] [\fI\-\-offset=OFF[,DLEN]\fR] [\fI\-\-or\fR]
-[\fI\-\-raw\fR] [\fI\-\-ref\-tag=RT\fR] [\fI\-\-same=NDOB\fR]
-[\fI\-\-scat\-file=SF\fR] [\fI\-\-scattered=RD\fR] [\fI\-\-stream=ID\fR]
+[\fI\-\-ref\-tag=RT\fR] [\fI\-\-same=NDOB\fR] [\fI\-\-scat\-file=SF\fR]
+[\fI\-\-scat\-raw\fR] [\fI\-\-scattered=RD\fR] [\fI\-\-stream=ID\fR]
 [\fI\-\-strict\fR] [\fI\-\-tag\-mask=TM\fR] [\fI\-\-timeout=TO\fR]
 [\fI\-\-unmap=U_A\fR] [\fI\-\-verbose\fR] [\fI\-\-version\fR]
 [\fI\-\-wrprotect=WPR\fR] \fIDEVICE\fR
@@ -49,10 +49,10 @@
 \fI\-\-scattered=RD\fR [\fI\-\-16\fR] [\fI\-\-32\fR] [\fI\-\-app-tag=AT\fR]
 [\fI\-\-bs=LBS\fR] [\fI\-\-dld=DLD\fR] [\fI\-\-dpo\fR] [\fI\-\-fua\fR]
 [\fI\-\-grpnum=GN\fR] \fI\-\-in=IF\fR [\fI\-\-lba=LBA[,LBA...]\fR]
-[\fI\-\-num=NUM[,NUM...]\fR] [\fI\-\-offset=OFF[,DLEN]\fR] [\fI\-\-raw\fR]
-[\fI\-\-ref\-tag=RT\fR] [\fI\-\-scat\-file=SF\fR] [\fI\-\-strict\fR]
-[\fI\-\-tag\-mask=TM\fR] [\fI\-\-timeout=TO\fR] [\fI\-\-wrprotect=WPR\fR]
-\fIDEVICE\fR
+[\fI\-\-num=NUM[,NUM...]\fR] [\fI\-\-offset=OFF[,DLEN]\fR]
+[\fI\-\-ref\-tag=RT\fR] [\fI\-\-scat\-file=SF\fR] [\fI\-\-scat\-raw\fR]
+[\fI\-\-strict\fR] [\fI\-\-tag\-mask=TM\fR] [\fI\-\-timeout=TO\fR]
+[\fI\-\-wrprotect=WPR\fR] \fIDEVICE\fR
 .PP
 .B sg_write_x
 \fI\-\-stream=ID\fR [\fI\-\-16\fR] [\fI\-\-32\fR] [\fI\-\-app-tag=AT\fR]
@@ -100,12 +100,12 @@
 the logical block size in the response is used as the actual block size.
 .PP
 The number of bytes this utility will attempt to read from the file named by
-\fIIF\fR is the product of the actual block size and the number of
-blocks (\fINUM\fR or the sum of \fINUM\fR arguments). If less bytes are read
-from the file \fIIF\fR and the \fI\-\-strict\fR option is given then this
-utility exits at this point with an exit status of SG_LIB_FILE_ERROR. If less
-bytes are read from the file \fIIF\fR and the \fI\-\-strict\fR option is not
-given then zero bytes are substituted for the "missing" bytes and this
+\fIIF\fR is the product of the actual block size and the
+number_of_blocks (\fINUM\fR or the sum of \fINUM\fR arguments). If less bytes
+are read from the file \fIIF\fR and the \fI\-\-strict\fR option is given then
+this utility exits with an exit status of SG_LIB_FILE_ERROR. If less bytes
+are read from the file \fIIF\fR and the \fI\-\-strict\fR option is not
+given then bytes of zero are substituted for the "missing" bytes and this
 utility continues.
 .PP
 Attempts to write multi megabyte data with a single command are likely to fail
@@ -162,7 +162,7 @@
 selects the WRITE ATOMIC command and \fIAB\fR is placed in the Atomic
 Boundary field of its cdb. It is a 16 bit field so the maximum value
 is 0xffff. If unsure what value to set, try 0 which will attempt to
-write the whole data-out buffer in a single atomic operation.
+write the whole data\-out buffer in a single atomic operation.
 .TP
 \fB\-B\fR, \fB\-\-bmop\fR=\fIOP,PGP\fR
 where \fIOP\fR and \fIPGP\fR are the values to be placed in ORWRITE(32)'s
@@ -179,34 +179,39 @@
 CAPACITY(10) command is sent.
 .TP
 \fB\-c\fR, \fB\-\-combined\fR=\fIDOF\fR
-This option only applies to WRITE SCATTERED and assumes the whole data-out
-buffer (including the scatter list) can be read from \fIIF\fR given by
-the \fI\-\-in=IF\fR option. If the \fI\-\-lba=LBA[,LBA...]\fR,
+This option only applies to WRITE SCATTERED and assumes the whole data\-out
+buffer can be read from \fIIF\fR given by the \fI\-\-in=IF\fR option. The
+whole data\-out buffer is the parameter list header, followed by one or more
+LBA range descriptors, optional followed by some pad bytes and then the data
+to be written to the media. If the \fI\-\-lba=LBA[,LBA...]\fR,
 \fI\-\-num=NUM[,NUM...]\fR or \fI\-\-scat\-file=SF\fR options are given
-then they are ignored (or an error occurs if the \fI\-\-strict\fR option
-is present). The \fIDOF\fR argument should be the value suitable for
-the 'Logical Block Data Offset' field in the WRITE SCATTERED cdb. This is
-the offset in the data-out buffer where the data to write commences. The
-unit of that field is the actual block size which is the logical block
-size plus 8, if protection information (PI) is being sent. When \fIWPR\fR
-(from \fI\-\-wrprotect=WPR\fR) is greater than zero then PI is expected.
-SBC\-4 revision 15 does not state it but it would appear that a \fIDOF\fR
-value of 0 is invalid.
+then an error is generated. The \fIDOF\fR argument should be the value
+suitable for the 'Logical Block Data Offset' field in the WRITE SCATTERED
+cdb. This is the offset in the data\-out buffer where the data to write
+to the media commences. The unit of that field is the actual block size
+which is the logical block size plus 8, if protection information (PI) is
+being sent. When \fIWPR\fR (from \fI\-\-wrprotect=WPR\fR) is greater than
+zero then PI is expected. SBC\-4 revision 15 does not state it but it would
+appear that a \fIDOF\fR value of 0 is invalid. It is suggested that this
+option be used with the \fI\-\-strict\fR option while experimenting as
+random or incorrect data fed in via the \fI\-\-in=IF\fR option could write
+a lot of "interesting" data on the \fIDEVICE\fR. If \fIDOF\fR is given as 0
+the utility will scan the data in \fIIF\fR until \fIRD\fR LBA range
+descriptors are found; or if \fIRD\fR is also 0 until a degenerate LBA range
+descriptor is found.
 .TP
 \fB\-D\fR, \fB\-\-dld\fR=\fIDLD\fR
 where \fIDLD\fR is the duration limits descriptor spread across 3 bits in
-the SCSI WRITE(16) cdb. \fIDLD\fR is between 0 to 7 inclusive with a default
-of zero. The DLD0 field in WRITE(16) is set if (0x1 & \fIDLD\fR) is non\-zero.
-The DLD1 field in WRITE(16) is (0x2 & \fIDLD\fR) is non\-zero. The DLD2 field
-in WRITE(16) is (0x4 & \fIDLD\fR) is non\-zero.
+the SCSI WRITE(16) and the WRITE SCATTERED(16) cdbs. \fIDLD\fR is between 0
+to 7 inclusive with a default of zero. The DLD0 field in WRITE(16) and WRITE
+SCATTERED(16) is set if (0x1 & \fIDLD\fR) is non\-zero. The DLD1 field in
+both cdbs is set if (0x2 & \fIDLD\fR) is non\-zero. The DLD2 field in both
+cdbs is set if (0x4 & \fIDLD\fR) is non\-zero.
 .TP
 \fB\-d\fR, \fB\-\-dpo\fR
 if this option is given then the DPO (disable page out) bit field in the
-cdb is set. The default is to clear this bit field.
-.TP
-\fB\-f\fR, \fB\-\-fua\fR
-if this option is given then the FUA (force unit access) bit field in the
-cdb is set. The default is to clear this bit field.
+cdb is set. The default is to clear this bit field. Applies to all write
+commands except WRITE SAME.
 .TP
 \fB\-x\fR, \fB\-\-dry\-run\fR
 this option exits (with a status of 0) just before it would otherwise send
@@ -217,70 +222,168 @@
 and sanity checks (e.g. if the \fI\-\-strict\fR option is given) will be
 performed and if there is an error then there will be a non zero exit
 status value.
-xxxxxxxxxxxxxxxxxxxxxxxxxxx
+.TP
+\fB\-f\fR, \fB\-\-fua\fR
+if this option is given then the FUA (force unit access) bit field in the
+cdb is set. The default is to clear this bit field. Applies to all write
+commands except WRITE SAME.
+.TP
+\fB\-G\fR, \fB\-\-generation\fR=\fIEOG,NOG\fR
+the arguments for this option are used by the ORWITE(32) command only.
+\fIEOG\fR is placed in the "Expected ORWgeneration" field while \fINOG\fR
+is placed in the "New ORWgeneration" field. Both are 32 bits long and
+default to zero.
 .TP
 \fB\-g\fR, \fB\-\-grpnum\fR=\fIGN\fR
 sets the 'Group number' field to \fIGN\fR. Defaults to a value of zero.
 \fIGN\fR should be a value between 0 and 63.
 .TP
 \fB\-h\fR, \fB\-\-help\fR
-output the usage message then exit.
+output the usage message then exit. Use multiple times for more help.
+Currently '\-h' to '\-hhhh' provide different output.
 .TP
 \fB\-i\fR, \fB\-\-in\fR=\fIIF\fR
-read data (binary) from a file named \fIIF\fR in a single OS system
-call (read(2)). That data is placed in a continuous buffer and then used as
-the data out buffer for the SCSI WRITE ATOMIC(16 or 32) or the normal SCSI
-WRITE(16 or 32) command. The data read from \fIIF\fR starts from byte offset
-\fIOFF\fR which defaults to zero and that is the start of \fIIF\fR. The
-number of bytes read from \fIIF\fR is basically the product of \fINUM\fR and
-\fIBS\fR (i.e. the number_of_blocks multiplied by block_size). This option
-is mandatory. In Unix based OSes, any number of zeros can produced by
-using the /dev/zero device file.
+read data (in binary) from a file named \fIIF\fR in a single OS system
+call (in Unix: read(2)). That data is placed in a continuous buffer and then
+used as the data\-out buffer for all SCSI write commands apart from WRITE
+SCATTERED(16 or 32) which may include other data in the data\-out buffer.
+For WRITE SCATTERED (16 or 32) the data\-out buffer is made up of 3 or 4
+components in this order: a parameter list header (32 zero bytes); zero or
+more LBA range descriptors, optionally some pad bytes (zeros) and then data
+to write to the media. For WRITE
+SCATTERED \fIIF\fR only provides the data to write to the media unless
+\fI\-\-combined=DOF\fR is given. When the \fI\-\-combined=DOF\fR option is
+given \fIIF\fR contains all 3 components of the WRITE SCATTERED data\-out
+buffer in binary. The data read from \fIIF\fR starts from byte offset
+\fIOFF\fR which defaults to zero and no more than \fIDLEN\fR bytes are read
+from that point (i.e. the file byte offset \fIOFF\fR). If \fIDLEN\fR is
+zero or not given the rest of the file \fIIF\fR is read. This option is
+mandatory apart from when \-\-same=1 is given (that sets the NDOB bit which
+stands for "No Data Out Buffer"). In Unix based OSes, any number of zeros
+can produced by using the /dev/zero device file.
 .TP
-\fB\-l\fR, \fB\-\-lba\fR=\fILBA\fR
-where \fILBA\fR is the logical block address (lba) of the first block written
-by the SCSI WRITE ATOMIC(16 or 32) or SCSI WRITE(16 or 32) command. Defaults
-to lba 0 which is a dangerous block to overwrite on a disk that is in use.
-\fILBA\fR is assumed to be in decimal unless prefixed with '0x' or has a
-trailing 'h'.
+\fB\-l\fR, \fB\-\-lba\fR=\fILBA[,LBA...]\fR
+where the argument is a single Logical Block Address (LBA) or a comma
+separated list of \fILBA\fRs each of which is the address of the first block
+written by the selected write command. Only the WRITE SCATTERED command
+can usefully take more than one \fILBA\fR. Whatever number of \fILBA\fRs is
+given, there needs to be an equal number of \fINUM\fRs given to the
+\fI\-\-num=NUM[,NUM...]\fR option. The first given \fILBA\fR joins with the
+first given \fINUM\fR to form the first LBA range descriptor (which T10
+number from zero in SBC\-4). The second \fILBA\ffR joins with the second
+\fILBA\fR to form the second LBA range descriptor, etc. A more convenient
+way to define a large number of LBA range descriptors is the \fISF\fR
+name of the file given with the \fI\-\-scat\-file=SF\fR option. Defaults
+to logical block 0 (which could be dangerous) while \fINUM\fR defaults to
+0 which makes the combination harmless. \fILBA\fR is assumed to be in decimal
+unless prefixed with '0x' or has a trailing 'h'.
 .TP
-\fB\-N\fR, \fB\-\-non\-atomic\fR
-when this option is given either a SCSI WRITE(16) or SCSI WRITE(32) command
-is sent. The default (i.e. in the absence of this option) is to send
-either SCSI WRITE ATOMIC(16) or SCSI WRITE ATOMIC(32) command.
+\fB\-N\fR, \fB\-\-normal\fR
+as well as implicitly selecting a "normal" WRITE (16 or 32) in the absence
+of selecting any other command, the choice of a "normal" WRITE can be made
 .TP
-\fB\-n\fR, \fB\-\-num\fR=\fINUM\fR
-where \fINUM\fR is the number of blocks, to read from the file named \fIIF\fR.
-It is also the number of blocks written using a SCSI WRITE ATOMIC(16 or 32)
-or a SCSI WRITE(16 or 32). The default is 0 which is the degenerate case
-that will not modify the \fIDEVICE\fR but is still valid.
+\fB\-n\fR, \fB\-\-num\fR=\fINUM[,NUM...]\fR
+where the argument is a single NUMber of blocks (NUM) or a comma separated
+list of \fINUM\fRs that pair with the corresponding entries in the
+\fI\-\-lba=LBA[,LBA...]\fR option. If a \fINUM\fR is given and is not
+provided by another method (e.g. by using the \fI\-\-scat\-file=SF\fR option)
+then it defaults to the number of blocks derived from the size of the file
+named by \fIIF\fR (starting at byte offset \fIOFF\fR to the end or the file
+or \fIDLEN\fR). Apart from the \fI\-\-combined=DOF\fR option, an LBA must
+be explicitly given (either with \fII\-\-lba=LBA\fR or via
+\fI\-\-scat\-file=SF\fR), if not \fINUM\fR defaults to 0 as a safety measure.
 .TP
-\fB\-o\fR, \fB\-\-offset\fR=\fIOFF\fR
+\fB\-o\fR, \fB\-\-offset\fR=\fIOFF[,DLEN]\fR
 where \fIOFF\fR is the byte offset within the file named \fIIF\fR to start
-reading from. The default value is zero which is the beginning \fIIF\fR.
+reading from. The default value of \fIOFF\fR is zero which is the beginning
+of file named \fIIF\fR. \fIDLEN\fR is the maximum number of bytes to read,
+starting at byte offset \fIOFF\fR, from the file named \fIIF\fR. Less bytes
+will be read if an end of file occurs before \fIDLEN\fR is exhausted. If
+\fIDLEN\fR is zer or not given then reading from byte offset \fIOFF\fR to
+the end of the file named \fIIF\fR is assumed.
+.TP
+\fB\-O\fR, \fB\-\-or\fR
+selects the ORWRITE command. ORWRITE(16) has similar fields to WRITE(16)
+apart from the WRPROTECT field being named ORPROTECT with slightly different
+semantics and the absence of the 3 DLD bit fields. ORWRITE(32) has four
+extra fields that are set with the \fI\-\-bmop=OP,PGP\fR and
+\fI\-\-generation=EOG,NOG\fR options. ORWRITE(32) is the only 32 byte cdb
+command in this utility that does not require a \fIDEVICE\fR formatted with
+type 1, 2 or 3 PI (although it will still work if it is formatted with PI).
 .TP
 \fB\-r\fR, \fB\-\-ref\-tag\fR=\fIRT\fR
-where \fIRT\fR is the expected logical block reference tag field in the
-WRITE ATOMIC(32) and WRITE(32) cdbs. It is 32 bit field which means the
-maximum value is 0xffffffff. The default value is zero.
+where \fIRT\fR is the expected logical block reference tag field found in
+the 32 byte cdb variants of WRITE, WRITE ATOMIC, WRITE SAME and WRITE STREAM.
+The field is also found in the WRITE SCATTERED(32) LBA range descriptors.
+It is 32 bit field which means the maximum value is 0xffffffff. The default
+value is 0xffffffff.
+.TP
+\fB\-S\fR, \fB\-\-same\fR=\fINDOB\fR
+selects the WRITE SAME command with the NDOB field set to \fINDOB\fR which
+stands for No Data\-Out Buffer. \fINDOB\fR can take values 0 or 1 (i.e. i
+is a single bit field). When \-\-same=1 are options associated with the
+data\-out buffer are ignored.
+.TP
+\fB\-q\fR, \fB\-\-scat\-file\fR=\fISF\fR
+where \fISF\fR is the name of an auxiliary file containing the scatter list
+for the WRITE SCATTERED command. If the \fI\-\-scat\-raw\fR option is also
+given then \fISF\fR is assumed to contain both the parameter list header (32
+bytes of zeros) followed by one or more LBA range descriptors which are
+also 32 bytes long each. These components are as defined by SBC\-4 (i.e.
+in binary with integers in big endian format). If the \fI\-\-scat\-raw\fR
+option is not given then a file of ACSII hexadecimal is expected as described
+in the SCATTERED FILE ASCII FORMAT section below.
+.br
+If this option is given with the \fI\-\-combined=DOF\fR option then this
+utility will exit with a syntax error.
+.TP
+\fB\-R\fR, \fB\-\-scat\-raw\fR
+this option only effects the way that the file named \fISF\fR from the
+\fI\-\-scat\-file=SF\fR option for WRITE SCATTERED is interpreted. By
+default (i.e. without this option), \fISF\fR is parsed as ASCII hexadecimal
+with blank lines and lines contents from and including '#' to the end of
+line ignored. Hence it can contain comments and other indications. When
+this option is given, the file named \fISF\fR is interpreted as binary.
+It is assumed to contain 32 bytes of zeros (the WRITE SCATTERED parameter
+list header) followed by one or more LBA range descriptors (which are 32
+bytes each). If the \fI\-\-strict\fR option is given the reserved field
+in those two items are checked with any non zero bytes causing an error.
+.TP
+\fB\-S\fR, \fB\-\-scattered\fR=\fIRD\fR
+selects the WRITE SCATTERED command with \fIRD\fR being the number of LBA
+range descriptors that will be placed in the data\-out buffer. If \fIRD\fR
+is zero then the logic will try and determine the number of range descriptors
+by other means (e.g. by parsing the file named by \fISF\fR, if there is one).
+The LBA range descriptors differ between the 16 and 32 byte cdb variants of
+WRITE SCATTERED. In the 16 byte cdb variant the 32 byte LBA range descriptor
+is made up of an 8 byte LBA, followed by a 4 byte number_of_blocks followed
+by 20 bytes of zeros. In the 32 byte variant the LBA and number_of_blocks
+are followed by a RT (4 bytes), an AT (2 bytes) and a TM (2 bytes) then
+12 bytes of zeros.
+.TP
+\fB\-T\fR, \fB\-\-stream\fR=\fIID\fR
+selects the WRITE STREAM command with the STR_ID field set to \fIID\fR.
+\fIID\fR can take values from 0 to 0xffff (i.e. it is a 16 bit field).
 .TP
 \fB\-s\fR, \fB\-\-strict\fR
-when this option is present, if the read of the file named \fIIF\fR yields
-less bytes than requested then this utility will exit at this point
-with an exit status of SG_LIB_FILE_ERROR. The default is to fill the
-remaining part of the buffer with zeros and attempt to write the
-full buffer to the \fIDEVICE\fR.
+when this option is present, more things (e.g. that reserved fields contain
+zeros) and any irregularities will terminate the utility with a message to
+stderr and an indicative exit status. While experimenting with these commands,
+especially WRITE SCATTERED, it is recommended to use this option.
 .TP
-\fB\-T\fR, \fB\-\-tag\-mask\fR=\fITM\fR
+\fB\-t\fR, \fB\-\-tag\-mask\fR=\fITM\fR
 where \fITM\fR is the logical block application tag mask field in the
 WRITE ATOMIC(32) and WRITE(32) cdbs. It is 16 bit field which means the
-maximum value is 0xffff. The default value is zero.
+maximum value is 0xffff. The default value is 0xffff.
 .TP
-\fB\-t\fR, \fB\-\-timeout\fR=\fITO\fR
+\fB\-I\fR, \fB\-\-timeout\fR=\fITO\fR
 where \fITO\fR is the command timeout value in seconds. The default value is
 120 seconds. If \fINUM\fR is large a WRITE ATOMIC command may require
 considerably more time than 120 seconds to complete.
 .TP
+\fB\-u\fR, \fB\-\-unmap\fR=\fIU_A\fR
+where \fITO\fR is the command timeout value in seconds. The default value is
+.TP
 \fB\-v\fR, \fB\-\-verbose\fR
 increase the degree of verbosity (debug messages). These messages are usually
 written to stderr.
@@ -289,11 +392,49 @@
 output version string then exit.
 .TP
 \fB\-w\fR, \fB\-\-wrprotect\fR=\fIWPR\fR
-sets the "Write protect" field in the WRITE SAME cdb to \fIWPR\fR. The
-default value is zero. \fIWPR\fR should be a value between 0 and 7.
-When \fIWPR\fR is 1 or greater, and the disk's protection type is 1 or
-greater, then 8 extra bytes of protection information are expected or
-generated (to place in the command's data out buffer).
+sets the WRPROTECT field (3 bits) in all sg_write_x commands apart from
+ORWRITE which has a 3 bit ORPROTECT field. In all cases \fIWPR\fR is placed
+in that 3 bit field. The default value is zero which does not send any PI
+in the data\-out buffer. \fIWPR\fR should be a value between 0 and 7.
+.SH SCATTERED FILE ASCII FORMAT
+This file named with the \fI\-\-scat\-file=SF\fR option only applies to
+the WRITE SCATTERED (16 and 32) command. If the \fI\-\-scat\-raw\fR option
+is also given then the file named \fISF\fR is expected to be binary and
+contain the parameter list header (32 bytes of zeros for both the 16 and 32
+byte variants) followed by "n" LBA range descriptors, each of 32 bytes each.
+This section describes what is expected in \fISF\fR when the
+\fI\-\-scat\-raw\fR option is not given.
+.PP
+The ASCII hexadecimal "scatter file" (named by \fISF\fR) can contain
+comments, empty lines and numbers. If multiple numbers appear on one line
+they can be separated by spaces, tabs or a single comma. Numbers are parsed
+as decimal unless prefixed by "0x" (or "0X") or have a suffix of "h". Ox is
+the prefix of hexadecimal number is the C language while T10 uses the "h"
+suffix for the same purpose. Anything from and including a "#" character
+to the end\-of\-line is ignored, so comments can be placed there.
+.PP
+For the WRITE SCATTERED (16) command, its LBA range descriptors contain two
+items per descriptor: an 8 byte LBA followed by a 4 byte number_of_blocks.
+The remaining 20 bytes of the descriptor are zeros. The format accepted
+is relatively loose with each decoded value being placed in an LBA and
+then a number_of_blocks until the end\-of\-file is reached. The pattern
+starts with a LBA and if it doesn't finish with a number_of_blocks (i.e.
+an odd number of values are parsed) an error occurs. So the number of
+LBA range descriptors generated will be half the number of values parsed
+in \fISF\fR.
+.PP
+For the WRITE SCATTERED (32) command, its LBA range descriptors contain five
+items per descriptor: an 8 byte LBA followed by a 4 byte number_of_blocks,
+then a 4 byte RT, a 2 byte AT, and a 2 byte TM. The last three items are
+associated with protection information (PI). The accepted format in the
+\fISF\fR file is more constrained than the 16 byte cdb variant. The items
+for each LBA range descriptor must be found on one line with adjacent items
+being comma separated. The first two items (LBA and number_of_blocks) must be
+given, and if no more items are on the line then RT, AT and TM are given
+their default values (all "ff" bytes). Spaces and tabs may appear between
+items but commas are the separators. Two commas with no value between them
+will cause the "missing" item to receive its default value.
+xxxxx
 .SH NOTES
 Various numeric arguments (e.g. \fILBA\fR) may include multiplicative
 suffixes or be given in hexadecimal. See the "NUMERIC ARGUMENTS" section
@@ -306,13 +447,13 @@
 /dev/bsg/6:0:0:1 does support cdb sizes greater than 16 bytes since its
 introduction in lk 2.6.28 .
 .SH EXIT STATUS
-The exit status of sg_write_atomic is 0 when it is successful. Otherwise see
+The exit status of sg_write_x is 0 when it is successful. Otherwise see
 the sg3_utils(8) man page.
 .SH EXAMPLES
 One simple usage is to write 4 blocks of zeros from (and including) a given
 LBA:
 .PP
-  sg_write_atomic \-\-in=/dev/zero \-\-lba=0x1234 \-\-num=4 /dev/sdc
+  sg_write_x \-\-in=/dev/zero \-\-lba=0x1234 \-\-num=4 /dev/sdc
 .PP
 Since \fI\-\-bs=BS\fR has not been given, then this utility will call the
 READ CAPACITY(16) command on /dev/sdc to determine the number of bytes in a
diff --git a/include/Makefile.am b/include/Makefile.am
index ba92185..3dc1ef3 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -8,12 +8,14 @@
 	sg_cmds_basic.h \
 	sg_cmds_extra.h \
 	sg_cmds_mmc.h \
-	sg_pt.h
+	sg_pt.h \
+	sg_pt_nvme.h
 
 if OS_LINUX
 scsiinclude_HEADERS += \
 	sg_linux_inc.h \
-	sg_io_linux.h
+	sg_io_linux.h \
+	sg_pt_linux.h
 	
 noinst_HEADERS = \
 	sg_pt_win32.h
diff --git a/include/Makefile.in b/include/Makefile.in
index 08495eb..6d40386 100644
--- a/include/Makefile.in
+++ b/include/Makefile.in
@@ -90,7 +90,8 @@
 host_triplet = @host@
 @OS_LINUX_TRUE@am__append_1 = \
 @OS_LINUX_TRUE@	sg_linux_inc.h \
-@OS_LINUX_TRUE@	sg_io_linux.h
+@OS_LINUX_TRUE@	sg_io_linux.h \
+@OS_LINUX_TRUE@	sg_pt_linux.h
 
 @OS_WIN32_MINGW_TRUE@am__append_2 = sg_pt_win32.h
 @OS_WIN32_CYGWIN_TRUE@am__append_3 = sg_pt_win32.h
@@ -127,7 +128,8 @@
 am__noinst_HEADERS_DIST = sg_linux_inc.h sg_io_linux.h sg_pt_win32.h
 am__scsiinclude_HEADERS_DIST = sg_lib.h sg_lib_data.h sg_cmds.h \
 	sg_cmds_basic.h sg_cmds_extra.h sg_cmds_mmc.h sg_pt.h \
-	sg_linux_inc.h sg_io_linux.h sg_pt_win32.h
+	sg_pt_nvme.h sg_linux_inc.h sg_io_linux.h sg_pt_linux.h \
+	sg_pt_win32.h
 am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
 am__vpath_adj = case $$p in \
     $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
@@ -300,8 +302,8 @@
 top_srcdir = @top_srcdir@
 scsiincludedir = $(includedir)/scsi
 scsiinclude_HEADERS = sg_lib.h sg_lib_data.h sg_cmds.h sg_cmds_basic.h \
-	sg_cmds_extra.h sg_cmds_mmc.h sg_pt.h $(am__append_1) \
-	$(am__append_2) $(am__append_3)
+	sg_cmds_extra.h sg_cmds_mmc.h sg_pt.h sg_pt_nvme.h \
+	$(am__append_1) $(am__append_2) $(am__append_3)
 @OS_FREEBSD_TRUE@noinst_HEADERS = \
 @OS_FREEBSD_TRUE@	sg_linux_inc.h \
 @OS_FREEBSD_TRUE@	sg_io_linux.h \
diff --git a/include/sg_cmds_basic.h b/include/sg_cmds_basic.h
index 499d847..507effa 100644
--- a/include/sg_cmds_basic.h
+++ b/include/sg_cmds_basic.h
@@ -42,7 +42,7 @@
  * an argument to set it has been removed (use the REPORT SUPPORTED OPERATION
  * CODES command instead). Adds the ability to set the command abort timeout
  * and the ability to report the residual count. If timeout_secs is zero
- * or less the the default command abort timeout (60 seconds) is used.
+ * or less the default command abort timeout (60 seconds) is used.
  * If residp is non-NULL then the residual value is written where residp
  * points. A residual value of 0 implies mx_resp_len bytes have be written
  * where resp points. If the residual value equals mx_resp_len then no
diff --git a/include/sg_cmds_extra.h b/include/sg_cmds_extra.h
index 9869ae9..3240506 100644
--- a/include/sg_cmds_extra.h
+++ b/include/sg_cmds_extra.h
@@ -167,7 +167,7 @@
 /* Same as sg_ll_receive_diag() but with added timeout_secs and residp
  * arguments. Adds the ability to set the command abort timeout
  * and the ability to report the residual count. If timeout_secs is zero
- * or less the the default command abort timeout (60 seconds) is used.
+ * or less the default command abort timeout (60 seconds) is used.
  * If residp is non-NULL then the residual value is written where residp
  * points. A residual value of 0 implies mx_resp_len bytes have be written
  * where resp points. If the residual value equals mx_resp_len then no
@@ -210,7 +210,7 @@
                            int verbose);
 
 /* Invokes a SCSI SEND DIAGNOSTIC command. Foreground, extended self tests can
- * take a long time, if so set long_duration flag in which case the timout
+ * take a long time, if so set long_duration flag in which case the timeout
  * is set to 7200 seconds; if the value of long_duration is > 7200 then that
  * value is taken as the timeout value in seconds. Return of 0 -> success,
  * SG_LIB_CAT_INVALID_OP -> Send diagnostic not supported,
diff --git a/include/sg_lib.h b/include/sg_lib.h
index fd64e31..9c45735 100644
--- a/include/sg_lib.h
+++ b/include/sg_lib.h
@@ -504,6 +504,18 @@
  * negative numbers and '-1' must be treated separately. */
 int64_t sg_get_llnum_nomult(const char * buf);
 
+/* Returns pointer to heap (or NULL) that is aligned to a align_to byte
+ * boundary. Sends back *buff_to_free pointer in third argument that may be
+ * different from the return value. If it is different then the *buff_to_free
+ * pointer should be freed (rather than the returned value) when the heap is
+ * no longer needed. If align_to is 0 then aligns to OS's page size. Sets all
+ * returned heap to zeros. If num_bytes is 0 then set to page size. */
+uint8_t * sg_memalign(uint32_t num_bytes, uint32_t align_to,
+                      uint8_t ** buff_to_free, bool vb);
+
+/* Returns OS page size in bytes. If uncertain returns 4096. */
+uint32_t sg_get_page_size(void);
+
 
 /* <<< Architectural support functions [is there a better place?] >>> */
 
diff --git a/include/sg_pt.h b/include/sg_pt.h
index cf185cc..d11bae2 100644
--- a/include/sg_pt.h
+++ b/include/sg_pt.h
@@ -71,7 +71,7 @@
 
 /* Forget any previous dev_fd 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 the same value as get_scsi_pt_os_err()
+ * Returns 0 for success or the same value as get_scsi_pt_os_err()
  * will return. dev_fd should be >= 0 for a valid file handle or -1 . */
 int set_pt_file_handle(struct sg_pt_base * objp, int dev_fd, int verbose);
 
@@ -99,7 +99,7 @@
 /* Set a pointer and length to be used for metadata transferred to
  * (out_true=true) or from (out_true-false) device */
 void set_pt_metadata_xfer(struct sg_pt_base * objp, unsigned char * mdxferp,
-		          uint32_t mdxfer_len, bool out_true);
+                          uint32_t mdxfer_len, bool out_true);
 /* The following "set_"s implementations may be dummies */
 void set_scsi_pt_packet_id(struct sg_pt_base * objp, int pack_id);
 void set_scsi_pt_tag(struct sg_pt_base * objp, uint64_t tag);
@@ -114,7 +114,7 @@
  * are given, use the pass-through default. */
 #define SCSI_PT_FLAGS_QUEUE_AT_TAIL 0x10
 #define SCSI_PT_FLAGS_QUEUE_AT_HEAD 0x20
-/* Set (potentially OS dependant) flags for pass-through mechanism.
+/* Set (potentially OS dependent) flags for pass-through mechanism.
  * Apart from contradictions, flags can be OR-ed together. */
 void set_scsi_pt_flags(struct sg_pt_base * objp, int flags);
 
@@ -185,7 +185,7 @@
 /* Should be invoked once per objp after other processing is complete in
  * order to clean up resources. For ever successful construct_scsi_pt_obj()
  * call there should be one destruct_scsi_pt_obj(). If the
- * construct_scsi_pt_obj_with_fd() function was used to create thsi object
+ * construct_scsi_pt_obj_with_fd() function was used to create this object
  * then the dev_fd provided to that constructor is not altered by this
  * destructor. So the user should still close dev_fd (perhaps with
  * scsi_pt_close_device() ).  */
diff --git a/include/sg_pt_linux.h b/include/sg_pt_linux.h
new file mode 100644
index 0000000..4e68f28
--- /dev/null
+++ b/include/sg_pt_linux.h
@@ -0,0 +1,158 @@
+#ifndef SG_PT_LINUX_H
+#define SG_PT_LINUX_H
+
+/*
+ * Copyright (c) 2017 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.
+ */
+
+#include <stdint.h>
+#include <stdbool.h>
+
+#include <linux/types.h>
+
+#include "sg_pt_nvme.h"
+
+/* This header is for internal use by the sg3_utils library (libsgutils)
+ * and is Linux specific. Best not to include it directly in code that
+ * is meant to be OS independent. */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef HAVE_LINUX_BSG_H
+
+#define BSG_PROTOCOL_SCSI               0
+
+#define BSG_SUB_PROTOCOL_SCSI_CMD       0
+#define BSG_SUB_PROTOCOL_SCSI_TMF       1
+#define BSG_SUB_PROTOCOL_SCSI_TRANSPORT 2
+
+/*
+ * For flag constants below:
+ * sg.h sg_io_hdr also has bits defined for it's flags member. These
+ * two flag values (0x10 and 0x20) have the same meaning in sg.h . For
+ * bsg the BSG_FLAG_Q_AT_HEAD flag is ignored since it is the default.
+ */
+#define BSG_FLAG_Q_AT_TAIL 0x10 /* default is Q_AT_HEAD */
+#define BSG_FLAG_Q_AT_HEAD 0x20
+
+struct sg_io_v4 {
+        __s32 guard;            /* [i] 'Q' to differentiate from v3 */
+        __u32 protocol;         /* [i] 0 -> SCSI , .... */
+        __u32 subprotocol;      /* [i] 0 -> SCSI command, 1 -> SCSI task
+                                   management function, .... */
+
+        __u32 request_len;      /* [i] in bytes */
+        __u64 request;          /* [i], [*i] {SCSI: cdb} */
+        __u64 request_tag;      /* [i] {SCSI: task tag (only if flagged)} */
+        __u32 request_attr;     /* [i] {SCSI: task attribute} */
+        __u32 request_priority; /* [i] {SCSI: task priority} */
+        __u32 request_extra;    /* [i] {spare, for padding} */
+        __u32 max_response_len; /* [i] in bytes */
+        __u64 response;         /* [i], [*o] {SCSI: (auto)sense data} */
+
+        /* "dout_": data out (to device); "din_": data in (from device) */
+        __u32 dout_iovec_count; /* [i] 0 -> "flat" dout transfer else
+                                   dout_xfer points to array of iovec */
+        __u32 dout_xfer_len;    /* [i] bytes to be transferred to device */
+        __u32 din_iovec_count;  /* [i] 0 -> "flat" din transfer */
+        __u32 din_xfer_len;     /* [i] bytes to be transferred from device */
+        __u64 dout_xferp;       /* [i], [*i] */
+        __u64 din_xferp;        /* [i], [*o] */
+
+        __u32 timeout;          /* [i] units: millisecond */
+        __u32 flags;            /* [i] bit mask */
+        __u64 usr_ptr;          /* [i->o] unused internally */
+        __u32 spare_in;         /* [i] */
+
+        __u32 driver_status;    /* [o] 0 -> ok */
+        __u32 transport_status; /* [o] 0 -> ok */
+        __u32 device_status;    /* [o] {SCSI: command completion status} */
+        __u32 retry_delay;      /* [o] {SCSI: status auxiliary information} */
+        __u32 info;             /* [o] additional information */
+        __u32 duration;         /* [o] time to complete, in milliseconds */
+        __u32 response_len;     /* [o] bytes of response actually written */
+        __s32 din_resid;        /* [o] din_xfer_len - actual_din_xfer_len */
+        __s32 dout_resid;       /* [o] dout_xfer_len - actual_dout_xfer_len */
+        __u64 generated_tag;    /* [o] {SCSI: transport generated task tag} */
+        __u32 spare_out;        /* [o] */
+
+        __u32 padding;
+};
+
+#else
+
+#include <linux/bsg.h>
+
+#endif
+
+
+struct sg_pt_linux_scsi {
+    struct sg_io_v4 io_hdr;     /* use v4 header as it is more general */
+    int dev_fd;                 /* -1 if not given (yet) */
+    int in_err;
+    int os_err;
+    unsigned char tmf_request[4];
+    bool is_sg;
+    bool is_bsg;
+    bool is_nvme;
+    bool mdxfer_out;    /* direction of metadata xfer, true->data-out */
+    bool scsi_dsense;   /* SCSI descriptor sense active when true */
+    uint32_t nvme_nsid;         /* 1 to 0xfffffffe are possibly valid, 0
+                                 * implies dev_fd is not a NVMe device
+                                 * (is_nvme=false) or it is a NVMe char
+                                 * device (e.g. /dev/nvme0 ) */
+    uint32_t nvme_result;
+    uint32_t mdxfer_len;
+    void * mdxferp;
+    uint8_t * nvme_id_ctlp;	/* cached response to controller IDENTIFY */
+    uint8_t * free_nvme_id_ctlp;
+};
+
+struct sg_pt_base {
+    struct sg_pt_linux_scsi impl;
+};
+
+
+#ifndef sg_nvme_admin_cmd
+#define sg_nvme_admin_cmd sg_nvme_passthru_cmd
+#endif
+
+/* Linux NVMe related ioctls */
+#ifndef NVME_IOCTL_ID
+#define NVME_IOCTL_ID           _IO('N', 0x40)
+#endif
+#ifndef NVME_IOCTL_ADMIN_CMD
+#define NVME_IOCTL_ADMIN_CMD    _IOWR('N', 0x41, struct sg_nvme_admin_cmd)
+#endif
+#ifndef NVME_IOCTL_SUBMIT_IO
+#define NVME_IOCTL_SUBMIT_IO    _IOW('N', 0x42, struct sg_nvme_user_io)
+#endif
+#ifndef NVME_IOCTL_IO_CMD
+#define NVME_IOCTL_IO_CMD       _IOWR('N', 0x43, struct sg_nvme_passthru_cmd)
+#endif
+#ifndef NVME_IOCTL_RESET
+#define NVME_IOCTL_RESET        _IO('N', 0x44)
+#endif
+#ifndef NVME_IOCTL_SUBSYS_RESET
+#define NVME_IOCTL_SUBSYS_RESET _IO('N', 0x45)
+#endif
+
+extern bool sg_bsg_nvme_char_major_checked;
+extern int sg_bsg_major;
+extern volatile int sg_nvme_char_major;
+extern long sg_lin_page_size;
+
+void sg_find_bsg_nvme_char_major(int verbose);
+int sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif          /* end of SG_PT_LINUX_H */
diff --git a/include/sg_pt_nvme.h b/include/sg_pt_nvme.h
index 89cee29..0586210 100644
--- a/include/sg_pt_nvme.h
+++ b/include/sg_pt_nvme.h
@@ -47,6 +47,21 @@
 ;
 #endif
 
+/* Using byte offsets and unaligned be/le copies safer than packed
+ * structures. These are for sg_nvme_user_io . */
+#define SG_NVME_IO_OPCODE 0
+#define SG_NVME_IO_FLAGS 1
+#define SG_NVME_IO_CONTROL 2
+#define SG_NVME_IO_NBLOCKS 4
+#define SG_NVME_IO_RSVD 6
+#define SG_NVME_IO_METADATA 8
+#define SG_NVME_IO_ADDR 16
+#define SG_NVME_IO_SLBA 24
+#define SG_NVME_IO_DSMGMT 32
+#define SG_NVME_IO_REFTAG 36
+#define SG_NVME_IO_APPTAG 40
+#define SG_NVME_IO_APPMASK 42
+
 #ifdef __GNUC__
 #ifndef __clang__
   struct __attribute__((__packed__)) sg_nvme_passthru_cmd
@@ -83,6 +98,28 @@
 ;
 #endif
 
+/* Using byte offsets and unaligned be/le copies safer than packed
+ * structures. These are for sg_nvme_passthru_cmd . */
+#define SG_NVME_PT_OPCODE 0
+#define SG_NVME_PT_FLAGS 1
+#define SG_NVME_PT_RSVD1 2
+#define SG_NVME_PT_NSID 4
+#define SG_NVME_PT_CDW2 8
+#define SG_NVME_PT_CDW3 12
+#define SG_NVME_PT_METADATA 16
+#define SG_NVME_PT_ADDR 24
+#define SG_NVME_PT_METADATA_LEN 32
+#define SG_NVME_PT_DATA_LEN 36
+#define SG_NVME_PT_CDW10 40
+#define SG_NVME_PT_CDW11 44
+#define SG_NVME_PT_CDW12 48
+#define SG_NVME_PT_CDW13 52
+#define SG_NVME_PT_CDW14 56
+#define SG_NVME_PT_CDW15 60
+
+#define SG_NVME_PT_TIMEOUT_MS 64
+#define SG_NVME_PT_RESULT 68
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/lib/Makefile.am b/lib/Makefile.am
index 84202dc..24d3c95 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -10,7 +10,8 @@
 if OS_LINUX
 libsgutils2_la_SOURCES += \
 	sg_pt_linux.c \
-	sg_io_linux.c
+	sg_io_linux.c \
+	sg_pt_linux_nvme.c
 endif
 
 if OS_WIN32_MINGW
diff --git a/lib/Makefile.in b/lib/Makefile.in
index fa695ce..a06aa38 100644
--- a/lib/Makefile.in
+++ b/lib/Makefile.in
@@ -90,7 +90,8 @@
 host_triplet = @host@
 @OS_LINUX_TRUE@am__append_1 = \
 @OS_LINUX_TRUE@	sg_pt_linux.c \
-@OS_LINUX_TRUE@	sg_io_linux.c
+@OS_LINUX_TRUE@	sg_io_linux.c \
+@OS_LINUX_TRUE@	sg_pt_linux_nvme.c
 
 @OS_WIN32_MINGW_TRUE@am__append_2 = sg_pt_win32.c
 @OS_WIN32_CYGWIN_TRUE@am__append_3 = sg_pt_win32.c
@@ -138,9 +139,10 @@
 LTLIBRARIES = $(lib_LTLIBRARIES)
 am__libsgutils2_la_SOURCES_DIST = sg_lib.c sg_lib_data.c \
 	sg_cmds_basic.c sg_cmds_basic2.c sg_cmds_extra.c sg_cmds_mmc.c \
-	sg_pt_common.c sg_pt_linux.c sg_io_linux.c sg_pt_win32.c \
-	sg_pt_freebsd.c sg_pt_solaris.c sg_pt_osf1.c
-@OS_LINUX_TRUE@am__objects_1 = sg_pt_linux.lo sg_io_linux.lo
+	sg_pt_common.c sg_pt_linux.c sg_io_linux.c sg_pt_linux_nvme.c \
+	sg_pt_win32.c sg_pt_freebsd.c sg_pt_solaris.c sg_pt_osf1.c
+@OS_LINUX_TRUE@am__objects_1 = sg_pt_linux.lo sg_io_linux.lo \
+@OS_LINUX_TRUE@	sg_pt_linux_nvme.lo
 @OS_WIN32_MINGW_TRUE@am__objects_2 = sg_pt_win32.lo
 @OS_WIN32_CYGWIN_TRUE@am__objects_3 = sg_pt_win32.lo
 @OS_FREEBSD_TRUE@am__objects_4 = sg_pt_freebsd.lo
@@ -448,6 +450,7 @@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_common.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_freebsd.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_linux.Plo@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_linux_nvme.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_osf1.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_solaris.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/sg_pt_win32.Plo@am__quote@
diff --git a/lib/sg_lib.c b/lib/sg_lib.c
index d6c011a..d4549b7 100644
--- a/lib/sg_lib.c
+++ b/lib/sg_lib.c
@@ -3025,6 +3025,91 @@
     return op - ochars;
 }
 
+int
+pr2serr(const char * fmt, ...)
+{
+    va_list args;
+    int n;
+
+    va_start(args, fmt);
+    n = vfprintf(stderr, fmt, args);
+    va_end(args);
+    return n;
+}
+
+uint32_t
+sg_get_page_size(void)
+{
+#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
+    return sysconf(_SC_PAGESIZE); /* POSIX.1 (was getpagesize()) */
+#elif defined(SG_LIB_WIN32)
+    return win_pagesize();
+#elif defined(SG_LIB_FREEBSD)
+#include <sys/param.h>
+    return PAGE_SIZE;
+#else
+    return 4096;     /* give up, pick likely figure */
+#endif
+}
+
+/* Returns pointer to heap (or NULL) that is aligned to a align_to byte
+ * boundary. Sends back *buff_to_free pointer in third argument that may be
+ * different from the return value. If it is different then the *buff_to_free
+ * pointer should be freed (rather than the returned value) when the heap is
+ * no longer needed. If align_to is 0 then aligns to OS's page size. Sets all
+ * returned heap to zeros. If num_bytes is 0 then set to page size. */
+uint8_t *
+sg_memalign(uint32_t num_bytes, uint32_t align_to, uint8_t ** buff_to_free,
+            bool vb)
+{
+    size_t psz;
+    uint8_t * res;
+
+    psz = (align_to > 0) ? align_to : sg_get_page_size();
+    if (0 == num_bytes)
+        num_bytes = psz;        /* ugly to handle otherwise */
+
+#ifdef HAVE_POSIX_MEMALIGN
+    {
+        int err;
+        void * wp = NULL;
+
+        err = posix_memalign(&wp, psz, num_bytes);
+        if (err || (NULL == wp)) {
+            pr2ws("%s: posix_memalign: error [%d], out of memory?\n",
+                  __func__, err);
+            return NULL;
+        }
+        memset(wp, 0, num_bytes);
+        if (buff_to_free)
+            *buff_to_free = (uint8_t *)wp;
+        res = (uint8_t *)wp;
+        if (vb)
+            pr2ws("%s: posix, len=%d, wrkBuffp=%p, psz=%d, rp=%p\n",
+                  __func__, num_bytes, (void *)*buff_to_free, (int)psz,
+                  (void *)res);
+        return res;
+    }
+#else
+    {
+        uint8_t * wrkBuff;
+
+        wrkBuff = (uint8_t)calloc(length + psz, 1);
+        if (NULL == wrkBuff) {
+            if (buff_to_free)
+                *buff_to_free = NULL;
+            return NULL;
+        } else if (buff_to_free)
+            *buff_to_free = wrkBuff;
+        res = (uint8_t *)(((uintptr_t)wrkBuff + psz - 1) & (~(psz - 1)));
+        if (vb)
+            pr2ws("%s: hack, len=%d, wrkBuffp=%p, psz=%d, rp=%p\n", __func__,
+                  length, (void *)*wrkBuffp, (int)psz, (void *)res);
+        return res;
+    }
+#endif
+}
+
 const char *
 sg_lib_version()
 {
@@ -3069,15 +3154,3 @@
 }
 
 #endif
-
-int
-pr2serr(const char * fmt, ...)
-{
-    va_list args;
-    int n;
-
-    va_start(args, fmt);
-    n = vfprintf(stderr, fmt, args);
-    va_end(args);
-    return n;
-}
diff --git a/lib/sg_lib_data.c b/lib/sg_lib_data.c
index 535d3a0..ce2b9f0 100644
--- a/lib/sg_lib_data.c
+++ b/lib/sg_lib_data.c
@@ -17,7 +17,7 @@
 #endif
 
 
-const char * sg_lib_version_str = "2.32 20171127";/* spc5r17, sbc4r15 */
+const char * sg_lib_version_str = "2.33 20171205";/* spc5r17, sbc4r15 */
 
 
 /* indexed by pdt; those that map to own index do not decay */
@@ -432,8 +432,8 @@
 struct sg_lib_value_name_t sg_lib_variable_length_arr[] = {
     {0x1, 0, "Rebuild(32)"},
     {0x2, 0, "Regenerate(32)"},
-    {0x3, 0, "Xdread(32)"},     /* obsolete in SBC-3 r31 */
-    {0x4, 0, "Xdwrite(32)"},    /* obsolete in SBC-3 r31 */
+    {0x3, 0, "Xdread(32)"},             /* obsolete in SBC-3 r31 */
+    {0x4, 0, "Xdwrite(32)"},            /* obsolete in SBC-3 r31 */
     {0x5, 0, "Xdwrite extended(32)"},   /* obsolete in SBC-4 r15 */
     {0x6, 0, "Xpwrite(32)"},            /* obsolete in SBC-4 r15 */
     {0x7, 0, "Xdwriteread(32)"},        /* obsolete in SBC-4 r15 */
diff --git a/lib/sg_pt_linux.c b/lib/sg_pt_linux.c
index 7433b86..4ddf899 100644
--- a/lib/sg_pt_linux.c
+++ b/lib/sg_pt_linux.c
@@ -5,7 +5,7 @@
  * license that can be found in the BSD_LICENSE file.
  */
 
-/* sg_pt_linux version 1.30 20171113 */
+/* sg_pt_linux version 1.31 20171203 */
 
 
 #include <stdio.h>
@@ -34,88 +34,28 @@
 #include "sg_pt.h"
 #include "sg_lib.h"
 #include "sg_linux_inc.h"
-#include "sg_pt_nvme.h"
+#include "sg_pt_linux.h"
 
 #if (__STDC_VERSION__ >= 199901L)  /* C99 or later */
-typedef intptr_t sg_intptr_t;
+typedef uintptr_t sg_uintptr_t;
 #else
-typedef long sg_intptr_t;
+typedef unsigned long sg_uintptr_t;
 #endif
 
+/* Checking CDB for whether it is a SCSI or NVME command: all NVME coomands
+ * are 64 bytes long. There are very few standardized SCSI commands with
+ * a cdb of 64 bytes. If one does then byte 0 (in the CDB) must contain
+ * 7Fh and at byte offset 7 the value must be 56 (Additional CDB length)a */
+#if 0
 // xxxxxxxxxxxxxxxx testing <<<<<<<<<<<<<<<<<<<<<<<<
-// #undef HAVE_LINUX_NVME_IOCTL_H
+#undef HAVE_LINUX_NVME_IOCTL_H
 
 #ifdef HAVE_LINUX_NVME_IOCTL_H
 #include <linux/nvme_ioctl.h>
 #else
-
-/*
- * Definitions for the NVM Express ioctl interface
- * Copyright (c) 2011-2014, Intel Corporation.
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms and conditions of the GNU General Public License,
- * version 2, as published by the Free Software Foundation.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
- * more details.
- */
-
-#if 0
-#include <linux/types.h>
-
-struct nvme_user_io {
-        __u8    opcode;
-        __u8    flags;
-        __u16   control;
-        __u16   nblocks;
-        __u16   rsvd;
-        __u64   metadata;
-        __u64   addr;
-        __u64   slba;
-        __u32   dsmgmt;
-        __u32   reftag;
-        __u16   apptag;
-        __u16   appmask;
-};
-
-struct nvme_passthru_cmd {
-        __u8    opcode;
-        __u8    flags;
-        __u16   rsvd1;
-        __u32   nsid;
-        __u32   cdw2;
-        __u32   cdw3;
-        __u64   metadata;
-        __u64   addr;
-        __u32   metadata_len;
-        __u32   data_len;
-        __u32   cdw10;
-        __u32   cdw11;
-        __u32   cdw12;
-        __u32   cdw13;
-        __u32   cdw14;
-        __u32   cdw15;
-        __u32   timeout_ms;
-        __u32   result;
-};
+#include "sg_pt_linux.h"
 #endif
-
-#define nvme_admin_cmd nvme_passthru_cmd
-
-#define NVME_IOCTL_ID           _IO('N', 0x40)
-#define NVME_IOCTL_ADMIN_CMD    _IOWR('N', 0x41, struct nvme_admin_cmd)
-#define NVME_IOCTL_SUBMIT_IO    _IOW('N', 0x42, struct nvme_user_io)
-#define NVME_IOCTL_IO_CMD       _IOWR('N', 0x43, struct nvme_passthru_cmd)
-#define NVME_IOCTL_RESET        _IO('N', 0x44)
-#define NVME_IOCTL_SUBSYS_RESET _IO('N', 0x45)
-
-#endif  /* end of HAVE_LINUX_NVME_IOCTL_H */
-
-#include <linux/types.h>
-#include <linux/bsg.h>
+#endif
 
 #ifdef major
 #define SG_DEV_MAJOR major
@@ -188,9 +128,11 @@
 #define SG_LIB_SUGGEST_MASK     SUGGEST_MASK
 #define SG_LIB_DRIVER_SENSE    DRIVER_SENSE
 
-static bool bsg_nvme_char_major_checked = false;
-static int bsg_major = 0;
-static volatile int nvme_char_major = 0;
+bool sg_bsg_nvme_char_major_checked = false;
+int sg_bsg_major = 0;
+volatile int sg_nvme_char_major = 0;
+
+long sg_lin_page_size = 4096;   /* default, overridden with correct value */
 
 
 #if defined(__GNUC__) || defined(__clang__)
@@ -216,8 +158,8 @@
 /* This function only needs to be called once (unless a NVMe controller
  * can be hot-plugged into system in which case it should be called
  * (again) after that event). */
-static void
-find_bsg_nvme_char_major(int verbose)
+void
+sg_find_bsg_nvme_char_major(int verbose)
 {
     bool got_one = false;
     int n;
@@ -227,6 +169,7 @@
     char a[128];
     char b[128];
 
+    sg_lin_page_size = sysconf(_SC_PAGESIZE);
     if (NULL == (fp = fopen(proc_devices, "r"))) {
         if (verbose)
             pr2ws("fopen %s failed: %s\n", proc_devices, strerror(errno));
@@ -240,12 +183,12 @@
     while (cp && (cp = fgets(b, sizeof(b), fp))) {
         if (2 == sscanf(b, "%d %126s", &n, a)) {
             if (0 == strcmp("bsg", a)) {
-                bsg_major = n;
+                sg_bsg_major = n;
                 if (got_one)
                     break;
                 got_one = true;
             } else if (0 == strcmp("nvme", a)) {
-                nvme_char_major = n;
+                sg_nvme_char_major = n;
                 if (got_one)
                     break;
                 got_one = true;
@@ -255,17 +198,17 @@
     }
     if (verbose > 3) {
         if (cp) {
-            if (bsg_major > 0)
-                pr2ws("found bsg_major=%d\n", bsg_major);
-            if (nvme_char_major > 0)
-                pr2ws("found nvme_char_major=%d\n", nvme_char_major);
+            if (sg_bsg_major > 0)
+                pr2ws("found sg_bsg_major=%d\n", sg_bsg_major);
+            if (sg_nvme_char_major > 0)
+                pr2ws("found sg_nvme_char_major=%d\n", sg_nvme_char_major);
         } else
             pr2ws("found no bsg not nvme char device in %s\n", proc_devices);
     }
     fclose(fp);
 }
 
-/* Assumes that find_bsg_nvme_char_major() has already been called. Returns
+/* Assumes that sg_find_bsg_nvme_char_major() has already been called. Returns
  * true if dev_fd is a scsi generic pass-through device. If yields
  * *is_nvme_p = true with *nsid_p = 0 then dev_fd is a NVMe char device.
  * If yields *nsid_p > 0 then dev_fd is a NVMe block device. */
@@ -294,9 +237,9 @@
         if (S_ISCHR(dev_statp->st_mode)) {
             if (SCSI_GENERIC_MAJOR == major_num)
                 is_sg = true;
-            else if (bsg_major == major_num)
+            else if (sg_bsg_major == major_num)
                 is_bsg = true;
-            else if (nvme_char_major == major_num)
+            else if (sg_nvme_char_major == major_num)
                 is_nvme = true;
         } else if (S_ISBLK(dev_statp->st_mode)) {
             is_block = true;
@@ -361,9 +304,9 @@
         pr2ws("%s: dev_fd=%d, device_name: %s\n", __func__, dev_fd,
               device_name);
     /* Linux doesn't need device_name to determine which pass-through */
-    if (! bsg_nvme_char_major_checked) {
-        bsg_nvme_char_major_checked = true;
-        find_bsg_nvme_char_major(verbose);
+    if (! sg_bsg_nvme_char_major_checked) {
+        sg_bsg_nvme_char_major_checked = true;
+        sg_find_bsg_nvme_char_major(verbose);
     }
     if (dev_fd >= 0) {
         bool is_sg, is_bsg, is_nvme;
@@ -389,614 +332,11 @@
         return 0;
 }
 
-// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
-#if defined(IGNORE_LINUX_BSG) || ! defined(HAVE_LINUX_BSG_H)
 /*
- * sg(v3) via SG_IO ioctl on a sg node or other node that accepts that ioctl.
- * Decision has been made at compile time because either:
- *   a) no /usr/include/linux/bsg.h header file was found, or
- *   b) the builder gave the '--enable-no-linux-bsg' option to ./configure
- */
-
-
-struct sg_pt_linux_scsi {
-    struct sg_io_hdr io_hdr;
-    int dev_fd;                 /* -1 if not given */
-    int in_err;
-    int os_err;
-    bool is_sg;         /* is_sg,is_nvme: (F,F)-->unknown; (T,F)-->sg; */
-    bool is_nvme;       /* (F,T)-->nvme; (T,T)-->illegal */
-    bool mdxfer_out;    /* direction of metadata xfer, true->data-out */
-    uint32_t nvme_nsid;
-    uint32_t nvme_result;
-    uint32_t dxfer_ilen;
-    uint32_t dxfer_olen;
-    uint32_t mdxfer_len;
-    void * dxferip;
-    void * dxferop;
-    void * mdxferp;
-};
-
-struct sg_pt_base {
-    struct sg_pt_linux_scsi impl;
-};
-
-
-
-/* Returns >= 0 if successful. If error in Unix returns negated errno. */
-int
-scsi_pt_open_device(const char * device_name, bool read_only, int verbose)
-{
-    int oflags = O_NONBLOCK;
-
-    oflags |= (read_only ? O_RDONLY : O_RDWR);
-    return scsi_pt_open_flags(device_name, oflags, verbose);
-}
-
-/* Similar to scsi_pt_open_device() but takes Unix style open flags OR-ed */
-/* together. The 'flags' argument is advisory and may be ignored. */
-/* Returns >= 0 if successful, otherwise returns negated errno. */
-int
-scsi_pt_open_flags(const char * device_name, int flags, int verbose)
-{
-    int fd;
-
-    if (verbose > 1) {
-        pr2ws("open %s with flags=0x%x\n", device_name, flags);
-    }
-    if (! bsg_nvme_char_major_checked) {
-        bsg_nvme_char_major_checked = true;
-        find_bsg_nvme_char_major(verbose);
-    }
-    fd = open(device_name, flags);
-    if (fd < 0)
-        fd = -errno;
-    return fd;
-}
-
-/* Returns 0 if successful. If error in Unix returns negated errno. */
-int
-scsi_pt_close_device(int device_fd)
-{
-    int res;
-
-    res = close(device_fd);
-    if (res < 0)
-        res = -errno;
-    return res;
-}
-
-/* Caller should additionally call get_scsi_pt_os_err() after this call */
-struct sg_pt_base *
-construct_scsi_pt_obj_with_fd(int dev_fd, int verbose)
-{
-    int err;
-    struct sg_pt_linux_scsi * ptp;
-
-    /* The following 2 lines are temporary. It is to avoid a NULL pointer
-     * crash when an old utility is used with a newer library built after
-     * the sg_warnings_strm cleanup */
-    if (NULL == sg_warnings_strm)
-        sg_warnings_strm = stderr;
-
-    ptp = (struct sg_pt_linux_scsi *)
-          calloc(1, sizeof(struct sg_pt_linux_scsi));
-    if (ptp) {
-        err = set_pt_file_handle((struct sg_pt_base *)ptp, dev_fd, verbose);
-        if ((0 == err) && (! ptp->is_nvme)) {
-            ptp->io_hdr.interface_id = 'S';
-            ptp->io_hdr.dxfer_direction = SG_DXFER_NONE;
-        }
-    } else if (verbose)
-        pr2ws("%s: calloc() failed, out of memory?\n", __func__);
-
-    return (struct sg_pt_base *)ptp;
-}
-
-struct sg_pt_base *
-construct_scsi_pt_obj()
-{
-    return construct_scsi_pt_obj_with_fd(-1 /* dev_fd */, 0 /* verbose */);
-}
-
-void
-destruct_scsi_pt_obj(struct sg_pt_base * vp)
-{
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    if (ptp)
-        free(ptp);
-}
-
-/* Remembers previous device file descriptor */
-void
-clear_scsi_pt_obj(struct sg_pt_base * vp)
-{
-    bool is_sg, is_nvme;
-    int fd, nvme_nsid;
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    if (ptp) {
-        fd = ptp->dev_fd;
-        is_sg = ptp->is_sg;
-        is_nvme = ptp->is_nvme;
-        nvme_nsid = ptp->nvme_nsid;
-        memset(ptp, 0, sizeof(struct sg_pt_linux_scsi));
-        ptp->io_hdr.interface_id = 'S';
-        ptp->io_hdr.dxfer_direction = SG_DXFER_NONE;
-        ptp->dev_fd = fd;
-        ptp->is_sg = is_sg;
-        ptp->is_nvme = is_nvme;
-        ptp->nvme_nsid = nvme_nsid;
-    }
-}
-
-/* Forget any previous dev_fd 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 the same value as get_scsi_pt_os_err()
- * will return. dev_fd should be >= 0 for a valid file handle or -1 . */
-int
-set_pt_file_handle(struct sg_pt_base * vp, int dev_fd, int verbose)
-{
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-    struct stat a_stat;
-
-    if (! bsg_nvme_char_major_checked) {
-        bsg_nvme_char_major_checked = true;
-        find_bsg_nvme_char_major(verbose);
-    }
-    ptp->dev_fd = dev_fd;
-    if (dev_fd >= 0)
-        ptp->is_sg = check_file_type(dev_fd, &a_stat, NULL, &ptp->is_nvme,
-                                     &ptp->nvme_nsid, &ptp->os_err, verbose);
-    else {
-        ptp->is_sg = false;
-        ptp->is_nvme = false;
-        ptp->nvme_nsid = 0;
-        ptp->os_err = 0;
-    }
-    return ptp->os_err;
-}
-
-/* Valid file handles (which is the return value) are >= 0 . Returns -1
- * if there is no valid file handle. */
-int
-get_pt_file_handle(const struct sg_pt_base * vp)
-{
-    const struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    return ptp->dev_fd;
-}
-
-void
-set_scsi_pt_cdb(struct sg_pt_base * vp, const unsigned char * cdb,
-                int cdb_len)
-{
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    if (ptp->io_hdr.cmdp)
-        ++ptp->in_err;
-    ptp->io_hdr.cmdp = (unsigned char *)cdb;
-    ptp->io_hdr.cmd_len = cdb_len;
-}
-
-void
-set_scsi_pt_sense(struct sg_pt_base * vp, unsigned char * sense,
-                  int max_sense_len)
-{
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    if (ptp->io_hdr.sbp)
-        ++ptp->in_err;
-    memset(sense, 0, max_sense_len);
-    ptp->io_hdr.sbp = sense;
-    ptp->io_hdr.mx_sb_len = max_sense_len;
-}
-
-/* Setup for data transfer from device */
-void
-set_scsi_pt_data_in(struct sg_pt_base * vp, unsigned char * dxferp,
-                    int dxfer_ilen)
-{
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    if (ptp->dxferip)
-        ++ptp->in_err;
-    if (dxfer_ilen > 0) {
-        ptp->io_hdr.dxferp = dxferp;
-        ptp->dxferip = dxferp;
-        ptp->io_hdr.dxfer_len = dxfer_ilen;
-        ptp->dxfer_ilen = dxfer_ilen;
-        ptp->io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
-    }
-}
-
-/* Setup for data transfer toward device */
-void
-set_scsi_pt_data_out(struct sg_pt_base * vp, const unsigned char * dxferp,
-                     int dxfer_olen)
-{
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    if (ptp->dxferop)
-        ++ptp->in_err;
-    if (dxfer_olen > 0) {
-        ptp->io_hdr.dxferp = (unsigned char *)dxferp;
-        ptp->dxferop = (void *)dxferp;
-        ptp->io_hdr.dxfer_len = dxfer_olen;
-        ptp->dxfer_olen = dxfer_olen;
-        ptp->io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
-    }
-}
-
-void
-set_pt_metadata_xfer(struct sg_pt_base * vp, unsigned char * dxferp,
-                     uint32_t dxfer_len, bool out_true)
-{
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    if (ptp->mdxferp)
-        ++ptp->in_err;
-    if (dxfer_len > 0) {
-        ptp->mdxferp = dxferp;
-        ptp->mdxfer_len = dxfer_len;
-        ptp->mdxfer_out = out_true;
-    }
-}
-
-void
-set_scsi_pt_packet_id(struct sg_pt_base * vp, int pack_id)
-{
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    ptp->io_hdr.pack_id = pack_id;
-}
-
-void
-set_scsi_pt_tag(struct sg_pt_base * vp, uint64_t tag)
-{
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    ++ptp->in_err;
-    if (tag) { ; }     /* unused, suppress warning */
-}
-
-/* Note that task management function codes are transport specific */
-void
-set_scsi_pt_task_management(struct sg_pt_base * vp, int tmf_code)
-{
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    ++ptp->in_err;
-    if (tmf_code) { ; }     /* unused, suppress warning */
-}
-
-void
-set_scsi_pt_task_attr(struct sg_pt_base * vp, int attribute, int priority)
-{
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    ++ptp->in_err;
-    if (attribute) { ; }     /* unused, suppress warning */
-    if (priority) { ; }      /* unused, suppress warning */
-}
-
-#ifndef SG_FLAG_Q_AT_TAIL
-#define SG_FLAG_Q_AT_TAIL 0x10
-#endif
-#ifndef SG_FLAG_Q_AT_HEAD
-#define SG_FLAG_Q_AT_HEAD 0x20
-#endif
-
-void
-set_scsi_pt_flags(struct sg_pt_base * vp, int flags)
-{
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    /* default action of sg driver [sg v3 interface] is QUEUE_AT_HEAD */
-    /* default action of block layer SG_IO ioctl is QUEUE_AT_TAIL */
-    if (SCSI_PT_FLAGS_QUEUE_AT_HEAD & flags) {  /* favour AT_HEAD */
-        ptp->io_hdr.flags |= SG_FLAG_Q_AT_HEAD;
-        ptp->io_hdr.flags &= ~SG_FLAG_Q_AT_TAIL;
-    } else if (SCSI_PT_FLAGS_QUEUE_AT_TAIL & flags) {
-        ptp->io_hdr.flags |= SG_FLAG_Q_AT_TAIL;
-        ptp->io_hdr.flags &= ~SG_FLAG_Q_AT_HEAD;
-    }
-}
-
-/* Executes NVMe Admin command (or at least forwards it to lower layers).
- * Returns 0 for success, negative numbers are negated 'errno' values from
- * OS system calls. Positive return values are errors from this package.
- * When time_secs is 0 the Linux NVMe Admin command default of 60 seconds
- * is used. */
-int
-do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb)
-{
-    int n, len;
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-    struct sg_nvme_passthru_cmd cmd;
-
-    if (vb > 3)
-        pr2ws("%s: fd=%d, time_secs=%d\n", __func__, fd, time_secs);
-    if (! ptp->io_hdr.cmdp) {
-        if (vb)
-            pr2ws("No NVMe command given (set_scsi_pt_cdb())\n");
-        return SCSI_PT_DO_BAD_PARAMS;
-    }
-    n = ptp->io_hdr.cmd_len;
-    len = (int)sizeof(cmd);
-    n = (n < len) ? n : len;
-    if (n < 8) {
-        if (vb)
-            pr2ws("%s: command length of %d bytes is too short\n", __func__,
-                  n);
-        return SCSI_PT_DO_BAD_PARAMS;
-    }
-    memcpy(&cmd, (unsigned char *)ptp->io_hdr.cmdp, n);
-    if (n < len)        /* zero out rest of 'cmd' */
-        memset((unsigned char *)&cmd + n, 0, len - n);
-    if (ptp->io_hdr.dxfer_len > 0) {
-        cmd.data_len = ptp->io_hdr.dxfer_len;
-        cmd.addr = (__u64)(sg_intptr_t)ptp->io_hdr.dxferp;
-    }
-    if (time_secs < 0)
-        cmd.timeout_ms = 0;
-    else
-        cmd.timeout_ms = 1000 * cmd.timeout_ms;
-    if (vb > 2) {
-        pr2ws("NVMe command:\n");
-        dStrHex((const char *)&cmd, len, 1);
-    }
-    if (ioctl(ptp->dev_fd, NVME_IOCTL_ADMIN_CMD, &cmd) < 0) {
-        ptp->os_err = errno;
-        if (vb > 2)
-            pr2ws("%s: ioctl(NVME_IOCTL_ADMIN_CMD) failed: %s (errno=%d)\n",
-                  __func__, strerror(ptp->os_err), ptp->os_err);
-        return -ptp->os_err;
-    } else
-        ptp->os_err = 0;
-    n = ptp->io_hdr.mx_sb_len;
-    if ((n > 0) && ptp->io_hdr.sbp) {
-        n = (n < len) ? n : len;
-        memcpy(ptp->io_hdr.sbp, &cmd, n);
-        ptp->io_hdr.sb_len_wr = n;
-    } else
-        ptp->io_hdr.sb_len_wr = 0;
-    ptp->nvme_result = cmd.result;
-    if (vb > 2)
-        pr2ws("%s: timeout_ms=%u, result=%u\n", __func__, cmd.timeout_ms,
-              ptp->nvme_result);
-    return 0;
-}
-
-/* Executes SCSI command (or at least forwards it to lower layers).
- * Returns 0 for success, negative numbers are negated 'errno' values from
- * OS system calls. Positive return values are errors from this package. */
-int
-do_scsi_pt(struct sg_pt_base * vp, int fd, int time_secs, int verbose)
-{
-    int err;
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-    bool have_checked_for_type = (ptp->dev_fd >= 0);
-
-    ptp->os_err = 0;
-    if (ptp->in_err) {
-        if (verbose)
-            pr2ws("Replicated or unused set_scsi_pt... functions\n");
-        return SCSI_PT_DO_BAD_PARAMS;
-    }
-    if (fd >= 0) {
-        if ((ptp->dev_fd >= 0) && (fd != ptp->dev_fd)) {
-            if (verbose)
-                pr2ws("%s: file descriptor given to create() and here "
-                      "differ\n", __func__);
-            return SCSI_PT_DO_BAD_PARAMS;
-        }
-        ptp->dev_fd = fd;
-    } else if (ptp->dev_fd < 0) {
-        if (verbose)
-            pr2ws("%s: invalid file descriptors\n", __func__);
-        return SCSI_PT_DO_BAD_PARAMS;
-    }
-    if (! have_checked_for_type) {
-        err = set_pt_file_handle(vp, ptp->dev_fd, verbose);
-        if (err)
-            return -ptp->os_err;
-    }
-    if (ptp->is_nvme)
-        return do_nvme_pt(vp, ptp->dev_fd, time_secs, verbose);
-    if (NULL == ptp->io_hdr.cmdp) {
-        if (verbose)
-            pr2ws("No SCSI command (cdb) given\n");
-        return SCSI_PT_DO_BAD_PARAMS;
-    }
-    /* io_hdr.timeout is in milliseconds */
-    ptp->io_hdr.timeout = ((time_secs > 0) ? (time_secs * 1000) :
-                                             DEF_TIMEOUT);
-    if (ptp->io_hdr.sbp && (ptp->io_hdr.mx_sb_len > 0))
-        memset(ptp->io_hdr.sbp, 0, ptp->io_hdr.mx_sb_len);
-    if (ioctl(ptp->dev_fd, SG_IO, &ptp->io_hdr) < 0) {
-        ptp->os_err = errno;
-        if (verbose > 1)
-            pr2ws("ioctl(SG_IO) failed: %s (errno=%d)\n",
-                  strerror(ptp->os_err), ptp->os_err);
-        return -ptp->os_err;
-    }
-    return 0;
-}
-
-int
-get_scsi_pt_result_category(const struct sg_pt_base * vp)
-{
-    const struct sg_pt_linux_scsi * ptp = &vp->impl;
-    int dr_st = ptp->io_hdr.driver_status & SG_LIB_DRIVER_MASK;
-    int scsi_st = ptp->io_hdr.status & 0x7e;
-
-    if (ptp->os_err)
-        return SCSI_PT_RESULT_OS_ERR;
-    else if (ptp->io_hdr.host_status)
-        return SCSI_PT_RESULT_TRANSPORT_ERR;
-    else if (dr_st && (SG_LIB_DRIVER_SENSE != dr_st))
-        return SCSI_PT_RESULT_TRANSPORT_ERR;
-    else if ((SG_LIB_DRIVER_SENSE == dr_st) ||
-             (SAM_STAT_CHECK_CONDITION == scsi_st) ||
-             (SAM_STAT_COMMAND_TERMINATED == scsi_st))
-        return SCSI_PT_RESULT_SENSE;
-    else if (scsi_st)
-        return SCSI_PT_RESULT_STATUS;
-    else
-        return SCSI_PT_RESULT_GOOD;
-}
-
-int
-get_scsi_pt_resid(const struct sg_pt_base * vp)
-{
-    const struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    return ((NULL == ptp) || ptp->is_nvme) ? 0 : ptp->io_hdr.resid;
-}
-
-int
-get_scsi_pt_status_response(const struct sg_pt_base * vp)
-{
-    const struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    if (NULL == ptp)
-        return 0;
-    return ptp->is_nvme ? (int)ptp->nvme_result : ptp->io_hdr.status;
-}
-
-uint32_t
-get_pt_result(const struct sg_pt_base * vp)
-{
-    const struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    if (NULL == ptp)
-        return 0;
-    return ptp->is_nvme ? ptp->nvme_result : (uint32_t)ptp->io_hdr.status;
-}
-
-int
-get_scsi_pt_sense_len(const struct sg_pt_base * vp)
-{
-    const struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    if (NULL == ptp)
-        return 0;
-    return ptp->io_hdr.sb_len_wr;       /* NVMe stuffs that variable */
-}
-
-int
-get_scsi_pt_duration_ms(const struct sg_pt_base * vp)
-{
-    const struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    return ptp->io_hdr.duration;
-}
-
-int
-get_scsi_pt_transport_err(const struct sg_pt_base * vp)
-{
-    const struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    return (ptp->io_hdr.host_status << 8) + ptp->io_hdr.driver_status;
-}
-
-int
-get_scsi_pt_os_err(const struct sg_pt_base * vp)
-{
-    const struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    return ptp->os_err;
-}
-
-bool
-pt_device_is_nvme(const struct sg_pt_base * vp)
-{
-    const struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    return ptp->is_nvme;
-}
-
-/* If a NVMe block device (which includes the NSID) handle is associated
- * with 'vp', then its NSID is returned (values range from 0x1 to
- * 0xffffffe). Otherwise 0 is returned. */
-uint32_t
-get_pt_nvme_nsid(const struct sg_pt_base * vp)
-{
-    const struct sg_pt_linux_scsi * ptp = &vp->impl;
-
-    return ptp->nvme_nsid;
-}
-
-/* Returns b which will contain a null char terminated string (if
- * max_b_len > 0). That string should decode Linux driver and host
- * status values. */
-char *
-get_scsi_pt_transport_err_str(const struct sg_pt_base * vp, int max_b_len,
-                              char * b)
-{
-    const struct sg_pt_linux_scsi * ptp = &vp->impl;
-    int ds = ptp->io_hdr.driver_status;
-    int hs = ptp->io_hdr.host_status;
-    int n, m;
-    char * cp = b;
-    int driv;
-    const char * driv_cp = "unknown";
-
-    if (max_b_len < 1)
-        return b;
-    m = max_b_len;
-    n = 0;
-    if (hs) {
-        if ((hs < 0) || (hs >= LINUX_HOST_BYTES_SZ))
-            n = snprintf(cp, m, "Host_status=0x%02x is unknown\n", hs);
-        else
-            n = snprintf(cp, m, "Host_status=0x%02x [%s]\n", hs,
-                         linux_host_bytes[hs]);
-    }
-    m -= n;
-    if (m < 1) {
-        b[max_b_len - 1] = '\0';
-        return b;
-    }
-    cp += n;
-    driv = ds & SG_LIB_DRIVER_MASK;
-    if (driv < LINUX_DRIVER_BYTES_SZ)
-        driv_cp = linux_driver_bytes[driv];
-#if 0
-    sugg = (ds & SG_LIB_SUGGEST_MASK) >> 4;
-    if (sugg < LINUX_DRIVER_SUGGESTS_SZ)
-        sugg_cp = linux_driver_suggests[sugg];
-#endif
-    n = snprintf(cp, m, "Driver_status=0x%02x [%s]\n", ds, driv_cp);
-    m -= n;
-    if (m < 1)
-        b[max_b_len - 1] = '\0';
-    return b;
-}
-
-char *
-get_scsi_pt_os_err_str(const struct sg_pt_base * vp, int max_b_len, char * b)
-{
-    const struct sg_pt_linux_scsi * ptp = &vp->impl;
-    const char * cp;
-
-    cp = safe_strerror(ptp->os_err);
-    strncpy(b, cp, max_b_len);
-    if ((int)strlen(cp) >= max_b_len)
-        b[max_b_len - 1] = '\0';
-    return b;
-}
-
-
-// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
-#else /* allow for runtime selection of sg v3 or v4 (via bsg) */
-// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
-/*
- * So bsg is an option. Thus we make a runtime decision. If all the following
- * are true we use sg v4 which is only currently supported on bsg device
- * nodes:
+ * We make a runtime decision whether to use the sg v3 interface or the sg
+ * v4 interface (currently exclusively used by the bsg driver). If all the
+ * following are true we use sg v4 which is only currently supported on bsg
+ * device nodes:
  *   a) there is a bsg entry in the /proc/devices file
  *   b) the device node given to scsi_pt_open() is a char device
  *   c) the char major number of the device node given to scsi_pt_open()
@@ -1014,9 +354,6 @@
  */
 
 
-#include <linux/types.h>
-#include <linux/bsg.h>
-
 #ifdef major
 #define SG_DEV_MAJOR major
 #else
@@ -1027,30 +364,6 @@
 #endif
 
 
-struct sg_pt_linux_scsi {
-    struct sg_io_v4 io_hdr;     /* use v4 header as it is more general */
-    int dev_fd;                 /* -1 if not given */
-    int in_err;
-    int os_err;
-    unsigned char tmf_request[4];
-    bool is_sg;
-    bool is_bsg;
-    bool is_nvme;
-    bool mdxfer_out;    /* direction of metadata xfer, true->data-out */
-    uint32_t nvme_nsid;         /* 1 to 0xfffffffe are possibly valid, 0
-                                 * implies dev_fd is not a NVMe device
-                                 * (is_nvme=false) or it is a NVMe char
-                                 * device (e.g. /dev/nvme0 ) */
-    uint32_t nvme_result;
-    uint32_t mdxfer_len;
-    void * mdxferp;
-};
-
-struct sg_pt_base {
-    struct sg_pt_linux_scsi impl;
-};
-
-
 /* Returns >= 0 if successful. If error in Unix returns negated errno. */
 int
 scsi_pt_open_device(const char * device_name, bool read_only, int verbose)
@@ -1069,9 +382,9 @@
 {
     int fd;
 
-    if (! bsg_nvme_char_major_checked) {
-        bsg_nvme_char_major_checked = true;
-        find_bsg_nvme_char_major(verbose);
+    if (! sg_bsg_nvme_char_major_checked) {
+        sg_bsg_nvme_char_major_checked = true;
+        sg_find_bsg_nvme_char_major(verbose);
     }
     if (verbose > 1) {
         pr2ws("open %s with flags=0x%x\n", device_name, flags);
@@ -1186,9 +499,9 @@
     struct sg_pt_linux_scsi * ptp = &vp->impl;
     struct stat a_stat;
 
-    if (! bsg_nvme_char_major_checked) {
-        bsg_nvme_char_major_checked = true;
-        find_bsg_nvme_char_major(verbose);
+    if (! sg_bsg_nvme_char_major_checked) {
+        sg_bsg_nvme_char_major_checked = true;
+        sg_find_bsg_nvme_char_major(verbose);
     }
     ptp->dev_fd = dev_fd;
     if (dev_fd >= 0)
@@ -1223,7 +536,7 @@
 
     if (ptp->io_hdr.request)
         ++ptp->in_err;
-    ptp->io_hdr.request = (__u64)(sg_intptr_t)cdb;
+    ptp->io_hdr.request = (__u64)(sg_uintptr_t)cdb;
     ptp->io_hdr.request_len = cdb_len;
 }
 
@@ -1236,7 +549,7 @@
     if (ptp->io_hdr.response)
         ++ptp->in_err;
     memset(sense, 0, max_sense_len);
-    ptp->io_hdr.response = (__u64)(sg_intptr_t)sense;
+    ptp->io_hdr.response = (__u64)(sg_uintptr_t)sense;
     ptp->io_hdr.max_response_len = max_sense_len;
 }
 
@@ -1250,7 +563,7 @@
     if (ptp->io_hdr.din_xferp)
         ++ptp->in_err;
     if (dxfer_ilen > 0) {
-        ptp->io_hdr.din_xferp = (__u64)(sg_intptr_t)dxferp;
+        ptp->io_hdr.din_xferp = (__u64)(sg_uintptr_t)dxferp;
         ptp->io_hdr.din_xfer_len = dxfer_ilen;
     }
 }
@@ -1265,7 +578,7 @@
     if (ptp->io_hdr.dout_xferp)
         ++ptp->in_err;
     if (dxfer_olen > 0) {
-        ptp->io_hdr.dout_xferp = (__u64)(sg_intptr_t)dxferp;
+        ptp->io_hdr.dout_xferp = (__u64)(sg_uintptr_t)dxferp;
         ptp->io_hdr.dout_xfer_len = dxfer_olen;
     }
 }
@@ -1307,7 +620,7 @@
 
     ptp->io_hdr.subprotocol = 1;        /* SCSI task management function */
     ptp->tmf_request[0] = (unsigned char)tmf_code;      /* assume it fits */
-    ptp->io_hdr.request = (__u64)(sg_intptr_t)(&(ptp->tmf_request[0]));
+    ptp->io_hdr.request = (__u64)(sg_uintptr_t)(&(ptp->tmf_request[0]));
     ptp->io_hdr.request_len = 1;
 }
 
@@ -1581,73 +894,6 @@
     return 0;
 }
 
-/* Executes NVMe Admin command (or at least forwards it to lower layers).
- * Returns 0 for success, negative numbers are negated 'errno' values from
- * OS system calls. Positive return values are errors from this package.
- * When time_secs is 0 the Linux NVMe Admin command default of 60 seconds
- * is used. */
-static int
-do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb)
-{
-    int n, len;
-    struct sg_pt_linux_scsi * ptp = &vp->impl;
-    struct sg_nvme_passthru_cmd cmd;
-
-    if (vb > 3)
-        pr2ws("%s: fd=%d, time_secs=%d\n", __func__, fd, time_secs);
-    if (! ptp->io_hdr.request) {
-        if (vb)
-            pr2ws("No NVMe command given (set_scsi_pt_cdb())\n");
-        return SCSI_PT_DO_BAD_PARAMS;
-    }
-    n = ptp->io_hdr.request_len;
-    len = (int)sizeof(cmd);
-    n = (n < len) ? n : len;
-    if (n < 64) {
-        if (vb)
-            pr2ws("%s: command length of %d bytes is too short\n", __func__,
-                  n);
-        return SCSI_PT_DO_BAD_PARAMS;
-    }
-    memcpy(&cmd, (unsigned char *)ptp->io_hdr.request, n);
-    if (n < len)        /* zero out rest of 'cmd' */
-        memset((unsigned char *)&cmd + n, 0, len - n);
-    if (ptp->io_hdr.din_xfer_len > 0) {
-        cmd.data_len = ptp->io_hdr.din_xfer_len;
-        cmd.addr = (__u64)(sg_intptr_t)ptp->io_hdr.din_xferp;
-    } else if (ptp->io_hdr.dout_xfer_len > 0) {
-        cmd.data_len = ptp->io_hdr.dout_xfer_len;
-        cmd.addr = (__u64)(sg_intptr_t)ptp->io_hdr.dout_xferp;
-    }
-    if (time_secs < 0)
-        cmd.timeout_ms = 0;
-    else
-        cmd.timeout_ms = 1000 * cmd.timeout_ms;
-    if (vb > 2) {
-        pr2ws("NVMe command:\n");
-        dStrHex((const char *)&cmd, len, 1);
-    }
-    if (ioctl(ptp->dev_fd, NVME_IOCTL_ADMIN_CMD, &cmd) < 0) {
-        ptp->os_err = errno;
-        if (vb > 2)
-            pr2ws("%s: ioctl(NVME_IOCTL_ADMIN_CMD) failed: %s (errno=%d)\n",
-                  __func__, strerror(ptp->os_err), ptp->os_err);
-        return -ptp->os_err;
-    } else
-        ptp->os_err = 0;
-    ptp->nvme_result = cmd.result;
-    n = ptp->io_hdr.max_response_len;
-    if ((n > 0) && ptp->io_hdr.response) {
-        n = (n < len) ? n : len;
-        memcpy((uint8_t *)ptp->io_hdr.response, &cmd, n);
-        ptp->io_hdr.response_len = n;
-    }
-    if (vb > 2)
-        pr2ws("%s: timeout_ms=%u, result=%u\n", __func__, cmd.timeout_ms,
-              cmd.result);
-    return 0;
-}
-
 /* Executes SCSI command (or at least forwards it to lower layers).
  * Returns 0 for success, negative numbers are negated 'errno' values from
  * OS system calls. Positive return values are errors from this package. */
@@ -1658,9 +904,9 @@
     struct sg_pt_linux_scsi * ptp = &vp->impl;
     bool have_checked_for_type = (ptp->dev_fd >= 0);
 
-    if (! bsg_nvme_char_major_checked) {
-        bsg_nvme_char_major_checked = true;
-        find_bsg_nvme_char_major(verbose);
+    if (! sg_bsg_nvme_char_major_checked) {
+        sg_bsg_nvme_char_major_checked = true;
+        sg_find_bsg_nvme_char_major(verbose);
     }
     if (ptp->in_err) {
         if (verbose)
@@ -1688,8 +934,8 @@
     if (ptp->os_err)
         return -ptp->os_err;
     if (ptp->is_nvme)
-        return do_nvme_pt(vp, ptp->dev_fd, time_secs, verbose);
-    else if (bsg_major <= 0)
+        return sg_do_nvme_pt(vp, ptp->dev_fd, time_secs, verbose);
+    else if (sg_bsg_major <= 0)
         return do_scsi_pt_v3(ptp, fd, time_secs, verbose);
     else if (ptp->is_bsg)
         ; /* drop through to sg v4 implementation */
@@ -1722,6 +968,3 @@
     }
     return 0;
 }
-
-#endif
-// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
diff --git a/lib/sg_pt_linux_nvme.c b/lib/sg_pt_linux_nvme.c
new file mode 100644
index 0000000..89e5df4
--- /dev/null
+++ b/lib/sg_pt_linux_nvme.c
@@ -0,0 +1,387 @@
+/*
+ * Copyright (c) 2017 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.
+ */
+
+/* sg_pt_linux_nvme version 1.00 20171206 */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>      /* to define 'major' */
+#ifndef major
+#include <sys/types.h>
+#endif
+
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <linux/major.h>
+
+#include "sg_pt.h"
+#include "sg_lib.h"
+#include "sg_linux_inc.h"
+#include "sg_pt_linux.h"
+#include "sg_unaligned.h"
+
+#define SCSI_INQUIRY_OPC     0x12
+#define SCSI_TEST_UNIT_READY_OPC  0x0
+
+/* Additional Sense Code (ASC) */
+#define NO_ADDITIONAL_SENSE 0x0
+#define LOGICAL_UNIT_NOT_READY 0x4
+#define LOGICAL_UNIT_COMMUNICATION_FAILURE 0x8
+#define UNRECOVERED_READ_ERR 0x11
+#define PARAMETER_LIST_LENGTH_ERR 0x1a
+#define INVALID_OPCODE 0x20
+#define LBA_OUT_OF_RANGE 0x21
+#define INVALID_FIELD_IN_CDB 0x24
+#define INVALID_FIELD_IN_PARAM_LIST 0x26
+#define UA_RESET_ASC 0x29
+#define UA_CHANGED_ASC 0x2a
+#define TARGET_CHANGED_ASC 0x3f
+#define LUNS_CHANGED_ASCQ 0x0e
+#define INSUFF_RES_ASC 0x55
+#define INSUFF_RES_ASCQ 0x3
+#define POWER_ON_RESET_ASCQ 0x0
+#define BUS_RESET_ASCQ 0x2      /* scsi bus reset occurred */
+#define MODE_CHANGED_ASCQ 0x1   /* mode parameters changed */
+#define CAPACITY_CHANGED_ASCQ 0x9
+#define SAVING_PARAMS_UNSUP 0x39
+#define TRANSPORT_PROBLEM 0x4b
+#define THRESHOLD_EXCEEDED 0x5d
+#define LOW_POWER_COND_ON 0x5e
+#define MISCOMPARE_VERIFY_ASC 0x1d
+#define MICROCODE_CHANGED_ASCQ 0x1      /* with TARGET_CHANGED_ASC */
+#define MICROCODE_CHANGED_WO_RESET_ASCQ 0x16
+
+#if (__STDC_VERSION__ >= 199901L)  /* C99 or later */
+typedef intptr_t sg_uintptr_t;
+#else
+typedef long sg_uintptr_t;
+#endif
+
+
+static inline bool is_aligned(const void *restrict pointer,
+                              size_t byte_count)
+{
+       return (sg_uintptr_t)pointer % byte_count == 0;
+}
+
+
+#if defined(__GNUC__) || defined(__clang__)
+static int pr2ws(const char * fmt, ...)
+        __attribute__ ((format (printf, 1, 2)));
+#else
+static int pr2ws(const char * fmt, ...);
+#endif
+
+
+static int
+pr2ws(const char * fmt, ...)
+{
+    va_list args;
+    int n;
+
+    va_start(args, fmt);
+    n = vfprintf(sg_warnings_strm ? sg_warnings_strm : stderr, fmt, args);
+    va_end(args);
+    return n;
+}
+
+/* The web claims that all NVMe commands are 64 bytes long. Believe it until
+ * contradicted. The only SCSI commands that can be longer than 16 bytes are
+ * the Variable Length Commands (opcode 0x7f) and the XCDB wrapped commands
+ * (opcode 0x7e). Both have an inbuilt length field which can be cross
+ * checked with clen. */
+static bool
+is_scsi_command(const uint8_t * cdbp, int clen)
+{
+    int ilen, sa;
+
+    if (clen <= 16)
+        return true;
+    if (0 == (clen % 4)) {
+        if (0x7f == cdbp[0]) {
+            ilen = 8 + cdbp[7];
+            sa = sg_get_unaligned_be16(cdbp + 8);
+            if ((ilen == clen) && sa)
+                return true;
+        } else if (0x7e == cdbp[0]) {
+            ilen = 4 + sg_get_unaligned_be16(cdbp + 2);
+            if (ilen == clen)
+                return true;
+        }
+    }
+    if ((clen >= 64) && (clen <= 72))
+        return false;
+    pr2ws("%s: irregular command, assume NVMe:\n", __func__);
+    dStrHexErr((const char *)cdbp, clen, 1);
+    return false;
+}
+
+static void
+build_sense_buffer(bool desc, uint8_t *buf, uint8_t key, uint8_t asc,
+                   uint8_t ascq)
+{
+    if (desc) {
+        buf[0] = 0x72;  /* descriptor, current */
+        buf[1] = key;
+        buf[2] = asc;
+        buf[3] = ascq;
+        buf[7] = 0;
+    } else {
+        buf[0] = 0x70;  /* fixed, current */
+        buf[2] = key;
+        buf[7] = 0xa;
+        buf[12] = asc;
+        buf[13] = ascq;
+    }
+}
+
+/* Set in_bit to -1 to indicate no bit position of invalid field */
+static void
+mk_sense_invalid_fld(struct sg_pt_linux_scsi * ptp, bool in_cdb, int in_byte,
+                     int in_bit, int vb)
+{
+    bool dsense = ptp->scsi_dsense;
+    int sl, asc, n;
+    uint8_t * sbp = (uint8_t *)ptp->io_hdr.response;
+    uint8_t sks[4];
+
+    ptp->io_hdr.device_status = SAM_STAT_CHECK_CONDITION;
+    asc = in_cdb ? INVALID_FIELD_IN_CDB : INVALID_FIELD_IN_PARAM_LIST;
+    n = ptp->io_hdr.max_response_len;
+    if ((n < 8) || ((! dsense) && (n < 14))) {
+        pr2ws("%s: max_response_len=%d too short, want 14 or more\n",
+              __func__, n);
+        return;
+    } else
+        ptp->io_hdr.response_len = dsense ? 8 : ((n < 18) ? n : 18);
+    memset(sbp, 0, n);
+    build_sense_buffer(dsense, sbp, SPC_SK_ILLEGAL_REQUEST, asc, 0);
+    memset(sks, 0, sizeof(sks));
+    sks[0] = 0x80;
+    if (in_cdb)
+        sks[0] |= 0x40;
+    if (in_bit >= 0) {
+        sks[0] |= 0x8;
+        sks[0] |= (0x7 & in_bit);
+    }
+    sg_put_unaligned_be16(in_byte, sks + 1);
+    if (dsense) {
+        sl = sbp[7] + 8;
+        sbp[7] = sl;
+        sbp[sl] = 0x2;
+        sbp[sl + 1] = 0x6;
+        memcpy(sbp + sl + 4, sks, 3);
+    } else
+        memcpy(sbp + 15, sks, 3);
+    if (vb > 1)
+        pr2ws("%s:  [sense_key,asc,ascq]: [0x5,0x%x,0x0] %c byte=%d, bit=%d\n",
+              __func__, asc, in_cdb ? 'C' : 'D', in_byte, in_bit);
+}
+
+static const char * nvme_scsi_vendor_str = "NVMe    ";
+static const uint16_t inq_resp_len = 36;
+
+static int
+sntl_inq(struct sg_pt_linux_scsi * ptp, const uint8_t * cdbp, int fd,
+         int time_secs, int vb)
+{
+    bool evpd;
+    int err;
+    uint16_t k, n, alloc_len, pg_cd;
+    uint8_t inq_dout[128];
+
+    if (vb > 3)
+        pr2ws("%s: fd=%d, time_secs=%d\n", __func__, fd, time_secs);
+
+    if (0x2 & cdbp[1]) {
+        mk_sense_invalid_fld(ptp, true, 1, 1, vb);
+        return 0;
+    }
+    if (NULL == ptp->nvme_id_ctlp) {
+        struct sg_nvme_passthru_cmd cmd;
+        uint32_t pg_sz = sg_get_page_size();
+
+        ptp->nvme_id_ctlp = sg_memalign(pg_sz, pg_sz, &ptp->free_nvme_id_ctlp,
+                                        vb > 3);
+        if (NULL == ptp->nvme_id_ctlp) {
+            pr2ws("%s: sg_memalign() failed to get memory\n", __func__);
+            return SG_LIB_OS_BASE_ERR + ENOMEM;
+        }
+        memset(&cmd, 0, sizeof(cmd));
+        cmd.opcode = 0x6;
+        cmd.cdw10 = 0x1;       /* CNS=0x1 Identify controller */
+        cmd.addr = (uint64_t)ptp->nvme_id_ctlp;
+        cmd.data_len = pg_sz;
+        if (ioctl(ptp->dev_fd, NVME_IOCTL_ADMIN_CMD, &cmd) < 0) {
+            err = errno;
+            if (vb > 2)
+                pr2ws("%s: ioctl(NVME_IOCTL_ADMIN_CMD) failed: %s (errno=%d)"
+                      "\n", __func__, strerror(err), err);
+            ptp->os_err = err;
+            return -err;
+        }
+    }
+    memset(inq_dout, 0, sizeof(inq_dout));
+    alloc_len = sg_get_unaligned_be16(cdbp + 3);
+    evpd = !!(0x1 & cdbp[1]);
+    pg_cd = cdbp[2];
+    if (evpd) {         /* VPD page responses */
+        inq_dout[1] = pg_cd;
+        n = 0;
+        switch (pg_cd) {
+        case 0:
+            /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */
+            sg_put_unaligned_be16(3, inq_dout + 2);
+            inq_dout[4] = 0x0;
+            inq_dout[5] = 0x80;
+            inq_dout[6] = 0x83;
+            n = 7;
+            break;
+        case 0x80:
+            /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */
+            sg_put_unaligned_be16(20, inq_dout + 2);
+            memcpy(inq_dout + 4, ptp->nvme_id_ctlp + 4, 20);    /* SN */
+            n = 24;
+            break;
+        case 0x83:
+            /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); prefer pdt=0xd --> SES */
+            inq_dout[4] = 0x2;  /* Prococol id=0, code_set=2 (ASCII) */
+            inq_dout[5] = 0x1;  /* PIV=0, ASSOC=0 (LU ??), desig_id=1 */
+            /* Building T10 Vendor ID base designator, SNTL document 1.5
+             * dated 20150624 confuses this with SCSI name string
+             * descriptor, desig_id=8 */
+            memcpy(inq_dout + 8, nvme_scsi_vendor_str, 8);
+            memcpy(inq_dout + 16, ptp->nvme_id_ctlp + 24, 40);  /* MN */
+            for (k = 40; k > 0; --k) {
+                if (' ' == inq_dout[16 + k - 1])
+                    inq_dout[16 + k - 1] = '_'; /* convert trailing spaces */
+                else
+                    break;
+            }
+            memcpy(inq_dout + 16 + k + 1, ptp->nvme_id_ctlp + 4, 20); /* SN */
+            n = 16 + k + 1 + 20;
+            inq_dout[7] = 8 + k + 1 + 20;
+            sg_put_unaligned_be16(n - 4, inq_dout + 2);
+            break;
+        default:        /* Point to page_code field in cdb */
+            mk_sense_invalid_fld(ptp, true, 2, 7, vb);
+            return 0;
+        }
+        if (alloc_len > 0) {
+            n = (alloc_len < n) ? alloc_len : n;
+            n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len;
+            if (n > 0)
+                memcpy((uint8_t *)ptp->io_hdr.din_xferp, inq_dout, n);
+        }
+    } else {            /* Standard INQUIRY response */
+        /* inq_dout[0] = (PQ=0)<<5 | (PDT=0); pdt=0 --> SBC; 0xd --> SES */
+        inq_dout[2] = 6;   /* version: SPC-4 */
+        inq_dout[3] = 2;   /* NORMACA=0, HISUP=0, response data format: 2 */
+        inq_dout[4] = 31;  /* so response length is (or could be) 36 bytes */
+        inq_dout[6] = 0x40;   /* ENCSERV=1 */
+        inq_dout[7] = 0x2;    /* CMDQUE=1 */
+        memcpy(inq_dout + 8, nvme_scsi_vendor_str, 8);  /* NVMe not Intel */
+        memcpy(inq_dout + 16, ptp->nvme_id_ctlp + 24, 16); /* Prod <-- MN */
+        memcpy(inq_dout + 32, ptp->nvme_id_ctlp + 64, 4);  /* Rev <-- FR */
+        if (alloc_len > 0) {
+            n = (alloc_len < inq_resp_len) ? alloc_len : inq_resp_len;
+            n = (n < ptp->io_hdr.din_xfer_len) ? n : ptp->io_hdr.din_xfer_len;
+            if (n > 0)
+                memcpy((uint8_t *)ptp->io_hdr.din_xferp, inq_dout, n);
+        }
+    }
+    return 0;
+}
+
+/* Executes NVMe Admin command (or at least forwards it to lower layers).
+ * Returns 0 for success, negative numbers are negated 'errno' values from
+ * OS system calls. Positive return values are errors from this package.
+ * When time_secs is 0 the Linux NVMe Admin command default of 60 seconds
+ * is used. */
+int
+sg_do_nvme_pt(struct sg_pt_base * vp, int fd, int time_secs, int vb)
+{
+    bool scsi_cmd;
+    int n, len;
+    struct sg_pt_linux_scsi * ptp = &vp->impl;
+    struct sg_nvme_passthru_cmd cmd;
+    const uint8_t * cdbp;
+
+    if (vb > 4)
+        pr2ws("%s: fd=%d, time_secs=%d\n", __func__, fd, time_secs);
+    if (! ptp->io_hdr.request) {
+        if (vb)
+            pr2ws("No NVMe command given (set_scsi_pt_cdb())\n");
+        return SCSI_PT_DO_BAD_PARAMS;
+    }
+    n = ptp->io_hdr.request_len;
+    cdbp = (const uint8_t *)ptp->io_hdr.request;
+    scsi_cmd = is_scsi_command(cdbp, n);
+    if (scsi_cmd) {
+        if (SCSI_INQUIRY_OPC == cdbp[0])
+            return sntl_inq(ptp, cdbp, fd, time_secs, vb);
+
+    }
+    len = (int)sizeof(cmd);
+    n = (n < len) ? n : len;
+    if (n < 64) {
+        if (vb)
+            pr2ws("%s: command length of %d bytes is too short\n", __func__,
+                  n);
+        return SCSI_PT_DO_BAD_PARAMS;
+    }
+    memcpy(&cmd, (unsigned char *)ptp->io_hdr.request, n);
+    if (n < len)        /* zero out rest of 'cmd' */
+        memset((unsigned char *)&cmd + n, 0, len - n);
+    if (ptp->io_hdr.din_xfer_len > 0) {
+        cmd.data_len = ptp->io_hdr.din_xfer_len;
+        cmd.addr = (__u64)(sg_uintptr_t)ptp->io_hdr.din_xferp;
+    } else if (ptp->io_hdr.dout_xfer_len > 0) {
+        cmd.data_len = ptp->io_hdr.dout_xfer_len;
+        cmd.addr = (__u64)(sg_uintptr_t)ptp->io_hdr.dout_xferp;
+    }
+    if (time_secs < 0)
+        cmd.timeout_ms = 0;
+    else
+        cmd.timeout_ms = 1000 * cmd.timeout_ms;
+    if (vb > 2) {
+        pr2ws("NVMe command:\n");
+        dStrHex((const char *)&cmd, len, 1);
+    }
+    if (ioctl(ptp->dev_fd, NVME_IOCTL_ADMIN_CMD, &cmd) < 0) {
+        ptp->os_err = errno;
+        if (vb > 2)
+            pr2ws("%s: ioctl(NVME_IOCTL_ADMIN_CMD) failed: %s (errno=%d)\n",
+                  __func__, strerror(ptp->os_err), ptp->os_err);
+        return -ptp->os_err;
+    } else
+        ptp->os_err = 0;
+    ptp->nvme_result = cmd.result;
+    n = ptp->io_hdr.max_response_len;
+    if ((n > 0) && ptp->io_hdr.response) {
+        n = (n < len) ? n : len;
+        memcpy((uint8_t *)ptp->io_hdr.response, &cmd, n);
+        ptp->io_hdr.response_len = n;
+    }
+    if (vb > 2)
+        pr2ws("%s: timeout_ms=%u, result=%u\n", __func__, cmd.timeout_ms,
+              cmd.result);
+    return 0;
+}
diff --git a/sg3_utils.spec b/sg3_utils.spec
index 2a24fcb..6a60354 100644
--- a/sg3_utils.spec
+++ b/sg3_utils.spec
@@ -79,7 +79,7 @@
 %{_libdir}/*.la
 
 %changelog
-* Fri Nov 03 2017 - dgilbert at interlog dot com
+* Mon Dec 04 2017 - dgilbert at interlog dot com
 - track t10 changes
   * sg3_utils-1.43
 
diff --git a/src/sg_dd.c b/src/sg_dd.c
index 736d1e0..74b0547 100644
--- a/src/sg_dd.c
+++ b/src/sg_dd.c
@@ -62,7 +62,7 @@
 #include "sg_unaligned.h"
 #include "sg_pr2serr.h"
 
-static const char * version_str = "5.92 20171023";
+static const char * version_str = "5.93 20171206";
 
 
 #define ME "sg_dd: "
@@ -1838,7 +1838,7 @@
     }
 
     if (iflag.dio || iflag.direct || oflag.direct || (FT_RAW & in_type) ||
-        (FT_RAW & out_type)) {
+        (FT_RAW & out_type)) {  /* want heap buffer aligned to page_size */
         size_t psz;
 
 #if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
@@ -1847,26 +1847,11 @@
         psz = 4096;     /* give up, pick likely figure */
 #endif
 
-#ifdef HAVE_POSIX_MEMALIGN
-        {
-            int err;
-
-            err = posix_memalign((void **)&wrkBuff, psz, blk_sz * bpt);
-            if (err) {
-                pr2serr("posix_memalign: error [%d] out of memory?\n", err);
-                return SG_LIB_CAT_OTHER;
-            }
-            wrkPos = wrkBuff;
-        }
-#else
-        wrkBuff = (unsigned char*)malloc(blk_sz * bpt + psz);
-        if (0 == wrkBuff) {
-            pr2serr("Not enough user memory for work buffer\n");
+        wrkPos = sg_memalign(blk_sz * bpt, psz, &wrkBuff, verbose > 3);
+        if (NULL == wrkPos) {
+            pr2serr("sg_memalign: error, out of memory?\n");
             return SG_LIB_CAT_OTHER;
         }
-        wrkPos = (unsigned char *)(((uintptr_t)wrkBuff + psz - 1) &
-                                   (~(psz - 1)));
-#endif
     } else {
         wrkBuff = (unsigned char*)malloc(blk_sz * bpt);
         if (0 == wrkBuff) {
diff --git a/src/sg_inq.c b/src/sg_inq.c
index 6cdaf75..29dcc7a 100644
--- a/src/sg_inq.c
+++ b/src/sg_inq.c
@@ -46,7 +46,7 @@
 #include "sg_pt_nvme.h"
 #endif
 
-static const char * version_str = "1.73 20171115";    /* SPC-5 rev 17 */
+static const char * version_str = "1.74 20171206";    /* SPC-5 rev 17 */
 
 /* INQUIRY notes:
  * It is recommended that the initial allocation length given to a
@@ -300,7 +300,8 @@
             "                    only supported for VPD pages 0x80 and 0x83\n"
             "    --extended|-E|-x    decode extended INQUIRY data VPD page "
             "(0x86)\n"
-            "    --force|-f      skip VPD page 0 checking\n"
+            "    --force|-f      skip VPD page 0 checking; provide more "
+            "NVMe info\n"
             "    --help|-h       print usage message then exit\n"
             "    --hex|-H        output response in hex\n"
             "    --id|-i         decode device identification VPD page "
@@ -1748,7 +1749,7 @@
                        "identifier\n",
                        sg_get_trans_proto_str(p_id, sizeof(b), b));
             break;
-        case 0xa: /* UUID identifier [spc5r08] */
+        case 0xa: /* UUID identifier [spc5r08] RFC 4122 */
             if (1 != c_set) {
                 pr2serr("      << expected binary code_set >>\n");
                 dStrHexErr((const char *)ip, i_len, 0);
@@ -3793,7 +3794,7 @@
 
 static int
 do_nvme_id_ns(struct sg_pt_base * ptvp, uint32_t nsid,
-              struct sg_nvme_passthru_cmd * id_cmdp, uint8_t * id_din,
+              struct sg_nvme_passthru_cmd * id_cmdp, uint8_t * id_dinp,
               int id_din_len, const struct opts_t * op)
 {
     bool got_eui_128 = false;
@@ -3806,7 +3807,7 @@
     clear_scsi_pt_obj(ptvp);
     id_cmdp->nsid = nsid;
     id_cmdp->cdw10 = 0x0;       /* CNS=0x0 Identify NS */
-    set_scsi_pt_data_in(ptvp, id_din, id_din_len);
+    set_scsi_pt_data_in(ptvp, id_dinp, id_din_len);
     set_scsi_pt_sense(ptvp, (unsigned char *)&cmd_back, sizeof(cmd_back));
     set_scsi_pt_cdb(ptvp, (const uint8_t *)id_cmdp, sizeof(*id_cmdp));
     ret = do_scsi_pt(ptvp, -1, 0 /* timeout (def: 1 min) */, vb);
@@ -3817,24 +3818,24 @@
     }
     if (ret)
         return ret;
-    num_lbaf = id_din[25] + 1;  /* spec says this is "0's based value" */
-    flbas = id_din[26] & 0xf;   /* index of active LBA format (for this ns) */
+    num_lbaf = id_dinp[25] + 1;  /* spec says this is "0's based value" */
+    flbas = id_dinp[26] & 0xf;   /* index of active LBA format (for this ns) */
     if (op->do_hex || op->do_raw) {
-        do_nvme_identify_hex_raw(id_din, sizeof(id_din), op);
+        do_nvme_identify_hex_raw(id_dinp, id_din_len, op);
         return ret;
     }
-    ns_sz = sg_get_unaligned_le64(id_din + 0);
-    eui_64 = sg_get_unaligned_be64(id_din + 120);  /* N.B. big endian */
-    if (! sg_all_zeros(id_din + 104, 16))
+    ns_sz = sg_get_unaligned_le64(id_dinp + 0);
+    eui_64 = sg_get_unaligned_be64(id_dinp + 120);  /* N.B. big endian */
+    if (! sg_all_zeros(id_dinp + 104, 16))
         got_eui_128 = true;
     printf("    Namespace size/capacity: %" PRIu64 "/%" PRIu64
-           " blocks\n", ns_sz, sg_get_unaligned_le64(id_din + 8));
+           " blocks\n", ns_sz, sg_get_unaligned_le64(id_dinp + 8));
     printf("    Namespace utilization: %" PRIu64 " blocks\n",
-           sg_get_unaligned_le64(id_din + 16));
+           sg_get_unaligned_le64(id_dinp + 16));
     if (got_eui_128) {          /* N.B. big endian */
-        printf("    NGUID: 0x%02x", id_din[104]);
+        printf("    NGUID: 0x%02x", id_dinp[104]);
         for (k = 1; k < 16; ++k)
-            printf("%02x", id_din[104 + k]);
+            printf("%02x", id_dinp[104 + k]);
         printf("\n");
     } else if (op->do_force)
         printf("    NGUID: 0x0\n");
@@ -3848,7 +3849,7 @@
             printf(" <-- active\n");
         else
             printf("\n");
-        flba_info = sg_get_unaligned_le32(id_din + off);
+        flba_info = sg_get_unaligned_le32(id_dinp + off);
         md_size = flba_info & 0xffff;
         lb_size = flba_info >> 16 & 0xff;
         if (lb_size > 31) {
@@ -3889,7 +3890,9 @@
     struct sg_nvme_passthru_cmd identify_cmd;
     struct sg_nvme_passthru_cmd cmd_back;
     struct sg_nvme_passthru_cmd * id_cmdp = &identify_cmd;
-    uint8_t id_din[4096];
+    uint8_t * id_dinp = NULL;
+    uint8_t * free_id_dinp = NULL;
+    const uint32_t pg_sz = sg_get_page_size();
 
     if (op->do_raw) {
         if (sg_set_binary_mode(STDOUT_FILENO) < 0) {
@@ -3906,7 +3909,8 @@
     id_cmdp->opcode = 0x6;
     nsid = get_pt_nvme_nsid(ptvp);
     id_cmdp->cdw10 = 0x1;       /* CNS=0x1 Identify controller */
-    set_scsi_pt_data_in(ptvp, id_din, sizeof(id_din));
+    id_dinp = sg_memalign(pg_sz, pg_sz, &free_id_dinp, vb > 3);
+    set_scsi_pt_data_in(ptvp, id_dinp, pg_sz);
     set_scsi_pt_cdb(ptvp, (const uint8_t *)id_cmdp, sizeof(*id_cmdp));
     set_scsi_pt_sense(ptvp, (unsigned char *)&cmd_back, sizeof(cmd_back));
     ret = do_scsi_pt(ptvp, -1, 0 /* timeout (def: 1 min) */, vb);
@@ -3917,16 +3921,16 @@
     }
     if (ret)
         goto err_out;
-    max_nsid = sg_get_unaligned_le16(id_din + 516);
+    max_nsid = sg_get_unaligned_le16(id_dinp + 516);
     if (op->do_raw || op->do_hex) {
-        do_nvme_identify_hex_raw(id_din, sizeof(id_din), op);
+        do_nvme_identify_hex_raw(id_dinp, pg_sz, op);
         goto skip1;
     }
     printf("Identify controller for %s:\n", op->device_name);
-    printf("  Model number: %.40s\n", (const char *)(id_din + 24));
-    printf("  Serial number: %.20s\n", (const char *)(id_din + 4));
-    printf("  Firmware revision: %.8s\n", (const char *)(id_din + 64));
-    ver = sg_get_unaligned_le32(id_din + 80);
+    printf("  Model number: %.40s\n", (const char *)(id_dinp + 24));
+    printf("  Serial number: %.20s\n", (const char *)(id_dinp + 4));
+    printf("  Firmware revision: %.8s\n", (const char *)(id_dinp + 64));
+    ver = sg_get_unaligned_le32(id_dinp + 80);
     ver_maj = (ver >> 16);
     ver_min = (ver >> 8) & 0xff;
     ver_ter = (ver & 0xff);
@@ -3937,28 +3941,28 @@
     else
         printf("\n");
     printf("  PCI vendor ID VID/SSVID: 0x%x/0x%x\n",
-           sg_get_unaligned_le16(id_din + 0),
-           sg_get_unaligned_le16(id_din + 2));
+           sg_get_unaligned_le16(id_dinp + 0),
+           sg_get_unaligned_le16(id_dinp + 2));
     printf("  IEEE OUI Identifier: 0x%x\n",
-           sg_get_unaligned_le24(id_din + 73));
-    got_fguid = ! sg_all_zeros(id_din + 112, 16);
+           sg_get_unaligned_le24(id_dinp + 73));
+    got_fguid = ! sg_all_zeros(id_dinp + 112, 16);
     if (got_fguid) {
-        printf("  FGUID: 0x%02x", id_din[112]);
+        printf("  FGUID: 0x%02x", id_dinp[112]);
         for (k = 1; k < 16; ++k)
-            printf("%02x", id_din[112 + k]);
+            printf("%02x", id_dinp[112 + k]);
         printf("\n");
     } else if (op->do_force)
         printf("  FGUID: 0x0\n");
-    printf("  Controller ID: 0x%x\n", sg_get_unaligned_le16(id_din + 78));
+    printf("  Controller ID: 0x%x\n", sg_get_unaligned_le16(id_dinp + 78));
     if (op->do_force) {
         printf("  Management endpoint capabilities, over a PCIe port: %d\n",
-               !! (0x2 & id_din[255]));
+               !! (0x2 & id_dinp[255]));
         printf("  Management endpoint capabilities, over a SMBus/I2C port: "
-               "%d\n", !! (0x1 & id_din[255]));
+               "%d\n", !! (0x1 & id_dinp[255]));
     }
     printf("  Number of namespaces: %u\n", max_nsid);
-    sz1 = sg_get_unaligned_le64(id_din + 280);  /* lower 64 bits */
-    sz2 = sg_get_unaligned_le64(id_din + 288);  /* upper 64 bits */
+    sz1 = sg_get_unaligned_le64(id_dinp + 280);  /* lower 64 bits */
+    sz2 = sg_get_unaligned_le64(id_dinp + 288);  /* upper 64 bits */
     if (sz2)
         printf("  Total NVM capacity: huge ...\n");
     else if (sz1)
@@ -3969,8 +3973,8 @@
         const char * cp;
 
         printf("  Total NVM capacity: 0 bytes\n");
-        npss = id_din[263] + 1;
-        up = id_din + 2048;
+        npss = id_dinp[263] + 1;
+        up = id_dinp + 2048;
         for (k = 0; k < npss; ++k, up += 32) {
             n = sg_get_unaligned_le16(up + 0);
             n *= (0x1 & up[3]) ? 1 : 100;    /* unit: 100 microWatts */
@@ -4013,7 +4017,7 @@
                 pr2serr("NSID from device (%u) should not exceed number of "
                         "namespaces (%u)\n", nsid, max_nsid);
         }
-        ret = do_nvme_id_ns(ptvp, nsid, id_cmdp, id_din, sizeof(id_din), op);
+        ret = do_nvme_id_ns(ptvp, nsid, id_cmdp, id_dinp, pg_sz, op);
         if (ret)
             goto err_out;
 
@@ -4021,13 +4025,14 @@
         for (k = 1; k <= max_nsid; ++k) {
             if ((! op->do_raw) || (op->do_hex < 3))
                 printf("  Namespace %u (of %u):\n", k, max_nsid);
-            ret = do_nvme_id_ns(ptvp, k, id_cmdp, id_din, sizeof(id_din), op);
+            ret = do_nvme_id_ns(ptvp, k, id_cmdp, id_dinp, pg_sz, op);
             if (ret)
                 goto err_out;
         }
     }
 err_out:
     destruct_scsi_pt_obj(ptvp);
+    free(free_id_dinp);
     return ret;
 }
 
diff --git a/src/sg_raw.c b/src/sg_raw.c
index 0f513e4..537b9b1 100644
--- a/src/sg_raw.c
+++ b/src/sg_raw.c
@@ -32,7 +32,7 @@
 #include "sg_pr2serr.h"
 #include "sg_unaligned.h"
 
-#define SG_RAW_VERSION "0.4.18 (2017-10-09)"
+#define SG_RAW_VERSION "0.4.19 (2017-12-06)"
 
 #ifdef SG_LIB_WIN32
 #ifndef HAVE_SYSCONF
@@ -275,63 +275,6 @@
     return 0;
 }
 
-/* Allocate aligned memory (heap) starting on page boundary */
-static unsigned char *
-my_memalign(int length, unsigned char ** wrkBuffp, const struct opts_t * op)
-{
-    size_t psz;
-    unsigned char * res;
-
-#if defined(HAVE_SYSCONF) && defined(_SC_PAGESIZE)
-    psz = sysconf(_SC_PAGESIZE); /* POSIX.1 (was getpagesize()) */
-#elif defined(SG_LIB_WIN32)
-    psz = win_pagesize();
-#else
-    psz = 4096;     /* give up, pick likely figure */
-#endif
-
-#ifdef HAVE_POSIX_MEMALIGN
-    {
-        int err;
-        void * wp = NULL;
-
-        err = posix_memalign(&wp, psz, length);
-        if (err || (NULL == wp)) {
-            pr2serr("posix_memalign: error [%d], out of memory?\n", err);
-            return NULL;
-        }
-        memset(wp, 0, length);
-        if (wrkBuffp)
-            *wrkBuffp = (unsigned char *)wp;
-        res = (unsigned char *)wp;
-        if (op->verbose > 3)
-            pr2serr("%s: posix, len=%d, wrkBuffp=%p, psz=%d, rp=%p\n",
-                    __func__, length, (void *)*wrkBuffp, (int)psz,
-                    (void *)res);
-        return res;
-    }
-#else
-    {
-        unsigned char * wrkBuff;
-
-        wrkBuff = (unsigned char*)calloc(length + psz, 1);
-        if (NULL == wrkBuff) {
-            if (wrkBuffp)
-                *wrkBuffp = NULL;
-            return NULL;
-        } else if (wrkBuffp)
-            *wrkBuffp = wrkBuff;
-        res = (unsigned char *)(((uintptr_t)wrkBuff + psz - 1) &
-                                (~(psz - 1)));
-        if (op->verbose > 3)
-            pr2serr("%s: hack, len=%d, wrkBuffp=%p, psz=%d, rp=%p\n",
-                    __func__, length, (void *)*wrkBuffp, (int)psz,
-                    (void *)res);
-        return res;
-    }
-#endif
-}
-
 static int
 skip(int fd, off_t offset)
 {
@@ -392,7 +335,8 @@
         }
     }
 
-    buf = my_memalign(op->dataout_len, &wrkBuf, op);
+    buf = sg_memalign(op->dataout_len, 0 /* page_size */, &wrkBuf,
+                      op->verbose > 3);
     if (buf == NULL) {
         perror("malloc");
         goto bail;
@@ -528,7 +472,8 @@
         set_scsi_pt_data_out(ptvp, dxfer_buffer_out, op->dataout_len);
     }
     if (op->do_datain) {
-        dxfer_buffer_in = my_memalign(op->datain_len, &wrkBuf, op);
+        dxfer_buffer_in = sg_memalign(op->datain_len, 0 /* page_size */,
+                                      &wrkBuf, op->verbose > 3);
         if (dxfer_buffer_in == NULL) {
             perror("malloc");
             ret = SG_LIB_CAT_OTHER;
diff --git a/src/sg_ses.c b/src/sg_ses.c
index ce977ec..80153b3 100644
--- a/src/sg_ses.c
+++ b/src/sg_ses.c
@@ -314,27 +314,11 @@
 static struct join_row_t * join_arr_lastp = join_arr + MX_JOIN_ROWS - 1;
 static bool join_done = false;
 
-#ifdef SG_LIB_FREEBSD
-
-#include <sys/param.h>  /* contains PAGE_SIZE */
-
-static uint8_t enc_stat_rsp[MX_ALLOC_LEN]
-        __attribute__ ((aligned (PAGE_SIZE)));
-static uint8_t elem_desc_rsp[MX_ALLOC_LEN]
-        __attribute__ ((aligned (PAGE_SIZE)));
-static uint8_t add_elem_rsp[MX_ALLOC_LEN]
-        __attribute__ ((aligned (PAGE_SIZE)));
-static uint8_t threshold_rsp[MX_ALLOC_LEN]
-        __attribute__ ((aligned (PAGE_SIZE)));
-
-#else
-
-static uint8_t enc_stat_rsp[MX_ALLOC_LEN];
-static uint8_t elem_desc_rsp[MX_ALLOC_LEN];
-static uint8_t add_elem_rsp[MX_ALLOC_LEN];
-static uint8_t threshold_rsp[MX_ALLOC_LEN];
-
-#endif
+/* Large buffers on heap, aligned to page size and zeroed */
+static uint8_t * enc_stat_rsp;
+static uint8_t * elem_desc_rsp;
+static uint8_t * add_elem_rsp;
+static uint8_t * threshold_rsp;
 
 static int enc_stat_rsp_len;
 static int elem_desc_rsp_len;
@@ -5096,24 +5080,31 @@
 int
 main(int argc, char * argv[])
 {
-    int sg_fd, k, res;
-    int pd_type = 0;
     bool have_cgs = false;
+    int k, res;
+    int sg_fd = -1;
+    int pd_type = 0;
     int ret = 0;
-    struct sg_simple_inquiry_resp inq_resp;
-    char buff[128];
-    char b[80];
-    struct tuple_acronym_val tav_arr[CGS_CL_ARR_MAX_SZ];
+    uint32_t pg_sz;
     const char * cp;
     struct opts_t opts;
     struct opts_t * op;
     struct tuple_acronym_val * tavp;
     struct cgs_cl_t * cgs_clp;
+    uint8_t * free_enc_stat_rsp = NULL;
+    uint8_t * free_elem_desc_rsp = NULL;
+    uint8_t * free_add_elem_rsp = NULL;
+    uint8_t * free_threshold_rsp = NULL;
+    struct tuple_acronym_val tav_arr[CGS_CL_ARR_MAX_SZ];
+    struct sg_simple_inquiry_resp inq_resp;
+    char buff[128];
+    char b[80];
 
     op = &opts;
     memset(op, 0, sizeof(*op));
     op->dev_slot_num = -1;
     op->ind_indiv_last = -1;
+    pg_sz = sg_get_page_size();
     res = cl_process(op, argc, argv);
     if (res)
         return SG_LIB_SYNTAX_ERROR;
@@ -5129,6 +5120,31 @@
         enumerate_work(op);
         return 0;
     }
+    enc_stat_rsp = sg_memalign(MX_ALLOC_LEN, pg_sz, &free_enc_stat_rsp,
+                               op->verbose > 3);
+    if (NULL == enc_stat_rsp) {
+        pr2serr("Unable to get heap for enc_stat_rsp\n");
+        goto err_out;
+    }
+    elem_desc_rsp = sg_memalign(MX_ALLOC_LEN, pg_sz, &free_elem_desc_rsp,
+                               op->verbose > 3);
+    if (NULL == elem_desc_rsp) {
+        pr2serr("Unable to get heap for elem_desc_rsp\n");
+        goto err_out;
+    }
+    add_elem_rsp = sg_memalign(MX_ALLOC_LEN, pg_sz, &free_add_elem_rsp,
+                               op->verbose > 3);
+    if (NULL == add_elem_rsp) {
+        pr2serr("Unable to get heap for add_elem_rsp\n");
+        goto err_out;
+    }
+    threshold_rsp = sg_memalign(MX_ALLOC_LEN, pg_sz, &free_threshold_rsp,
+                               op->verbose > 3);
+    if (NULL == threshold_rsp) {
+        pr2serr("Unable to get heap for threshold_rsp\n");
+        goto err_out;
+    }
+
     if (op->num_cgs) {
         have_cgs = true;
         if (op->page_code_given &&
@@ -5332,7 +5348,18 @@
     if (ret && (0 == op->verbose))
         pr2serr("Problem detected, try again with --verbose option for more "
                 "information\n");
-    res = sg_cmds_close_device(sg_fd);
+    if (sg_fd >= 0)
+        res = sg_cmds_close_device(sg_fd);
+    else
+        res = 0;
+    if (free_enc_stat_rsp)
+        free(free_enc_stat_rsp);
+    if (free_elem_desc_rsp)
+        free(free_elem_desc_rsp);
+    if (free_add_elem_rsp)
+        free(free_add_elem_rsp);
+    if (free_threshold_rsp)
+        free(free_threshold_rsp);
     if (res < 0) {
         pr2serr("close error: %s\n", safe_strerror(-res));
         if (0 == ret)
diff --git a/src/sg_vpd.c b/src/sg_vpd.c
index 728c7f9..a3cf9fe 100644
--- a/src/sg_vpd.c
+++ b/src/sg_vpd.c
@@ -38,7 +38,7 @@
 
 */
 
-static const char * version_str = "1.31 20171104";  /* spc5r17 + sbc4r14 */
+static const char * version_str = "1.32 20171206";  /* spc5r17 + sbc4r14 */
 
 /* standard VPD pages, in ascending page number order */
 #define VPD_SUPPORTED_VPDS 0x0
@@ -1086,7 +1086,7 @@
             break;
         case 9: /* Protocol specific port identifier */
             break;
-        case 0xa: /* UUID identifier */
+        case 0xa: /* UUID identifier [spc5r08] RFC 4122 */
             if ((1 != c_set) || (18 != i_len) || (1 != ((ip[0] >> 4) & 0xf)))
                 break;
             for (m = 0; m < 16; ++m) {
@@ -2261,10 +2261,10 @@
         printf("  Manufacturer-assigned serial number: %.*s\n",
                len - 4, buff + 4);
         break;
-    case PDT_SES:	/* T10/17-142r1 -> ses4r02 ?? */
+    case PDT_SES:       /* T10/17-142r1 -> ses4r02 ?? */
         if (len < 8) {
             pr2serr("Enclosure service device characteristics VPD page "
-		    "length too short=%d\n", len);
+                    "length too short=%d\n", len);
             return;
         }
         printf("  SESDNLD=%d\n", !! (0x2 & buff[4]));
@@ -2276,7 +2276,7 @@
         printf("  DMOSASDS=%d\n", !! (0x8 & buff[6]));
         printf("  DMOSDS=%d\n", !! (0x4 & buff[6]));
         printf("  ADMS=%d\n", !! (0x1 & buff[6]));
-	break;
+        break;
     default:
         pr2serr("  Unable to decode pdt=0x%x, in hex:\n", pdt);
         dStrHexErr((const char *)buff, len, 0);
diff --git a/src/sg_write_x.c b/src/sg_write_x.c
index 4c4207c..12d0b63 100644
--- a/src/sg_write_x.c
+++ b/src/sg_write_x.c
@@ -37,10 +37,17 @@
 #include "sg_unaligned.h"
 #include "sg_pr2serr.h"
 
-static const char * version_str = "1.05 20171127";
+static const char * version_str = "1.05 20171202";
 
-
-#define ME "sg_write_x: "
+/* Protection Information refers to 8 bytes of extra information usually
+ * associated with each logical block and is often abbreviated to PI while
+ * its fields: reference-tag (4 bytes), application-tag (2 bytes) and
+ * tag-mask (2 bytes) are often abbreviated to RT, AT and TM respectively.
+ * And the LBA Range Descriptor associated with the WRITE SCATTERED command
+ * is abbreviated to RD. A degenerate RD is one where both the LBA and length
+ * components are zero; they are not illegal according to T10 but are a
+ * little tricky to handle when scanning and little extra information
+ * is provided. */
 
 #define ORWRITE16_OP 0x8b
 #define WRITE_16_OP 0x8a
@@ -102,12 +109,13 @@
     {"num", required_argument, 0, 'n'},
     {"offset", required_argument, 0, 'o'},
     {"or", no_argument, 0, 'O'},
-    {"raw", no_argument, 0, 'r'},
-    {"ref-tag", required_argument, 0, 'R'},
-    {"ref_tag", required_argument, 0, 'R'},
+    {"ref-tag", required_argument, 0, 'r'},
+    {"ref_tag", required_argument, 0, 'r'},
     {"same", required_argument, 0, 'M'},
     {"scat-file", required_argument, 0, 'q'},
     {"scat_file", required_argument, 0, 'q'},
+    {"scat-raw", no_argument, 0, 'R'},
+    {"scat_raw", no_argument, 0, 'R'},
     {"scattered", required_argument, 0, 'S'},
     {"stream", required_argument, 0, 'T'},
     {"strict", no_argument, 0, 's'},
@@ -130,11 +138,11 @@
     bool do_combined;           /* -c DOF --> .scat_lbdof */
     bool do_dry_run;
     bool do_or;                 /* -O  ORWRITE(16 or 32) */
-    bool do_raw;
+    bool do_scat_raw;
     bool do_same;               /* -M  WRITE SAME(16 or 32) */
                                 /*  --same=NDOB  NDOB --> .ndob */
     bool do_scattered;          /* -S  WRITE SCATTERED(16 or 32) */
-                                /*  --scattered=RD  RD --> .scat_num_lbard */
+                                /*  --scattered=RD  RD --> .scat_num_lbrd */
     bool do_stream;             /* -T  WRITE STREAM(16 or 32) */
                                 /*  --stream=ID  ID --> .str_id */
     bool do_unmap;
@@ -143,6 +151,7 @@
                                  * is 8 bytes long following each logical
                                  * block in the data out buffer. */
     bool dpo;
+    bool explicit_lba;          /* .numblocks defaults to 0 when false */
     bool fua;
     bool ndob;
     bool strict;
@@ -158,19 +167,21 @@
     uint16_t app_tag;   /* part of protection information (def: 0xffff) */
     uint16_t atomic_boundary;
     uint16_t scat_lbdof;
-    uint16_t scat_num_lbard;
+    uint16_t scat_num_lbrd;
     uint16_t str_id;    /* (stream ID) is for WRITE STREAM */
     uint16_t tag_mask;  /* part of protection information (def: 0xffff) */
     uint32_t bs;        /* logical block size (def: 0). 0 implies use READ
                          * CAPACITY(10 or 16) to determine */
     uint32_t bs_pi_do;  /* logical block size plus PI, if any */
-    uint32_t numblocks;
+    uint32_t if_dlen;   /* bytes to read after .if_offset from .if_name,
+                         * if 0 given, read rest of .if_name */
+    uint32_t numblocks; /* defaults to 0 if .explicit_lba is false, else
+                         * derives from IF and/or sum of NUMs */
     uint32_t orw_eog;
     uint32_t orw_nog;
     uint32_t ref_tag;   /* part of protection information (def: 0xffffffff) */
     uint64_t lba;
-    uint64_t offset;    /* byte offset in if_name to start reading */
-    uint64_t dlen;    /* bytes to read after offset from if_name, 0->rest */
+    uint64_t if_offset; /* byte offset in .if_name to start reading */
     uint64_t tot_lbs;   /* from READ CAPACITY */
     ssize_t xfer_bytes;     /* derived value: bs_pi_do * numblocks */
     const char * device_name;
@@ -193,24 +204,24 @@
             "           [--fua] [--generation=EOG,NOG] [--grpnum=GN] "
             "[--help] --in=IF\n"
             "           [--lba=LBA,LBA...] [--normal] [--num=NUM,NUM...]\n"
-            "           [--offset=OFF[,DLEN]] [--or] [--raw] [--ref-tag=RT] "
+            "           [--offset=OFF[,DLEN]] [--or] [--ref-tag=RT] "
             "[--same=NDOB]\n"
-            "           [--scat-file=SF] [--scattered=RD] [--stream=ID] "
-            "[--strict]\n"
-            "           [--tag-mask=TM] [--timeout=TO] [--unmap=U_A] "
-            "[--verbose]\n"
-            "           [--version] [--wrprotect=WRP] DEVICE\n");
+            "           [--scat-file=SF] [--scat-raw] [--scattered=RD] "
+            "[--stream=ID]\n"
+            "           [--strict] [--tag-mask=TM] [--timeout=TO] "
+            "[--unmap=U_A]\n"
+            "           [--verbose] [--version] [--wrprotect=WRP] DEVICE\n");
         if (1 != do_help) {
             pr2serr("\nOr the corresponding short option usage:\n"
                 "sg_write_x [-6] [-3] [-a AT] [-A AB] [-B OP,PGP] [-b LBS] "
                 "[-c DOF] [-D DLD]\n"
                 "           [-d] [-x] [-f] [-G EOG,NOG] [-g GN] [-h] -i IF "
                 "[-l LBA,LBA...]\n"
-                "           [-N] [-n NUM,NUM...] [-o OFF[,DLEN]] [-O] [-r] "
-                "[-R RT] [-M NDOB]\n"
-                "           [-q SF] [-S RD] [-T ID] [-s] [-t TM] [-I TO] "
-                "[-u U_A] [-v] [-V]\n"
-                "           [-w WPR] DEVICE\n"
+                "           [-N] [-n NUM,NUM...] [-o OFF[,DLEN]] [-O] "
+                "[-r RT] [-M NDOB]\n"
+                "           [-q SF] [-R] [-S RD] [-T ID] [-s] [-t TM] [-I TO] "
+                "[-u U_A] [-v]\n"
+                "           [-V] [-w WPR] DEVICE\n"
                    );
             pr2serr("\nUse '-h' or '--help' for more help\n");
             return;
@@ -274,9 +285,7 @@
             "        |-o OFF[,DLEN]     (def: 0), then read DLEN bytes(def: "
             "rest of IF)\n"
             "    --or|-O            send ORWRITE command\n"
-            "    --raw|-r           read --scat_file=SF as binary (def: "
-            "ASCII hex)\n"
-            "    --ref-tag=RT|-R RT     expected reference tag field (def: "
+            "    --ref-tag=RT|-r RT     expected reference tag field (def: "
             "0xffffffff)\n"
             "    --same=NDOB|-M NDOB    send WRITE SAME command. NDOB (no "
             "data out buffer)\n"
@@ -284,6 +293,8 @@
             "1 (don't)\n"
             "    --scat-file=SF|-q SF    file containing LBA, NUM pairs, "
             "see manpage\n"
+            "    --scat-raw|-R      read --scat_file=SF as binary (def: "
+            "ASCII hex)\n"
             "    --scattered=RD|-S RD    send WRITE SCATTERED command with "
             "RD range\n"
             "                            descriptors (RD can be 0 when "
@@ -376,7 +387,7 @@
             "             [--combined=DOF] [--dpo] [--fua] [--grpnum=GN]\n"
             "             [--lba=LBA,LBA...] [--num=NUM,NUM...] "
             "[--offset=OFF[,DLEN]]\n"
-            "             [--raw] [--ref-tag=RT] [--scat-file=SF] "
+            "             [--ref-tag=RT] [--scat-file=SF] [--scat-raw] "
             "[--strict]\n"
             "             [--tag-mask=TM] [--timeout=TO] [--wrprotect=WRP] "
             "DEVICE\n"
@@ -385,10 +396,11 @@
             "  sg_write_x --scattered --in=IF [--bs=LBS] [--combined=DOF] "
             "[--dld=DLD]\n"
             "             [--dpo] [--fua] [--grpnum=GN] [--lba=LBA,LBA...]\n"
-            "             [--num=NUM,NUM...] [--offset=OFF[,DLEN]] [--raw] "
-            "[--scat-file=SF]\n"
-            "             [--strict] [--timeout=TO] [--wrprotect=WRP] "
-            "DEVICE\n"
+            "             [--num=NUM,NUM...] [--offset=OFF[,DLEN]] "
+            "[--scat-raw]\n"
+            "             [--scat-file=SF] [--strict] [--timeout=TO] "
+            "[--wrprotect=WRP]\n"
+            "             DEVICE\n"
             "\n"
             "WRITE STREAM (32) applicable options:\n"
             "  sg_write_x --stream=ID --in=IF --32 [--app-tag=AT] "
@@ -417,10 +429,10 @@
             "and/or the\n"
             "   --dry-run option\n"
             " - all WRITE X commands will accept --scat-file=SF and "
-            "optionally --raw\n"
+            "optionally --scat-raw\n"
             "   options but only the first addr,num pair is used (any "
             "more are ignored)\n"
-            " - when '--raw --scat-file=SF' are used then the binary "
+            " - when '--rscat-aw --scat-file=SF' are used then the binary "
             "format expected in\n"
             "   SF is as defined for the WRITE SCATTERED commands. "
             "That is 32 bytes\n"
@@ -456,7 +468,7 @@
 
 /* Returns true if num_of_f_chars of ASCII 'f' or 'F' characters are found
  * in sequence. Any leading "0x" or "0X" is ignored; otherwise false is
- * returned (and the comparsion stops when the first mismatch is found).
+ * returned (and the comparison stops when the first mismatch is found).
  * For example a sequence of 'f' characters in a null terminated C string
  * that is two characters shorter than the requested num_of_f_chars will
  * compare the null character in the string with 'f', find them unequal,
@@ -601,13 +613,13 @@
  * SG_LIB_SYNTAX_ERROR). If protection information fields not given, then
  * default values are given (i.e. all 0xff bytes). Ignores all spaces and
  * tabs and everything after '#' on lcp (assumed to be an ASCII line that
- * is null terminated. If successful writes a LBA range descriptor starting
- * at 'up'. */
+ * is null terminated). If successful writes a LBA range descriptor starting
+ * at 'up'. The array starting at 'up' should be at least 20 bytes long. */
 static int
 parse_scat_pi_line(const char * lcp, uint8_t * up, uint32_t * sum_num)
 {
     bool ok;
-    int n;
+    int k;
     int64_t ll;
     const char * cp;
     const char * bp;
@@ -615,13 +627,12 @@
 
     bp = c;
     cp = strchr(lcp, '#');
-    n = strspn(lcp, " \t");
-    lcp = lcp + n;
+    lcp += strspn(lcp, " \t");
     if (('\0' == *lcp) || (cp && (lcp >= cp)))
         return 999;   /* blank line or blank prior to first '#' */
     if (cp) {   /* copy from first non whitespace ... */
-        memcpy(c, lcp, cp - lcp);
-        c[cp - lcp] = '\0';     /* ... to just before first '#' */
+        memcpy(c, lcp, cp - lcp);  /* ... to just prior to first '#' */
+        c[cp - lcp] = '\0';
     } else
         strcpy(c, lcp);         /* ... to end of line, including null */
     ll = sg_get_llnum(bp);
@@ -635,7 +646,7 @@
     cp = strchr(bp, ',');
     if (cp) {
         bp = cp + 1;
-        if (*cp) {
+        if (*bp) {
             ll = sg_get_llnum(bp);
             if (-1 != ll)
                 ok = true;
@@ -647,64 +658,92 @@
     }
     sg_put_unaligned_be32((uint32_t)ll, up + 8);
     *sum_num += (uint32_t)ll;
-    cp = strchr(bp, ',');
-    if (NULL == cp) {
-        sg_put_unaligned_be32((uint32_t)DEF_RT, up + 12);
-        sg_put_unaligned_be16((uint16_t)DEF_AT, up + 16);
-        sg_put_unaligned_be16((uint16_t)DEF_TM, up + 18);
-        return 0;
-    }
-    ok = false;
-    bp = cp + 1;
-    if (*cp) {
-        ll = sg_get_llnum(bp);
-        if (-1 != ll)
-            ok = true;
-    }
-    if ((! ok) || (ll > UINT32_MAX)) {
-        pr2serr("%s: error reading RT (third) item on ", __func__);
-        return SG_LIB_SYNTAX_ERROR;
-    }
-    sg_put_unaligned_be32((uint32_t)ll, up + 12);
-    ok = false;
-    cp = strchr(bp, ',');
-    if (cp) {
+    /* now for 3 PI items */
+    for (k = 0; k < 3; ++k) {
+        ok = true;
+        cp = strchr(bp, ',');
+        if (NULL == cp)
+            break;
         bp = cp + 1;
-        if (*cp) {
-            n = sg_get_num(bp);
-            if (-1 != ll)
-                ok = true;
+        if (*bp) {
+            cp += strspn(bp, " \t");
+            if ('\0' == *cp)
+                break;
+            else if (',' == *cp) {
+                if (0 == k)
+                    ll = DEF_RT;
+                else
+                    ll = DEF_AT; /* DEF_AT and DEF_TM have same value */
+            } else {
+                ll = sg_get_llnum(bp);
+                if (-1 == ll)
+                    ok = false;
+            }
+        }
+        if (! ok) {
+            pr2serr("%s: error reading item %d NUM item on ", __func__,
+                    k + 3);
+            break;
+        }
+        switch (k) {
+        case 0:
+            if (ll > UINT32_MAX) {
+                pr2serr("%s: error with item 3, >0xffffffff; on ", __func__);
+                ok = false;
+            } else
+                sg_put_unaligned_be32((uint32_t)ll, up + 12);
+            break;
+        case 1:
+            if (ll > UINT16_MAX) {
+                pr2serr("%s: error with item 4, >0xffff; on ", __func__);
+                ok = false;
+            } else
+                sg_put_unaligned_be16((uint16_t)ll, up + 16);
+            break;
+        case 2:
+            if (ll > UINT16_MAX) {
+                pr2serr("%s: error with item 5, >0xffff; on ", __func__);
+                ok = false;
+            } else
+                sg_put_unaligned_be16((uint16_t)ll, up + 18);
+            break;
+        default:
+            pr2serr("%s: k=%d should not be >= 3\n", __func__, k);
+            ok = false;
+            break;
+        }
+        if (! ok)
+            break;
+    }
+    if (! ok)
+        return SG_LIB_SYNTAX_ERROR;
+    for ( ; k < 3; ++k) {
+        switch (k) {
+        case 0:
+            sg_put_unaligned_be32((uint32_t)DEF_RT, up + 12);
+            break;
+        case 1:
+            sg_put_unaligned_be16((uint16_t)DEF_AT, up + 16);
+            break;
+        case 2:
+            sg_put_unaligned_be16((uint16_t)DEF_TM, up + 18);
+            break;
+        default:
+            pr2serr("%s: k=%d should not be >= 3\n", __func__, k);
+            ok = false;
+            break;
         }
     }
-    if ((! ok) || (n > UINT16_MAX)) {
-        pr2serr("%s: error reading AT (fourth) item on ", __func__);
-        return SG_LIB_SYNTAX_ERROR;
-    }
-    sg_put_unaligned_be32((uint16_t)n, up + 16);
-    ok = false;
-    cp = strchr(bp, ',');
-    if (cp) {
-        bp = cp + 1;
-        if (*cp) {
-            n = sg_get_num(bp);
-            if (-1 != ll)
-                ok = true;
-        }
-    }
-    if ((! ok) || (n > UINT16_MAX)) {
-        pr2serr("%s: error reading TM (fifth) item on ", __func__);
-        return SG_LIB_SYNTAX_ERROR;
-    }
-    return 0;
+    return ok ? 0 : SG_LIB_SYNTAX_ERROR;
 }
 
 /* Read pairs or LBAs and NUMs from a scat_file. A T10 scatter list array is
  * built at t10_scat_list_out (e.g. as per T10 the first 32 bytes are zeros
  * followed by the first LBA range descriptor (also 32 bytes long) then the
- * second LBA range descriptor, etc. If pi_as_well is false then only LBA,NUM
+ * second LBA range descriptor, etc. If do_16 is true then only LBA,NUM
  * pairs are expected, loosely formatted if they are in the scat_file (e.g.
  * single line entries alternating LBA and NUM, with an even number of
- * elements. If pa_as_well is true then a stricter format for quintets is
+ * elements. If do_16 is false then a stricter format for quintets is
  * expected: on each non comment line should contain: LBA,NUM[,RT,AT,TM] . If
  * RT,AT,TM are not given then they assume their defaults (i.e. 0xffffffff,
  * 0xffff, 0xffff). Each number (up to 64 bits in size) from command line or
@@ -714,7 +753,7 @@
  * actual byte length of t10_scat_list_out written into act_list_blen and the
  * number of LBA range descriptors written in num_scat_elems . */
 static int
-build_t10_scat(const char * scat_fname, bool pi_as_well,
+build_t10_scat(const char * scat_fname, bool do_16,
                uint8_t * t10_scat_list_out, uint32_t * act_list_blen,
                uint32_t * num_scat_elems, uint32_t * sum_num,
                int max_list_blen)
@@ -776,7 +815,7 @@
                     __func__, scat_fname, j + 1, m + k + 1);
             goto bad_exit;
         }
-        if (pi_as_well) {
+        if (! do_16) {
             res = parse_scat_pi_line(lcp, up + n, sum_num);
             if (999 == res)
                 ;
@@ -829,7 +868,7 @@
         }   /* inner for loop(k) over line elements */
         off += (k + 1);
     }       /* outer for loop(j) over lines */
-    if ((! pi_as_well) && (0x1 & off)) {
+    if (do_16 && (0x1 & off)) {
         pr2serr("%s: expect LBA,NUM pairs but decoded odd number\n  from "
                 "%s\n", __func__, scat_fname);
         goto bad_exit;
@@ -852,6 +891,99 @@
             (DEF_TM == op->tag_mask));
 }
 
+/* Given a t10 parameter list header (32 zero bytes) for WRITE SCATTERED
+ * (16 or 32) followed by n RDs with a total length of at least
+ * max_lbrds_blen bytes, find "n" and increment where num_lbrds points
+ * n times. Further get the LBA length component from each RD and add each
+ * length into where sum_num points. Note: the caller probably wants to zero
+ * where num_lbrds and sum_num point before invoking this function. If all
+ * goes well return true, else false. If a degenerate RD is detected then
+ * if 'RD' (from --scattered=RD) is 0 then stop looking for further RDs;
+ * otherwise keep going. Currently overlapping LBA range descriptors are no
+ * checked for. If op->strict > 0 then the first 32 bytes are checked for
+ * zeros; any non-zero bytes will report to stderr, stop the check and
+ * return false. If op->strict > 0 then the trailing 20 or 12 bytes (only
+ * 12 if RT, AT and TM fields (for PI) are present) are checked for zeros;
+ * any non-zero bytes cause the same action as the previous check. If
+ * the number of RDs (when 'RD' from --scattered=RD > 0) is greater than
+ * the number of RDs found then a report is sent to stderr and if op->strict
+ * > 0 then returns false, else returns true.  */
+static bool
+check_lbrds(const uint8_t * up, int max_lbrds_blen, const struct opts_t * op,
+            uint32_t * num_lbrds, uint32_t * sum_num)
+{
+    bool ok;
+    int k, j, n;
+    const int max_lbrd_start = max_lbrds_blen - 32;
+    int vb = op->verbose;
+
+    if (op->strict) {
+        if (max_lbrds_blen < 32) {
+            pr2serr("%s: logical block range descriptors too short "
+                    "(%d < 32)\n", __func__, max_lbrds_blen);
+            return false;
+        }
+        if (! sg_all_zeros(up, 32)) {
+            pr2serr("%s: first 32 bytes of WRITE SCATTERED data-out buffer "
+                    "should be zero.\nFound non-zero byte.\n", __func__);
+            return false;
+        }
+    }
+    if (max_lbrds_blen < 64) {
+        *num_lbrds = 0;
+        return true;
+    }
+    n = op->scat_num_lbrd ? -1 : (int)op->scat_num_lbrd;
+    for (k = 32, j = 0; k < max_lbrd_start; k += 32) {
+        if ((n < 0) && sg_all_zeros(up + k + 0, 12)) { /* degenerate LBA */
+            if (vb)   /* ... range descriptor terminator if --scattered=0 */
+                pr2serr("%s: degenerate LBA range descriptor stops scan at "
+                        "k=%d (RD=0)\n", __func__, k);
+            break;
+        }
+        *sum_num += sg_get_unaligned_be32(up + k + 8);
+        *num_lbrds += 1;
+        if (op->strict) {
+            ok = true;
+            if (op->wrprotect) {
+                if (! sg_all_zeros(up + k + 20, 12))
+                    ok = false;
+            } else if (! sg_all_zeros(up + k + 12, 20))
+                ok = false;
+            if (! ok) {
+                pr2serr("%s: LB range descriptor %d non zero in reserved "
+                        "fields\n", __func__, (k / 32) - 1);
+                return false;
+            }
+        }
+        ++j;
+        if (n >= 0) {
+            if (--n <= 0)
+                break;
+        }
+    }
+    if ((k < max_lbrd_start) && op->strict) { /* check pad all zeros */
+        k += 32;
+        n = max_lbrds_blen - k;
+        if (! sg_all_zeros(up + k, n)) {
+            pr2serr("%s: pad (%d bytes) following LB range descriptors is "
+                    "non zero\n", __func__, n);
+            return false;
+        }
+    }
+    if (vb > 2)
+        pr2serr("%s: about to return true, num_lbrds=%u, sum_num=%u "
+                "[k=%d, n=%d]\n", __func__, *num_lbrds, *sum_num, k, n);
+    if (n > 0) {
+        pr2serr("%s: number of range descriptors found (%d) less than RD "
+                "(%u) given to --scattered=\n", __func__, j,
+                op->scat_num_lbrd);
+        if (op->strict)
+            return false;
+    }
+    return true;
+}
+
 static int
 do_write_x(int sg_fd, const void * dataoutp, int dout_len,
            const struct opts_t * op)
@@ -996,7 +1128,7 @@
                     x_cdb[2] |= 0x1;
             }
             sg_put_unaligned_be16(op->scat_lbdof, x_cdb + 4);
-            sg_put_unaligned_be16(op->scat_num_lbard, x_cdb + 8);
+            sg_put_unaligned_be16(op->scat_num_lbrd, x_cdb + 8);
             sg_put_unaligned_be32(op->numblocks, x_cdb + 10);
 
         } else {
@@ -1007,8 +1139,9 @@
             if (op->fua)
                 x_cdb[10] |= 0x8;
             sg_put_unaligned_be16(op->scat_lbdof, x_cdb + 12);
-            sg_put_unaligned_be16(op->scat_num_lbard, x_cdb + 16);
+            sg_put_unaligned_be16(op->scat_num_lbrd, x_cdb + 16);
             sg_put_unaligned_be32(op->numblocks, x_cdb + 28);
+            /* ref_tag, app_tag and tag_mask placed in scatter list */
         }
     } else if (op->do_stream) {
         if (16 == cdb_len) {
@@ -1044,6 +1177,29 @@
             pr2serr("%02x ", x_cdb[k]);
         pr2serr("\n");
     }
+    if (op->do_scattered && (op->verbose > 2) && (dout_len > 63)) {
+        uint32_t sod_off = op->bs_pi_do * op->scat_lbdof;
+        const uint8_t * up = dataoutp;
+
+        pr2serr("    %s scatter list, number of LBA range descriptors: %u\n",
+                op->cdb_name, op->scat_num_lbrd);
+        pr2serr("      byte offset of data_to_write: %u, dout_len: %d\n",
+                sod_off, dout_len);
+        up += 32;       /* step over parameter list header */
+        for (k = 0; k < (int)op->scat_num_lbrd; ++k, up += 32) {
+            pr2serr("        desc %d: LBA=0x%" PRIx64 " numblocks=%" PRIu32
+                    "%s", k, sg_get_unaligned_be64(up + 0),
+                    sg_get_unaligned_be32(up + 8), (op->do_16 ? "\n" : " "));
+            if (! op->do_16)
+                pr2serr("rt=0x%x at=0x%x tm=0x%x\n",
+                        sg_get_unaligned_be32(up + 12),
+                        sg_get_unaligned_be16(up + 16),
+                        sg_get_unaligned_be16(up + 18));
+            if ((uint32_t)(((k + 2) * 32) + 20) > sod_off)
+                pr2serr("Warning: possible clash of descriptor %u with "
+                        "data_to_write\n", k);
+        }
+    }
     if ((op->verbose > 3) && (dout_len > 0)) {
         pr2serr("    Data-out buffer contents:\n");
         dStrHexErr((const char *)dataoutp, op->xfer_bytes, 1);
@@ -1205,7 +1361,7 @@
 
 #define WANT_ZERO_EXIT 9999
 static const char * const opt_long_ctl_str =
-    "36a:A:b:B:c:dD:Efg:G:hi:I:l:M:n:No:Oq:rR:sS:t:T:u:vVw:x";
+    "36a:A:b:B:c:dD:Efg:G:hi:I:l:M:n:No:Oq:r:RsS:t:T:u:vVw:x";
 
 /* command line processing, options and arguments. Returns 0 if ok,
  * returns WANT_ZERO_EXIT so upper level yields an exist status of zero.
@@ -1379,14 +1535,19 @@
                 pr2serr("bad first argument to '--offset='\n");
                 return SG_LIB_SYNTAX_ERROR;
             }
-            op->offset = (uint64_t)ll;
+            op->if_offset = (uint64_t)ll;
             if ((cp = strchr(optarg, ','))) {
                 ll = sg_get_llnum(cp + 1);
                 if (-1 == ll) {
                     pr2serr("bad second argument to '--offset='\n");
                     return SG_LIB_SYNTAX_ERROR;
                 }
-                op->dlen = (uint64_t)ll;
+                if (ll > UINT32_MAX) {
+                    pr2serr("bad second argument to '--offset=', cannot "
+                            "exceed 32 bits\n");
+                    return SG_LIB_SYNTAX_ERROR;
+                }
+                op->if_dlen = (uint32_t)ll;
             }
             break;
         case 'O':
@@ -1396,7 +1557,10 @@
         case 'q':
             op->scat_filename = optarg;
             break;
-        case 'r':
+        case 'R':
+            op->do_scat_raw = true;
+            break;
+        case 'r':               /* same as --ref-tag= */
             ll = sg_get_llnum(optarg);
             if ((ll < 0) || (ll > UINT32_MAX)) {
                 pr2serr("bad argument to '--ref-tag='. Expect 0 to "
@@ -1405,15 +1569,6 @@
             }
             op->ref_tag = (uint32_t)ll;
             break;
-        case 'R':               /* same as --ref-tag= */
-            ll = sg_get_llnum(optarg);
-            if ((ll < 0) || (ll > UINT32_MAX)) {
-                pr2serr("bad argument to '--ref-tag='. Expect 0 to 0xffffffff "
-                        "inclusive\n");
-                return SG_LIB_SYNTAX_ERROR;
-            }
-            op->ref_tag = (uint32_t)ll;
-            break;
         case 's':
             op->strict = true;
             break;
@@ -1424,7 +1579,7 @@
                         "inclusive\n");
                 return SG_LIB_SYNTAX_ERROR;
             }
-            op->scat_num_lbard = (uint16_t)j;
+            op->scat_num_lbrd = (uint16_t)j;
             op->do_scattered = true;
             op->cmd_name = "Write scattered";
             break;
@@ -1461,7 +1616,7 @@
             ++op->verbose;
             break;
         case 'V':
-            pr2serr(ME "version: %s\n", version_str);
+            pr2serr("sg_write_x version: %s\n", version_str);
             return WANT_ZERO_EXIT;
         case 'w':       /* WRPROTECT field (or ORPROTECT for ORWRITE) */
             op->wrprotect = sg_get_num(optarg);
@@ -1506,9 +1661,11 @@
     int sg_fd = -1;
     int rsl_fd = -1;
     int ret = -1;
-    uint32_t addr_arr_len, num_arr_len, num_lbard, do_len;
+    uint32_t addr_arr_len, num_arr_len, do_len, s;
+    uint32_t num_lbrd = 0;
+    uint32_t if_len = 0;
     ssize_t res;
-    off_t if_len = 0;
+    off_t if_tot_len = 0;
     struct opts_t * op;
     unsigned char * wBuff = NULL;
     const char * lba_op = NULL;
@@ -1528,10 +1685,11 @@
     op->numblocks = DEF_WR_NUMBLOCKS;
     op->pi_type = -1;           /* Protection information type unknown */
     op->ref_tag = DEF_RT;       /* first 4 bytes of 8 byte protection info */
-    op->app_tag = DEF_AT;       /* part of protection information */
-    op->tag_mask = DEF_TM;      /* part of protection information */
+    op->app_tag = DEF_AT;       /* 2 bytes of protection information */
+    op->tag_mask = DEF_TM;      /* final 2 bytes of protection information */
     op->timeout = DEF_TIMEOUT_SECS;
 
+    /* Process command line */
     ret = cl_process(op, argc, argv, &lba_op, &num_op);
     if (ret) {
         if (WANT_ZERO_EXIT == ret)
@@ -1543,6 +1701,7 @@
         return 0;
     }
     vb = op->verbose;
+    /* sanity checks */
     if ((! op->do_16) && (! op->do_32)) {
         op->do_16 = true;
         if (vb > 1)
@@ -1566,8 +1725,23 @@
     }
     snprintf(op->cdb_name, sizeof(op->cdb_name), "%s(%d)", op->cmd_name,
              (op->do_16 ? 16 : 32));
+    if (op->do_combined) {
+        if (! op->do_scattered) {
+            pr2serr("--combined=DOF only allowed with --scattered=RD (i.e. "
+                    "only with\nWRITE SCATTERED command)\n");
+            return SG_LIB_SYNTAX_ERROR;
+        }
+        if (lba_op || num_op) {
+            pr2serr("--scattered=RD --combined=DOF does not use --lba= or "
+                    "--num=\nPlease remove.\n");
+            return SG_LIB_SYNTAX_ERROR;
+        }
+    }
 
+    /* examine .if_name . Open, move to .if_offset, calculate length that we
+     * want to read. */
     if (! op->ndob) {
+        if_len = op->if_dlen;
         if (NULL == op->if_name) {
             pr2serr("Need --if=FN option to be given, exiting.\n");
             if (vb > 1)
@@ -1599,16 +1773,17 @@
             }
             got_stat = true;
             if (S_ISREG(if_stat.st_mode))
-                if_len = if_stat.st_size;
+                if_tot_len = if_stat.st_size;
         }
-        if (got_stat && if_len && ((int64_t)op->offset >= (if_len - 1))) {
+        if (got_stat && if_tot_len &&
+            ((int64_t)op->if_offset >= (if_tot_len - 1))) {
             pr2serr("Offset (%" PRIu64 ") is at or beyond IF byte length (%"
-                    PRIu64 ")\n", op->offset, if_len);
+                    PRIu64 ")\n", op->if_offset, if_tot_len);
             ret = SG_LIB_FILE_ERROR;
             goto err_out;
         }
-        if (op->offset > 0) {
-            off_t off = op->offset;
+        if (op->if_offset > 0) {
+            off_t off = op->if_offset;
 
             if (got_stdin) {
                 if (vb)
@@ -1623,17 +1798,27 @@
                     ret = SG_LIB_FILE_ERROR;
                     goto err_out;
                 }
-                if_len -= op->offset;
-                if (if_len <= 0) {
+                if_tot_len -= op->if_offset;
+                if (if_tot_len <= 0) {
                     pr2serr("--offset [0x%" PRIx64 "] at or beyond file "
-                            "length[0x%" PRIx64 "]\n", (uint64_t)op->offset,
-                            (uint64_t)if_len);
+                            "length[0x%" PRIx64 "]\n",
+                            (uint64_t)op->if_offset, (uint64_t)if_tot_len);
                     ret = SG_LIB_FILE_ERROR;
                     goto err_out;
                 }
+                if_len = (uint32_t)((if_tot_len < (off_t)op->if_dlen) ?
+                                        if_tot_len : (off_t)op->if_dlen);
             }
         }
         if (0 != (if_len % op->bs_pi_do)) {
+            if (op->strict) {
+                pr2serr("Error: number of bytes to read from IF [%u] is "
+                        "not a multiple\nblock size %u (including"
+                        "protection information\n", (unsigned int)if_len,
+                        op->bs_pi_do);
+                ret = SG_LIB_FILE_ERROR;
+                goto err_out;
+            }
             pr2serr("Warning: number of bytes to read from IF [%u] is not a "
                     "multiple\nblock size %u (including protection "
                     "information, if any);\npad with zeros",
@@ -1642,32 +1827,26 @@
                      op->bs_pi_do;      /* round up */
         }
     }
+    /* A bit more sanity */
     if (NULL == op->device_name) {
         pr2serr("missing device name!\n");
         usage((op->help > 0) ? op->help : 0);
         ret = SG_LIB_SYNTAX_ERROR;
         goto err_out;
     }
-    if (op->scat_filename && (lba_op || num_op)) {
-        pr2serr("expect '--scat-file=' by itself, or both '--lba=' and "
-                "'--num='\n");
-        ret = SG_LIB_SYNTAX_ERROR;
-        goto err_out;
-    } else if (op->scat_filename || (lba_op && num_op))
-        ;       /* we want this path */
-    else {
-        if (lba_op)
-            pr2serr("since '--lba=' is given, also need '--num='\n");
-        else
-            pr2serr("expect either both '--lba=' and '--num=', or "
-                    "'--scat-file=' by itself\n");
+    n = (!! op->scat_filename) + (!! (lba_op || num_op)) +
+        (!! op->do_combined);
+    if (1 != n) {
+        pr2serr("want one and only one of: (--lba=LBA or --num=NUM), or "
+                "--scat-file=SF,\nor --combined=DOF\n");
         ret = SG_LIB_SYNTAX_ERROR;
         goto err_out;
     }
 
+    /* Open device file, do READ CAPACITY(16, maybe 10) if no BS */
     sg_fd = sg_cmds_open_device(op->device_name, false /* rw */, vb);
     if (sg_fd < 0) {
-        pr2serr(ME "open error: %s: %s\n", op->device_name,
+        pr2serr("open error: %s: %s\n", op->device_name,
                 safe_strerror(-sg_fd));
         return SG_LIB_FILE_ERROR;
     }
@@ -1677,6 +1856,7 @@
             goto err_out;
     }
 
+    /* decode --lba= and --num= options */
     memset(addr_arr, 0, sizeof(addr_arr));
     memset(num_arr, 0, sizeof(num_arr));
     addr_arr_len = 0;
@@ -1687,6 +1867,8 @@
             ret = SG_LIB_SYNTAX_ERROR;
             goto err_out;
         }
+        if (addr_arr_len > 0)
+            op->explicit_lba = true;
         if (0 != build_num_arr(num_op, num_arr, &num_arr_len,
                                MAX_NUM_ADDR)) {
             pr2serr("bad argument to '--num'\n");
@@ -1705,6 +1887,7 @@
         uint32_t sum_num = 0;
 
         do_len = 0;
+        /* if WRITE SCATTERED check for --scat-file=SF, if so state(SF) */
         if (op->scat_filename) {
             if (op->do_combined) {
                 pr2serr("Ambiguous: got --combined=DOF and --scat-file=SF "
@@ -1720,31 +1903,34 @@
                 goto err_out;
             }
         }
-        if (op->do_combined && op->do_raw) {
-            pr2serr("Ambiguous: do expect --combined=DOF and --raw\n"
+        /* some WRITE SCATTERED sanity checks */
+        if (op->do_combined && op->do_scat_raw) {
+            pr2serr("Ambiguous: do expect --combined=DOF and --scat-raw\n"
                     "Give one or the other\n");
             ret = SG_LIB_SYNTAX_ERROR;
             goto err_out;
         }
-        if ((NULL == op->scat_filename) && op->do_raw) {
-            pr2serr("--raw only applies to the --scat-file=SF option\n"
-                    "Give both or neither\n");
+        if ((NULL == op->scat_filename) && op->do_scat_raw) {
+            pr2serr("--scat-raw only applies to the --scat-file=SF option\n"
+                    "--scat-raw without the --scat-file=SF option is an "
+                    "error\n");
             ret = SG_LIB_SYNTAX_ERROR;
             goto err_out;
         }
-        if ((addr_arr_len > 0) && (op->scat_num_lbard > 0) &&
-            (op->scat_num_lbard < addr_arr_len)) {
+        if ((addr_arr_len > 0) && (op->scat_num_lbrd > 0) &&
+            (op->scat_num_lbrd < addr_arr_len)) {
             pr2serr("less LBA,NUM pairs (%d )than --scattered=%d\n",
-                    addr_arr_len, op->scat_num_lbard);
+                    addr_arr_len, op->scat_num_lbrd);
             ret = SG_LIB_SYNTAX_ERROR;
             goto err_out;
         }
-        num_lbard = (addr_arr_len > 0) ? addr_arr_len : op->scat_num_lbard;
-        if (num_lbard < 15)
-            num_lbard = 15; /* 32 byte leadin, 15  32 byte LRD = 512 bytes */
-        if (op->do_combined)
+        num_lbrd = (addr_arr_len > 0) ? addr_arr_len : op->scat_num_lbrd;
+        if (num_lbrd < 15)
+            num_lbrd = 15; /* 32 byte leadin, 15  32 byte LRD = 512 bytes */
+        if (op->do_combined) {
             goto skip_scat_build;
-        if (op->do_raw) {
+        }
+        if (op->do_scat_raw) {
             if (S_ISREG(sf_stat.st_mode)) {
                 do_len = sf_stat.st_size;
                 d = sf_stat.st_size / 32;
@@ -1756,13 +1942,13 @@
                 }
                 if (sf_stat.st_size % 32)
                     d += 1;     /* round up, will zero pad unfinished RD */
-                if (op->scat_num_lbard) {
-                    if (op->scat_num_lbard != (d - 1)) {
+                if (op->scat_num_lbrd) {
+                    if (op->scat_num_lbrd != (d - 1)) {
                         pr2serr("Command line RD (%u) contradicts value "
                                 "calculated from raw SF (%u)\n",
-                                 op->scat_num_lbard, d - 1);
-                        if (op->scat_num_lbard < (d - 1))
-                            d = op->scat_num_lbard + 1;
+                                 op->scat_num_lbrd, d - 1);
+                        if (op->scat_num_lbrd < (d - 1))
+                            d = op->scat_num_lbrd + 1;
                         else {
                             pr2serr("Command line RD greater than raw SF "
                                     "file length implies, exit\n");
@@ -1772,14 +1958,16 @@
                     }
                 }
             } else {
-                pr2serr("--scat-file= --raw wants regular file for length\n");
+                pr2serr("--scat-file= --scat-raw wants regular file for "
+                        "length\n");
                 ret = SG_LIB_FILE_ERROR;
                 goto err_out;
             }
-            num_lbard = d;
+            num_lbrd = d;
         }
 
-        do_len = (1 + num_lbard) * 32;
+        /* Calculations to work out initial dout length */
+        do_len = (1 + num_lbrd) * 32;
         op->scat_lbdof = do_len / op->bs_pi_do;
         if (0 != (do_len % op->bs_pi_do)) { /* if not multiple, round up */
             op->scat_lbdof += 1;
@@ -1789,13 +1977,13 @@
             do_len += (uint32_t)if_len;
         } else {        /* IF is stdin, a pipe or a device (special) ... */
             op->xfer_bytes = _SC_PAGE_SIZE;        /* ... so need length */
-            if (op->bs_pi_do > (op->xfer_bytes / 2))
+            if (op->bs_pi_do > ((uint32_t)op->xfer_bytes / 2))
                 op->xfer_bytes = op->bs_pi_do * 3;
-            else if (do_len >= (op->xfer_bytes / 2)) {
+            else if (do_len >= ((uint32_t)op->xfer_bytes / 2)) {
                 op->xfer_bytes *= 4;
-                if (do_len >= (op->xfer_bytes / 2)) {
+                if (do_len >= ((uint32_t)op->xfer_bytes / 2)) {
                     op->xfer_bytes *= 4;
-                    if (do_len >= (op->xfer_bytes / 2)) {
+                    if (do_len >= ((uint32_t)op->xfer_bytes / 2)) {
                         pr2serr("Giving up guessing big enough buffers, "
                                 "please use --offset=OFF,DLEN\n");
                         ret = SG_LIB_SYNTAX_ERROR;
@@ -1805,8 +1993,8 @@
             }
             do_len = op->xfer_bytes;
         }
-            if (0 != (do_len % op->bs_pi_do)) /* round up */
-                do_len = ((do_len / op->bs_pi_do) + 1) * op->bs_pi_do;
+        if (0 != (do_len % op->bs_pi_do)) /* round up */
+            do_len = ((do_len / op->bs_pi_do) + 1) * op->bs_pi_do;
         if (do_len < op->bs_pi_do) {
             pr2serr("failed calculating data-out buffer size (%u)\n",
                     do_len);
@@ -1825,7 +2013,8 @@
             ret = SG_LIB_OS_BASE_ERR + ENOMEM;
             goto err_out;
         }
-        if (op->do_raw) {
+
+        if (op->do_scat_raw) {
             rsl_fd = open(op->scat_filename, O_RDONLY);
             if (rsl_fd < 0) {
                 err = errno;
@@ -1861,12 +2050,17 @@
             close(rsl_fd);
             rsl_fd = -1;
         } else if (op->scat_filename) {
-            ret = build_t10_scat(op->scat_filename, op->expect_pi_do, up, &d,
-                                 &num_lbard, &sum_num,
+            ret = build_t10_scat(op->scat_filename, op->do_16, up, &d,
+                                 &num_lbrd, &sum_num,
                                  op->scat_lbdof * op->bs_pi_do);
             if (ret)
                 goto err_out;
+            if (num_lbrd > 0)
+                op->explicit_lba = true;
             op->numblocks = sum_num;
+            if (vb > 1)
+                pr2serr("After build_t10_scat(): num_lbrd=%u sum_num=%u\n",
+                        num_lbrd, sum_num);
         } else if (addr_arr_len > 0) {  /* build RDs for --addr= --num= */
             for (n = 32, k = 0; k < (int)addr_arr_len; ++k, n += 32) {
                 sg_put_unaligned_be64(addr_arr[k], up + n + 0);
@@ -1886,14 +2080,30 @@
             }
             op->numblocks = sum_num;
         }
-        /* now read data to write component into up */
+        /* now read data to write component into 'up' */
         d = op->scat_lbdof * op->bs_pi_do;
-        if (d > op->xfer_bytes) {
+        if (d > (uint32_t)op->xfer_bytes) {
             pr2serr("Logic error in scattered, read data into buffer "
                     "(d=%u)\n", d);
             ret = SG_LIB_FILE_ERROR;
             goto err_out;
         }
+        s = op->scat_lbdof + op->numblocks;
+        if ((uint32_t)op->xfer_bytes < (s * op->bs_pi_do)) {
+            uint8_t * u2p;
+
+            u2p = calloc(s, op->bs_pi_do);
+            if (NULL == u2p) {
+                pr2serr("unable to allocate memory for final "
+                        "scatterlist+data\n");
+                ret = SG_LIB_OS_BASE_ERR + ENOMEM;
+                goto err_out;
+            }
+            memcpy(u2p, up, d);
+            free(up);
+            up = u2p;
+            op->xfer_bytes = s * op->bs_pi_do;
+        }
         res = read(infd, up + d, op->xfer_bytes - d);
         d = op->xfer_bytes - d;
         if (res < 0) {
@@ -1902,7 +2112,7 @@
             ret = SG_LIB_FILE_ERROR;
             goto err_out;
         }
-        if (res < d) {
+        if ((uint32_t)res < d) {
             pr2serr("Short (%u) read of IF file, wanted %u\n",
                     (unsigned int)res, d);
             ret = SG_LIB_FILE_ERROR;
@@ -1919,7 +2129,35 @@
     if (op->do_same)
         op->xfer_bytes = 1 * op->bs_pi_do;
     else if (op->do_scattered) {
-        ;       /* already done, scatter_list+data waiting in 'up' */
+        if (op->do_combined) {
+            int up_len;
+            uint32_t sum_num;
+
+            if ((if_len < 32) || (op->bs_pi_do < 32)) {
+                pr2serr("Logic error combined calloc should be > %u, "
+                        "bs_pi_do=%u\n", (uint32_t)if_len, op->bs_pi_do);
+                ret = SG_LIB_FILE_ERROR;
+                goto err_out;
+            }
+            /* assume if_len % op->bs_pi_do is zero (i.e. no remainder) */
+            up = calloc(if_len / op->bs_pi_do, op->bs_pi_do);
+            if (NULL == up) {
+                pr2serr("unable to allocate memory for combined\n");
+                ret = SG_LIB_OS_BASE_ERR + ENOMEM;
+                goto err_out;
+            }
+            up_len = (op->scat_lbdof > 0) ? (op->scat_lbdof * op->bs_pi_do) :
+                                            if_len;
+            num_lbrd = 0;
+            sum_num = 0;
+            if (! check_lbrds(up, up_len, op, &num_lbrd, &sum_num)) {
+                ret = SG_LIB_FILE_ERROR;
+                goto err_out;
+            }
+            op->numblocks = sum_num;
+        } else {
+            ; /* already done, scatter_list+data waiting in 'up' */
+        }
     } else
         op->xfer_bytes = op->numblocks * op->bs_pi_do;